diff --git a/web/src/components/IdeContainer/IdeContainer.js b/web/src/components/IdeContainer/IdeContainer.js
index e61b426..6f852aa 100644
--- a/web/src/components/IdeContainer/IdeContainer.js
+++ b/web/src/components/IdeContainer/IdeContainer.js
@@ -1,6 +1,7 @@
import { useContext, useRef, useEffect } from 'react'
import { Mosaic, MosaicWindow } from 'react-mosaic-component'
import { IdeContext } from 'src/components/IdeToolbarNew'
+import { requestRender } from 'src/helpers/hooks/useIdeState'
import IdeEditor from 'src/components/IdeEditor'
import IdeViewer from 'src/components/IdeViewer'
import IdeConsole from 'src/components/IdeConsole'
@@ -13,7 +14,7 @@ const ELEMENT_MAP = {
}
const IdeContainer = () => {
- const { state, dispatch } = useContext(IdeContext)
+ const { state, thunkDispatch } = useContext(IdeContext)
const viewerDOM = useRef(null)
const debounceTimeoutId = useRef
@@ -22,12 +23,22 @@ const IdeContainer = () => {
function handleViewerSizeUpdate() {
if (viewerDOM !== null && viewerDOM.current) {
const { width, height } = viewerDOM.current.getBoundingClientRect()
- dispatch({
- type: 'render',
- payload: {
- code: state.code,
- viewerSize: { width, height },
- },
+ thunkDispatch({
+ type: 'updateViewerSize',
+ payload: { viewerSize: { width, height } },
+ })
+ thunkDispatch((dispatch, getState) => {
+ const state = getState()
+ if (state.ideType === 'openScad') {
+ dispatch({ type: 'setLoading' })
+ requestRender({
+ state,
+ dispatch,
+ code: state.code,
+ viewerSize: { width, height },
+ camera: state.camera,
+ })
+ }
})
}
}
@@ -49,20 +60,27 @@ const IdeContainer = () => {
return (
(
-
- {id === 'Viewer' ? (
-
- {ELEMENT_MAP[id]}
-
- ) : (
- ELEMENT_MAP[id]
- )}
-
- )}
+ renderTile={(id, path) => {
+ const title = id === 'Editor' ? `${id} (${state.ideType})` : id
+ return (
+
+ {id === 'Viewer' ? (
+
+ {ELEMENT_MAP[id]}
+
+ ) : (
+ ELEMENT_MAP[id]
+ )}
+
+ )
+ }}
value={state.layout}
onChange={(newLayout) =>
- dispatch({ type: 'setLayout', payload: { message: newLayout } })
+ thunkDispatch({ type: 'setLayout', payload: { message: newLayout } })
}
onRelease={handleViewerSizeUpdate}
/>
diff --git a/web/src/components/IdeEditor/IdeEditor.js b/web/src/components/IdeEditor/IdeEditor.js
index 7ad5ad5..bbc0edd 100644
--- a/web/src/components/IdeEditor/IdeEditor.js
+++ b/web/src/components/IdeEditor/IdeEditor.js
@@ -2,10 +2,15 @@ import { useContext, useEffect, Suspense, lazy } from 'react'
import { isBrowser } from '@redwoodjs/prerender/browserUtils'
import { IdeContext } from 'src/components/IdeToolbarNew'
import { codeStorageKey } from 'src/helpers/hooks/useIdeState'
+import { requestRender } from 'src/helpers/hooks/useIdeState'
const Editor = lazy(() => import('@monaco-editor/react'))
const IdeEditor = () => {
- const { state, dispatch } = useContext(IdeContext)
+ const { state, thunkDispatch } = useContext(IdeContext)
+ const ideTypeToLanguageMap = {
+ cadQuery: 'python',
+ openScad: 'cpp',
+ }
const scriptKey = 'encoded_script'
useEffect(() => {
@@ -17,7 +22,7 @@ const IdeEditor = () => {
const [key, scriptBase64] = hash.slice(1).split('=')
if (key === scriptKey) {
const script = atob(scriptBase64)
- dispatch({ type: 'updateCode', payload: script })
+ thunkDispatch({ type: 'updateCode', payload: script })
}
}, [])
useEffect(() => {
@@ -27,25 +32,38 @@ const IdeEditor = () => {
}, [state.code])
function handleCodeChange(value, _event) {
- dispatch({ type: 'updateCode', payload: value })
+ thunkDispatch({ type: 'updateCode', payload: value })
}
function handleSaveHotkey(event) {
//ctrl|meta + s is very intuitive for most devs
const { key, ctrlKey, metaKey } = event
if (key === 's' && (ctrlKey || metaKey)) {
event.preventDefault()
- dispatch({ type: 'render', payload: { code: state.code } })
+ thunkDispatch((dispatch, getState) => {
+ const state = getState()
+ dispatch({ type: 'setLoading' })
+ requestRender({
+ state,
+ dispatch,
+ code: state.code,
+ viewerSize: state.viewerSize,
+ camera: state.camera,
+ })
+ })
localStorage.setItem(codeStorageKey, state.code)
}
}
-
return (
-
+
. . . loading
}>
diff --git a/web/src/components/IdeToolbarNew/IdeToolbarNew.js b/web/src/components/IdeToolbarNew/IdeToolbarNew.js
index 4920515..fa10a77 100644
--- a/web/src/components/IdeToolbarNew/IdeToolbarNew.js
+++ b/web/src/components/IdeToolbarNew/IdeToolbarNew.js
@@ -3,15 +3,26 @@ import IdeContainer from 'src/components/IdeContainer'
import { isBrowser } from '@redwoodjs/prerender/browserUtils'
import { useIdeState, codeStorageKey } from 'src/helpers/hooks/useIdeState'
import { copyTextToClipboard } from 'src/helpers/clipboard'
+import { requestRender } from 'src/helpers/hooks/useIdeState'
export const IdeContext = createContext()
const IdeToolbarNew = () => {
- const [state, dispatch] = useIdeState()
+ const [state, thunkDispatch] = useIdeState()
function setIdeType(ide) {
- dispatch({ type: 'setIdeType', payload: { message: ide } })
+ thunkDispatch({ type: 'setIdeType', payload: { message: ide } })
}
function handleRender() {
- dispatch({ type: 'render', payload: { code: state.code } })
+ thunkDispatch((dispatch, getState) => {
+ const state = getState()
+ dispatch({ type: 'setLoading' })
+ requestRender({
+ state,
+ dispatch,
+ code: state.code,
+ viewerSize: state.viewerSize,
+ camera: state.camera,
+ })
+ })
localStorage.setItem(codeStorageKey, state.code)
}
function handleMakeLink() {
@@ -23,21 +34,17 @@ const IdeToolbarNew = () => {
}
return (
-
+
)}
- {state.isLoading && (
-
- )}
- setIsDragging(true)}
>
+ {state.isLoading && (
+
+ )}
)
}
diff --git a/web/src/helpers/cadPackages/cadQueryController.js b/web/src/helpers/cadPackages/cadQueryController.js
new file mode 100644
index 0000000..b57a7a7
--- /dev/null
+++ b/web/src/helpers/cadPackages/cadQueryController.js
@@ -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}`
+}
diff --git a/web/src/helpers/cadPackages/index.js b/web/src/helpers/cadPackages/index.js
index ab938ae..19494ad 100644
--- a/web/src/helpers/cadPackages/index.js
+++ b/web/src/helpers/cadPackages/index.js
@@ -1,7 +1,7 @@
import openScad from './openScadController'
-import openCascade from './newCascadeController'
+import cadQuery from './cadQueryController'
export const cadPackages = {
openScad,
- openCascade,
-}
\ No newline at end of file
+ cadQuery,
+}
diff --git a/web/src/helpers/cadPackages/newCascadeController.js b/web/src/helpers/cadPackages/newCascadeController.js
deleted file mode 100644
index 5367fc3..0000000
--- a/web/src/helpers/cadPackages/newCascadeController.js
+++ /dev/null
@@ -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
\ No newline at end of file
diff --git a/web/src/helpers/cadPackages/openScadController.js b/web/src/helpers/cadPackages/openScadController.js
index 167174b..9672c5d 100644
--- a/web/src/helpers/cadPackages/openScadController.js
+++ b/web/src/helpers/cadPackages/openScadController.js
@@ -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,
})
diff --git a/web/src/helpers/hooks/useIdeState.js b/web/src/helpers/hooks/useIdeState.js
index a6e123b..971b7ee 100644
--- a/web/src/helpers/hooks/useIdeState.js
+++ b/web/src/helpers/hooks/useIdeState.js
@@ -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
}