Fix camera perspective and other polish for openscad viewer #238
@@ -17,7 +17,7 @@ module.exports.runScad = async ({
|
||||
const { x: rx, y: ry, z: rz } = rotation
|
||||
const { x: px, y: py, z: pz } = position
|
||||
const cameraArg = `--camera=${px},${py},${pz},${rx},${ry},${rz},300`
|
||||
const command = `xvfb-run --auto-servernum --server-args "-screen 0 1024x768x24" openscad -o /tmp/${tempFile}/output.png ${cameraArg} --imgsize=${x},${y} /tmp/${tempFile}/main.scad`
|
||||
const command = `xvfb-run --auto-servernum --server-args "-screen 0 1024x768x24" openscad -o /tmp/${tempFile}/output.png ${cameraArg} --imgsize=${x},${y} --colorscheme DeepOcean /tmp/${tempFile}/main.scad`
|
||||
console.log('command', command)
|
||||
|
||||
try {
|
||||
|
||||
@@ -1,49 +1,64 @@
|
||||
import { IdeContext } from 'src/components/IdeToolbarNew'
|
||||
import { useRef, useState, useEffect, useContext, useMemo } from 'react'
|
||||
import { useRef, useState, useEffect, useContext } from 'react'
|
||||
import { Canvas, extend, useFrame, useThree } from 'react-three-fiber'
|
||||
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
|
||||
|
||||
extend({ OrbitControls })
|
||||
|
||||
function Controls({ onCameraChange }) {
|
||||
let debounceTimeoutId
|
||||
function Controls({ onCameraChange, onDragStart }) {
|
||||
const controls = useRef()
|
||||
const { scene, camera, gl } = useThree()
|
||||
useEffect(() => {
|
||||
// init camera position
|
||||
camera.position.x = 12
|
||||
camera.position.x = 16
|
||||
camera.position.y = 12
|
||||
// Euler rotation can be problematic since order matters
|
||||
// We want it to rotate around the z or vertical axis first then the x axis
|
||||
// in Three Y is the vertical axis (Z for openscad)
|
||||
camera.fov = 22.5 // matches default openscad fov
|
||||
|
||||
// Order matters with Euler rotations
|
||||
// We want it to rotate around the z or vertical axis first then the x axis to match openscad
|
||||
// in Three.js Y is the vertical axis (Z for openscad)
|
||||
camera.rotation._order = 'YXZ'
|
||||
const getRotations = () => {
|
||||
const { x, y, z } = camera.rotation
|
||||
const rad2Deg = 180 / Math.PI
|
||||
const scadX = (x + Math.PI / 2) * rad2Deg
|
||||
const scadZ = y * rad2Deg
|
||||
const scadY = z * rad2Deg
|
||||
return [scadX, scadY, scadZ]
|
||||
}
|
||||
const getPositions = () => {
|
||||
const { x: scadX, y: scadZ, z: negScadY } = camera.position
|
||||
const scale = 10
|
||||
const scadY = -negScadY
|
||||
return [scadX * scale, scadY * scale, scadZ * scale]
|
||||
}
|
||||
|
||||
if (controls.current) {
|
||||
const callback = ({ target }) => {
|
||||
const getRotations = () => {
|
||||
const { x, y, z } = target.object.rotation
|
||||
const rad2Deg = 180 / Math.PI
|
||||
const scadX = (x + Math.PI / 2) * rad2Deg
|
||||
const scadZ = y * rad2Deg
|
||||
const scadY = z * rad2Deg
|
||||
return [scadX, scadY, scadZ]
|
||||
}
|
||||
const getPositions = () => {
|
||||
const { x: scadX, y: scadZ, z: negScadY } = target.object.position
|
||||
const scale = 10 // I'm not sure why this is exactly 10
|
||||
const scadY = -negScadY
|
||||
return [scadX * scale, scadY * scale, scadZ * scale]
|
||||
}
|
||||
const [rx, ry, rz] = getRotations()
|
||||
const [x, y, z] = getPositions()
|
||||
const dragCallback = () => {
|
||||
clearTimeout(debounceTimeoutId)
|
||||
debounceTimeoutId = setTimeout(() => {
|
||||
const [rx, ry, rz] = getRotations()
|
||||
const [x, y, z] = getPositions()
|
||||
|
||||
onCameraChange({
|
||||
position: { x, y, z },
|
||||
rotation: { x: rx, y: ry, z: rz },
|
||||
})
|
||||
onCameraChange({
|
||||
position: { x, y, z },
|
||||
rotation: { x: rx, y: ry, z: rz },
|
||||
})
|
||||
}, 400)
|
||||
}
|
||||
controls.current.addEventListener('end', callback)
|
||||
const dragStart = () => {
|
||||
onDragStart()
|
||||
clearTimeout(debounceTimeoutId)
|
||||
}
|
||||
controls.current.addEventListener('end', dragCallback)
|
||||
controls.current.addEventListener('start', dragStart)
|
||||
const oldCurrent = controls.current
|
||||
return () => oldCurrent.removeEventListener('end', callback)
|
||||
dragCallback()
|
||||
return () => {
|
||||
oldCurrent.removeEventListener('end', dragCallback)
|
||||
oldCurrent.removeEventListener('start', dragStart)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
@@ -52,8 +67,6 @@ function Controls({ onCameraChange }) {
|
||||
<orbitControls
|
||||
ref={controls}
|
||||
args={[camera, gl.domElement]}
|
||||
enableDamping
|
||||
dampingFactor={0.1}
|
||||
rotateSpeed={0.5}
|
||||
/>
|
||||
)
|
||||
@@ -65,7 +78,7 @@ function Box(props) {
|
||||
|
||||
return (
|
||||
<mesh {...props} ref={mesh} scale={[1, 1, 1]}>
|
||||
<boxBufferGeometry args={[props.size, props.size, props.size]} />
|
||||
<boxBufferGeometry args={props.size} />
|
||||
<meshStandardMaterial color={props.color} />
|
||||
</mesh>
|
||||
)
|
||||
@@ -74,43 +87,43 @@ let currentCode // I have no idea why this works and using state.code is the dis
|
||||
const IdeViewer = () => {
|
||||
const { state, dispatch } = useContext(IdeContext)
|
||||
const [isDragging, setIsDragging] = useState(false)
|
||||
const [image, setImage] = useState()
|
||||
|
||||
const image = useMemo(
|
||||
() =>
|
||||
useEffect(() => {
|
||||
setImage(
|
||||
state.objectData?.type === 'png' &&
|
||||
state.objectData?.data &&
|
||||
window.URL.createObjectURL(state.objectData?.data),
|
||||
[state.objectData]
|
||||
)
|
||||
state.objectData?.data &&
|
||||
window.URL.createObjectURL(state.objectData?.data)
|
||||
)
|
||||
setIsDragging(false)
|
||||
}, [state.objectData])
|
||||
currentCode = state.code
|
||||
return (
|
||||
<div className="p-8 border-2 m-2">
|
||||
<div className="pb-4">
|
||||
Rotating the Three.js Cube should than update the openscad camera to
|
||||
view the CAD object from the same angle. But having trouble translating
|
||||
between the two coordinate systems. OpenScad's camera only changes X and
|
||||
Z angles and Y is always 0. Where as rotation values from Three seem to
|
||||
change all x, y and z. I probably need to learn a bit more about 3d
|
||||
math.
|
||||
</div>
|
||||
<div className="relative" style={{ height: '500px', width: '500px' }}>
|
||||
{state.isLoading && (
|
||||
<div className="inset-0 absolute flex items-center justify-center">
|
||||
<div className="h-16 w-16 bg-pink-600 rounded-full animate-ping"></div>
|
||||
</div>
|
||||
)}
|
||||
{image && (
|
||||
<div
|
||||
className="absolute inset-0"
|
||||
style={{ opacity: isDragging ? '0%' : '100%' }}
|
||||
className={`absolute inset-0 transition-opacity duration-500 ${
|
||||
isDragging ? 'opacity-25' : 'opacity-100'
|
||||
}`}
|
||||
>
|
||||
<img src={image} className="" />
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={`opacity-0 absolute inset-0 ${
|
||||
className={`opacity-0 absolute inset-0 transition-opacity duration-500 ${
|
||||
isDragging ? 'opacity-100' : 'hover:opacity-50'
|
||||
}`}
|
||||
onMouseDown={() => setIsDragging(true)}
|
||||
onMouseUp={() => setIsDragging(false)}
|
||||
>
|
||||
<Canvas>
|
||||
<Controls
|
||||
onDragStart={() => setIsDragging(true)}
|
||||
onCameraChange={({ position, rotation }) => {
|
||||
dispatch({
|
||||
type: 'render',
|
||||
@@ -126,10 +139,17 @@ const IdeViewer = () => {
|
||||
/>
|
||||
<ambientLight />
|
||||
<pointLight position={[15, 5, 10]} />
|
||||
<Box position={[0, 0, 0]} size={10} color="orange" />
|
||||
<Box position={[0, 5, 0]} size={1} color="cyan" />
|
||||
<Box position={[0, 0, 5]} size={1} color="magenta" />
|
||||
<Box position={[5, 0, 0]} size={1} color="hotpink" />
|
||||
<Box position={[0, 4.95, 0]} size={[0.1, 10, 0.1]} color="cyan" />
|
||||
<Box
|
||||
position={[0, 0, -5.05]}
|
||||
size={[0.1, 0.1, 10]}
|
||||
color="orange"
|
||||
/>
|
||||
<Box
|
||||
position={[5.05, 0, 0]}
|
||||
size={[10, 0.1, 0.1]}
|
||||
color="hotpink"
|
||||
/>
|
||||
</Canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,11 +4,14 @@ import { cadPackages } from 'src/helpers/cadPackages'
|
||||
export const useIdeState = () => {
|
||||
const initialState = {
|
||||
ideType: 'openScad',
|
||||
consoleMessages: [
|
||||
{ type: 'error', message: 'line 15 is being very naughty' },
|
||||
{ type: 'message', message: '5 bodies produced' },
|
||||
],
|
||||
code: 'cube(60);sphere(25);',
|
||||
consoleMessages: [{ type: 'message', message: 'Initialising OpenSCAD' }],
|
||||
code: `difference(){
|
||||
union(){
|
||||
cube(60);
|
||||
sphere(25);
|
||||
}
|
||||
translate([30,30,30])cylinder(r=25,h=100);
|
||||
}`,
|
||||
objectData: {
|
||||
type: 'stl',
|
||||
data: 'some binary',
|
||||
@@ -23,6 +26,7 @@ export const useIdeState = () => {
|
||||
splitPercentage: 70,
|
||||
},
|
||||
},
|
||||
isLoading: false,
|
||||
}
|
||||
const reducer = (state, { type, payload }) => {
|
||||
switch (type) {
|
||||
@@ -38,6 +42,7 @@ export const useIdeState = () => {
|
||||
consoleMessages: payload.message
|
||||
? [...state.consoleMessages, payload.message]
|
||||
: payload.message,
|
||||
isLoading: false,
|
||||
}
|
||||
case 'errorRender':
|
||||
return {
|
||||
@@ -45,6 +50,7 @@ export const useIdeState = () => {
|
||||
consoleMessages: payload.message
|
||||
? [...state.consoleMessages, payload.message]
|
||||
: payload.message,
|
||||
isLoading: false,
|
||||
}
|
||||
case 'setIdeType':
|
||||
return {
|
||||
@@ -56,6 +62,11 @@ export const useIdeState = () => {
|
||||
...state,
|
||||
layout: payload.message,
|
||||
}
|
||||
case 'setLoading':
|
||||
return {
|
||||
...state,
|
||||
isLoading: true,
|
||||
}
|
||||
default:
|
||||
return state
|
||||
}
|
||||
@@ -83,6 +94,7 @@ export const useIdeState = () => {
|
||||
})
|
||||
}
|
||||
})
|
||||
dispatch({ type: 'setLoading' })
|
||||
break
|
||||
|
||||
default:
|
||||
|
||||
Reference in New Issue
Block a user