Fix camera perspective and other polish for openscad viewer #238

Merged
Irev-Dev merged 5 commits from kurt/237 into main 2021-03-13 07:14:47 +01:00
3 changed files with 92 additions and 60 deletions

View File

@@ -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 {

View File

@@ -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>

View File

@@ -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: