Add axes w/ rotation sync w/ main model viewer
This commit is contained in:
parent
a01a7df0a0
commit
7a9e3f927f
3 changed files with 109 additions and 22 deletions
46
axes.scad
Normal file
46
axes.scad
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
module arrow(length=30, shaft_radius=1, head_radius=2, head_length=5) {
|
||||||
|
cylinder(h=length-head_length, r=shaft_radius, center=false);
|
||||||
|
|
||||||
|
translate([0, 0, length-head_length])
|
||||||
|
cylinder(h=head_length, r1=head_radius, r2=0, center=false);
|
||||||
|
}
|
||||||
|
|
||||||
|
color("red")
|
||||||
|
rotate([0, 90, 0])
|
||||||
|
arrow();
|
||||||
|
|
||||||
|
color("green")
|
||||||
|
rotate([-90, 0, 0])
|
||||||
|
arrow();
|
||||||
|
|
||||||
|
color("blue")
|
||||||
|
rotate([0, 0, 90])
|
||||||
|
arrow();
|
||||||
|
|
||||||
|
module letter(text)
|
||||||
|
linear_extrude(1) text(text, halign="center", valign="center");
|
||||||
|
|
||||||
|
letter_dist = 38;
|
||||||
|
union() {
|
||||||
|
color("red")
|
||||||
|
translate([letter_dist, 0, 0])
|
||||||
|
rotate([45, 0, 45])
|
||||||
|
letter("X");
|
||||||
|
color("green")
|
||||||
|
rotate([0, 0, 90])
|
||||||
|
translate([letter_dist, 0, 0])
|
||||||
|
rotate([45, 0, -45])
|
||||||
|
letter("Y");
|
||||||
|
color("blue")
|
||||||
|
rotate([0, -90, 0])
|
||||||
|
translate([letter_dist, 0, 0])
|
||||||
|
rotate([90+45, 0, 0])
|
||||||
|
rotate([0, 0, -90])
|
||||||
|
letter("Z");
|
||||||
|
}
|
||||||
|
|
||||||
|
color("grey")
|
||||||
|
cube(10, center=true);
|
||||||
|
|
||||||
|
color([0, 0, 0, $preview ? 0.05 : 0])
|
||||||
|
sphere(r=43);
|
||||||
BIN
public/axes.glb
Normal file
BIN
public/axes.glb
Normal file
Binary file not shown.
|
|
@ -1,10 +1,7 @@
|
||||||
// Portions of this file are Copyright 2021 Google LLC, and licensed under GPL2+. See COPYING.
|
// Portions of this file are Copyright 2021 Google LLC, and licensed under GPL2+. See COPYING.
|
||||||
|
|
||||||
import { CSSProperties, useContext, useRef } from 'react';
|
import { CSSProperties, useContext, useEffect, useRef, useState } from 'react';
|
||||||
import { ModelContext } from './contexts';
|
import { ModelContext } from './contexts';
|
||||||
import { StlViewer} from "react-stl-viewer";
|
|
||||||
import { ColorPicker } from 'primereact/colorpicker';
|
|
||||||
import { defaultModelColor } from '../state/initial-state';
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
namespace JSX {
|
namespace JSX {
|
||||||
|
|
@ -19,34 +16,78 @@ export default function ViewerPanel({className, style}: {className?: string, sty
|
||||||
if (!model) throw new Error('No model');
|
if (!model) throw new Error('No model');
|
||||||
|
|
||||||
const state = model.state;
|
const state = model.state;
|
||||||
const modelRef = useRef();
|
const modelViewerRef = useRef<any>();
|
||||||
|
const axesViewerRef = useRef<any>();
|
||||||
|
|
||||||
|
for (const ref of [modelViewerRef, axesViewerRef]) {
|
||||||
|
const otherRef = ref === modelViewerRef ? axesViewerRef : modelViewerRef;
|
||||||
|
useEffect(() => {
|
||||||
|
function handleCameraChange(e: any) {
|
||||||
|
if (e.detail.source === 'user-interaction') {
|
||||||
|
const cameraOrbit = ref.current.getCameraOrbit();
|
||||||
|
cameraOrbit.radius = otherRef.current.getCameraOrbit().radius;
|
||||||
|
|
||||||
|
otherRef.current.cameraOrbit = cameraOrbit.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ref.current.addEventListener('camera-change', handleCameraChange);
|
||||||
|
return () => ref.current.removeEventListener('camera-change', handleCameraChange);
|
||||||
|
}, [ref.current, otherRef.current]);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className}
|
<div className={className}
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
|
position: 'relative',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
width: '100%',
|
width: '100%',
|
||||||
...(style ?? {})
|
...(style ?? {})
|
||||||
}}>
|
}}>
|
||||||
{(state.output?.displayFileURL || state.output?.outFile && state.output.outFile.name.endsWith('.glb') && state.output?.outFileURL) && (
|
<model-viewer
|
||||||
<model-viewer
|
orientation="0deg -90deg 0deg"
|
||||||
orientation="0deg -90deg 0deg"
|
src={state.output?.displayFileURL ?? state.output?.outFileURL ?? ''}
|
||||||
src={state.output?.displayFileURL ?? state.output?.outFileURL ?? ''}
|
style={{
|
||||||
style={{
|
width: '100%',
|
||||||
width: '100%',
|
height: '100%',
|
||||||
height: '100%',
|
}}
|
||||||
}}
|
environment-image="./skybox-lights.jpg"
|
||||||
environment-image="./skybox-lights.jpg"
|
max-camera-orbit="auto 180deg auto"
|
||||||
max-camera-orbit="auto 180deg auto"
|
min-camera-orbit="auto 0deg auto"
|
||||||
min-camera-orbit="auto 0deg auto"
|
camera-controls
|
||||||
camera-controls
|
ar
|
||||||
ar
|
ref={modelViewerRef}
|
||||||
ref={(ref: any) => {
|
>
|
||||||
modelRef.current = ref;
|
<span slot="progress-bar"></span>
|
||||||
}}
|
</model-viewer>
|
||||||
/>
|
{state.view.showAxes && (
|
||||||
|
<model-viewer
|
||||||
|
orientation="0deg -90deg 0deg"
|
||||||
|
src="./axes.glb"
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: 0,
|
||||||
|
right: 0,
|
||||||
|
zIndex: 10,
|
||||||
|
height: '200px',
|
||||||
|
width: '200px',
|
||||||
|
}}
|
||||||
|
loading="eager"
|
||||||
|
interpolation-decay="0"
|
||||||
|
environment-image="./skybox-lights.jpg"
|
||||||
|
max-camera-orbit="auto 180deg auto"
|
||||||
|
min-camera-orbit="auto 0deg auto"
|
||||||
|
orbit-sensitivity="5"
|
||||||
|
interaction-prompt="none"
|
||||||
|
disable-zoom
|
||||||
|
camera-controls="false"
|
||||||
|
disable-tap
|
||||||
|
disable-pan
|
||||||
|
ref={axesViewerRef}
|
||||||
|
>
|
||||||
|
<span slot="progress-bar"></span>
|
||||||
|
</model-viewer>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue