Add stl download for OpenSCAD and CadQuery IDEs

Resolves #330.
This commit is contained in:
Kurt Hutten
2021-05-29 21:06:33 +10:00
parent 32fa22efcd
commit bd58e6c7cb
12 changed files with 311 additions and 168 deletions

View File

@@ -29,7 +29,7 @@ const IdeContainer = () => {
})
thunkDispatch((dispatch, getState) => {
const state = getState()
if (state.ideType === 'openScad') {
if (['png', 'INIT'].includes(state.objectData?.type)) {
dispatch({ type: 'setLoading' })
requestRender({
state,

View File

@@ -5,7 +5,10 @@ import { useIdeState, codeStorageKey } from 'src/helpers/hooks/useIdeState'
import { copyTextToClipboard } from 'src/helpers/clipboard'
import { requestRender } from 'src/helpers/hooks/useIdeState'
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) =>
url.includes('github.com')
@@ -80,6 +83,63 @@ const IdeToolbarNew = ({ cadPackage }) => {
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 (
<IdeContext.Provider value={{ state, thunkDispatch }}>
@@ -97,6 +157,12 @@ const IdeToolbarNew = ({ cadPackage }) => {
>
Copy link
</button>
<button
onClick={handleStlDownload}
className="border-2 text-gray-700 px-2 text-sm m-1 ml-2"
>
Download STL
</button>
</nav>
<IdeContainer />
</div>

View File

@@ -9,33 +9,16 @@ import {
} from 'react-three-fiber'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { Vector3 } from 'three'
import { STLLoader } from 'three/examples/jsm/loaders/STLLoader'
import { requestRender } from 'src/helpers/hooks/useIdeState'
extend({ OrbitControls })
function Asset({ url, resetLoading, setLoading }) {
const [loadedGeometry, setLoadedGeometry] = useState()
function Asset({ geometry: incomingGeo }) {
const mesh = useRef()
const ref = useUpdate((geometry) => {
geometry.attributes = loadedGeometry.attributes
geometry.attributes = incomingGeo.attributes
})
useEffect(() => {
if (url) {
const loader = new STLLoader()
setLoading()
loader.load(
url,
(geometry) => {
setLoadedGeometry(geometry)
resetLoading()
},
null,
resetLoading
)
}
}, [url])
if (!loadedGeometry) return null
if (!incomingGeo) return null
return (
<mesh ref={mesh} scale={[1, 1, 1]}>
<bufferGeometry attach="geometry" ref={ref} />
@@ -158,9 +141,6 @@ const IdeViewer = () => {
const [isDragging, setIsDragging] = useState(false)
const [image, setImage] = useState()
const resetLoading = () => thunkDispatch({ type: 'resetLoading' })
const setLoading = () => thunkDispatch({ type: 'setLoading' })
useEffect(() => {
setImage(state.objectData?.type === 'png' && state.objectData?.data)
setIsDragging(false)
@@ -192,7 +172,7 @@ const IdeViewer = () => {
)}
<div // eslint-disable-line jsx-a11y/no-static-element-interactions
className={`opacity-0 absolute inset-0 transition-opacity duration-500 ${
!(isDragging || state.ideType !== 'openScad')
!(isDragging || state.objectData?.type !== 'png')
? 'hover:opacity-50'
: 'opacity-100'
}`}
@@ -208,7 +188,7 @@ const IdeViewer = () => {
})
thunkDispatch((dispatch, getState) => {
const state = getState()
if (state.ideType === 'openScad') {
if (['png', 'INIT'].includes(state.objectData?.type)) {
dispatch({ type: 'setLoading' })
requestRender({
state,
@@ -223,7 +203,7 @@ const IdeViewer = () => {
/>
<ambientLight />
<pointLight position={[15, 5, 10]} />
{state.ideType === 'openScad' && (
{state.objectData?.type === 'png' && (
<>
<Sphere position={[0, 0, 0]} color={pink400} />
<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} />
</>
)}
{state.ideType === 'cadQuery' && (
<Asset
url={state.objectData?.type === 'stl' && state.objectData?.data}
resetLoading={resetLoading}
setLoading={setLoading}
/>
)}
<Asset
geometry={
state.objectData?.type === 'geometry' && state.objectData?.data
}
/>
</Canvas>
</div>
{state.isLoading && (