Front end changes for cadquery
Basic changes to get the proof of concept working. Lots of attention was given to the store/reducer to solve existing problems with async code and stale closures, it seems even today how to handle this with use reducer is not quiet settle, I guess because once an app reaches a certain level of maturity everyone grabs an off the shelf solution to state management. I ended up implementing thunks because they are really rather simple. Interesting thread about it all here: https://gist.github.com/astoilkov/013c513e33fe95fa8846348038d8fe42#gistcomment-3377800 I also move some of settings that were persisted in the openScad controller into the data store as I ulimately thing what I was doing in that file was very confusing, with the fact that it had to be called multiple times with different information before it would be able to render something properly.
This commit is contained in:
68
web/src/helpers/cadPackages/cadQueryController.js
Normal file
68
web/src/helpers/cadPackages/cadQueryController.js
Normal file
@@ -0,0 +1,68 @@
|
||||
let openScadBaseURL = process.env.CADQUERY_BASE_URL
|
||||
|
||||
export const render = async ({ code }) => {
|
||||
const body = JSON.stringify({
|
||||
settings: {},
|
||||
file: code,
|
||||
})
|
||||
try {
|
||||
const response = await fetch(openScadBaseURL, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body,
|
||||
})
|
||||
if (response.status === 400) {
|
||||
// TODO add proper error messages for CadQuery
|
||||
const { error } = await response.json()
|
||||
const cleanedErrorMessage = error.replace(
|
||||
/["|']\/tmp\/.+\/main.scad["|']/g,
|
||||
"'main.scad'"
|
||||
)
|
||||
return {
|
||||
status: 'error',
|
||||
message: {
|
||||
type: 'error',
|
||||
message: addDateToLog(cleanedErrorMessage),
|
||||
},
|
||||
}
|
||||
}
|
||||
const data = await response.json()
|
||||
return {
|
||||
status: 'healthy',
|
||||
objectData: {
|
||||
type: 'stl',
|
||||
data: data.imageBase64,
|
||||
},
|
||||
message: {
|
||||
type: 'message',
|
||||
message: addDateToLog(data.result),
|
||||
},
|
||||
}
|
||||
} catch (e) {
|
||||
// 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: addDateToLog('network issue'),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const openScad = {
|
||||
render,
|
||||
// more functions to come
|
||||
}
|
||||
|
||||
export default openScad
|
||||
|
||||
function addDateToLog(message) {
|
||||
return `-> ${new Date().toLocaleString()}
|
||||
${message}`
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import openScad from './openScadController'
|
||||
import openCascade from './newCascadeController'
|
||||
import cadQuery from './cadQueryController'
|
||||
|
||||
export const cadPackages = {
|
||||
openScad,
|
||||
openCascade,
|
||||
}
|
||||
cadQuery,
|
||||
}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
// Rename this file to remove "new" once Cascade integration is complete
|
||||
|
||||
export const render = async ({ code, settings }) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
const shouldReject = Math.random() < 0.7
|
||||
if (shouldReject) {
|
||||
resolve({
|
||||
objectData: {
|
||||
type: 'stl',
|
||||
data: ((Math.random() * 256 + 1) >>> 0).toString(2), // Randomized 8-bit numbers for funzies
|
||||
},
|
||||
message: {
|
||||
type: 'message',
|
||||
message: `bodies rendered by: ${code}`,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
reject({
|
||||
message: {
|
||||
type: 'error',
|
||||
message: 'unable to parse line: x',
|
||||
},
|
||||
})
|
||||
}
|
||||
}, 700)
|
||||
})
|
||||
}
|
||||
|
||||
const openCascade = {
|
||||
render,
|
||||
// More functions to come
|
||||
}
|
||||
|
||||
export default openCascade
|
||||
@@ -2,33 +2,16 @@ let openScadBaseURL =
|
||||
process.env.OPENSCAD_BASE_URL ||
|
||||
'https://x2wvhihk56.execute-api.us-east-1.amazonaws.com/dev'
|
||||
|
||||
let lastViewPortSize = 'INIT'
|
||||
let lastCameraSettings = 'INIT'
|
||||
|
||||
export const render = async ({ code, settings }) => {
|
||||
const pixelRatio = window.devicePixelRatio || 1
|
||||
const size = settings.viewerSize
|
||||
? {
|
||||
x: Math.round(settings.viewerSize?.width * pixelRatio),
|
||||
y: Math.round(settings.viewerSize?.height * pixelRatio),
|
||||
}
|
||||
: lastViewPortSize
|
||||
const camera = settings.camera || lastCameraSettings
|
||||
if (settings.camera) {
|
||||
lastCameraSettings = settings.camera
|
||||
}
|
||||
if (settings.viewerSize) {
|
||||
lastViewPortSize = size
|
||||
}
|
||||
if ([camera, size].includes('INIT')) {
|
||||
return {
|
||||
status: 'insufficient-preview-info',
|
||||
}
|
||||
const size = {
|
||||
x: Math.round(settings.viewerSize?.width * pixelRatio),
|
||||
y: Math.round(settings.viewerSize?.height * pixelRatio),
|
||||
}
|
||||
const body = JSON.stringify({
|
||||
settings: {
|
||||
size,
|
||||
camera,
|
||||
camera: settings.camera,
|
||||
},
|
||||
file: code,
|
||||
})
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import { useReducer } from 'react'
|
||||
import { cadPackages } from 'src/helpers/cadPackages'
|
||||
|
||||
function withThunk(dispatch, getState) {
|
||||
return (actionOrThunk) =>
|
||||
typeof actionOrThunk === 'function'
|
||||
? actionOrThunk(dispatch, getState)
|
||||
: dispatch(actionOrThunk)
|
||||
}
|
||||
|
||||
const donutInitCode = `
|
||||
color(c="DarkGoldenrod")rotate_extrude()translate([20,0])circle(d=30);
|
||||
donut();
|
||||
@@ -17,16 +24,17 @@ module stick(basewid, angl){
|
||||
}`
|
||||
|
||||
export const codeStorageKey = 'Last-openscad-code'
|
||||
let mutableState = null
|
||||
|
||||
export const useIdeState = () => {
|
||||
const code = localStorage.getItem(codeStorageKey) || donutInitCode
|
||||
const initialState = {
|
||||
ideType: 'openScad',
|
||||
ideType: 'cadQuery',
|
||||
consoleMessages: [{ type: 'message', message: 'Initialising OpenSCAD' }],
|
||||
code,
|
||||
objectData: {
|
||||
type: 'stl',
|
||||
data: 'some binary',
|
||||
data: null,
|
||||
},
|
||||
layout: {
|
||||
direction: 'row',
|
||||
@@ -38,6 +46,8 @@ export const useIdeState = () => {
|
||||
splitPercentage: 70,
|
||||
},
|
||||
},
|
||||
camera: {},
|
||||
viewerSize: { width: 0, height: 0 },
|
||||
isLoading: false,
|
||||
}
|
||||
const reducer = (state, { type, payload }) => {
|
||||
@@ -74,51 +84,64 @@ export const useIdeState = () => {
|
||||
...state,
|
||||
layout: payload.message,
|
||||
}
|
||||
case 'updateCamera':
|
||||
return {
|
||||
...state,
|
||||
camera: payload.camera,
|
||||
}
|
||||
case 'updateViewerSize':
|
||||
return {
|
||||
...state,
|
||||
viewerSize: payload.viewerSize,
|
||||
}
|
||||
case 'setLoading':
|
||||
return {
|
||||
...state,
|
||||
isLoading: true,
|
||||
}
|
||||
case 'resetLoading':
|
||||
return {
|
||||
...state,
|
||||
isLoading: false,
|
||||
}
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
function dispatchMiddleware(dispatch, state) {
|
||||
return ({ type, payload }) => {
|
||||
switch (type) {
|
||||
case 'render':
|
||||
cadPackages[state.ideType]
|
||||
.render({
|
||||
code: payload.code,
|
||||
settings: {
|
||||
camera: payload.camera,
|
||||
viewerSize: payload.viewerSize,
|
||||
},
|
||||
})
|
||||
.then(({ objectData, message, status }) => {
|
||||
if (status === 'insufficient-preview-info') return
|
||||
if (status === 'error') {
|
||||
dispatch({
|
||||
type: 'errorRender',
|
||||
payload: { message },
|
||||
})
|
||||
} else {
|
||||
dispatch({
|
||||
type: 'healthyRender',
|
||||
payload: { objectData, message },
|
||||
})
|
||||
}
|
||||
})
|
||||
dispatch({ type: 'setLoading' })
|
||||
break
|
||||
|
||||
default:
|
||||
return dispatch({ type, payload })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const [state, dispatch] = useReducer(reducer, initialState)
|
||||
return [state, dispatchMiddleware(dispatch, state)]
|
||||
mutableState = state
|
||||
const getState = () => mutableState
|
||||
return [state, withThunk(dispatch, getState)]
|
||||
}
|
||||
|
||||
export const requestRender = ({
|
||||
state,
|
||||
dispatch,
|
||||
code,
|
||||
camera,
|
||||
viewerSize,
|
||||
}) => {
|
||||
cadPackages[state.ideType]
|
||||
.render({
|
||||
code,
|
||||
settings: {
|
||||
camera,
|
||||
viewerSize,
|
||||
},
|
||||
})
|
||||
.then(({ objectData, message, status }) => {
|
||||
if (status === 'error') {
|
||||
dispatch({
|
||||
type: 'errorRender',
|
||||
payload: { message },
|
||||
})
|
||||
} else {
|
||||
dispatch({
|
||||
type: 'healthyRender',
|
||||
payload: { objectData, message },
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch(() => dispatch({ type: 'resetLoading' })) // TODO should probably display something to the user here
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user