mirror of
https://github.com/nerfstudio-project/nerfstudio.git
synced 2026-11-12 02:45:34 +08:00
1 parent
d4119d4086
commit
0a5331fc5c
9 files changed
+312
-6
No files matched your search
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "viewer",
|
||||
"homepage": ".",
|
||||
"version": "22-10-13-0",
|
||||
"version": "22-11-10-0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.10.4",
|
||||
|
||||
@@ -369,3 +369,27 @@ $viewer_button_size: 50px;
|
||||
.ViewerWindow-iconbutton {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
// ------------------------------------
|
||||
// Export panel styling
|
||||
|
||||
.ExportModal-code {
|
||||
padding: 10px;
|
||||
background-color: $base-color3;
|
||||
overflow-x: scroll;
|
||||
font-family: monospace;
|
||||
white-space: pre;
|
||||
margin-top: 16px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.ExportModal-text {
|
||||
text-align: left;
|
||||
margin-top: 20px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.ExportModal-button {
|
||||
// margin: 15px;
|
||||
padding: 20px;
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/* eslint-disable react/jsx-props-no-spreading */
|
||||
import * as React from 'react';
|
||||
|
||||
import { Box, Typography, Tab, Tabs } from '@mui/material';
|
||||
import { LevaPanel, LevaStoreProvider, useCreateStore } from 'leva';
|
||||
import BlurOnIcon from '@mui/icons-material/BlurOn';
|
||||
import CategoryIcon from '@mui/icons-material/Category';
|
||||
import PointcloudSubPanel from './PointcloudSubPanel';
|
||||
import LevaTheme from '../../../themes/leva_theme.json';
|
||||
|
||||
interface TabPanelProps {
|
||||
children: React.ReactNode;
|
||||
index: number;
|
||||
value: number;
|
||||
}
|
||||
|
||||
function TabPanel(props: TabPanelProps) {
|
||||
const { children, value, index, ...other } = props;
|
||||
|
||||
return (
|
||||
<div
|
||||
role="tabpanel"
|
||||
hidden={value !== index}
|
||||
id={`simple-tabpanel-${index}`}
|
||||
aria-labelledby={`simple-tab-${index}`}
|
||||
{...other}
|
||||
>
|
||||
{value === index && (
|
||||
<Box sx={{ p: 3 }}>
|
||||
<Typography>{children}</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function a11yProps(index: number) {
|
||||
return {
|
||||
id: `simple-tab-${index}`,
|
||||
'aria-controls': `simple-tabpanel-${index}`,
|
||||
};
|
||||
}
|
||||
|
||||
export default function ExportPanel(props) {
|
||||
// unpack relevant information
|
||||
const sceneTree = props.sceneTree;
|
||||
const showExportBox = props.showExportBox;
|
||||
|
||||
const [type_value, setTypeValue] = React.useState(0);
|
||||
|
||||
const handleTypeChange = (event: React.SyntheticEvent, newValue: number) => {
|
||||
setTypeValue(newValue);
|
||||
};
|
||||
|
||||
const pointcloudStore = useCreateStore();
|
||||
|
||||
return (
|
||||
<div className="ExportPanel">
|
||||
<Tabs
|
||||
value={type_value}
|
||||
onChange={handleTypeChange}
|
||||
aria-label="export type"
|
||||
variant="fullWidth"
|
||||
centered
|
||||
sx={{ height: 55, minHeight: 55 }}
|
||||
>
|
||||
<Tab
|
||||
icon={<BlurOnIcon />}
|
||||
iconPosition="start"
|
||||
label="Point Cloud"
|
||||
disableRipple
|
||||
{...a11yProps(0)}
|
||||
/>
|
||||
<Tab
|
||||
icon={<CategoryIcon />}
|
||||
iconPosition="start"
|
||||
label="Mesh"
|
||||
disableRipple
|
||||
{...a11yProps(1)}
|
||||
/>
|
||||
</Tabs>
|
||||
<TabPanel value={type_value} index={0}>
|
||||
<LevaPanel
|
||||
store={pointcloudStore}
|
||||
className="Leva-panel"
|
||||
theme={LevaTheme}
|
||||
titleBar={false}
|
||||
fill
|
||||
flat
|
||||
/>
|
||||
<LevaStoreProvider store={pointcloudStore}>
|
||||
<PointcloudSubPanel
|
||||
sceneTree={sceneTree}
|
||||
showExportBox={showExportBox}
|
||||
/>
|
||||
</LevaStoreProvider>
|
||||
</TabPanel>
|
||||
<TabPanel value={type_value} index={1}>
|
||||
Coming Soon
|
||||
</TabPanel>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
import * as React from 'react';
|
||||
import * as THREE from 'three';
|
||||
import { useControls, useStoreContext } from 'leva';
|
||||
import { useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import ContentCopyRoundedIcon from '@mui/icons-material/ContentCopyRounded';
|
||||
|
||||
import { Button } from '@mui/material';
|
||||
|
||||
const PCD_CLIPPING_BOX_NAME = 'PCD Clipping Box';
|
||||
|
||||
export default function PointcloudSubPanel(props) {
|
||||
const sceneTree = props.sceneTree;
|
||||
const showExportBox = props.showExportBox;
|
||||
const store = useStoreContext();
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
// redux store state
|
||||
const config_base_dir = useSelector(
|
||||
(state) => state.renderingState.config_base_dir,
|
||||
);
|
||||
const clippingEnabled = useSelector(
|
||||
(state) => state.renderingState.clipping_enabled,
|
||||
);
|
||||
const clippingCenter = useSelector(
|
||||
(state) => state.renderingState.clipping_center,
|
||||
);
|
||||
const clippingScale = useSelector(
|
||||
(state) => state.renderingState.clipping_box_scale,
|
||||
);
|
||||
|
||||
const config_filename = `${config_base_dir}/config.yml`;
|
||||
|
||||
const geometry = new THREE.BoxGeometry(1, 1, 1);
|
||||
const material = new THREE.MeshBasicMaterial({
|
||||
color: 0xffd369,
|
||||
transparent: true,
|
||||
opacity: 0.7,
|
||||
side: THREE.DoubleSide,
|
||||
});
|
||||
const cube = new THREE.Mesh(geometry, material);
|
||||
sceneTree.set_object_from_path([PCD_CLIPPING_BOX_NAME], cube);
|
||||
cube.visible = showExportBox && clippingEnabled;
|
||||
|
||||
const update_box_center = (value) => {
|
||||
const box = sceneTree.find_object_no_create([PCD_CLIPPING_BOX_NAME]);
|
||||
box.position.set(value[0], value[1], value[2]);
|
||||
};
|
||||
|
||||
const update_box_scale = (value) => {
|
||||
const box = sceneTree.find_object_no_create([PCD_CLIPPING_BOX_NAME]);
|
||||
box.scale.set(value[0], value[1], value[2]);
|
||||
};
|
||||
|
||||
const controlValues = useControls(
|
||||
{
|
||||
numPoints: { label: 'Number of Points', value: 1000000, min: 1 },
|
||||
removeOutliers: { label: 'Remove Outliers', value: true },
|
||||
estimateNormals: { label: 'Estimate Normals', value: false },
|
||||
useBoundingBox: {
|
||||
label: 'Crop',
|
||||
value: clippingEnabled,
|
||||
onChange: (value) => {
|
||||
const box = sceneTree.find_object_no_create([PCD_CLIPPING_BOX_NAME]);
|
||||
box.visible = value;
|
||||
dispatch({
|
||||
type: 'write',
|
||||
path: 'renderingState/clipping_enabled',
|
||||
data: value,
|
||||
});
|
||||
},
|
||||
},
|
||||
center: {
|
||||
label: '| Center',
|
||||
value: clippingCenter,
|
||||
render: (get) => get('useBoundingBox'),
|
||||
onChange: (value) => {
|
||||
update_box_center(value);
|
||||
},
|
||||
onEditEnd: (value) => {
|
||||
dispatch({
|
||||
type: 'write',
|
||||
path: 'renderingState/clipping_center',
|
||||
data: value,
|
||||
});
|
||||
},
|
||||
},
|
||||
scale: {
|
||||
label: '| Scale',
|
||||
value: clippingScale,
|
||||
min: 0.01,
|
||||
render: (get) => get('useBoundingBox'),
|
||||
onChange: (value) => {
|
||||
update_box_scale(value);
|
||||
},
|
||||
onEditEnd: (value) => {
|
||||
dispatch({
|
||||
type: 'write',
|
||||
path: 'renderingState/clipping_box_scale',
|
||||
data: value,
|
||||
});
|
||||
},
|
||||
},
|
||||
outputDir: { label: 'Output Directory', value: 'pcd/' },
|
||||
},
|
||||
{ store },
|
||||
);
|
||||
|
||||
const getPythonBool = (boolean) => {
|
||||
return boolean ? 'True' : 'False';
|
||||
};
|
||||
|
||||
const bbox_min = [
|
||||
clippingCenter[0] - clippingScale[0] / 2,
|
||||
clippingCenter[1] - clippingScale[1] / 2,
|
||||
clippingCenter[2] - clippingScale[2] / 2,
|
||||
];
|
||||
const bbox_max = [
|
||||
clippingCenter[0] + clippingScale[0] / 2,
|
||||
clippingCenter[1] + clippingScale[1] / 2,
|
||||
clippingCenter[2] + clippingScale[2] / 2,
|
||||
];
|
||||
|
||||
const cmd =
|
||||
`ns-export pointcloud` +
|
||||
` --load-config ${config_filename}` +
|
||||
` --output-dir ${controlValues.outputDir}` +
|
||||
` --num-points ${controlValues.numPoints}` +
|
||||
` --remove-outliers ${getPythonBool(controlValues.removeOutliers)}` +
|
||||
` --estimate-normals ${getPythonBool(controlValues.estimateNormals)}` +
|
||||
` --use-bounding-box ${getPythonBool(clippingEnabled)}` +
|
||||
` --bounding-box-min ${bbox_min[0]} ${bbox_min[1]} ${bbox_min[2]}` +
|
||||
` --bounding-box-max ${bbox_max[0]} ${bbox_max[1]} ${bbox_max[2]}`;
|
||||
|
||||
const handleCopy = () => {
|
||||
navigator.clipboard.writeText(cmd);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
update_box_center(clippingCenter);
|
||||
update_box_scale(clippingScale);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="PointcloudPanel">
|
||||
<div className="ExportModal-text">
|
||||
Run the following command in a terminal to export the pointcloud:
|
||||
</div>
|
||||
<div className="ExportModal-code">{cmd}</div>
|
||||
<div className="ExportModal-button" style={{ textAlign: 'center' }}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
size="small"
|
||||
startIcon={<ContentCopyRoundedIcon />}
|
||||
onClick={handleCopy}
|
||||
>
|
||||
Copy Command
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import ExportPanel from './ExportPanel';
|
||||
|
||||
export default ExportPanel;
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
VisibilityOff,
|
||||
} from '@mui/icons-material/';
|
||||
import SceneNode from '../../../SceneNode';
|
||||
import LevaTheme from '../../ConfigPanel/leva_theme.json';
|
||||
import LevaTheme from '../../../themes/leva_theme.json';
|
||||
|
||||
export const snap_to_camera = (sceneTree, camera, matrix) => {
|
||||
const mat = new THREE.Matrix4();
|
||||
|
||||
@@ -13,13 +13,14 @@ import {
|
||||
CameraAltRounded,
|
||||
TuneRounded,
|
||||
WidgetsRounded,
|
||||
ImportExportRounded,
|
||||
} from '@mui/icons-material/';
|
||||
import StatusPanel from './StatusPanel';
|
||||
import LevaTheme from '../ConfigPanel/leva_theme.json';
|
||||
import LevaTheme from '../../themes/leva_theme.json';
|
||||
import CameraPanel from './CameraPanel';
|
||||
import ScenePanel from './ScenePanel';
|
||||
import { RenderControls } from '../ConfigPanel/ConfigPanel';
|
||||
import { LogPanel } from '../LogPanel/LogPanel';
|
||||
import ExportPanel from './ExportPanel';
|
||||
|
||||
export const snap_to_camera = (sceneTree, camera, matrix) => {
|
||||
const mat = new THREE.Matrix4();
|
||||
@@ -77,9 +78,11 @@ export function BasicTabs(props: BasicTabsProps) {
|
||||
const sceneTree = props.sceneTree;
|
||||
|
||||
const [value, setValue] = React.useState(0);
|
||||
const [showExportBox, setShowExportBox] = React.useState(false);
|
||||
|
||||
const handleChange = (event: React.SyntheticEvent, newValue: number) => {
|
||||
setValue(newValue);
|
||||
setShowExportBox(newValue === 3);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -97,6 +100,11 @@ export function BasicTabs(props: BasicTabsProps) {
|
||||
<Tab icon={<TuneRounded />} label="Controls" {...a11yProps(0)} />
|
||||
<Tab icon={<CameraAltRounded />} label="Render" {...a11yProps(1)} />
|
||||
<Tab icon={<WidgetsRounded />} label="Scene" {...a11yProps(2)} />
|
||||
<Tab
|
||||
icon={<ImportExportRounded />}
|
||||
label="Export"
|
||||
{...a11yProps(3)}
|
||||
/>
|
||||
</Tabs>
|
||||
</Box>
|
||||
<TabPanel value={value} index={0}>
|
||||
@@ -124,7 +132,7 @@ export function BasicTabs(props: BasicTabsProps) {
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel value={value} index={3}>
|
||||
<LogPanel>TODO: Something maybe?</LogPanel>
|
||||
<ExportPanel sceneTree={sceneTree} showExportBox={showExportBox} />
|
||||
</TabPanel>
|
||||
</Box>
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@ const initialState = {
|
||||
// the websocket connection state
|
||||
websocketState: {
|
||||
isConnected: false,
|
||||
websocket_url: "ws://localhost:7007",
|
||||
websocket_url: 'ws://localhost:7007',
|
||||
},
|
||||
// the webrtc connection state
|
||||
webrtcState: {
|
||||
@@ -34,6 +34,11 @@ const initialState = {
|
||||
train_eta: 'Paused',
|
||||
vis_train_ratio: 'Paused',
|
||||
log_errors: '',
|
||||
|
||||
// export options
|
||||
clipping_enabled: true,
|
||||
clipping_center: [0.0, 0.0, 0.0],
|
||||
clipping_box_scale: [2.0, 2.0, 2.0],
|
||||
},
|
||||
// the scene state
|
||||
sceneState: {
|
||||
|
||||
File renamed without changes.
Reference in new issue
Block a user