@@ -1,4 +1,4 @@
|
|||||||
const { runScad } = require('./runScad')
|
const { runScad, stlExport } = require('./runScad')
|
||||||
const middy = require('middy')
|
const middy = require('middy')
|
||||||
const { cors } = require('middy/middlewares')
|
const { cors } = require('middy/middlewares')
|
||||||
const AWS = require('aws-sdk')
|
const AWS = require('aws-sdk')
|
||||||
@@ -12,25 +12,52 @@ const {
|
|||||||
|
|
||||||
const s3 = new AWS.S3()
|
const s3 = new AWS.S3()
|
||||||
|
|
||||||
|
const openScadStlKey = (eventBody) => {
|
||||||
|
const { file } = JSON.parse(eventBody)
|
||||||
|
return `${makeHash(JSON.stringify(file))}.stl`
|
||||||
|
}
|
||||||
|
|
||||||
const preview = async (req, _context, callback) => {
|
const preview = async (req, _context, callback) => {
|
||||||
_context.callbackWaitsForEmptyEventLoop = false
|
_context.callbackWaitsForEmptyEventLoop = false
|
||||||
const eventBody = req.body
|
const eventBody = req.body
|
||||||
console.log('eventBody', eventBody)
|
console.log('eventBody', eventBody)
|
||||||
const key = `${makeHash(eventBody)}.png`
|
const key = `${makeHash(eventBody)}.png`
|
||||||
|
const stlKey = openScadStlKey(eventBody)
|
||||||
|
|
||||||
console.log('key', key)
|
console.log('key', key)
|
||||||
|
|
||||||
|
const stlParams = {
|
||||||
|
Bucket: process.env.BUCKET,
|
||||||
|
Key: stlKey,
|
||||||
|
}
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
Bucket: process.env.BUCKET,
|
Bucket: process.env.BUCKET,
|
||||||
Key: key,
|
Key: key,
|
||||||
}
|
}
|
||||||
const previousAsset = await checkIfAlreadyExists(params, s3)
|
const [previousAssetStl, previousAssetPng] = await Promise.all([
|
||||||
|
checkIfAlreadyExists(stlParams, s3),
|
||||||
|
checkIfAlreadyExists(params, s3),
|
||||||
|
])
|
||||||
|
const type = previousAssetStl.isAlreadyInBucket ? 'stl' : 'png'
|
||||||
|
const previousAsset = previousAssetStl.isAlreadyInBucket
|
||||||
|
? previousAssetStl
|
||||||
|
: previousAssetPng
|
||||||
if (previousAsset.isAlreadyInBucket) {
|
if (previousAsset.isAlreadyInBucket) {
|
||||||
console.log('already in bucket')
|
console.log('already in bucket')
|
||||||
const response = {
|
const response = {
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
url: getObjectUrl(params, s3),
|
url: getObjectUrl(
|
||||||
consoleMessage: previousAsset.consoleMessage,
|
{
|
||||||
|
Bucket: process.env.BUCKET,
|
||||||
|
Key: previousAssetStl.isAlreadyInBucket ? stlKey : key,
|
||||||
|
},
|
||||||
|
s3
|
||||||
|
),
|
||||||
|
consoleMessage:
|
||||||
|
previousAsset.consoleMessage || previousAssetPng.consoleMessage,
|
||||||
|
type,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
callback(null, response)
|
callback(null, response)
|
||||||
@@ -50,39 +77,46 @@ const preview = async (req, _context, callback) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// const stl = async (req, _context, callback) => {
|
const stl = async (req, _context, callback) => {
|
||||||
// _context.callbackWaitsForEmptyEventLoop = false
|
_context.callbackWaitsForEmptyEventLoop = false
|
||||||
// const eventBody = Buffer.from(req.body, 'base64').toString('ascii')
|
const eventBody = req.body
|
||||||
// console.log(eventBody, 'eventBody')
|
console.log(eventBody, 'eventBody')
|
||||||
// const { file } = JSON.parse(eventBody)
|
const stlKey = openScadStlKey(eventBody)
|
||||||
// const { error, result, tempFile } = await stlExport({ file })
|
|
||||||
// if (error) {
|
console.log('key', stlKey)
|
||||||
// const response = {
|
|
||||||
// statusCode: 400,
|
const params = {
|
||||||
// body: { error, tempFile },
|
Bucket: process.env.BUCKET,
|
||||||
// }
|
Key: stlKey,
|
||||||
// callback(null, response)
|
}
|
||||||
// } else {
|
console.log('original params', params)
|
||||||
// console.log(`got result in route: ${result}, file is: ${tempFile}`)
|
const previousAsset = await checkIfAlreadyExists(params, s3)
|
||||||
// const fs = require('fs')
|
if (previousAsset.isAlreadyInBucket) {
|
||||||
// const stl = fs.readFileSync(`/tmp/${tempFile}/output.stl`, {
|
console.log('already in bucket')
|
||||||
// encoding: 'base64',
|
const response = {
|
||||||
// })
|
statusCode: 200,
|
||||||
// console.log('encoded stl', stl)
|
body: JSON.stringify({
|
||||||
// const response = {
|
url: getObjectUrl({ ...params }, s3),
|
||||||
// statusCode: 200,
|
consoleMessage: previousAsset.consoleMessage,
|
||||||
// headers: {
|
}),
|
||||||
// 'content-type': 'application/stl',
|
}
|
||||||
// },
|
callback(null, response)
|
||||||
// body: stl,
|
return
|
||||||
// isBase64Encoded: true,
|
}
|
||||||
// }
|
const { file } = JSON.parse(eventBody)
|
||||||
// console.log('callback fired')
|
const { error, consoleMessage, fullPath } = await stlExport({ file })
|
||||||
// callback(null, response)
|
await storeAssetAndReturnUrl({
|
||||||
// }
|
error,
|
||||||
// }
|
callback,
|
||||||
|
fullPath,
|
||||||
|
consoleMessage,
|
||||||
|
key: stlKey,
|
||||||
|
s3,
|
||||||
|
params,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
// stl: middy(stl).use(cors()),
|
stl: middy(stl).use(cors()),
|
||||||
preview: middy(loggerWrap(preview)).use(cors()),
|
preview: middy(loggerWrap(preview)).use(cors()),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,14 +35,15 @@ module.exports.runScad = async ({
|
|||||||
|
|
||||||
module.exports.stlExport = async ({ file } = {}) => {
|
module.exports.stlExport = async ({ file } = {}) => {
|
||||||
const tempFile = await makeFile(file, '.scad', nanoid)
|
const tempFile = await makeFile(file, '.scad', nanoid)
|
||||||
|
const fullPath = `/tmp/${tempFile}/output.stl`
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await runCommand(
|
const consoleMessage = await runCommand(
|
||||||
`openscad -o /tmp/${tempFile}/output.stl /tmp/${tempFile}/main.scad`,
|
`xvfb-run --auto-servernum --server-args "-screen 0 1024x768x24" openscad -o ${fullPath} /tmp/${tempFile}/main.scad`,
|
||||||
300000 // lambda will time out before this, we might need to look at background jobs if we do git integration stl generation
|
60000 // lambda will time out before this, we might need to look at background jobs if we do git integration stl generation
|
||||||
)
|
)
|
||||||
return { result, tempFile }
|
return { consoleMessage, fullPath }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return { error, tempFile }
|
return { error, fullPath }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,18 +63,21 @@ functions:
|
|||||||
timeout: 25
|
timeout: 25
|
||||||
environment:
|
environment:
|
||||||
BUCKET: cad-preview-bucket-prod-001
|
BUCKET: cad-preview-bucket-prod-001
|
||||||
# openscadstl:
|
openscadstl:
|
||||||
# image:
|
image:
|
||||||
# name: openscadimage
|
name: openscadimage
|
||||||
# command:
|
command:
|
||||||
# - openscad.stl
|
- openscad.stl
|
||||||
# entryPoint:
|
entryPoint:
|
||||||
# - '/entrypoint.sh'
|
- '/entrypoint.sh'
|
||||||
# events:
|
events:
|
||||||
# - http:
|
- http:
|
||||||
# path: openscad/stl
|
path: openscad/stl
|
||||||
# method: post
|
method: post
|
||||||
# timeout: 30
|
cors: true
|
||||||
|
timeout: 30
|
||||||
|
environment:
|
||||||
|
BUCKET: cad-preview-bucket-prod-001
|
||||||
cadquerystl:
|
cadquerystl:
|
||||||
image:
|
image:
|
||||||
name: cadqueryimage
|
name: cadqueryimage
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
"@redwoodjs/forms": "^0.31.0",
|
"@redwoodjs/forms": "^0.31.0",
|
||||||
"@redwoodjs/router": "^0.31.0",
|
"@redwoodjs/router": "^0.31.0",
|
||||||
"@redwoodjs/web": "^0.31.0",
|
"@redwoodjs/web": "^0.31.0",
|
||||||
|
"browser-fs-access": "^0.17.2",
|
||||||
"cloudinary-react": "^1.6.7",
|
"cloudinary-react": "^1.6.7",
|
||||||
"controlkit": "^0.1.9",
|
"controlkit": "^0.1.9",
|
||||||
"get-active-classes": "^0.0.11",
|
"get-active-classes": "^0.0.11",
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ const IdeContainer = () => {
|
|||||||
})
|
})
|
||||||
thunkDispatch((dispatch, getState) => {
|
thunkDispatch((dispatch, getState) => {
|
||||||
const state = getState()
|
const state = getState()
|
||||||
if (state.ideType === 'openScad') {
|
if (['png', 'INIT'].includes(state.objectData?.type)) {
|
||||||
dispatch({ type: 'setLoading' })
|
dispatch({ type: 'setLoading' })
|
||||||
requestRender({
|
requestRender({
|
||||||
state,
|
state,
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ import { useIdeState, codeStorageKey } from 'src/helpers/hooks/useIdeState'
|
|||||||
import { copyTextToClipboard } from 'src/helpers/clipboard'
|
import { copyTextToClipboard } from 'src/helpers/clipboard'
|
||||||
import { requestRender } from 'src/helpers/hooks/useIdeState'
|
import { requestRender } from 'src/helpers/hooks/useIdeState'
|
||||||
import { encode, decode } from 'src/helpers/compress'
|
import { encode, decode } from 'src/helpers/compress'
|
||||||
import { flow } from 'lodash/fp'
|
import { flow, identity } from 'lodash/fp'
|
||||||
|
import { fileSave } from 'browser-fs-access'
|
||||||
|
import { MeshBasicMaterial, Mesh, Scene } from 'three'
|
||||||
|
import { STLExporter } from 'three/examples/jsm/exporters/STLExporter'
|
||||||
|
|
||||||
export const githubSafe = (url) =>
|
export const githubSafe = (url) =>
|
||||||
url.includes('github.com')
|
url.includes('github.com')
|
||||||
@@ -80,6 +83,63 @@ const IdeToolbarNew = ({ cadPackage }) => {
|
|||||||
copyTextToClipboard(window.location.href)
|
copyTextToClipboard(window.location.href)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const PullTitleFromFirstLine = (code) => {
|
||||||
|
const firstLine = code.split('\n').filter(identity)[0]
|
||||||
|
if (!(firstLine.startsWith('//') || firstLine.startsWith('#'))) {
|
||||||
|
return 'object.stl'
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
(firstLine.replace(/^(\/\/|#)\s*(.+)/, (_, __, titleWithSpaces) =>
|
||||||
|
titleWithSpaces.replaceAll(/\s/g, '-')
|
||||||
|
) || 'object') + '.stl'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleStlDownload = (({ geometry, fileName, type }) => () => {
|
||||||
|
const makeStlBlobFromGeo = flow(
|
||||||
|
(geo) => new Mesh(geo, new MeshBasicMaterial()),
|
||||||
|
(mesh) => new Scene().add(mesh),
|
||||||
|
(scene) => new STLExporter().parse(scene),
|
||||||
|
(stl) =>
|
||||||
|
new Blob([stl], {
|
||||||
|
type: 'text/plain',
|
||||||
|
})
|
||||||
|
)
|
||||||
|
const saveFile = (geometry) => {
|
||||||
|
const blob = makeStlBlobFromGeo(geometry)
|
||||||
|
fileSave(blob, {
|
||||||
|
fileName,
|
||||||
|
extensions: ['.stl'],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (geometry) {
|
||||||
|
if (type === 'geometry') {
|
||||||
|
saveFile(geometry)
|
||||||
|
} else {
|
||||||
|
thunkDispatch((dispatch, getState) => {
|
||||||
|
const state = getState()
|
||||||
|
if (state.ideType === 'openScad') {
|
||||||
|
thunkDispatch((dispatch, getState) => {
|
||||||
|
const state = getState()
|
||||||
|
dispatch({ type: 'setLoading' })
|
||||||
|
requestRender({
|
||||||
|
state,
|
||||||
|
dispatch,
|
||||||
|
code: state.code,
|
||||||
|
viewerSize: state.viewerSize,
|
||||||
|
camera: state.camera,
|
||||||
|
specialCadProcess: 'stl',
|
||||||
|
}).then((result) => result && saveFile(result.data))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})({
|
||||||
|
type: state.objectData?.type,
|
||||||
|
geometry: state.objectData?.data,
|
||||||
|
fileName: PullTitleFromFirstLine(state.code),
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IdeContext.Provider value={{ state, thunkDispatch }}>
|
<IdeContext.Provider value={{ state, thunkDispatch }}>
|
||||||
@@ -97,6 +157,12 @@ const IdeToolbarNew = ({ cadPackage }) => {
|
|||||||
>
|
>
|
||||||
Copy link
|
Copy link
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={handleStlDownload}
|
||||||
|
className="border-2 text-gray-700 px-2 text-sm m-1 ml-2"
|
||||||
|
>
|
||||||
|
Download STL
|
||||||
|
</button>
|
||||||
</nav>
|
</nav>
|
||||||
<IdeContainer />
|
<IdeContainer />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,33 +9,16 @@ import {
|
|||||||
} from 'react-three-fiber'
|
} from 'react-three-fiber'
|
||||||
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
|
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
|
||||||
import { Vector3 } from 'three'
|
import { Vector3 } from 'three'
|
||||||
import { STLLoader } from 'three/examples/jsm/loaders/STLLoader'
|
|
||||||
import { requestRender } from 'src/helpers/hooks/useIdeState'
|
import { requestRender } from 'src/helpers/hooks/useIdeState'
|
||||||
|
|
||||||
extend({ OrbitControls })
|
extend({ OrbitControls })
|
||||||
|
|
||||||
function Asset({ url, resetLoading, setLoading }) {
|
function Asset({ geometry: incomingGeo }) {
|
||||||
const [loadedGeometry, setLoadedGeometry] = useState()
|
|
||||||
const mesh = useRef()
|
const mesh = useRef()
|
||||||
const ref = useUpdate((geometry) => {
|
const ref = useUpdate((geometry) => {
|
||||||
geometry.attributes = loadedGeometry.attributes
|
geometry.attributes = incomingGeo.attributes
|
||||||
})
|
})
|
||||||
useEffect(() => {
|
if (!incomingGeo) return null
|
||||||
if (url) {
|
|
||||||
const loader = new STLLoader()
|
|
||||||
setLoading()
|
|
||||||
loader.load(
|
|
||||||
url,
|
|
||||||
(geometry) => {
|
|
||||||
setLoadedGeometry(geometry)
|
|
||||||
resetLoading()
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
resetLoading
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}, [url])
|
|
||||||
if (!loadedGeometry) return null
|
|
||||||
return (
|
return (
|
||||||
<mesh ref={mesh} scale={[1, 1, 1]}>
|
<mesh ref={mesh} scale={[1, 1, 1]}>
|
||||||
<bufferGeometry attach="geometry" ref={ref} />
|
<bufferGeometry attach="geometry" ref={ref} />
|
||||||
@@ -158,9 +141,6 @@ const IdeViewer = () => {
|
|||||||
const [isDragging, setIsDragging] = useState(false)
|
const [isDragging, setIsDragging] = useState(false)
|
||||||
const [image, setImage] = useState()
|
const [image, setImage] = useState()
|
||||||
|
|
||||||
const resetLoading = () => thunkDispatch({ type: 'resetLoading' })
|
|
||||||
const setLoading = () => thunkDispatch({ type: 'setLoading' })
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setImage(state.objectData?.type === 'png' && state.objectData?.data)
|
setImage(state.objectData?.type === 'png' && state.objectData?.data)
|
||||||
setIsDragging(false)
|
setIsDragging(false)
|
||||||
@@ -192,7 +172,7 @@ const IdeViewer = () => {
|
|||||||
)}
|
)}
|
||||||
<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 || state.ideType !== 'openScad')
|
!(isDragging || state.objectData?.type !== 'png')
|
||||||
? 'hover:opacity-50'
|
? 'hover:opacity-50'
|
||||||
: 'opacity-100'
|
: 'opacity-100'
|
||||||
}`}
|
}`}
|
||||||
@@ -208,7 +188,7 @@ const IdeViewer = () => {
|
|||||||
})
|
})
|
||||||
thunkDispatch((dispatch, getState) => {
|
thunkDispatch((dispatch, getState) => {
|
||||||
const state = getState()
|
const state = getState()
|
||||||
if (state.ideType === 'openScad') {
|
if (['png', 'INIT'].includes(state.objectData?.type)) {
|
||||||
dispatch({ type: 'setLoading' })
|
dispatch({ type: 'setLoading' })
|
||||||
requestRender({
|
requestRender({
|
||||||
state,
|
state,
|
||||||
@@ -223,7 +203,7 @@ const IdeViewer = () => {
|
|||||||
/>
|
/>
|
||||||
<ambientLight />
|
<ambientLight />
|
||||||
<pointLight position={[15, 5, 10]} />
|
<pointLight position={[15, 5, 10]} />
|
||||||
{state.ideType === 'openScad' && (
|
{state.objectData?.type === 'png' && (
|
||||||
<>
|
<>
|
||||||
<Sphere position={[0, 0, 0]} color={pink400} />
|
<Sphere position={[0, 0, 0]} color={pink400} />
|
||||||
<Box position={[0, 50, 0]} size={[1, 100, 1]} color={indigo900} />
|
<Box position={[0, 50, 0]} size={[1, 100, 1]} color={indigo900} />
|
||||||
@@ -235,13 +215,11 @@ const IdeViewer = () => {
|
|||||||
<Box position={[50, 0, 0]} size={[100, 1, 1]} color={pink400} />
|
<Box position={[50, 0, 0]} size={[100, 1, 1]} color={pink400} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{state.ideType === 'cadQuery' && (
|
|
||||||
<Asset
|
<Asset
|
||||||
url={state.objectData?.type === 'stl' && state.objectData?.data}
|
geometry={
|
||||||
resetLoading={resetLoading}
|
state.objectData?.type === 'geometry' && state.objectData?.data
|
||||||
setLoading={setLoading}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
</Canvas>
|
</Canvas>
|
||||||
</div>
|
</div>
|
||||||
{state.isLoading && (
|
{state.isLoading && (
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
import { lambdaBaseURL } from './common'
|
import {
|
||||||
|
lambdaBaseURL,
|
||||||
|
stlToGeometry,
|
||||||
|
createHealthyResponse,
|
||||||
|
createUnhealthyResponse,
|
||||||
|
} from './common'
|
||||||
|
|
||||||
export const render = async ({ code }) => {
|
export const render = async ({ code }) => {
|
||||||
const body = JSON.stringify({
|
const body = JSON.stringify({
|
||||||
@@ -14,47 +19,26 @@ export const render = async ({ code }) => {
|
|||||||
body,
|
body,
|
||||||
})
|
})
|
||||||
if (response.status === 400) {
|
if (response.status === 400) {
|
||||||
// TODO add proper error messages for CadQuery
|
|
||||||
const { error } = await response.json()
|
const { error } = await response.json()
|
||||||
const cleanedErrorMessage = error.replace(
|
|
||||||
/["|']\/tmp\/.+\/main.scad["|']/g,
|
|
||||||
"'main.scad'"
|
|
||||||
)
|
|
||||||
return {
|
return {
|
||||||
status: 'error',
|
status: 'error',
|
||||||
message: {
|
message: {
|
||||||
type: 'error',
|
type: 'error',
|
||||||
message: cleanedErrorMessage,
|
message: error,
|
||||||
time: new Date(),
|
time: new Date(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const data = await response.json()
|
const data = await response.json()
|
||||||
return {
|
const geometry = await stlToGeometry(data.url)
|
||||||
status: 'healthy',
|
return createHealthyResponse({
|
||||||
objectData: {
|
type: 'geometry',
|
||||||
type: 'stl',
|
data: geometry,
|
||||||
data: data.url,
|
consoleMessage: data.consoleMessage,
|
||||||
},
|
date: new Date(),
|
||||||
message: {
|
})
|
||||||
type: 'message',
|
|
||||||
message: data.consoleMessage || 'Successful Render',
|
|
||||||
time: new Date(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// TODO handle errors better
|
return createUnhealthyResponse(new Date())
|
||||||
// I think we should display something overlayed on the viewer window something like "network issue try again"
|
|
||||||
// and in future I think we need timeouts differently as they maybe from a user trying to render something too complex
|
|
||||||
// or something with minkowski in it :/ either way something like "render timed out, try again or here are tips to reduce part complexity" with a link talking about $fn and minkowski etc
|
|
||||||
return {
|
|
||||||
status: 'error',
|
|
||||||
message: {
|
|
||||||
type: 'error',
|
|
||||||
message: 'network issue',
|
|
||||||
time: new Date(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,40 @@
|
|||||||
|
import { STLLoader } from 'three/examples/jsm/loaders/STLLoader'
|
||||||
|
|
||||||
export const lambdaBaseURL =
|
export const lambdaBaseURL =
|
||||||
process.env.CAD_LAMBDA_BASE_URL ||
|
process.env.CAD_LAMBDA_BASE_URL ||
|
||||||
'https://2inlbple1b.execute-api.us-east-1.amazonaws.com/prod2'
|
'https://2inlbple1b.execute-api.us-east-1.amazonaws.com/prod2'
|
||||||
|
|
||||||
|
export const stlToGeometry = (url) =>
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
new STLLoader().load(url, resolve, null, reject)
|
||||||
|
})
|
||||||
|
|
||||||
|
export function createHealthyResponse({ date, data, consoleMessage, type }) {
|
||||||
|
return {
|
||||||
|
status: 'healthy',
|
||||||
|
objectData: {
|
||||||
|
type,
|
||||||
|
data: data,
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
type: 'message',
|
||||||
|
message: consoleMessage,
|
||||||
|
time: date,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createUnhealthyResponse(date, message = 'network issue') {
|
||||||
|
// TODO handle errors better
|
||||||
|
// I think we should display something overlayed on the viewer window something like "network issue try again"
|
||||||
|
// and in future I think we need timeouts differently as they maybe from a user trying to render something too complex
|
||||||
|
// or something with minkowski in it :/ either way something like "render timed out, try again or here are tips to reduce part complexity" with a link talking about $fn and minkowski etc
|
||||||
|
return {
|
||||||
|
status: 'error',
|
||||||
|
message: {
|
||||||
|
type: 'error',
|
||||||
|
message,
|
||||||
|
time: date,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
import { lambdaBaseURL } from './common'
|
import {
|
||||||
|
lambdaBaseURL,
|
||||||
|
stlToGeometry,
|
||||||
|
createHealthyResponse,
|
||||||
|
createUnhealthyResponse,
|
||||||
|
} from './common'
|
||||||
|
|
||||||
export const render = async ({ code, settings }) => {
|
export const render = async ({ code, settings }) => {
|
||||||
const pixelRatio = window.devicePixelRatio || 1
|
const pixelRatio = window.devicePixelRatio || 1
|
||||||
@@ -41,51 +46,61 @@ export const render = async ({ code, settings }) => {
|
|||||||
})
|
})
|
||||||
if (response.status === 400) {
|
if (response.status === 400) {
|
||||||
const { error } = await response.json()
|
const { error } = await response.json()
|
||||||
const cleanedErrorMessage = error.replace(
|
const cleanedErrorMessage = cleanError(error)
|
||||||
/["|']\/tmp\/.+\/main.scad["|']/g,
|
return createUnhealthyResponse(new Date(), cleanedErrorMessage)
|
||||||
"'main.scad'"
|
|
||||||
)
|
|
||||||
return {
|
|
||||||
status: 'error',
|
|
||||||
message: {
|
|
||||||
type: 'error',
|
|
||||||
message: cleanedErrorMessage,
|
|
||||||
time: new Date(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
const data = await response.json()
|
const data = await response.json()
|
||||||
return {
|
const type = data.type !== 'stl' ? 'png' : 'geometry'
|
||||||
status: 'healthy',
|
const newData = data.type !== 'stl' ? data.url : stlToGeometry(data.url)
|
||||||
objectData: {
|
return createHealthyResponse({
|
||||||
type: 'png',
|
type,
|
||||||
data: data.url,
|
data: await newData,
|
||||||
},
|
consoleMessage: data.consoleMessage,
|
||||||
message: {
|
date: new Date(),
|
||||||
type: 'message',
|
})
|
||||||
message: data.consoleMessage,
|
|
||||||
time: new Date(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// TODO handle errors better
|
return createUnhealthyResponse(new Date())
|
||||||
// I think we should display something overlayed on the viewer window something like "network issue try again"
|
|
||||||
// and in future I think we need timeouts differently as they maybe from a user trying to render something too complex
|
|
||||||
// or something with minkowski in it :/ either way something like "render timed out, try again or here are tips to reduce part complexity" with a link talking about $fn and minkowski etc
|
|
||||||
return {
|
|
||||||
status: 'error',
|
|
||||||
message: {
|
|
||||||
type: 'error',
|
|
||||||
message: 'network issue',
|
|
||||||
time: new Date(),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const stl = async ({ code, settings }) => {
|
||||||
|
const body = JSON.stringify({
|
||||||
|
settings: {},
|
||||||
|
file: code,
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
const response = await fetch(lambdaBaseURL + '/openscad/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)
|
||||||
|
}
|
||||||
|
const data = await response.json()
|
||||||
|
const geometry = await stlToGeometry(data.url)
|
||||||
|
return createHealthyResponse({
|
||||||
|
type: 'geometry',
|
||||||
|
data: geometry,
|
||||||
|
consoleMessage: data.consoleMessage,
|
||||||
|
date: new Date(),
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
return createUnhealthyResponse(new Date())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const openScad = {
|
const openScad = {
|
||||||
render,
|
render,
|
||||||
// more functions to come
|
stl,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default openScad
|
export default openScad
|
||||||
|
|
||||||
|
function cleanError(error) {
|
||||||
|
return error.replace(/["|']\/tmp\/.+\/main.scad["|']/g, "'main.scad'")
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,7 +9,10 @@ function withThunk(dispatch, getState) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const initCodeMap = {
|
const initCodeMap = {
|
||||||
openScad: `
|
openScad: `// involute donut
|
||||||
|
|
||||||
|
// ^ first comment is used for download title (i.e "involute-donut.stl")
|
||||||
|
|
||||||
color(c="DarkGoldenrod")rotate_extrude()translate([20,0])circle(d=30);
|
color(c="DarkGoldenrod")rotate_extrude()translate([20,0])circle(d=30);
|
||||||
donut();
|
donut();
|
||||||
module donut() {
|
module donut() {
|
||||||
@@ -23,7 +26,11 @@ module stick(basewid, angl){
|
|||||||
translate([0,0,10])sphere(9);
|
translate([0,0,10])sphere(9);
|
||||||
}
|
}
|
||||||
}`,
|
}`,
|
||||||
cadQuery: `import cadquery as cq
|
cadQuery: `# demo shaft coupler
|
||||||
|
|
||||||
|
# ^ first comment is used for download title (i.e. "demo-shaft-coupler.stl")
|
||||||
|
|
||||||
|
import cadquery as cq
|
||||||
from cadquery import exporters
|
from cadquery import exporters
|
||||||
|
|
||||||
diam = 5.0
|
diam = 5.0
|
||||||
@@ -49,7 +56,7 @@ export const useIdeState = () => {
|
|||||||
],
|
],
|
||||||
code,
|
code,
|
||||||
objectData: {
|
objectData: {
|
||||||
type: 'stl',
|
type: 'INIT',
|
||||||
data: null,
|
data: null,
|
||||||
},
|
},
|
||||||
layout: {
|
layout: {
|
||||||
@@ -138,11 +145,16 @@ export const requestRender = ({
|
|||||||
code,
|
code,
|
||||||
camera,
|
camera,
|
||||||
viewerSize,
|
viewerSize,
|
||||||
|
specialCadProcess = null,
|
||||||
}) => {
|
}) => {
|
||||||
|
if (
|
||||||
state.ideType !== 'INIT' &&
|
state.ideType !== 'INIT' &&
|
||||||
!state.isLoading &&
|
(!state.isLoading || state.objectData?.type === 'INIT')
|
||||||
cadPackages[state.ideType]
|
) {
|
||||||
.render({
|
const renderFn = specialCadProcess
|
||||||
|
? cadPackages[state.ideType][specialCadProcess]
|
||||||
|
: cadPackages[state.ideType].render
|
||||||
|
return renderFn({
|
||||||
code,
|
code,
|
||||||
settings: {
|
settings: {
|
||||||
camera,
|
camera,
|
||||||
@@ -160,7 +172,9 @@ export const requestRender = ({
|
|||||||
type: 'healthyRender',
|
type: 'healthyRender',
|
||||||
payload: { objectData, message, lastRunCode: code },
|
payload: { objectData, message, lastRunCode: code },
|
||||||
})
|
})
|
||||||
|
return objectData
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => dispatch({ type: 'resetLoading' })) // TODO should probably display something to the user here
|
.catch(() => dispatch({ type: 'resetLoading' })) // TODO should probably display something to the user here
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -3097,6 +3097,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/line-column/-/line-column-1.0.0.tgz#fa5a59c21e885fef3739a273b43dacf55b63437f"
|
resolved "https://registry.yarnpkg.com/@types/line-column/-/line-column-1.0.0.tgz#fa5a59c21e885fef3739a273b43dacf55b63437f"
|
||||||
integrity sha512-wbw+IDRw/xY/RGy+BL6f4Eey4jsUgHQrMuA4Qj0CSG3x/7C2Oc57pmRoM2z3M4DkylWRz+G1pfX06sCXQm0J+w==
|
integrity sha512-wbw+IDRw/xY/RGy+BL6f4Eey4jsUgHQrMuA4Qj0CSG3x/7C2Oc57pmRoM2z3M4DkylWRz+G1pfX06sCXQm0J+w==
|
||||||
|
|
||||||
|
"@types/lodash@^4.14.170":
|
||||||
|
version "4.14.170"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.170.tgz#0d67711d4bf7f4ca5147e9091b847479b87925d6"
|
||||||
|
integrity sha512-bpcvu/MKHHeYX+qeEN8GE7DIravODWdACVA1ctevD8CN24RhPZIKMn9ntfAsrvLfSX3cR5RrBKAbYm9bGs0A+Q==
|
||||||
|
|
||||||
"@types/long@^4.0.0":
|
"@types/long@^4.0.0":
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9"
|
resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9"
|
||||||
@@ -4822,6 +4827,11 @@ brorand@^1.0.1, brorand@^1.1.0:
|
|||||||
resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
|
resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
|
||||||
integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=
|
integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=
|
||||||
|
|
||||||
|
browser-fs-access@^0.17.2:
|
||||||
|
version "0.17.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/browser-fs-access/-/browser-fs-access-0.17.2.tgz#6debfe35ebce77a1eecca7822a449c740a8de9db"
|
||||||
|
integrity sha512-z3H37rU3fmKkGJ1r09v3hAwByUI0vYIh8a7LsUaQnnMxdVUJm1UzsffvNK6Qnvk9jGqvY7uiX2DPu4BZXavcWA==
|
||||||
|
|
||||||
browser-process-hrtime@^1.0.0:
|
browser-process-hrtime@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626"
|
resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626"
|
||||||
|
|||||||
Reference in New Issue
Block a user