CadQuery MVP integration #281
@@ -1,6 +1,7 @@
|
||||
const express = require('express')
|
||||
var cors = require('cors')
|
||||
const axios = require('axios')
|
||||
const { restart } = require('nodemon')
|
||||
const app = express()
|
||||
const port = 8080
|
||||
app.use(express.json())
|
||||
@@ -10,19 +11,29 @@ const invocationURL = (port) =>
|
||||
`http://localhost:${port}/2015-03-31/functions/function/invocations`
|
||||
|
||||
app.post('/openscad/preview', async (req, res) => {
|
||||
const { data } = await axios.post(invocationURL(5052), {
|
||||
body: Buffer.from(JSON.stringify(req.body)).toString('base64'),
|
||||
})
|
||||
res.status(data.statusCode)
|
||||
res.send(data.body)
|
||||
try {
|
||||
const { data } = await axios.post(invocationURL(5052), {
|
||||
body: Buffer.from(JSON.stringify(req.body)).toString('base64'),
|
||||
})
|
||||
res.status(data.statusCode)
|
||||
res.send(data.body)
|
||||
} catch (e) {
|
||||
res.status(500)
|
||||
res.send()
|
||||
}
|
||||
})
|
||||
app.post('/cadquery/stl', async (req, res) => {
|
||||
console.log('making post request to 5060')
|
||||
const { data } = await axios.post(invocationURL(5060), {
|
||||
body: Buffer.from(JSON.stringify(req.body)).toString('base64'),
|
||||
})
|
||||
res.status(data.statusCode)
|
||||
res.send(data.body)
|
||||
try {
|
||||
const { data } = await axios.post(invocationURL(5060), {
|
||||
body: Buffer.from(JSON.stringify(req.body)).toString('base64'),
|
||||
})
|
||||
res.status(data.statusCode)
|
||||
res.send(data.body)
|
||||
} catch (e) {
|
||||
res.status(500)
|
||||
res.send()
|
||||
}
|
||||
})
|
||||
|
||||
app.listen(port, () => {
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@headlessui/react": "^1.0.0",
|
||||
|
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@monaco-editor/react": "^4.0.11",
|
||||
"@redwoodjs/auth": "^0.30.1",
|
||||
|
||||
@@ -35,7 +35,7 @@ const Routes = () => {
|
||||
)
|
||||
return (
|
||||
<Router>
|
||||
<Route path="/dev-ide" page={DevIdePage} name="devIde" />
|
||||
<Route path="/dev-ide/{cadPackage}" page={DevIdePage} name="devIde" />
|
||||
<Route path="/policies/privacy-policy" page={PrivacyPolicyPage} name="privacyPolicy" />
|
||||
|
Question about this @Irev-Dev : is this inclusion of the CAD package in the URL only for this Dev stage, or are you envisioning the package being encoded into people's project URLs? I think it can definitely work just checking your vision. Question about this @Irev-Dev : is this inclusion of the CAD package in the URL only for this Dev stage, or are you envisioning the package being encoded into people's project URLs? I think it can definitely work just checking your vision.
Good question, I think there's no harm in having cad-package in the url for new/draft parts, (I'm thinking we'll keep the swap over to the draft url that CascadeStudio currently uses at some point!?). But for saved parts, we'll be pulling information about what cad-package from the db along with the code so I think for that the URL will be the same as what is now for CascadeStudio Sorry I've already written too much, considerations for what's in this PR and the cad-package in the URL, If I wanted to instead put that info in the data-store. I would have had to add the context provider much higher in the app hierarchy. This is because the nav-bar where the plus button is, is higher than the ide right. That would be a fine thing to do and honestly I think it's only a matter of time before we do, but for the sake of not having to do that for this change I put it in the URL, and I like I said I don't see any harm for it staying there, unless you can think of one? but I wouldn't say I have a vision for it though haha. Good question, I think there's no harm in having cad-package in the url for new/draft parts, (I'm thinking we'll keep the swap over to the draft url that CascadeStudio currently uses at some point!?). But for saved parts, we'll be pulling information about what cad-package from the db along with the code so I think for that the URL will be the same as what is now for CascadeStudio `userName/partName`.
I don't think we've talked about it, but I think for parts that are hosted on github, in order for users to make the integration work they will need to include a `cadhub.json` in the root of the project (I'm leaning towards json, but could be yml or toml). It will be a place for us to get some meta information, the project title, the cad-package, and probably the entry/main file i.e `"main": "./src/cool.scad"` is what I can think of the top of my head (We'll need docs for these details). Maybe a Cadhub version too, incase we change the way the github integration works we might still be able to support the old integration style anyway getting ahead of myself. I was just trying to say for projects like this where they are not stored in our db but on github we can put insist on the cad-package info being in the repo (we could store meta-data in our db about projects but I think that defeats the purpose, better to have the repo to contain all the config).
Sorry I've already written too much, considerations for what's in this PR and the cad-package in the URL, If I wanted to instead put that info in the data-store. I would have had to add the context provider much higher in the app hierarchy. This is because the nav-bar where the plus button is, is higher than the ide right. That would be a fine thing to do and honestly I think it's only a matter of time before we do, but for the sake of not having to do that for this change I put it in the URL, and I like I said I don't see any harm for it staying there, unless you can think of one? but I wouldn't say I have a vision for it though haha.
Maybe in the future it could also have an array of files/directories that are dependencies so they can be collected by CadHub before execution?
In the future for performance reasons, would it make sense to cache the CodeCAD from GitHub on your side and only update that when a change is pushed to the main branch? > I think for parts that are hosted on github, in order for users to make the integration work they will need to include a cadhub.json in the root of the project
Maybe in the future it could also have an array of files/directories that are dependencies so they can be collected by CadHub before execution?
> I was just trying to say for projects like this where they are not stored in our db but on github we can put insist on the cad-package info being in the repo (we could store meta-data in our db about projects but I think that defeats the purpose, better to have the repo to contain all the config).
In the future for performance reasons, would it make sense to cache the CodeCAD from GitHub on your side and only update that when a change is pushed to the main branch?
Maybe, I know that it's possible to do a shallow clone, so maybe that will be fast enough that won't need an array of files but I guess performance might be the decider. In an ideal world I would prefer not to as that feels like an implementation leak to me, but we'll see.
I think long term some kind of caching makes sense. A friend of mine suggested "you can make the lambda post a SQS message and then use the docker container to process an SQS queue" so if I stick with AWS that might be an option that's more cache friendly than the current docker-lamda setup. > Maybe in the future it could also have an array of files/directories that are dependencies so they can be collected by CadHub before execution?
Maybe, I know that it's possible to do a shallow clone, so maybe that will be fast enough that won't need an array of files but I guess performance might be the decider. In an ideal world I would prefer not to as that feels like an implementation leak to me, but we'll see.
> In the future for performance reasons, would it make sense to cache the CodeCAD from GitHub on your side and only update that when a change is pushed to the main branch?
I think long term some kind of caching makes sense. A friend of mine suggested "you can make the lambda post a SQS message and then use the docker container to process an SQS queue" so if I stick with AWS that might be an option that's more cache friendly than the current docker-lamda setup.
|
||||
<Route path="/policies/code-of-conduct" page={CodeOfConductPage} name="codeOfConduct" />
|
||||
<Route path="/account-recovery/update-password" page={UpdatePasswordPage} name="updatePassword" />
|
||||
|
||||
@@ -12,25 +12,6 @@ const IdeEditor = () => {
|
||||
openScad: 'cpp',
|
||||
}
|
||||
|
||||
const scriptKey = 'encoded_script'
|
||||
useEffect(() => {
|
||||
// load code from hash if it's there
|
||||
let hash
|
||||
if (isBrowser) {
|
||||
hash = window.location.hash
|
||||
}
|
||||
const [key, scriptBase64] = hash.slice(1).split('=')
|
||||
if (key === scriptKey) {
|
||||
const script = atob(scriptBase64)
|
||||
thunkDispatch({ type: 'updateCode', payload: script })
|
||||
}
|
||||
}, [])
|
||||
useEffect(() => {
|
||||
if (isBrowser) {
|
||||
window.location.hash = ''
|
||||
}
|
||||
}, [state.code])
|
||||
|
||||
function handleCodeChange(value, _event) {
|
||||
thunkDispatch({ type: 'updateCode', payload: value })
|
||||
}
|
||||
@@ -61,6 +42,7 @@ const IdeEditor = () => {
|
||||
<Suspense fallback={<div>. . . loading</div>}>
|
||||
<Editor
|
||||
defaultValue={state.code}
|
||||
value={state.code}
|
||||
// TODO #247 cpp seems better than js for the time being
|
||||
defaultLanguage={ideTypeToLanguageMap[state.ideType] || 'cpp'}
|
||||
language={ideTypeToLanguageMap[state.ideType] || 'cpp'}
|
||||
|
I had missed you added this, what a great addition! And for it to be agnostic of the CAD package; this is the kind of unifying stuff that's gonna make it so nice to switch between packages. I had missed you added this, what a great addition! And for it to be agnostic of the CAD package; this is the kind of unifying stuff that's gonna make it so nice to switch between packages.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createContext } from 'react'
|
||||
import { createContext, useEffect } from 'react'
|
||||
import IdeContainer from 'src/components/IdeContainer'
|
||||
import { isBrowser } from '@redwoodjs/prerender/browserUtils'
|
||||
import { useIdeState, codeStorageKey } from 'src/helpers/hooks/useIdeState'
|
||||
@@ -6,11 +6,27 @@ import { copyTextToClipboard } from 'src/helpers/clipboard'
|
||||
import { requestRender } from 'src/helpers/hooks/useIdeState'
|
||||
|
||||
export const IdeContext = createContext()
|
||||
const IdeToolbarNew = () => {
|
||||
const IdeToolbarNew = ({ cadPackage }) => {
|
||||
const [state, thunkDispatch] = useIdeState()
|
||||
function setIdeType(ide) {
|
||||
thunkDispatch({ type: 'setIdeType', payload: { message: ide } })
|
||||
}
|
||||
const scriptKey = 'encoded_script'
|
||||
useEffect(() => {
|
||||
thunkDispatch({
|
||||
type: 'initIde',
|
||||
payload: { cadPackage },
|
||||
})
|
||||
// load code from hash if it's there
|
||||
let hash
|
||||
if (isBrowser) {
|
||||
hash = window.location.hash
|
||||
}
|
||||
const [key, scriptBase64] = hash.slice(1).split('=')
|
||||
if (key === scriptKey) {
|
||||
const script = atob(scriptBase64)
|
||||
thunkDispatch({ type: 'updateCode', payload: script })
|
||||
}
|
||||
window.location.hash = ''
|
||||
setTimeout(() => handleRender()) // definitely a little hacky, timeout with no delay is just to push it into the next event loop.
|
||||
}, [cadPackage])
|
||||
function handleRender() {
|
||||
thunkDispatch((dispatch, getState) => {
|
||||
const state = getState()
|
||||
@@ -37,14 +53,6 @@ const IdeToolbarNew = () => {
|
||||
<IdeContext.Provider value={{ state, thunkDispatch: thunkDispatch }}>
|
||||
<div className="h-full flex flex-col">
|
||||
<nav className="flex">
|
||||
<button
|
||||
onClick={() =>
|
||||
setIdeType(state.ideType === 'openScad' ? 'cadQuery' : 'openScad')
|
||||
}
|
||||
className="p-2 br-2 border-2 m-2 bg-blue-200"
|
||||
>
|
||||
Switch to {state.ideType === 'openScad' ? 'CadQuery' : 'OpenSCAD'}
|
||||
</button>
|
||||
<button onClick={handleRender} className="p-2 br-2 border-2 m-2">
|
||||
Render
|
||||
</button>
|
||||
|
||||
48
web/src/components/NavPlusButton/NavPlusButton.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import { Link, routes } from '@redwoodjs/router'
|
||||
import Svg from 'src/components/Svg/Svg'
|
||||
import { Popover } from '@headlessui/react'
|
||||
|
I think the I think the `<Menu />` component would have made more sense from headlessui, but I got it working with Popover first and when I tried swapping to Menu, I was just fighting to get the styles to not do weird things 🤷
I'll try to make a note of this for future UI work! To check out the Menu component. I'll try to make a note of this for future UI work! To check out the Menu component.
I'm not sure what we're missing out on by using the popover instead, probably some good accessibility defaults. I say I think menu would be better just from the description of when menu and popover should be used from the docs. I'm not sure what we're missing out on by using the popover instead, probably some good accessibility defaults. I say I think menu would be better just from the description of when menu and popover should be used from the docs.
|
||||
|
||||
const NavPlusButton: React.FC = () => {
|
||||
return (
|
||||
<Popover className="relative outline-none w-full h-full">
|
||||
<Popover.Button className="h-full w-full outline-none">
|
||||
<Svg name="plus" className="text-indigo-300" />
|
||||
</Popover.Button>
|
||||
|
||||
<Popover.Panel className="absolute z-10">
|
||||
<ul className="bg-gray-200 mt-4 rounded shadow-md overflow-hidden">
|
||||
{[
|
||||
{
|
||||
name: 'OpenSCAD',
|
||||
sub: 'beta',
|
||||
ideType: 'openScad',
|
||||
},
|
||||
{ name: 'CadQuery', sub: 'beta', ideType: 'cadQuery' },
|
||||
{
|
||||
name: 'CascadeStudio',
|
||||
sub: 'soon to be deprecated',
|
||||
},
|
||||
].map(({ name, sub, ideType }) => (
|
||||
<li
|
||||
key={name}
|
||||
className="px-4 py-2 hover:bg-gray-400 text-gray-800"
|
||||
>
|
||||
<Link
|
||||
to={
|
||||
name === 'CascadeStudio'
|
||||
? routes.draftPart()
|
||||
: routes.devIde({ cadPackage: ideType })
|
||||
}
|
||||
>
|
||||
<div>{name}</div>
|
||||
<div className="text-xs text-gray-600 font-light">{sub}</div>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</Popover.Panel>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
|
||||
export default NavPlusButton
|
||||
@@ -13,6 +13,9 @@ export const render = async ({ code, settings }) => {
|
||||
},
|
||||
file: code,
|
||||
})
|
||||
if (!settings.camera.position) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
const response = await fetch(lambdaBaseURL + '/openscad/preview', {
|
||||
method: 'POST',
|
||||
|
||||
@@ -8,7 +8,8 @@ function withThunk(dispatch, getState) {
|
||||
: dispatch(actionOrThunk)
|
||||
}
|
||||
|
||||
|
I think redux thunks might be a little more complex than this, but not by much. Basically you can use this like a normal dispatch, but if you want to something async you just pass it a function instead, you would have already scrolled passed examples. I think redux thunks might be a little more complex than this, but not by much. Basically you can use this like a normal dispatch, but if you want to something async you just pass it a function instead, you would have already scrolled passed examples.
|
||||
const donutInitCode = `
|
||||
const initCodeMap = {
|
||||
openScad: `
|
||||
color(c="DarkGoldenrod")rotate_extrude()translate([20,0])circle(d=30);
|
||||
donut();
|
||||
module donut() {
|
||||
@@ -21,16 +22,31 @@ module stick(basewid, angl){
|
||||
sphere(7);
|
||||
translate([0,0,10])sphere(9);
|
||||
}
|
||||
}`
|
||||
}`,
|
||||
cadQuery: `import cadquery as cq
|
||||
from cadquery import exporters
|
||||
|
||||
diam = 5.0
|
||||
|
||||
result = (cq.Workplane().circle(diam).extrude(20.0)
|
||||
.faces(">Z").workplane(invert=True).circle(1.05).cutBlind(8.0)
|
||||
.faces("<Z").workplane(invert=True).circle(0.8).cutBlind(12.0)
|
||||
.edges("%CIRCLE").chamfer(0.15))
|
||||
|
||||
# exporters.export(coupler, "/home/jwright/Downloads/coupler.stl", exporters.ExportTypes.STL)
|
||||
|
||||
show_object(result)
|
||||
`,
|
||||
}
|
||||
|
||||
export const codeStorageKey = 'Last-openscad-code'
|
||||
let mutableState = null
|
||||
|
||||
export const useIdeState = () => {
|
||||
const code = localStorage.getItem(codeStorageKey) || donutInitCode
|
||||
const code = localStorage.getItem(codeStorageKey) || initCodeMap.openscad
|
||||
const initialState = {
|
||||
ideType: 'cadQuery',
|
||||
consoleMessages: [{ type: 'message', message: 'Initialising OpenSCAD' }],
|
||||
ideType: 'INIT',
|
||||
consoleMessages: [{ type: 'message', message: 'Initialising' }],
|
||||
code,
|
||||
objectData: {
|
||||
type: 'stl',
|
||||
@@ -52,6 +68,12 @@ export const useIdeState = () => {
|
||||
}
|
||||
const reducer = (state, { type, payload }) => {
|
||||
switch (type) {
|
||||
case 'initIde':
|
||||
return {
|
||||
...state,
|
||||
code: initCodeMap[payload.cadPackage] || initCodeMap.openscad,
|
||||
ideType: payload.cadPackage,
|
||||
}
|
||||
case 'updateCode':
|
||||
return { ...state, code: payload }
|
||||
case 'healthyRender':
|
||||
@@ -74,11 +96,6 @@ export const useIdeState = () => {
|
||||
: payload.message,
|
||||
isLoading: false,
|
||||
}
|
||||
case 'setIdeType':
|
||||
return {
|
||||
...state,
|
||||
ideType: payload.message,
|
||||
}
|
||||
case 'setLayout':
|
||||
return {
|
||||
...state,
|
||||
@@ -122,26 +139,28 @@ export const requestRender = ({
|
||||
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
|
||||
state.ideType !== 'INIT' &&
|
||||
!state.isLoading &&
|
||||
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, lastRunCode: code },
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch(() => dispatch({ type: 'resetLoading' })) // TODO should probably display something to the user here
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { getActiveClasses } from 'get-active-classes'
|
||||
import Footer from 'src/components/Footer'
|
||||
import { useLocation } from '@redwoodjs/router'
|
||||
import LoginModal from 'src/components/LoginModal'
|
||||
import NavPlusButton from 'src/components/NavPlusButton'
|
||||
import ReactGA from 'react-ga'
|
||||
import { isBrowser } from '@redwoodjs/prerender/browserUtils'
|
||||
|
||||
@@ -132,9 +133,7 @@ const MainLayout = ({ children, shouldRemoveFooterInIde }) => {
|
||||
'mr-8 h-10 w-10 rounded-full border-2 border-indigo-300 flex items-center justify-center'
|
||||
)}
|
||||
>
|
||||
<Link className="h-full w-full" to={routes.draftPart()}>
|
||||
<Svg name="plus" className="text-indigo-300 w-full h-full" />
|
||||
</Link>
|
||||
<NavPlusButton />
|
||||
</li>
|
||||
{isAuthenticated ? (
|
||||
<li
|
||||
|
||||
@@ -3,7 +3,7 @@ import Seo from 'src/components/Seo/Seo'
|
||||
import IdeToolbar from 'src/components/IdeToolbarNew'
|
||||
import OutBound from 'src/components/OutBound'
|
||||
|
||||
const DevIdePage = () => {
|
||||
const DevIdePage = ({ cadPackage }) => {
|
||||
return (
|
||||
<div className="h-screen flex flex-col">
|
||||
<MainLayout shouldRemoveFooterInIde>
|
||||
@@ -12,10 +12,9 @@ const DevIdePage = () => {
|
||||
description="new ide in development"
|
||||
lang="en-US"
|
||||
/>
|
||||
<div className="py-4 bg-pink-200">
|
||||
<div className="mx-auto max-w-6xl">
|
||||
Woah, woah. You shouldn't be here! We're still working on this.
|
||||
Since you've seen it now, have a look what{' '}
|
||||
<div className="py-2 bg-pink-200">
|
||||
<div className="mx-auto max-w-3xl">
|
||||
We're still working on this. Since you're here, have a look what{' '}
|
||||
<OutBound
|
||||
className="text-pink-700"
|
||||
to="https://github.com/Irev-Dev/cadhub/discussions/212"
|
||||
@@ -27,7 +26,7 @@ const DevIdePage = () => {
|
||||
</div>
|
||||
</MainLayout>
|
||||
<div className="flex-auto">
|
||||
<IdeToolbar />
|
||||
<IdeToolbar cadPackage={cadPackage} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1404,6 +1404,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@hapi/bourne/-/bourne-2.0.0.tgz#5bb2193eb685c0007540ca61d166d4e1edaf918d"
|
||||
integrity sha512-WEezM1FWztfbzqIUbsDzFRVMxSoLy3HugVcux6KDDtTqzPsLE8NDRHfXvev66aH1i2oOKKar3/XDjbvh/OUBdg==
|
||||
|
||||
"@headlessui/react@^1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-1.0.0.tgz#661b50ebfd25041abb45d8eedd85e7559056bcaf"
|
||||
integrity sha512-mjqRJrgkbcHQBfAHnqH0yRxO/y/22jYrdltpE7WkurafREKZ+pj5bPBwYHMt935Sdz/n16yRcVmsSCqDFHee9A==
|
||||
|
||||
"@icons/material@^0.2.4":
|
||||
version "0.2.4"
|
||||
resolved "https://registry.yarnpkg.com/@icons/material/-/material-0.2.4.tgz#e90c9f71768b3736e76d7dd6783fc6c2afa88bc8"
|
||||
|
||||
Yay, I've been waiting for this 1.0 release. I had been using material-UI which I'd like to stop doing.