Files
PozzettiAndrea-ComfyUI-Dept…/web/viewer_vtk.html
T
2025-11-18 19:16:47 +01:00

382 lines
14 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>DepthAnythingV3 VTK Point Cloud Viewer</title>
<style>
body {
margin: 0;
overflow: hidden;
background: #1a1a1a;
font-family: monospace;
display: flex;
flex-direction: column;
height: 100vh;
}
#viewerContainer {
flex: 1;
position: relative;
min-height: 0;
}
#container {
width: 100%;
height: 100%;
position: relative;
}
#loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: white;
font-size: 14px;
text-align: center;
z-index: 1000;
pointer-events: none;
background: rgba(0,0,0,0.8);
padding: 20px;
border-radius: 8px;
}
#error {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: white;
font-size: 14px;
text-align: center;
z-index: 1000;
pointer-events: none;
background: rgba(180,0,0,0.9);
padding: 20px;
border-radius: 8px;
max-width: 80%;
display: none;
}
#info {
position: absolute;
top: 10px;
right: 10px;
background: rgba(0,0,0,0.7);
padding: 8px;
border-radius: 4px;
color: white;
font-size: 12px;
z-index: 100;
}
#controls {
background: #1a1a1a;
border-top: 1px solid #444;
padding: 8px 12px;
display: flex;
gap: 12px;
align-items: center;
flex-wrap: wrap;
font-size: 12px;
color: #ccc;
}
#controls label {
display: flex;
align-items: center;
gap: 4px;
}
#controls input[type="range"] {
width: 80px;
}
#controls button {
background: #333;
color: #ccc;
border: 1px solid #555;
padding: 4px 8px;
border-radius: 3px;
font-size: 12px;
font-family: monospace;
cursor: pointer;
}
#controls button:hover {
background: #444;
}
</style>
</head>
<body>
<div id="viewerContainer">
<div id="container"></div>
<div id="loading">Ready to load point cloud</div>
<div id="error"></div>
<div id="info">
<div>Left: Rotate | Right: Pan</div>
<div>Scroll: Zoom</div>
<div id="point-count">Points: 0</div>
</div>
</div>
<div id="controls">
<label>
Point Size:
<input type="range" id="pointSize" min="1" max="20" value="3" step="1">
<span id="pointSizeValue">3</span>
</label>
<label style="width: 1px; height: 20px; background: #444; margin: 0 8px;"></label>
<button id="viewPosX">+X</button>
<button id="viewNegX">-X</button>
<button id="viewPosY">+Y</button>
<button id="viewNegY">-Y</button>
<button id="viewPosZ">+Z</button>
<button id="viewNegZ">-Z</button>
<label style="width: 1px; height: 20px; background: #444; margin: 0 8px;"></label>
<button id="resetCamera">Reset View</button>
</div>
<script src="/extensions/ComfyUI-DepthAnythingV3/libs/vtk.min.lib"></script>
<script>
// Get VTK.js classes from global vtk object
const vtkFullScreenRenderWindow = vtk.Rendering.Misc.vtkFullScreenRenderWindow;
const vtkActor = vtk.Rendering.Core.vtkActor;
const vtkMapper = vtk.Rendering.Core.vtkMapper;
const vtkPLYReader = vtk.IO.Geometry.vtkPLYReader;
const vtkInteractorStyleTrackballCamera = vtk.Interaction.Style.vtkInteractorStyleTrackballCamera;
const vtkOrientationMarkerWidget = vtk.Interaction.Widgets.vtkOrientationMarkerWidget;
const vtkAnnotatedCubeActor = vtk.Rendering.Core.vtkAnnotatedCubeActor;
console.log('[DA3 VTK Viewer] Initializing...');
const loading = document.getElementById('loading');
const errorDiv = document.getElementById('error');
const container = document.getElementById('container');
const pointCountDiv = document.getElementById('point-count');
const pointSizeSlider = document.getElementById('pointSize');
const pointSizeValue = document.getElementById('pointSizeValue');
const MAX_POINTS = 2000000;
// Create full screen renderer
const fullScreenRenderer = vtkFullScreenRenderWindow.newInstance({
background: [0.1, 0.1, 0.1],
container: container,
});
fullScreenRenderer.resize();
const renderer = fullScreenRenderer.getRenderer();
const renderWindow = fullScreenRenderer.getRenderWindow();
const interactor = renderWindow.getInteractor();
// Set up trackball camera controls
const interactorStyle = vtkInteractorStyleTrackballCamera.newInstance();
interactor.setInteractorStyle(interactorStyle);
// Create actor and mapper
const actor = vtkActor.newInstance();
const mapper = vtkMapper.newInstance();
actor.setMapper(mapper);
// Configure for point cloud rendering
const property = actor.getProperty();
property.setPointSize(3);
property.setRepresentation(0); // Points mode
renderer.addActor(actor);
// Create axis orientation widget
const axes = vtkAnnotatedCubeActor.newInstance();
axes.setDefaultStyle({
fontStyle: 'bold',
fontFamily: 'Arial',
fontColor: 'white',
fontSizeScale: (res) => res / 2,
faceRotation: 0,
edgeThickness: 0.1,
edgeColor: 'white',
resolution: 400,
});
axes.setXPlusFaceProperty({ text: '+X', faceColor: '#ff5555' });
axes.setXMinusFaceProperty({ text: '-X', faceColor: '#ff5555' });
axes.setYPlusFaceProperty({ text: '+Y', faceColor: '#55ff55' });
axes.setYMinusFaceProperty({ text: '-Y', faceColor: '#55ff55' });
axes.setZPlusFaceProperty({ text: '+Z', faceColor: '#5555ff' });
axes.setZMinusFaceProperty({ text: '-Z', faceColor: '#5555ff' });
const orientationWidget = vtkOrientationMarkerWidget.newInstance({
actor: axes,
interactor: renderWindow.getInteractor(),
});
orientationWidget.setEnabled(true);
orientationWidget.setViewportCorner(vtkOrientationMarkerWidget.Corners.BOTTOM_LEFT);
orientationWidget.setViewportSize(0.15);
orientationWidget.setMinPixelSize(100);
orientationWidget.setMaxPixelSize(300);
let currentReader = null;
let meshBounds = null;
function showError(message) {
loading.style.display = 'none';
errorDiv.textContent = message;
errorDiv.style.display = 'block';
}
function hideError() {
errorDiv.style.display = 'none';
}
// Load point cloud function
async function loadPointCloud(filepath) {
console.log('[DA3 VTK Viewer] Loading:', filepath);
loading.textContent = 'Loading point cloud...';
loading.style.display = 'block';
hideError();
try {
currentReader = vtkPLYReader.newInstance();
const response = await fetch(filepath);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const arrayBuffer = await response.arrayBuffer();
console.log('[DA3 VTK Viewer] File size:', arrayBuffer.byteLength);
currentReader.parseAsArrayBuffer(arrayBuffer);
const polydata = currentReader.getOutputData();
if (!polydata) {
throw new Error('Failed to parse PLY file');
}
const numPoints = polydata.getNumberOfPoints();
console.log('[DA3 VTK Viewer] Loaded:', numPoints, 'points');
// Check point limit
if (numPoints > MAX_POINTS) {
throw new Error(`Point cloud too large: ${numPoints.toLocaleString()} points\nMaximum: ${MAX_POINTS.toLocaleString()} points`);
}
pointCountDiv.textContent = `Points: ${numPoints.toLocaleString()}`;
meshBounds = polydata.getBounds();
console.log('[DA3 VTK Viewer] Bounds:', meshBounds);
// Connect mapper directly to reader
mapper.setInputConnection(currentReader.getOutputPort());
mapper.update();
// Check if we have vertex colors
const scalars = polydata.getPointData().getScalars();
if (scalars) {
console.log('[DA3 VTK Viewer] Point colors found:', scalars.getName());
mapper.setScalarVisibility(true);
} else {
console.log('[DA3 VTK Viewer] No point colors, using white');
mapper.setScalarVisibility(false);
property.setColor(1.0, 1.0, 1.0);
}
// Reset camera
renderer.resetCamera();
const camera = renderer.getActiveCamera();
const center = [
(meshBounds[0] + meshBounds[1]) / 2,
(meshBounds[2] + meshBounds[3]) / 2,
(meshBounds[4] + meshBounds[5]) / 2
];
const maxDim = Math.max(
meshBounds[1] - meshBounds[0],
meshBounds[3] - meshBounds[2],
meshBounds[5] - meshBounds[4]
);
const distance = maxDim * 2.0;
camera.setPosition(
center[0] + distance * 0.5,
center[1] + distance * 0.5,
center[2] + distance
);
camera.setFocalPoint(center[0], center[1], center[2]);
camera.setViewUp(0, 1, 0);
renderWindow.render();
requestAnimationFrame(() => {
fullScreenRenderer.resize();
renderer.resetCameraClippingRange();
renderWindow.render();
interactor.enable();
});
loading.style.display = 'none';
console.log('[DA3 VTK Viewer] Load complete');
} catch (error) {
console.error('[DA3 VTK Viewer] Error:', error);
showError(error.message);
}
}
// Point size control
pointSizeSlider.addEventListener('input', () => {
const size = parseInt(pointSizeSlider.value);
pointSizeValue.textContent = size;
property.setPointSize(size);
renderWindow.render();
});
// Camera view controls
function setCameraView(position, up) {
if (!meshBounds) return;
const camera = renderer.getActiveCamera();
const center = [
(meshBounds[0] + meshBounds[1]) / 2,
(meshBounds[2] + meshBounds[3]) / 2,
(meshBounds[4] + meshBounds[5]) / 2
];
const maxDim = Math.max(
meshBounds[1] - meshBounds[0],
meshBounds[3] - meshBounds[2],
meshBounds[5] - meshBounds[4]
);
const distance = maxDim * 2.0;
camera.setPosition(
center[0] + position[0] * distance,
center[1] + position[1] * distance,
center[2] + position[2] * distance
);
camera.setFocalPoint(center[0], center[1], center[2]);
camera.setViewUp(up[0], up[1], up[2]);
renderer.resetCameraClippingRange();
renderWindow.render();
}
document.getElementById('viewPosX').addEventListener('click', () => setCameraView([1, 0, 0], [0, 1, 0]));
document.getElementById('viewNegX').addEventListener('click', () => setCameraView([-1, 0, 0], [0, 1, 0]));
document.getElementById('viewPosY').addEventListener('click', () => setCameraView([0, 1, 0], [0, 0, -1]));
document.getElementById('viewNegY').addEventListener('click', () => setCameraView([0, -1, 0], [0, 0, 1]));
document.getElementById('viewPosZ').addEventListener('click', () => setCameraView([0, 0, 1], [0, 1, 0]));
document.getElementById('viewNegZ').addEventListener('click', () => setCameraView([0, 0, -1], [0, 1, 0]));
document.getElementById('resetCamera').addEventListener('click', () => {
if (!meshBounds) return;
setCameraView([0.5, 0.5, 1], [0, 1, 0]);
});
// Handle resize
window.addEventListener('resize', () => {
fullScreenRenderer.resize();
renderWindow.render();
});
// Listen for messages from parent
window.addEventListener('message', (event) => {
console.log('[DA3 VTK Viewer] Received message:', event.data);
if (event.data.type === 'loadPointCloud' && event.data.url) {
loadPointCloud(event.data.url);
}
});
console.log('[DA3 VTK Viewer] Ready');
</script>
</body>
</html>