update viewer
Browse files- app.py +1 -1
- viewer/index.html +19 -6
- viewer/index.js +38 -15
- viewer/index.js.map +0 -0
app.py
CHANGED
|
@@ -322,7 +322,7 @@ def viewer_url_for_output(ply_filename: str, settings_filename: str) -> str:
|
|
| 322 |
# Use absolute paths with /gradio_api/file= prefix for content and settings
|
| 323 |
content_path = f"/gradio_api/file=outputs/{ply_filename}"
|
| 324 |
settings_path = f"/gradio_api/file=outputs/{settings_filename}"
|
| 325 |
-
return f"/gradio_api/file=viewer/index.html?content={content_path}&settings={settings_path}&noanim"
|
| 326 |
|
| 327 |
|
| 328 |
def viewer_placeholder_html() -> str:
|
|
|
|
| 322 |
# Use absolute paths with /gradio_api/file= prefix for content and settings
|
| 323 |
content_path = f"/gradio_api/file=outputs/{ply_filename}"
|
| 324 |
settings_path = f"/gradio_api/file=outputs/{settings_filename}"
|
| 325 |
+
return f"/gradio_api/file=viewer/index.html?content={content_path}&settings={settings_path}&noanim&minimal"
|
| 326 |
|
| 327 |
|
| 328 |
def viewer_placeholder_html() -> str:
|
viewer/index.html
CHANGED
|
@@ -18,7 +18,7 @@
|
|
| 18 |
const posterUrl = url.searchParams.get('poster');
|
| 19 |
const skyboxUrl = url.searchParams.get('skybox');
|
| 20 |
const settingsUrl = url.searchParams.has('settings') ? url.searchParams.get('settings') : './settings.json';
|
| 21 |
-
const contentUrl = url.searchParams.has('content') ? url.searchParams.get('content') : './scene.ply';
|
| 22 |
|
| 23 |
const sseConfig = {
|
| 24 |
poster: posterUrl && createImage(posterUrl),
|
|
@@ -27,6 +27,7 @@
|
|
| 27 |
contents: fetch(contentUrl),
|
| 28 |
noui: url.searchParams.has('noui'),
|
| 29 |
noanim: url.searchParams.has('noanim'),
|
|
|
|
| 30 |
ministats: url.searchParams.has('ministats'),
|
| 31 |
colorize: url.searchParams.has('colorize'),
|
| 32 |
unified: url.searchParams.has('unified'),
|
|
@@ -91,13 +92,13 @@
|
|
| 91 |
|
| 92 |
<div class="spacer"></div>
|
| 93 |
|
| 94 |
-
<button id="orbitCamera" class="controlButton toggle left">
|
| 95 |
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24">
|
| 96 |
<g class='stroke'><use href="#orbitIcon"/></g>
|
| 97 |
<g class='fill'><use href="#orbitIcon"/></g>
|
| 98 |
</svg>
|
| 99 |
</button>
|
| 100 |
-
<button id="flyCamera" class="controlButton toggle right">
|
| 101 |
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24">
|
| 102 |
<g class='stroke'><use href="#flyIcon"/></g>
|
| 103 |
<g class='fill'><use href="#flyIcon"/></g>
|
|
@@ -123,7 +124,7 @@
|
|
| 123 |
<g class='fill'><use href="#infoIcon"/></g>
|
| 124 |
</svg>
|
| 125 |
</button>
|
| 126 |
-
<button id="settings" class="controlButton">
|
| 127 |
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24">
|
| 128 |
<g class='stroke'><use href="#settingsIcon"/></g>
|
| 129 |
<g class='fill'><use href="#settingsIcon"/></g>
|
|
@@ -196,8 +197,20 @@
|
|
| 196 |
<span class="control-key">Left Mouse</span>
|
| 197 |
</div>
|
| 198 |
<div class="control-item">
|
| 199 |
-
<span class="control-action">Fly</span>
|
| 200 |
-
<span class="control-key">W
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 201 |
</div>
|
| 202 |
<div class="control-spacer"></div>
|
| 203 |
<div class="control-item">
|
|
|
|
| 18 |
const posterUrl = url.searchParams.get('poster');
|
| 19 |
const skyboxUrl = url.searchParams.get('skybox');
|
| 20 |
const settingsUrl = url.searchParams.has('settings') ? url.searchParams.get('settings') : './settings.json';
|
| 21 |
+
const contentUrl = url.searchParams.has('content') ? url.searchParams.get('content') : './scene.compressed.ply';
|
| 22 |
|
| 23 |
const sseConfig = {
|
| 24 |
poster: posterUrl && createImage(posterUrl),
|
|
|
|
| 27 |
contents: fetch(contentUrl),
|
| 28 |
noui: url.searchParams.has('noui'),
|
| 29 |
noanim: url.searchParams.has('noanim'),
|
| 30 |
+
minimal: url.searchParams.has('minimal'),
|
| 31 |
ministats: url.searchParams.has('ministats'),
|
| 32 |
colorize: url.searchParams.has('colorize'),
|
| 33 |
unified: url.searchParams.has('unified'),
|
|
|
|
| 92 |
|
| 93 |
<div class="spacer"></div>
|
| 94 |
|
| 95 |
+
<button id="orbitCamera" class="controlButton toggle left hidden">
|
| 96 |
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24">
|
| 97 |
<g class='stroke'><use href="#orbitIcon"/></g>
|
| 98 |
<g class='fill'><use href="#orbitIcon"/></g>
|
| 99 |
</svg>
|
| 100 |
</button>
|
| 101 |
+
<button id="flyCamera" class="controlButton toggle right hidden">
|
| 102 |
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24">
|
| 103 |
<g class='stroke'><use href="#flyIcon"/></g>
|
| 104 |
<g class='fill'><use href="#flyIcon"/></g>
|
|
|
|
| 124 |
<g class='fill'><use href="#infoIcon"/></g>
|
| 125 |
</svg>
|
| 126 |
</button>
|
| 127 |
+
<button id="settings" class="controlButton hidden">
|
| 128 |
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24">
|
| 129 |
<g class='stroke'><use href="#settingsIcon"/></g>
|
| 130 |
<g class='fill'><use href="#settingsIcon"/></g>
|
|
|
|
| 197 |
<span class="control-key">Left Mouse</span>
|
| 198 |
</div>
|
| 199 |
<div class="control-item">
|
| 200 |
+
<span class="control-action">Fly Forward / Backward</span>
|
| 201 |
+
<span class="control-key">W / S or Up / Down Arrows</span>
|
| 202 |
+
</div>
|
| 203 |
+
<div class="control-item">
|
| 204 |
+
<span class="control-action">Fly Left / Right</span>
|
| 205 |
+
<span class="control-key">A / D or Left / Right Arrows</span>
|
| 206 |
+
</div>
|
| 207 |
+
<div class="control-item">
|
| 208 |
+
<span class="control-action">Fly Up / Down</span>
|
| 209 |
+
<span class="control-key">Q / E</span>
|
| 210 |
+
</div>
|
| 211 |
+
<div class="control-item">
|
| 212 |
+
<span class="control-action">Fast / Slow</span>
|
| 213 |
+
<span class="control-key">Shift / Ctrl</span>
|
| 214 |
</div>
|
| 215 |
<div class="control-spacer"></div>
|
| 216 |
<div class="control-item">
|
viewer/index.js
CHANGED
|
@@ -101739,7 +101739,7 @@ const initUI = (global) => {
|
|
| 101739 |
state.animationPaused = true;
|
| 101740 |
});
|
| 101741 |
const updatePlayPause = () => {
|
| 101742 |
-
if (state.cameraMode !== 'anim' || state.animationPaused) {
|
| 101743 |
dom.play.classList.remove('hidden');
|
| 101744 |
dom.pause.classList.add('hidden');
|
| 101745 |
}
|
|
@@ -101747,12 +101747,16 @@ const initUI = (global) => {
|
|
| 101747 |
dom.play.classList.add('hidden');
|
| 101748 |
dom.pause.classList.remove('hidden');
|
| 101749 |
}
|
| 101750 |
-
if (state.cameraMode === 'anim') {
|
| 101751 |
dom.timelineContainer.classList.remove('hidden');
|
| 101752 |
}
|
| 101753 |
else {
|
| 101754 |
dom.timelineContainer.classList.add('hidden');
|
| 101755 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101756 |
};
|
| 101757 |
// Update UI on animation changes
|
| 101758 |
events.on('cameraMode:changed', updatePlayPause);
|
|
@@ -101797,10 +101801,20 @@ const initUI = (global) => {
|
|
| 101797 |
});
|
| 101798 |
});
|
| 101799 |
// Camera mode UI
|
| 101800 |
-
|
| 101801 |
dom.orbitCamera.classList[state.cameraMode === 'orbit' ? 'add' : 'remove']('active');
|
| 101802 |
dom.flyCamera.classList[state.cameraMode === 'fly' ? 'add' : 'remove']('active');
|
| 101803 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101804 |
dom.settings.addEventListener('click', () => {
|
| 101805 |
dom.settingsPanel.classList.toggle('hidden');
|
| 101806 |
});
|
|
@@ -102813,12 +102827,12 @@ class CameraManager {
|
|
| 102813 |
// set the global animation flag
|
| 102814 |
state.hasAnimation = !!controllers.anim;
|
| 102815 |
state.animationDuration = controllers.anim ? controllers.anim.animState.cursor.duration : 0;
|
| 102816 |
-
// initialize camera mode and initial camera position
|
| 102817 |
-
state.cameraMode =
|
| 102818 |
this.camera.copy(resetCamera);
|
| 102819 |
const target = new Camera(this.camera); // the active controller updates this
|
| 102820 |
const from = new Camera(this.camera); // stores the previous camera state during transition
|
| 102821 |
-
let fromMode =
|
| 102822 |
// enter the initial controller
|
| 102823 |
getController(state.cameraMode).onEnter(this.camera);
|
| 102824 |
// transition time between cameras
|
|
@@ -102856,7 +102870,7 @@ class CameraManager {
|
|
| 102856 |
controllers.orbit.goto(resetCamera);
|
| 102857 |
break;
|
| 102858 |
case 'playPause':
|
| 102859 |
-
if (state.hasAnimation) {
|
| 102860 |
if (state.cameraMode === 'anim') {
|
| 102861 |
state.animationPaused = !state.animationPaused;
|
| 102862 |
}
|
|
@@ -103234,7 +103248,10 @@ class InputController {
|
|
| 103234 |
}
|
| 103235 |
this._state.shift += key[keyCode.SHIFT];
|
| 103236 |
this._state.ctrl += key[keyCode.CTRL];
|
| 103237 |
-
|
|
|
|
|
|
|
|
|
|
| 103238 |
state.cameraMode = 'fly';
|
| 103239 |
}
|
| 103240 |
const orbit = +(state.cameraMode === 'orbit');
|
|
@@ -103242,13 +103259,17 @@ class InputController {
|
|
| 103242 |
const double = +(this._state.touches > 1);
|
| 103243 |
const pan = this._state.mouse[2] || +(button[2] === -1) || double;
|
| 103244 |
const orbitFactor = fly ? camera.fov / 120 : 1;
|
|
|
|
|
|
|
| 103245 |
const { deltas } = this.frame;
|
| 103246 |
// desktop move
|
| 103247 |
const v = tmpV1.set(0, 0, 0);
|
| 103248 |
const keyMove = this._state.axis.clone().normalize();
|
| 103249 |
-
v.add(keyMove.mulScalar(fly * this.moveSpeed *
|
| 103250 |
-
|
| 103251 |
-
|
|
|
|
|
|
|
| 103252 |
const wheelMove = new Vec3(0, 0, -wheel[0]);
|
| 103253 |
v.add(wheelMove.mulScalar(this.wheelSpeed * dt));
|
| 103254 |
// FIXME: need to flip z axis for orbit camera
|
|
@@ -103260,8 +103281,10 @@ class InputController {
|
|
| 103260 |
deltas.rotate.append([v.x, v.y, v.z]);
|
| 103261 |
// mobile move
|
| 103262 |
v.set(0, 0, 0);
|
| 103263 |
-
|
| 103264 |
-
|
|
|
|
|
|
|
| 103265 |
flyMove.set(leftInput[0], 0, -leftInput[1]);
|
| 103266 |
v.add(flyMove.mulScalar(fly * this.moveSpeed * dt));
|
| 103267 |
pinchMove.set(0, 0, pinch[0]);
|
|
@@ -104288,7 +104311,7 @@ const main = (app, camera, settingsJson, config) => {
|
|
| 104288 |
hqMode: true,
|
| 104289 |
progress: 0,
|
| 104290 |
inputMode: 'desktop',
|
| 104291 |
-
cameraMode: '
|
| 104292 |
hasAnimation: false,
|
| 104293 |
animationDuration: 0,
|
| 104294 |
animationTime: 0,
|
|
|
|
| 101739 |
state.animationPaused = true;
|
| 101740 |
});
|
| 101741 |
const updatePlayPause = () => {
|
| 101742 |
+
if (config.minimal || state.cameraMode !== 'anim' || state.animationPaused) {
|
| 101743 |
dom.play.classList.remove('hidden');
|
| 101744 |
dom.pause.classList.add('hidden');
|
| 101745 |
}
|
|
|
|
| 101747 |
dom.play.classList.add('hidden');
|
| 101748 |
dom.pause.classList.remove('hidden');
|
| 101749 |
}
|
| 101750 |
+
if (!config.minimal && state.cameraMode === 'anim') {
|
| 101751 |
dom.timelineContainer.classList.remove('hidden');
|
| 101752 |
}
|
| 101753 |
else {
|
| 101754 |
dom.timelineContainer.classList.add('hidden');
|
| 101755 |
}
|
| 101756 |
+
if (config.minimal) {
|
| 101757 |
+
dom.play.classList.add('hidden');
|
| 101758 |
+
dom.pause.classList.add('hidden');
|
| 101759 |
+
}
|
| 101760 |
};
|
| 101761 |
// Update UI on animation changes
|
| 101762 |
events.on('cameraMode:changed', updatePlayPause);
|
|
|
|
| 101801 |
});
|
| 101802 |
});
|
| 101803 |
// Camera mode UI
|
| 101804 |
+
const updateCameraMode = () => {
|
| 101805 |
dom.orbitCamera.classList[state.cameraMode === 'orbit' ? 'add' : 'remove']('active');
|
| 101806 |
dom.flyCamera.classList[state.cameraMode === 'fly' ? 'add' : 'remove']('active');
|
| 101807 |
+
};
|
| 101808 |
+
events.on('cameraMode:changed', updateCameraMode);
|
| 101809 |
+
updateCameraMode(); // Initialize on load
|
| 101810 |
+
// Hide mode buttons if nomode config is set
|
| 101811 |
+
if (config.minimal) {
|
| 101812 |
+
dom.orbitCamera.classList.add('hidden');
|
| 101813 |
+
dom.flyCamera.classList.add('hidden');
|
| 101814 |
+
dom.play.classList.add('hidden');
|
| 101815 |
+
dom.timelineContainer.classList.add('hidden');
|
| 101816 |
+
dom.settings.classList.add('hidden');
|
| 101817 |
+
}
|
| 101818 |
dom.settings.addEventListener('click', () => {
|
| 101819 |
dom.settingsPanel.classList.toggle('hidden');
|
| 101820 |
});
|
|
|
|
| 102827 |
// set the global animation flag
|
| 102828 |
state.hasAnimation = !!controllers.anim;
|
| 102829 |
state.animationDuration = controllers.anim ? controllers.anim.animState.cursor.duration : 0;
|
| 102830 |
+
// initialize camera mode and initial camera position - always start in fly mode
|
| 102831 |
+
state.cameraMode = 'fly';
|
| 102832 |
this.camera.copy(resetCamera);
|
| 102833 |
const target = new Camera(this.camera); // the active controller updates this
|
| 102834 |
const from = new Camera(this.camera); // stores the previous camera state during transition
|
| 102835 |
+
let fromMode = 'fly';
|
| 102836 |
// enter the initial controller
|
| 102837 |
getController(state.cameraMode).onEnter(this.camera);
|
| 102838 |
// transition time between cameras
|
|
|
|
| 102870 |
controllers.orbit.goto(resetCamera);
|
| 102871 |
break;
|
| 102872 |
case 'playPause':
|
| 102873 |
+
if (state.hasAnimation && !global.config.minimal && !global.config.noanim) {
|
| 102874 |
if (state.cameraMode === 'anim') {
|
| 102875 |
state.animationPaused = !state.animationPaused;
|
| 102876 |
}
|
|
|
|
| 103248 |
}
|
| 103249 |
this._state.shift += key[keyCode.SHIFT];
|
| 103250 |
this._state.ctrl += key[keyCode.CTRL];
|
| 103251 |
+
// Switch to fly mode when using keyboard axis or zooming with wheel
|
| 103252 |
+
const hasWheelZoom = wheel[0] !== 0;
|
| 103253 |
+
const hasPinchZoom = pinch[0] !== 0;
|
| 103254 |
+
if (state.cameraMode !== 'fly' && (this._state.axis.length() > 0 || hasWheelZoom || hasPinchZoom)) {
|
| 103255 |
state.cameraMode = 'fly';
|
| 103256 |
}
|
| 103257 |
const orbit = +(state.cameraMode === 'orbit');
|
|
|
|
| 103259 |
const double = +(this._state.touches > 1);
|
| 103260 |
const pan = this._state.mouse[2] || +(button[2] === -1) || double;
|
| 103261 |
const orbitFactor = fly ? camera.fov / 120 : 1;
|
| 103262 |
+
// Speed multiplier for consistent movement (keyboard uses moveSpeed)
|
| 103263 |
+
const speedMultiplier = this._state.shift ? 4 : this._state.ctrl ? 0.25 : 1;
|
| 103264 |
const { deltas } = this.frame;
|
| 103265 |
// desktop move
|
| 103266 |
const v = tmpV1.set(0, 0, 0);
|
| 103267 |
const keyMove = this._state.axis.clone().normalize();
|
| 103268 |
+
v.add(keyMove.mulScalar(fly * this.moveSpeed * speedMultiplier * dt));
|
| 103269 |
+
// Pan movement - use minimum distance of 1 to ensure panning works in fly mode
|
| 103270 |
+
const panDistance = Math.max(distance, 1);
|
| 103271 |
+
const panMove = screenToWorld(camera, mouse[0], mouse[1], panDistance);
|
| 103272 |
+
v.add(panMove.mulScalar(pan * this.moveSpeed * 0.15));
|
| 103273 |
const wheelMove = new Vec3(0, 0, -wheel[0]);
|
| 103274 |
v.add(wheelMove.mulScalar(this.wheelSpeed * dt));
|
| 103275 |
// FIXME: need to flip z axis for orbit camera
|
|
|
|
| 103281 |
deltas.rotate.append([v.x, v.y, v.z]);
|
| 103282 |
// mobile move
|
| 103283 |
v.set(0, 0, 0);
|
| 103284 |
+
// Touch pan movement - use minimum distance for fly mode compatibility
|
| 103285 |
+
const touchPanDistance = Math.max(distance, 1);
|
| 103286 |
+
const orbitMove = screenToWorld(camera, touch[0], touch[1], touchPanDistance);
|
| 103287 |
+
v.add(orbitMove.mulScalar(orbit * pan * this.moveSpeed * 0.15));
|
| 103288 |
flyMove.set(leftInput[0], 0, -leftInput[1]);
|
| 103289 |
v.add(flyMove.mulScalar(fly * this.moveSpeed * dt));
|
| 103290 |
pinchMove.set(0, 0, pinch[0]);
|
|
|
|
| 104311 |
hqMode: true,
|
| 104312 |
progress: 0,
|
| 104313 |
inputMode: 'desktop',
|
| 104314 |
+
cameraMode: 'fly',
|
| 104315 |
hasAnimation: false,
|
| 104316 |
animationDuration: 0,
|
| 104317 |
animationTime: 0,
|
viewer/index.js.map
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|