Zoom to fit for openscad (#569)
* Add viewall flag to openscad cli in prep for zoom to fit for scad previews * Fix remaining issues with social image capture
This commit was merged in pull request #569.
This commit is contained in:
@@ -11,6 +11,7 @@ const cleanOpenScadError = (error) =>
|
|||||||
export const runScad = async ({
|
export const runScad = async ({
|
||||||
file,
|
file,
|
||||||
settings: {
|
settings: {
|
||||||
|
viewAll = false,
|
||||||
size: { x = 500, y = 500 } = {},
|
size: { x = 500, y = 500 } = {},
|
||||||
parameters,
|
parameters,
|
||||||
camera: {
|
camera: {
|
||||||
@@ -44,10 +45,13 @@ export const runScad = async ({
|
|||||||
const fullPath = `/tmp/${tempFile}/output.gz`
|
const fullPath = `/tmp/${tempFile}/output.gz`
|
||||||
const imPath = `/tmp/${tempFile}/output.png`
|
const imPath = `/tmp/${tempFile}/output.png`
|
||||||
const customizerPath = `/tmp/${tempFile}/customizer.param`
|
const customizerPath = `/tmp/${tempFile}/customizer.param`
|
||||||
|
const summaryPath = `/tmp/${tempFile}/summary.json` // contains camera info
|
||||||
const command = [
|
const command = [
|
||||||
OPENSCAD_COMMON,
|
OPENSCAD_COMMON,
|
||||||
`-o ${customizerPath}`,
|
`-o ${customizerPath}`,
|
||||||
`-o ${imPath}`,
|
`-o ${imPath}`,
|
||||||
|
`--summary camera --summary-file ${summaryPath}`,
|
||||||
|
viewAll ? '--viewall' : '',
|
||||||
`-p /tmp/${tempFile}/params.json -P default`,
|
`-p /tmp/${tempFile}/params.json -P default`,
|
||||||
cameraArg,
|
cameraArg,
|
||||||
`--imgsize=${x},${y}`,
|
`--imgsize=${x},${y}`,
|
||||||
@@ -58,14 +62,20 @@ export const runScad = async ({
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const consoleMessage = await runCommand(command, 15000)
|
const consoleMessage = await runCommand(command, 15000)
|
||||||
const params = JSON.parse(
|
const files: string[] = await Promise.all(
|
||||||
await readFile(customizerPath, { encoding: 'ascii' })
|
[customizerPath, summaryPath].map((path) =>
|
||||||
).parameters
|
readFile(path, { encoding: 'ascii' })
|
||||||
|
)
|
||||||
|
)
|
||||||
|
const [params, cameraInfo] = files.map((fileStr: string) =>
|
||||||
|
JSON.parse(fileStr)
|
||||||
|
)
|
||||||
await writeFiles(
|
await writeFiles(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
file: JSON.stringify({
|
file: JSON.stringify({
|
||||||
customizerParams: params,
|
cameraInfo: viewAll ? cameraInfo.camera : undefined,
|
||||||
|
customizerParams: params.parameters,
|
||||||
consoleMessage,
|
consoleMessage,
|
||||||
type: 'png',
|
type: 'png',
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { RedwoodProvider } from '@redwoodjs/web'
|
|||||||
import FatalErrorBoundary from 'src/components/FatalErrorBoundary/FatalErrorBoundary'
|
import FatalErrorBoundary from 'src/components/FatalErrorBoundary/FatalErrorBoundary'
|
||||||
import { RedwoodApolloProvider } from '@redwoodjs/web/apollo'
|
import { RedwoodApolloProvider } from '@redwoodjs/web/apollo'
|
||||||
import FatalErrorPage from 'src/pages/FatalErrorPage'
|
import FatalErrorPage from 'src/pages/FatalErrorPage'
|
||||||
import { createMuiTheme } from '@material-ui/core/styles'
|
import { createTheme } from '@material-ui/core/styles'
|
||||||
import { ThemeProvider } from '@material-ui/styles'
|
import { ThemeProvider } from '@material-ui/styles'
|
||||||
import ReactGA from 'react-ga'
|
import ReactGA from 'react-ga'
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ const goTrueClient = new GoTrue({
|
|||||||
setCookie: true,
|
setCookie: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
const theme = createMuiTheme({
|
const theme = createTheme({
|
||||||
palette: {
|
palette: {
|
||||||
type: 'dark',
|
type: 'dark',
|
||||||
primary: {
|
primary: {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { toast } from '@redwoodjs/web/toast'
|
import { toast } from '@redwoodjs/web/toast'
|
||||||
import { toJpeg } from 'html-to-image'
|
import { toJpeg } from 'html-to-image'
|
||||||
|
|
||||||
@@ -6,21 +6,13 @@ import { useIdeContext } from 'src/helpers/hooks/useIdeContext'
|
|||||||
import { canvasToBlob, blobTo64 } from 'src/helpers/canvasToBlob'
|
import { canvasToBlob, blobTo64 } from 'src/helpers/canvasToBlob'
|
||||||
import { useUpdateProjectImages } from 'src/helpers/hooks/useUpdateProjectImages'
|
import { useUpdateProjectImages } from 'src/helpers/hooks/useUpdateProjectImages'
|
||||||
import { requestRenderStateless } from 'src/helpers/hooks/useIdeState'
|
import { requestRenderStateless } from 'src/helpers/hooks/useIdeState'
|
||||||
import { PureIdeViewer } from 'src/components/IdeViewer/IdeViewer'
|
import { PureIdeViewer } from 'src/components/IdeViewer/PureIdeViewer'
|
||||||
|
import { State } from 'src/helpers/hooks/useIdeState'
|
||||||
import SocialCardCell from 'src/components/SocialCardCell/SocialCardCell'
|
import SocialCardCell from 'src/components/SocialCardCell/SocialCardCell'
|
||||||
|
|
||||||
export const captureSize = { width: 500, height: 522 }
|
export const captureSize = { width: 500, height: 522 }
|
||||||
|
|
||||||
const anchorOrigin = {
|
const CaptureButtonViewer = ({
|
||||||
vertical: 'bottom',
|
|
||||||
horizontal: 'center',
|
|
||||||
}
|
|
||||||
const transformOrigin = {
|
|
||||||
vertical: 'top',
|
|
||||||
horizontal: 'center',
|
|
||||||
}
|
|
||||||
|
|
||||||
export const CaptureButtonViewer = ({
|
|
||||||
onInit,
|
onInit,
|
||||||
onScadImage,
|
onScadImage,
|
||||||
canvasRatio = 1,
|
canvasRatio = 1,
|
||||||
@@ -34,11 +26,12 @@ export const CaptureButtonViewer = ({
|
|||||||
const [dataType, dataTypeSetter] = useState(state?.objectData?.type)
|
const [dataType, dataTypeSetter] = useState(state?.objectData?.type)
|
||||||
const [artifact, artifactSetter] = useState(state?.objectData?.data)
|
const [artifact, artifactSetter] = useState(state?.objectData?.data)
|
||||||
const [isLoading, isLoadingSetter] = useState(false)
|
const [isLoading, isLoadingSetter] = useState(false)
|
||||||
|
const [camera, cameraSetter] = useState<State['camera'] | null>(null)
|
||||||
const getThreeInstance = (_threeInstance) => {
|
const getThreeInstance = (_threeInstance) => {
|
||||||
threeInstance.current = _threeInstance
|
threeInstance.current = _threeInstance
|
||||||
onInit(_threeInstance)
|
onInit(_threeInstance)
|
||||||
}
|
}
|
||||||
const onCameraChange = (camera) => {
|
const onCameraChange = (camera, isFirstCameraChange) => {
|
||||||
const renderPromise =
|
const renderPromise =
|
||||||
state.ideType === 'openscad' &&
|
state.ideType === 'openscad' &&
|
||||||
requestRenderStateless({
|
requestRenderStateless({
|
||||||
@@ -48,12 +41,16 @@ export const CaptureButtonViewer = ({
|
|||||||
width: threeInstance.current.size.width * canvasRatio,
|
width: threeInstance.current.size.width * canvasRatio,
|
||||||
height: threeInstance.current.size.height * canvasRatio,
|
height: threeInstance.current.size.height * canvasRatio,
|
||||||
},
|
},
|
||||||
|
viewAll: isFirstCameraChange,
|
||||||
})
|
})
|
||||||
if (!renderPromise) {
|
if (!renderPromise) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
isLoadingSetter(true)
|
isLoadingSetter(true)
|
||||||
renderPromise.then(async ({ objectData }) => {
|
renderPromise.then(async ({ objectData, camera }) => {
|
||||||
|
if (camera?.isScadUpdate) {
|
||||||
|
cameraSetter(camera)
|
||||||
|
}
|
||||||
isLoadingSetter(false)
|
isLoadingSetter(false)
|
||||||
dataTypeSetter(objectData?.type)
|
dataTypeSetter(objectData?.type)
|
||||||
artifactSetter(objectData?.data)
|
artifactSetter(objectData?.data)
|
||||||
@@ -71,6 +68,7 @@ export const CaptureButtonViewer = ({
|
|||||||
onInit={getThreeInstance}
|
onInit={getThreeInstance}
|
||||||
onCameraChange={onCameraChange}
|
onCameraChange={onCameraChange}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
|
camera={camera}
|
||||||
isMinimal
|
isMinimal
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
@@ -115,8 +113,9 @@ function SocialCardLiveViewer({
|
|||||||
userName={project.user.userName}
|
userName={project.user.userName}
|
||||||
projectTitle={project.title}
|
projectTitle={project.title}
|
||||||
image64={partSnapShot64}
|
image64={partSnapShot64}
|
||||||
LiveProjectViewer={() => children}
|
>
|
||||||
/>
|
{children}
|
||||||
|
</SocialCardCell>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
101
app/web/src/components/IdeViewer/Asset.tsx
Normal file
101
app/web/src/components/IdeViewer/Asset.tsx
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
import * as THREE from 'three'
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { useThree } from '@react-three/fiber'
|
||||||
|
import { useTexture } from '@react-three/drei'
|
||||||
|
import { useEdgeSplit } from 'src/helpers/hooks/useEdgeSplit'
|
||||||
|
import texture from './dullFrontLitMetal.png'
|
||||||
|
import type { ArtifactTypes } from 'src/helpers/cadPackages/common'
|
||||||
|
|
||||||
|
const thresholdAngle = 12
|
||||||
|
|
||||||
|
export function Asset({
|
||||||
|
geometry: incomingGeo,
|
||||||
|
dataType,
|
||||||
|
controlsRef,
|
||||||
|
}: {
|
||||||
|
geometry: any // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
|
dataType: 'INIT' | ArtifactTypes
|
||||||
|
controlsRef: React.MutableRefObject<any> // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
|
}) {
|
||||||
|
const threeInstance = useThree()
|
||||||
|
const [initZoom, setInitZoom] = useState(true)
|
||||||
|
const mesh = useEdgeSplit((thresholdAngle * Math.PI) / 180, true, incomingGeo)
|
||||||
|
const edges = React.useMemo(
|
||||||
|
() =>
|
||||||
|
incomingGeo.length || dataType !== 'geometry'
|
||||||
|
? null
|
||||||
|
: new THREE.EdgesGeometry(incomingGeo, thresholdAngle),
|
||||||
|
[incomingGeo, dataType]
|
||||||
|
)
|
||||||
|
React.useEffect(() => {
|
||||||
|
const getBoundingSphere = () => {
|
||||||
|
if (dataType === 'geometry') {
|
||||||
|
return incomingGeo.boundingSphere
|
||||||
|
}
|
||||||
|
const group = new THREE.Group()
|
||||||
|
incomingGeo.forEach((mesh) => group.add(mesh))
|
||||||
|
const bbox = new THREE.Box3().setFromObject(group)
|
||||||
|
return bbox.getBoundingSphere(new THREE.Sphere())
|
||||||
|
}
|
||||||
|
const bSphere = getBoundingSphere()
|
||||||
|
|
||||||
|
const zoomToFit = () => {
|
||||||
|
const { center, radius } = bSphere
|
||||||
|
const { camera } = threeInstance
|
||||||
|
const offset = 3
|
||||||
|
controlsRef.current.reset()
|
||||||
|
controlsRef.current.target.copy(center)
|
||||||
|
|
||||||
|
camera.position.copy(
|
||||||
|
center
|
||||||
|
.clone()
|
||||||
|
.add(
|
||||||
|
new THREE.Vector3(
|
||||||
|
offset * radius,
|
||||||
|
-offset * radius,
|
||||||
|
offset * radius
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
camera.updateProjectionMatrix()
|
||||||
|
}
|
||||||
|
if (initZoom) {
|
||||||
|
if (!bSphere) return
|
||||||
|
zoomToFit()
|
||||||
|
setInitZoom(false)
|
||||||
|
}
|
||||||
|
}, [incomingGeo, dataType, controlsRef, initZoom, threeInstance])
|
||||||
|
const PrimitiveArray = React.useMemo(
|
||||||
|
() =>
|
||||||
|
dataType === 'primitive-array' &&
|
||||||
|
incomingGeo?.map((mesh) => mesh.clone()),
|
||||||
|
[dataType, incomingGeo]
|
||||||
|
)
|
||||||
|
const colorMap = useTexture(texture)
|
||||||
|
|
||||||
|
if (!incomingGeo) return null
|
||||||
|
if (PrimitiveArray)
|
||||||
|
return PrimitiveArray.map((mesh, index) => (
|
||||||
|
<primitive object={mesh} key={index} />
|
||||||
|
))
|
||||||
|
|
||||||
|
return (
|
||||||
|
<group dispose={null}>
|
||||||
|
<mesh ref={mesh} scale={[1, 1, 1]} geometry={incomingGeo}>
|
||||||
|
<meshPhysicalMaterial
|
||||||
|
envMapIntensity={0.1}
|
||||||
|
color="#F472B6"
|
||||||
|
map={colorMap}
|
||||||
|
clearcoat={0.1}
|
||||||
|
clearcoatRoughness={0.2}
|
||||||
|
roughness={10}
|
||||||
|
metalness={0.7}
|
||||||
|
smoothShading
|
||||||
|
/>
|
||||||
|
</mesh>
|
||||||
|
<lineSegments geometry={edges} renderOrder={100}>
|
||||||
|
<lineBasicMaterial color="#aaaaff" opacity={0.5} transparent />
|
||||||
|
</lineSegments>
|
||||||
|
</group>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,337 +1,6 @@
|
|||||||
import { useIdeContext } from 'src/helpers/hooks/useIdeContext'
|
import { useIdeContext } from 'src/helpers/hooks/useIdeContext'
|
||||||
import * as THREE from 'three'
|
|
||||||
import { useRef, useState, useEffect, Suspense } from 'react'
|
|
||||||
import { Canvas, useThree } from '@react-three/fiber'
|
|
||||||
import {
|
|
||||||
PerspectiveCamera,
|
|
||||||
GizmoHelper,
|
|
||||||
GizmoViewport,
|
|
||||||
OrbitControls,
|
|
||||||
useTexture,
|
|
||||||
} from '@react-three/drei'
|
|
||||||
import { useEdgeSplit } from 'src/helpers/hooks/useEdgeSplit'
|
|
||||||
import { Vector3 } from 'three'
|
|
||||||
import { requestRender } from 'src/helpers/hooks/useIdeState'
|
import { requestRender } from 'src/helpers/hooks/useIdeState'
|
||||||
import texture from './dullFrontLitMetal.png'
|
import { PureIdeViewer } from './PureIdeViewer'
|
||||||
import Customizer from 'src/components/Customizer/Customizer'
|
|
||||||
import DelayedPingAnimation from 'src/components/DelayedPingAnimation/DelayedPingAnimation'
|
|
||||||
import type { ArtifactTypes } from 'src/helpers/cadPackages/common'
|
|
||||||
|
|
||||||
const thresholdAngle = 12
|
|
||||||
|
|
||||||
function Asset({
|
|
||||||
geometry: incomingGeo,
|
|
||||||
dataType,
|
|
||||||
controlsRef,
|
|
||||||
}: {
|
|
||||||
geometry: any
|
|
||||||
dataType: 'INIT' | ArtifactTypes
|
|
||||||
controlsRef: React.MutableRefObject<any>
|
|
||||||
}) {
|
|
||||||
const threeInstance = useThree()
|
|
||||||
const [initZoom, setInitZoom] = useState(true)
|
|
||||||
const mesh = useEdgeSplit((thresholdAngle * Math.PI) / 180, true, incomingGeo)
|
|
||||||
const edges = React.useMemo(
|
|
||||||
() =>
|
|
||||||
incomingGeo.length || dataType !== 'geometry'
|
|
||||||
? null
|
|
||||||
: new THREE.EdgesGeometry(incomingGeo, thresholdAngle),
|
|
||||||
[incomingGeo, dataType]
|
|
||||||
)
|
|
||||||
React.useEffect(() => {
|
|
||||||
const getBoundingSphere = () => {
|
|
||||||
if (dataType === 'geometry') {
|
|
||||||
return incomingGeo.boundingSphere
|
|
||||||
}
|
|
||||||
const group = new THREE.Group()
|
|
||||||
incomingGeo.forEach((mesh) => group.add(mesh))
|
|
||||||
const bbox = new THREE.Box3().setFromObject(group)
|
|
||||||
return bbox.getBoundingSphere(new THREE.Sphere())
|
|
||||||
}
|
|
||||||
const bSphere = getBoundingSphere()
|
|
||||||
|
|
||||||
const zoomToFit = () => {
|
|
||||||
const { center, radius } = bSphere
|
|
||||||
const { camera } = threeInstance
|
|
||||||
const offset = 3
|
|
||||||
controlsRef.current.reset()
|
|
||||||
controlsRef.current.target.copy(center)
|
|
||||||
|
|
||||||
camera.position.copy(
|
|
||||||
center
|
|
||||||
.clone()
|
|
||||||
.add(
|
|
||||||
new THREE.Vector3(
|
|
||||||
offset * radius,
|
|
||||||
-offset * radius,
|
|
||||||
offset * radius
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
camera.updateProjectionMatrix()
|
|
||||||
}
|
|
||||||
if (initZoom) {
|
|
||||||
if (!bSphere) return
|
|
||||||
zoomToFit()
|
|
||||||
setInitZoom(false)
|
|
||||||
}
|
|
||||||
}, [incomingGeo, dataType])
|
|
||||||
const PrimitiveArray = React.useMemo(
|
|
||||||
() =>
|
|
||||||
dataType === 'primitive-array' &&
|
|
||||||
incomingGeo?.map((mesh) => mesh.clone()),
|
|
||||||
[dataType, incomingGeo]
|
|
||||||
)
|
|
||||||
const colorMap = useTexture(texture)
|
|
||||||
|
|
||||||
if (!incomingGeo) return null
|
|
||||||
if (PrimitiveArray)
|
|
||||||
return PrimitiveArray.map((mesh, index) => (
|
|
||||||
<primitive object={mesh} key={index} />
|
|
||||||
))
|
|
||||||
|
|
||||||
return (
|
|
||||||
<group dispose={null}>
|
|
||||||
<mesh ref={mesh} scale={[1, 1, 1]} geometry={incomingGeo}>
|
|
||||||
<meshPhysicalMaterial
|
|
||||||
envMapIntensity={0.1}
|
|
||||||
color="#F472B6"
|
|
||||||
map={colorMap}
|
|
||||||
clearcoat={0.1}
|
|
||||||
clearcoatRoughness={0.2}
|
|
||||||
roughness={10}
|
|
||||||
metalness={0.7}
|
|
||||||
smoothShading
|
|
||||||
/>
|
|
||||||
</mesh>
|
|
||||||
<lineSegments geometry={edges} renderOrder={100}>
|
|
||||||
<lineBasicMaterial color="#aaaaff" opacity={0.5} transparent />
|
|
||||||
</lineSegments>
|
|
||||||
</group>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let debounceTimeoutId
|
|
||||||
function Controls({ onCameraChange, onDragStart, onInit, controlsRef }) {
|
|
||||||
const threeInstance = useThree()
|
|
||||||
const { camera, gl } = threeInstance
|
|
||||||
useEffect(() => {
|
|
||||||
onInit(threeInstance)
|
|
||||||
// init camera position
|
|
||||||
camera.position.x = 200
|
|
||||||
camera.position.y = 140
|
|
||||||
camera.position.z = 20
|
|
||||||
camera.far = 10000
|
|
||||||
camera.fov = 22.5 // matches default openscad fov
|
|
||||||
camera.updateProjectionMatrix()
|
|
||||||
|
|
||||||
camera.rotation._order = 'ZYX'
|
|
||||||
const getRotations = (): number[] => {
|
|
||||||
const { x, y, z } = camera?.rotation || {}
|
|
||||||
return [x, y, z].map((rot) => (rot * 180) / Math.PI)
|
|
||||||
}
|
|
||||||
const getPositions = () => {
|
|
||||||
// Difficult to make this clean since I'm not sure why it works
|
|
||||||
// The OpenSCAD camera seems hard to work with but maybe it's just me
|
|
||||||
|
|
||||||
// this gives us a vector the same length as the camera.position
|
|
||||||
const cameraViewVector = new Vector3(0, 0, 1)
|
|
||||||
.applyQuaternion(camera.quaternion) // make unit vector of the camera
|
|
||||||
.multiplyScalar(camera.position.length()) // make it the same length as the position vector
|
|
||||||
|
|
||||||
// make a vector from the position vector to the cameraView vector
|
|
||||||
const head2Head = new Vector3().subVectors(
|
|
||||||
camera.position,
|
|
||||||
cameraViewVector
|
|
||||||
)
|
|
||||||
const { x, y, z } = head2Head.add(camera.position)
|
|
||||||
return {
|
|
||||||
position: { x, y, z },
|
|
||||||
dist: camera.position.length(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (controlsRef.current) {
|
|
||||||
const dragCallback = () => {
|
|
||||||
clearTimeout(debounceTimeoutId)
|
|
||||||
debounceTimeoutId = setTimeout(() => {
|
|
||||||
const [x, y, z] = getRotations()
|
|
||||||
const { position, dist } = getPositions()
|
|
||||||
|
|
||||||
onCameraChange({
|
|
||||||
position,
|
|
||||||
rotation: { x, y, z },
|
|
||||||
dist,
|
|
||||||
})
|
|
||||||
}, 400)
|
|
||||||
}
|
|
||||||
const dragStart = () => {
|
|
||||||
onDragStart()
|
|
||||||
clearTimeout(debounceTimeoutId)
|
|
||||||
}
|
|
||||||
controlsRef?.current?.addEventListener('end', dragCallback)
|
|
||||||
controlsRef?.current?.addEventListener('start', dragStart)
|
|
||||||
const oldCurrent = controlsRef.current
|
|
||||||
dragCallback()
|
|
||||||
return () => {
|
|
||||||
oldCurrent.removeEventListener('end', dragCallback)
|
|
||||||
oldCurrent.removeEventListener('start', dragStart)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [camera, controlsRef])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<OrbitControls
|
|
||||||
makeDefault
|
|
||||||
ref={controlsRef}
|
|
||||||
args={[camera, gl.domElement]}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function Box(props) {
|
|
||||||
// This reference will give us direct access to the mesh
|
|
||||||
const mesh = useRef()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<mesh {...props} ref={mesh} scale={[1, 1, 1]}>
|
|
||||||
<boxBufferGeometry args={props.size} />
|
|
||||||
<meshStandardMaterial color={props.color} />
|
|
||||||
</mesh>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
function Sphere(props) {
|
|
||||||
const mesh = useRef()
|
|
||||||
return (
|
|
||||||
<mesh {...props} ref={mesh} scale={[1, 1, 1]}>
|
|
||||||
<sphereBufferGeometry args={[2, 30, 30]} />
|
|
||||||
<meshStandardMaterial color={props.color} />
|
|
||||||
</mesh>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function PureIdeViewer({
|
|
||||||
dataType,
|
|
||||||
artifact,
|
|
||||||
onInit,
|
|
||||||
onCameraChange,
|
|
||||||
isLoading,
|
|
||||||
isMinimal = false,
|
|
||||||
scadRatio = 1,
|
|
||||||
}: {
|
|
||||||
dataType: 'INIT' | ArtifactTypes
|
|
||||||
artifact: any
|
|
||||||
isLoading: boolean
|
|
||||||
onInit: Function
|
|
||||||
onCameraChange: Function
|
|
||||||
isMinimal?: boolean
|
|
||||||
scadRatio?: number
|
|
||||||
}) {
|
|
||||||
const [isDragging, setIsDragging] = useState(false)
|
|
||||||
const [image, setImage] = useState()
|
|
||||||
const controlsRef = useRef<any>()
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setImage(dataType === 'png' && artifact)
|
|
||||||
setIsDragging(false)
|
|
||||||
}, [dataType, artifact])
|
|
||||||
|
|
||||||
// the following are tailwind colors in hex, can't use these classes to color three.js meshes.
|
|
||||||
const pink400 = '#F472B6'
|
|
||||||
const indigo300 = '#A5B4FC'
|
|
||||||
const indigo900 = '#312E81'
|
|
||||||
const jscadLightIntensity = dataType === 'primitive-array' ? 0.5 : 1.1
|
|
||||||
return (
|
|
||||||
<div className="relative h-full bg-ch-gray-800 cursor-grab">
|
|
||||||
{image && (
|
|
||||||
<div
|
|
||||||
className={`absolute inset-0 transition-opacity duration-500 ${
|
|
||||||
isDragging ? 'opacity-25' : 'opacity-100'
|
|
||||||
}`}
|
|
||||||
style={{
|
|
||||||
transform: `translate(${
|
|
||||||
scadRatio !== 1 ? '-250px, -261px' : '0px, 0px'
|
|
||||||
})`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
alt="code-cad preview"
|
|
||||||
id="special"
|
|
||||||
src={URL.createObjectURL(image)}
|
|
||||||
className="h-full w-full"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div // eslint-disable-line jsx-a11y/no-static-element-interactions
|
|
||||||
className={`opacity-0 absolute inset-0 transition-opacity duration-500 ${
|
|
||||||
!(isDragging || dataType !== 'png')
|
|
||||||
? 'hover:opacity-50'
|
|
||||||
: 'opacity-100'
|
|
||||||
}`}
|
|
||||||
onMouseDown={() => setIsDragging(true)}
|
|
||||||
>
|
|
||||||
<Canvas linear={true} dpr={[1, 2]}>
|
|
||||||
<Controls
|
|
||||||
onDragStart={() => setIsDragging(true)}
|
|
||||||
onInit={onInit}
|
|
||||||
onCameraChange={onCameraChange}
|
|
||||||
controlsRef={controlsRef}
|
|
||||||
/>
|
|
||||||
<PerspectiveCamera makeDefault up={[0, 0, 1]}>
|
|
||||||
<pointLight
|
|
||||||
position={[0, 0, 100]}
|
|
||||||
intensity={jscadLightIntensity}
|
|
||||||
/>
|
|
||||||
</PerspectiveCamera>
|
|
||||||
<ambientLight intensity={2 * jscadLightIntensity} />
|
|
||||||
<pointLight
|
|
||||||
position={[-1000, -1000, -1000]}
|
|
||||||
color="#5555FF"
|
|
||||||
intensity={1 * jscadLightIntensity}
|
|
||||||
/>
|
|
||||||
<pointLight
|
|
||||||
position={[-1000, 0, 1000]}
|
|
||||||
color="#5555FF"
|
|
||||||
intensity={1 * jscadLightIntensity}
|
|
||||||
/>
|
|
||||||
<gridHelper
|
|
||||||
args={[200, 20, 0xff5555, 0x555555]}
|
|
||||||
material-opacity={0.2}
|
|
||||||
material-transparent
|
|
||||||
rotation-x={Math.PI / 2}
|
|
||||||
/>
|
|
||||||
{!isMinimal && (
|
|
||||||
<GizmoHelper alignment={'top-left'} margin={[80, 80]}>
|
|
||||||
<GizmoViewport
|
|
||||||
axisColors={['red', 'green', 'blue']}
|
|
||||||
labelColor="black"
|
|
||||||
/>
|
|
||||||
</GizmoHelper>
|
|
||||||
)}
|
|
||||||
{dataType === 'png' && (
|
|
||||||
<>
|
|
||||||
<Sphere position={[0, 0, 0]} color={pink400} />
|
|
||||||
<Box position={[0, 50, 0]} size={[1, 100, 1]} color={indigo900} />
|
|
||||||
<Box position={[0, 0, 50]} size={[1, 1, 100]} color={indigo300} />
|
|
||||||
<Box position={[50, 0, 0]} size={[100, 1, 1]} color={pink400} />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{dataType !== 'png' && artifact && (
|
|
||||||
<Suspense fallback={null}>
|
|
||||||
<Asset
|
|
||||||
geometry={artifact}
|
|
||||||
dataType={dataType}
|
|
||||||
controlsRef={controlsRef}
|
|
||||||
/>
|
|
||||||
</Suspense>
|
|
||||||
)}
|
|
||||||
</Canvas>
|
|
||||||
</div>
|
|
||||||
<DelayedPingAnimation isLoading={isLoading} />
|
|
||||||
{!isMinimal && <Customizer />}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const IdeViewer = ({
|
const IdeViewer = ({
|
||||||
handleOwnCamera = false,
|
handleOwnCamera = false,
|
||||||
@@ -347,7 +16,6 @@ const IdeViewer = ({
|
|||||||
}
|
}
|
||||||
const onCameraChange = (camera) => {
|
const onCameraChange = (camera) => {
|
||||||
if (handleOwnCamera) {
|
if (handleOwnCamera) {
|
||||||
console.log('yo')
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
thunkDispatch({
|
thunkDispatch({
|
||||||
@@ -362,6 +30,7 @@ const IdeViewer = ({
|
|||||||
state,
|
state,
|
||||||
dispatch,
|
dispatch,
|
||||||
camera,
|
camera,
|
||||||
|
viewAll: state?.objectData?.type === 'INIT',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -374,6 +43,7 @@ const IdeViewer = ({
|
|||||||
onInit={onInit}
|
onInit={onInit}
|
||||||
onCameraChange={onCameraChange}
|
onCameraChange={onCameraChange}
|
||||||
isLoading={state.isLoading}
|
isLoading={state.isLoading}
|
||||||
|
camera={state?.camera}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
287
app/web/src/components/IdeViewer/PureIdeViewer.tsx
Normal file
287
app/web/src/components/IdeViewer/PureIdeViewer.tsx
Normal file
@@ -0,0 +1,287 @@
|
|||||||
|
import * as THREE from 'three'
|
||||||
|
import { useRef, useState, useEffect, Suspense } from 'react'
|
||||||
|
import { Canvas, useThree } from '@react-three/fiber'
|
||||||
|
import {
|
||||||
|
PerspectiveCamera,
|
||||||
|
GizmoHelper,
|
||||||
|
GizmoViewport,
|
||||||
|
OrbitControls,
|
||||||
|
} from '@react-three/drei'
|
||||||
|
import { Vector3 } from 'three'
|
||||||
|
import Customizer from 'src/components/Customizer/Customizer'
|
||||||
|
import DelayedPingAnimation from 'src/components/DelayedPingAnimation/DelayedPingAnimation'
|
||||||
|
import type { ArtifactTypes } from 'src/helpers/cadPackages/common'
|
||||||
|
import { State } from 'src/helpers/hooks/useIdeState'
|
||||||
|
import { Asset } from './Asset'
|
||||||
|
|
||||||
|
function Controls({
|
||||||
|
onCameraChange,
|
||||||
|
onDragStart,
|
||||||
|
onInit,
|
||||||
|
controlsRef,
|
||||||
|
camera: scadCamera,
|
||||||
|
}: {
|
||||||
|
onCameraChange: Function
|
||||||
|
onDragStart: () => void
|
||||||
|
onInit: Function
|
||||||
|
controlsRef: React.MutableRefObject<any>
|
||||||
|
camera: State['camera']
|
||||||
|
}) {
|
||||||
|
const debounceTimeoutId = useRef(0)
|
||||||
|
const isFirstCameraChange = useRef(true)
|
||||||
|
const threeInstance = useThree()
|
||||||
|
const { camera, gl } = threeInstance
|
||||||
|
useEffect(() => {
|
||||||
|
// setup three to openscad camera sync
|
||||||
|
|
||||||
|
onInit(threeInstance)
|
||||||
|
// init camera position
|
||||||
|
camera.position.x = 80
|
||||||
|
camera.position.y = 50
|
||||||
|
camera.position.z = 50
|
||||||
|
camera.far = 10000
|
||||||
|
camera.fov = 22.5 // matches default openscad fov
|
||||||
|
camera.updateProjectionMatrix()
|
||||||
|
|
||||||
|
camera.rotation._order = 'ZYX'
|
||||||
|
const getRotations = (): number[] => {
|
||||||
|
const { x, y, z } = camera?.rotation || {}
|
||||||
|
return [x, y, z].map((rot) => (rot * 180) / Math.PI)
|
||||||
|
}
|
||||||
|
const getPositions = () => {
|
||||||
|
// Difficult to make this clean since I'm not sure why it works
|
||||||
|
// The OpenSCAD camera seems hard to work with but maybe it's just me
|
||||||
|
|
||||||
|
// this gives us a vector the same length as the camera.position
|
||||||
|
const cameraViewVector = new Vector3(0, 0, 1)
|
||||||
|
.applyQuaternion(camera.quaternion) // make unit vector of the camera
|
||||||
|
.multiplyScalar(camera.position.length()) // make it the same length as the position vector
|
||||||
|
|
||||||
|
// make a vector from the position vector to the cameraView vector
|
||||||
|
const head2Head = new Vector3().subVectors(
|
||||||
|
camera.position,
|
||||||
|
cameraViewVector
|
||||||
|
)
|
||||||
|
const { x, y, z } = head2Head.add(camera.position)
|
||||||
|
return {
|
||||||
|
position: { x: x / 2, y: y / 2, z: z / 2 },
|
||||||
|
dist: camera.position.length() / 2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (controlsRef.current) {
|
||||||
|
const dragCallback = () => {
|
||||||
|
clearTimeout(debounceTimeoutId.current)
|
||||||
|
debounceTimeoutId.current = setTimeout(() => {
|
||||||
|
const [x, y, z] = getRotations()
|
||||||
|
const { position, dist } = getPositions()
|
||||||
|
|
||||||
|
onCameraChange(
|
||||||
|
{
|
||||||
|
position,
|
||||||
|
rotation: { x, y, z },
|
||||||
|
dist,
|
||||||
|
},
|
||||||
|
isFirstCameraChange.current
|
||||||
|
)
|
||||||
|
isFirstCameraChange.current = false
|
||||||
|
}, 400) as unknown as number
|
||||||
|
}
|
||||||
|
const dragStart = () => {
|
||||||
|
onDragStart()
|
||||||
|
clearTimeout(debounceTimeoutId.current)
|
||||||
|
}
|
||||||
|
controlsRef?.current?.addEventListener('end', dragCallback)
|
||||||
|
controlsRef?.current?.addEventListener('start', dragStart)
|
||||||
|
const oldCurrent = controlsRef.current
|
||||||
|
dragCallback()
|
||||||
|
return () => {
|
||||||
|
oldCurrent.removeEventListener('end', dragCallback)
|
||||||
|
oldCurrent.removeEventListener('start', dragStart)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [camera, controlsRef])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!scadCamera?.isScadUpdate || !scadCamera?.position) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// sync Three camera to OpenSCAD
|
||||||
|
const { x, y, z } = scadCamera.position || {}
|
||||||
|
const scadCameraPos = new Vector3(x * 2, y * 2, z * 2)
|
||||||
|
const cameraViewVector = new Vector3(0, 0, 1)
|
||||||
|
const { x: rx, y: ry, z: rz } = scadCamera.rotation || {}
|
||||||
|
const scadCameraEuler = new THREE.Euler(
|
||||||
|
...[rx, ry, rz].map((r) => (r * Math.PI) / 180),
|
||||||
|
'YZX'
|
||||||
|
) // I don't know why it seems to like 'YZX' order
|
||||||
|
cameraViewVector.applyEuler(scadCameraEuler)
|
||||||
|
cameraViewVector.multiplyScalar(scadCamera.dist * 2)
|
||||||
|
|
||||||
|
const scadToThreeCameraPosition = new Vector3().subVectors(
|
||||||
|
// I have no idea why this works
|
||||||
|
cameraViewVector.clone().add(scadCameraPos),
|
||||||
|
cameraViewVector
|
||||||
|
)
|
||||||
|
scadToThreeCameraPosition.multiplyScalar(
|
||||||
|
scadCamera.dist / scadToThreeCameraPosition.length()
|
||||||
|
)
|
||||||
|
camera.position.copy(scadToThreeCameraPosition.clone())
|
||||||
|
camera.updateProjectionMatrix()
|
||||||
|
}, [scadCamera, camera])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<OrbitControls
|
||||||
|
makeDefault
|
||||||
|
ref={controlsRef}
|
||||||
|
args={[camera, gl.domElement]}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Box(props) {
|
||||||
|
// This reference will give us direct access to the mesh
|
||||||
|
const mesh = useRef()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<mesh {...props} ref={mesh} scale={[1, 1, 1]}>
|
||||||
|
<boxBufferGeometry args={props.size} />
|
||||||
|
<meshStandardMaterial color={props.color} />
|
||||||
|
</mesh>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
function Sphere(props) {
|
||||||
|
const mesh = useRef()
|
||||||
|
return (
|
||||||
|
<mesh {...props} ref={mesh} scale={[1, 1, 1]}>
|
||||||
|
<sphereBufferGeometry args={[2, 30, 30]} />
|
||||||
|
<meshStandardMaterial color={props.color} />
|
||||||
|
</mesh>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PureIdeViewer({
|
||||||
|
dataType,
|
||||||
|
artifact,
|
||||||
|
onInit,
|
||||||
|
onCameraChange,
|
||||||
|
isLoading,
|
||||||
|
isMinimal = false,
|
||||||
|
scadRatio = 1,
|
||||||
|
camera,
|
||||||
|
}: {
|
||||||
|
dataType: 'INIT' | ArtifactTypes
|
||||||
|
artifact: any
|
||||||
|
isLoading: boolean
|
||||||
|
onInit: Function
|
||||||
|
onCameraChange: Function
|
||||||
|
isMinimal?: boolean
|
||||||
|
scadRatio?: number
|
||||||
|
camera?: State['camera']
|
||||||
|
}) {
|
||||||
|
const [isDragging, setIsDragging] = useState(false)
|
||||||
|
const [image, setImage] = useState()
|
||||||
|
const controlsRef = useRef<any>()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setImage(dataType === 'png' && artifact)
|
||||||
|
setIsDragging(false)
|
||||||
|
}, [dataType, artifact])
|
||||||
|
|
||||||
|
// the following are tailwind colors in hex, can't use these classes to color three.js meshes.
|
||||||
|
const pink400 = '#F472B6'
|
||||||
|
const indigo300 = '#A5B4FC'
|
||||||
|
const indigo900 = '#312E81'
|
||||||
|
const jscadLightIntensity = dataType === 'primitive-array' ? 0.5 : 1.1
|
||||||
|
return (
|
||||||
|
<div className="relative h-full bg-ch-gray-800 cursor-grab">
|
||||||
|
{image && (
|
||||||
|
<div
|
||||||
|
className={`absolute inset-0 transition-opacity duration-500 ${
|
||||||
|
isDragging ? 'opacity-25' : 'opacity-100'
|
||||||
|
}`}
|
||||||
|
style={{
|
||||||
|
transform: `translate(${
|
||||||
|
scadRatio !== 1 ? '-250px, -261px' : '0px, 0px'
|
||||||
|
})`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
alt="code-cad preview"
|
||||||
|
id="special"
|
||||||
|
src={URL.createObjectURL(image)}
|
||||||
|
className="h-full w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div // eslint-disable-line jsx-a11y/no-static-element-interactions
|
||||||
|
className={`opacity-0 absolute inset-0 transition-opacity duration-500 ${
|
||||||
|
!(isDragging || dataType !== 'png')
|
||||||
|
? 'hover:opacity-50'
|
||||||
|
: 'opacity-100'
|
||||||
|
}`}
|
||||||
|
onMouseDown={() => setIsDragging(true)}
|
||||||
|
>
|
||||||
|
<Canvas linear={true} dpr={[1, 2]}>
|
||||||
|
<Controls
|
||||||
|
onDragStart={() => setIsDragging(true)}
|
||||||
|
onInit={onInit}
|
||||||
|
onCameraChange={onCameraChange}
|
||||||
|
controlsRef={controlsRef}
|
||||||
|
camera={camera}
|
||||||
|
/>
|
||||||
|
<PerspectiveCamera makeDefault up={[0, 0, 1]}>
|
||||||
|
<pointLight
|
||||||
|
position={[0, 0, 100]}
|
||||||
|
intensity={jscadLightIntensity}
|
||||||
|
/>
|
||||||
|
</PerspectiveCamera>
|
||||||
|
<ambientLight intensity={2 * jscadLightIntensity} />
|
||||||
|
<pointLight
|
||||||
|
position={[-1000, -1000, -1000]}
|
||||||
|
color="#5555FF"
|
||||||
|
intensity={1 * jscadLightIntensity}
|
||||||
|
/>
|
||||||
|
<pointLight
|
||||||
|
position={[-1000, 0, 1000]}
|
||||||
|
color="#5555FF"
|
||||||
|
intensity={1 * jscadLightIntensity}
|
||||||
|
/>
|
||||||
|
<gridHelper
|
||||||
|
args={[200, 20, 0xff5555, 0x555555]}
|
||||||
|
material-opacity={0.2}
|
||||||
|
material-transparent
|
||||||
|
rotation-x={Math.PI / 2}
|
||||||
|
/>
|
||||||
|
{!isMinimal && (
|
||||||
|
<GizmoHelper alignment={'top-left'} margin={[80, 80]}>
|
||||||
|
<GizmoViewport
|
||||||
|
axisColors={['red', 'green', 'blue']}
|
||||||
|
labelColor="black"
|
||||||
|
/>
|
||||||
|
</GizmoHelper>
|
||||||
|
)}
|
||||||
|
{dataType === 'png' && (
|
||||||
|
<>
|
||||||
|
<Sphere position={[0, 0, 0]} color={pink400} />
|
||||||
|
<Box position={[0, 50, 0]} size={[1, 100, 1]} color={indigo900} />
|
||||||
|
<Box position={[0, 0, 50]} size={[1, 1, 100]} color={indigo300} />
|
||||||
|
<Box position={[50, 0, 0]} size={[100, 1, 1]} color={pink400} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{dataType !== 'png' && artifact && (
|
||||||
|
<Suspense fallback={null}>
|
||||||
|
<Asset
|
||||||
|
geometry={artifact}
|
||||||
|
dataType={dataType}
|
||||||
|
controlsRef={controlsRef}
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
|
)}
|
||||||
|
</Canvas>
|
||||||
|
</div>
|
||||||
|
<DelayedPingAnimation isLoading={isLoading} />
|
||||||
|
{!isMinimal && <Customizer />}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -35,10 +35,15 @@ export const Failure = ({ error }: CellFailureProps) => (
|
|||||||
<div style={{ color: 'red' }}>Error: {error.message}</div>
|
<div style={{ color: 'red' }}>Error: {error.message}</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
interface SocialCardProps extends CellSuccessProps<FindSocialCardQuery> {
|
||||||
|
children: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
export const Success = ({
|
export const Success = ({
|
||||||
userProject,
|
userProject,
|
||||||
variables: { image64, LiveProjectViewer },
|
variables: { image64 },
|
||||||
}: CellSuccessProps<FindSocialCardQuery>) => {
|
children,
|
||||||
|
}: SocialCardProps) => {
|
||||||
const image = userProject?.Project?.mainImage
|
const image = userProject?.Project?.mainImage
|
||||||
const gravatar = userProject?.image
|
const gravatar = userProject?.image
|
||||||
const truncatedDescription =
|
const truncatedDescription =
|
||||||
@@ -102,9 +107,9 @@ export const Success = ({
|
|||||||
image64 && 'opacity-0'
|
image64 && 'opacity-0'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{LiveProjectViewer && (
|
{children && (
|
||||||
<div className="w-full h-full" id="social-card-canvas">
|
<div className="w-full h-full" id="social-card-canvas">
|
||||||
<LiveProjectViewer />
|
{children}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { STLLoader } from 'three/examples/jsm/loaders/STLLoader'
|
import { STLLoader } from 'three/examples/jsm/loaders/STLLoader'
|
||||||
import { State } from 'src/helpers/hooks/useIdeState'
|
import { State } from 'src/helpers/hooks/useIdeState'
|
||||||
import { CadhubParams } from 'src/components/Customizer/customizerConverter'
|
import { CadhubParams } from 'src/components/Customizer/customizerConverter'
|
||||||
|
import type { Camera } from 'src/helpers/hooks/useIdeState'
|
||||||
|
|
||||||
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://oxt2p7ddgj.execute-api.us-east-1.amazonaws.com/prod'
|
||||||
|
|
||||||
export const stlToGeometry = (url) =>
|
export const stlToGeometry = (url) =>
|
||||||
new Promise((resolve, reject) => {
|
new Promise((resolve, reject) => {
|
||||||
@@ -18,6 +19,7 @@ export interface RenderArgs {
|
|||||||
camera: State['camera']
|
camera: State['camera']
|
||||||
viewerSize: State['viewerSize']
|
viewerSize: State['viewerSize']
|
||||||
quality: State['objectData']['quality']
|
quality: State['objectData']['quality']
|
||||||
|
viewAll: boolean
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,6 +38,7 @@ export interface HealthyResponse {
|
|||||||
}
|
}
|
||||||
customizerParams?: any[]
|
customizerParams?: any[]
|
||||||
currentParameters?: RawCustomizerParams
|
currentParameters?: RawCustomizerParams
|
||||||
|
camera?: Camera
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RawCustomizerParams {
|
export interface RawCustomizerParams {
|
||||||
@@ -48,12 +51,14 @@ export function createHealthyResponse({
|
|||||||
consoleMessage,
|
consoleMessage,
|
||||||
type,
|
type,
|
||||||
customizerParams,
|
customizerParams,
|
||||||
|
camera,
|
||||||
}: {
|
}: {
|
||||||
date: Date
|
date: Date
|
||||||
data: any
|
data: any
|
||||||
consoleMessage: string
|
consoleMessage: string
|
||||||
type: HealthyResponse['objectData']['type']
|
type: HealthyResponse['objectData']['type']
|
||||||
customizerParams?: CadhubParams[]
|
customizerParams?: CadhubParams[]
|
||||||
|
camera?: Camera
|
||||||
}): HealthyResponse {
|
}): HealthyResponse {
|
||||||
return {
|
return {
|
||||||
status: 'healthy',
|
status: 'healthy',
|
||||||
@@ -66,6 +71,7 @@ export function createHealthyResponse({
|
|||||||
message: consoleMessage,
|
message: consoleMessage,
|
||||||
time: date,
|
time: date,
|
||||||
},
|
},
|
||||||
|
camera,
|
||||||
customizerParams,
|
customizerParams,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ class WorkerHelper {
|
|||||||
}
|
}
|
||||||
render = (
|
render = (
|
||||||
code: string,
|
code: string,
|
||||||
parameters: { [key: string]: any }
|
parameters: { [key: string]: any } // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
): Promise<RenderResponse> => {
|
): Promise<RenderResponse> => {
|
||||||
const response: Promise<RenderResponse> = new Promise(
|
const response: Promise<RenderResponse> = new Promise(
|
||||||
(resolve: ResolveFn) => {
|
(resolve: ResolveFn) => {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
splitGziped,
|
splitGziped,
|
||||||
} from '../common'
|
} from '../common'
|
||||||
import { openScadToCadhubParams } from './openScadParams'
|
import { openScadToCadhubParams } from './openScadParams'
|
||||||
|
import type { XYZ, Camera } from 'src/helpers/hooks/useIdeState'
|
||||||
|
|
||||||
export const render = async ({ code, settings }: RenderArgs) => {
|
export const render = async ({ code, settings }: RenderArgs) => {
|
||||||
const pixelRatio = window.devicePixelRatio || 1
|
const pixelRatio = window.devicePixelRatio || 1
|
||||||
@@ -19,6 +20,7 @@ export const render = async ({ code, settings }: RenderArgs) => {
|
|||||||
const body = JSON.stringify({
|
const body = JSON.stringify({
|
||||||
settings: {
|
settings: {
|
||||||
size,
|
size,
|
||||||
|
viewAll: settings.viewAll,
|
||||||
parameters: settings.parameters,
|
parameters: settings.parameters,
|
||||||
camera: {
|
camera: {
|
||||||
// rounding to give our caching a chance to sometimes work
|
// rounding to give our caching a chance to sometimes work
|
||||||
@@ -59,7 +61,21 @@ export const render = async ({ code, settings }: RenderArgs) => {
|
|||||||
}
|
}
|
||||||
const blob = await response.blob()
|
const blob = await response.blob()
|
||||||
const text = await new Response(blob).text()
|
const text = await new Response(blob).text()
|
||||||
const { consoleMessage, customizerParams, type } = splitGziped(text)
|
const { consoleMessage, customizerParams, type, cameraInfo } =
|
||||||
|
splitGziped(text)
|
||||||
|
const vecArray2Obj = (arr: number[]): XYZ => ({
|
||||||
|
x: arr[0],
|
||||||
|
y: arr[1],
|
||||||
|
z: arr[2],
|
||||||
|
})
|
||||||
|
const camera: Camera = cameraInfo
|
||||||
|
? {
|
||||||
|
dist: cameraInfo?.distance,
|
||||||
|
position: vecArray2Obj(cameraInfo?.translation),
|
||||||
|
rotation: vecArray2Obj(cameraInfo?.rotation),
|
||||||
|
isScadUpdate: true,
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
return createHealthyResponse({
|
return createHealthyResponse({
|
||||||
type: type !== 'stl' ? 'png' : 'geometry',
|
type: type !== 'stl' ? 'png' : 'geometry',
|
||||||
data:
|
data:
|
||||||
@@ -67,6 +83,7 @@ export const render = async ({ code, settings }: RenderArgs) => {
|
|||||||
? blob
|
? blob
|
||||||
: await stlToGeometry(window.URL.createObjectURL(blob)),
|
: await stlToGeometry(window.URL.createObjectURL(blob)),
|
||||||
consoleMessage,
|
consoleMessage,
|
||||||
|
camera,
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
customizerParams: openScadToCadhubParams(customizerParams || []),
|
customizerParams: openScadToCadhubParams(customizerParams || []),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -18,12 +18,13 @@ export const use3dViewerResize = () => {
|
|||||||
})
|
})
|
||||||
thunkDispatch((dispatch, getState) => {
|
thunkDispatch((dispatch, getState) => {
|
||||||
const state = getState()
|
const state = getState()
|
||||||
if (['png', 'INIT'].includes(state.objectData?.type)) {
|
if (state.objectData?.type === 'png') {
|
||||||
dispatch({ type: 'setLoading' })
|
dispatch({ type: 'setLoading' })
|
||||||
requestRender({
|
requestRender({
|
||||||
state,
|
state,
|
||||||
dispatch,
|
dispatch,
|
||||||
viewerSize: { width, height },
|
viewerSize: { width, height },
|
||||||
|
viewAll: state.objectData?.type === 'INIT',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -19,11 +19,17 @@ const codeStorageKey = 'Last-editor-code'
|
|||||||
export const makeCodeStoreKey = (ideType) => `${codeStorageKey}-${ideType}`
|
export const makeCodeStoreKey = (ideType) => `${codeStorageKey}-${ideType}`
|
||||||
let mutableState: State = null
|
let mutableState: State = null
|
||||||
|
|
||||||
interface XYZ {
|
export interface XYZ {
|
||||||
x: number
|
x: number
|
||||||
y: number
|
y: number
|
||||||
z: number
|
z: number
|
||||||
}
|
}
|
||||||
|
export interface Camera {
|
||||||
|
dist?: number
|
||||||
|
position?: XYZ
|
||||||
|
rotation?: XYZ
|
||||||
|
isScadUpdate?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export interface MosaicTree {
|
export interface MosaicTree {
|
||||||
first: string | MosaicTree
|
first: string | MosaicTree
|
||||||
@@ -69,11 +75,7 @@ export interface State {
|
|||||||
currentParameters?: RawCustomizerParams
|
currentParameters?: RawCustomizerParams
|
||||||
isCustomizerOpen: boolean
|
isCustomizerOpen: boolean
|
||||||
layout: MosaicTree
|
layout: MosaicTree
|
||||||
camera: {
|
camera: Camera
|
||||||
dist?: number
|
|
||||||
position?: XYZ
|
|
||||||
rotation?: XYZ
|
|
||||||
}
|
|
||||||
viewerSize: { width: number; height: number }
|
viewerSize: { width: number; height: number }
|
||||||
isLoading: boolean
|
isLoading: boolean
|
||||||
threeInstance: RootState
|
threeInstance: RootState
|
||||||
@@ -165,6 +167,7 @@ const reducer = (state: State, { type, payload }): State => {
|
|||||||
? [...state.consoleMessages, payload.message]
|
? [...state.consoleMessages, payload.message]
|
||||||
: payload.message,
|
: payload.message,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
|
camera: payload.camera || state.camera,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case 'errorRender':
|
case 'errorRender':
|
||||||
@@ -308,6 +311,7 @@ export const useIdeState = (): [State, (actionOrThunk: any) => any] => {
|
|||||||
interface RequestRenderArgsStateless {
|
interface RequestRenderArgsStateless {
|
||||||
state: State
|
state: State
|
||||||
camera?: State['camera']
|
camera?: State['camera']
|
||||||
|
viewAll?: boolean
|
||||||
viewerSize?: State['viewerSize']
|
viewerSize?: State['viewerSize']
|
||||||
quality?: State['objectData']['quality']
|
quality?: State['objectData']['quality']
|
||||||
specialCadProcess?: string
|
specialCadProcess?: string
|
||||||
@@ -317,6 +321,7 @@ interface RequestRenderArgsStateless {
|
|||||||
export const requestRenderStateless = ({
|
export const requestRenderStateless = ({
|
||||||
state,
|
state,
|
||||||
camera,
|
camera,
|
||||||
|
viewAll,
|
||||||
viewerSize,
|
viewerSize,
|
||||||
quality = 'low',
|
quality = 'low',
|
||||||
specialCadProcess = null,
|
specialCadProcess = null,
|
||||||
@@ -339,6 +344,7 @@ export const requestRenderStateless = ({
|
|||||||
parameters: state.isCustomizerOpen
|
parameters: state.isCustomizerOpen
|
||||||
? parameters || state.currentParameters
|
? parameters || state.currentParameters
|
||||||
: {},
|
: {},
|
||||||
|
viewAll,
|
||||||
camera: camera || state.camera,
|
camera: camera || state.camera,
|
||||||
viewerSize: viewerSize || state.viewerSize,
|
viewerSize: viewerSize || state.viewerSize,
|
||||||
quality,
|
quality,
|
||||||
@@ -363,6 +369,7 @@ export const requestRender = ({ dispatch, ...rest }: RequestRenderArgs) => {
|
|||||||
status,
|
status,
|
||||||
customizerParams,
|
customizerParams,
|
||||||
currentParameters,
|
currentParameters,
|
||||||
|
camera,
|
||||||
}) => {
|
}) => {
|
||||||
if (status === 'error') {
|
if (status === 'error') {
|
||||||
dispatch({
|
dispatch({
|
||||||
@@ -378,6 +385,7 @@ export const requestRender = ({ dispatch, ...rest }: RequestRenderArgs) => {
|
|||||||
lastRunCode: code,
|
lastRunCode: code,
|
||||||
customizerParams,
|
customizerParams,
|
||||||
currentParameters,
|
currentParameters,
|
||||||
|
camera,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
return objectData
|
return objectData
|
||||||
|
|||||||
Reference in New Issue
Block a user