Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 167 additions & 0 deletions nicegui/elements/lib/three/modules/stats.module.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
var Stats = function () {

var mode = 0;

var container = document.createElement( 'div' );
container.style.cssText = 'position:fixed;top:0;left:0;cursor:pointer;opacity:0.9;z-index:10000';
container.addEventListener( 'click', function ( event ) {

event.preventDefault();
showPanel( ++ mode % container.children.length );

}, false );

//

function addPanel( panel ) {

container.appendChild( panel.dom );
return panel;

}

function showPanel( id ) {

for ( var i = 0; i < container.children.length; i ++ ) {

container.children[ i ].style.display = i === id ? 'block' : 'none';

}

mode = id;

}

//

var beginTime = ( performance || Date ).now(), prevTime = beginTime, frames = 0;

var fpsPanel = addPanel( new Stats.Panel( 'FPS', '#0ff', '#002' ) );
var msPanel = addPanel( new Stats.Panel( 'MS', '#0f0', '#020' ) );

if ( self.performance && self.performance.memory ) {

var memPanel = addPanel( new Stats.Panel( 'MB', '#f08', '#201' ) );

}

showPanel( 0 );

return {

REVISION: 16,

dom: container,

addPanel: addPanel,
showPanel: showPanel,

begin: function () {

beginTime = ( performance || Date ).now();

},

end: function () {

frames ++;

var time = ( performance || Date ).now();

msPanel.update( time - beginTime, 200 );

if ( time >= prevTime + 1000 ) {

fpsPanel.update( ( frames * 1000 ) / ( time - prevTime ), 100 );

prevTime = time;
frames = 0;

if ( memPanel ) {

var memory = performance.memory;
memPanel.update( memory.usedJSHeapSize / 1048576, memory.jsHeapSizeLimit / 1048576 );

}

}

return time;

},

update: function () {

beginTime = this.end();

},

// Backwards Compatibility

domElement: container,
setMode: showPanel

};

};

Stats.Panel = function ( name, fg, bg ) {

var min = Infinity, max = 0, round = Math.round;
var PR = round( window.devicePixelRatio || 1 );

var WIDTH = 80 * PR, HEIGHT = 48 * PR,
TEXT_X = 3 * PR, TEXT_Y = 2 * PR,
GRAPH_X = 3 * PR, GRAPH_Y = 15 * PR,
GRAPH_WIDTH = 74 * PR, GRAPH_HEIGHT = 30 * PR;

var canvas = document.createElement( 'canvas' );
canvas.width = WIDTH;
canvas.height = HEIGHT;
canvas.style.cssText = 'width:80px;height:48px';

var context = canvas.getContext( '2d' );
context.font = 'bold ' + ( 9 * PR ) + 'px Helvetica,Arial,sans-serif';
context.textBaseline = 'top';

context.fillStyle = bg;
context.fillRect( 0, 0, WIDTH, HEIGHT );

context.fillStyle = fg;
context.fillText( name, TEXT_X, TEXT_Y );
context.fillRect( GRAPH_X, GRAPH_Y, GRAPH_WIDTH, GRAPH_HEIGHT );

context.fillStyle = bg;
context.globalAlpha = 0.9;
context.fillRect( GRAPH_X, GRAPH_Y, GRAPH_WIDTH, GRAPH_HEIGHT );

return {

dom: canvas,

update: function ( value, maxValue ) {

min = Math.min( min, value );
max = Math.max( max, value );

context.fillStyle = bg;
context.globalAlpha = 1;
context.fillRect( 0, 0, WIDTH, GRAPH_Y );
context.fillStyle = fg;
context.fillText( round( value ) + ' ' + name + ' (' + round( min ) + '-' + round( max ) + ')', TEXT_X, TEXT_Y );

context.drawImage( canvas, GRAPH_X + PR, GRAPH_Y, GRAPH_WIDTH - PR, GRAPH_HEIGHT, GRAPH_X, GRAPH_Y, GRAPH_WIDTH - PR, GRAPH_HEIGHT );

context.fillRect( GRAPH_X + GRAPH_WIDTH - PR, GRAPH_Y, PR, GRAPH_HEIGHT );

context.fillStyle = bg;
context.globalAlpha = 0.9;
context.fillRect( GRAPH_X + GRAPH_WIDTH - PR, GRAPH_Y, PR, round( ( 1 - ( value / maxValue ) ) * GRAPH_HEIGHT ) );

}

};

};

export default Stats;
19 changes: 18 additions & 1 deletion nicegui/elements/scene.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { GLTFLoader } from "GLTFLoader";
import { OrbitControls } from "OrbitControls";
import { STLLoader } from "STLLoader";
import "tween";
import Stats from "stats";

function texture_geometry(coords) {
const geometry = new THREE.BufferGeometry();
Expand Down Expand Up @@ -74,6 +75,13 @@ export default {
this.draggable_objects = [];
this.is_initialized = false;

if (this.show_stats) {
this.stats = new Stats();
this.stats.domElement.style.position = 'absolute';
this.stats.domElement.style.top = '0px';
this.$el.appendChild(this.stats.domElement);
}

window["scene_" + this.$el.id] = this.scene; // NOTE: for selenium tests only

if (this.camera_type === "perspective") {
Expand Down Expand Up @@ -178,11 +186,12 @@ export default {
this.drag_controls.addEventListener("dragend", handleDrag);

const render = () => {
requestAnimationFrame(() => setTimeout(() => render(), 1000 / 20));
requestAnimationFrame(() => setTimeout(() => render(), 1000 / this.fps));
this.camera_tween?.update();
this.renderer.render(this.scene, this.camera);
this.text_renderer.render(this.scene, this.camera);
this.text3d_renderer.render(this.scene, this.camera);
if (this.stats) this.stats.update();
};
render();

Expand Down Expand Up @@ -547,5 +556,13 @@ export default {
click_events: Array,
drag_constraints: String,
background_color: String,
fps: {
type: Number,
default: 20,
},
show_stats: {
type: Boolean,
default: false,
},
},
};
7 changes: 7 additions & 0 deletions nicegui/elements/scene.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class Scene(Element,
'lib/three/modules/GLTFLoader.js',
'lib/three/modules/OrbitControls.js',
'lib/three/modules/STLLoader.js',
'lib/three/modules/stats.module.js',
'lib/tween/tween.umd.js',
],
default_classes='nicegui-scene'):
Expand Down Expand Up @@ -82,6 +83,8 @@ def __init__(self,
on_drag_end: Optional[Handler[SceneDragEventArguments]] = None,
drag_constraints: str = '',
background_color: str = '#eee',
fps: int = 20,
show_stats: bool = False
) -> None:
"""3D Scene

Expand All @@ -100,6 +103,8 @@ def __init__(self,
:param on_drag_end: callback to execute when a 3D object is dropped
:param drag_constraints: comma-separated JavaScript expression for constraining positions of dragged objects (e.g. ``'x = 0, z = y / 2'``)
:param background_color: background color of the scene (default: "#eee")
:param fps: target frame rate for the scene in frames per second (default: 20)
:param show_stats: whether to show the frame rate, frame times and memory usage in the top left corner of the scene (default: False)
"""
super().__init__()
self._props['width'] = width
Expand All @@ -120,6 +125,8 @@ def __init__(self,
self.on('dragstart', self._handle_drag)
self.on('dragend', self._handle_drag)
self._props['drag_constraints'] = drag_constraints
self._props['fps'] = fps
self._props['show_stats'] = show_stats

def on_click(self, callback: Handler[SceneClickEventArguments]) -> Self:
"""Add a callback to be invoked when a 3D object is clicked."""
Expand Down
19 changes: 18 additions & 1 deletion nicegui/elements/scene_view.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as THREE from "three";
import "tween";
import Stats from "stats";

export default {
template: `
Expand All @@ -11,6 +12,13 @@ export default {
await this.$nextTick();
this.scene = getElement(this.scene_id).scene;

if (this.show_stats) {
this.stats = new Stats();
this.stats.domElement.style.position = 'absolute';
this.stats.domElement.style.top = '0px';
this.$el.appendChild(this.stats.domElement);
}

if (this.camera_type === "perspective") {
this.camera = new THREE.PerspectiveCamera(
this.camera_params.fov,
Expand Down Expand Up @@ -56,9 +64,10 @@ export default {
window.addEventListener("DOMContentLoaded", this.resize, false);

const render = () => {
requestAnimationFrame(() => setTimeout(() => render(), 1000 / 20));
requestAnimationFrame(() => setTimeout(() => render(), 1000 / this.fps));
this.camera_tween?.update();
this.renderer.render(this.scene, this.camera);
if (this.stats) this.stats.update();
};
render();

Expand Down Expand Up @@ -157,5 +166,13 @@ export default {
camera_type: String,
camera_params: Object,
scene_id: String,
fps: {
type: Number,
default: 20,
},
show_stats: {
type: Boolean,
default: false,
},
},
};
6 changes: 6 additions & 0 deletions nicegui/elements/scene_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ def __init__(self,
height: int = 300,
camera: Optional[SceneCamera] = None,
on_click: Optional[Handler[ClickEventArguments]] = None,
fps: int = 20,
show_stats: bool = False
) -> None:
"""Scene View

Expand All @@ -43,6 +45,8 @@ def __init__(self,
:param height: height of the canvas
:param camera: camera definition, either instance of ``ui.scene.perspective_camera`` (default) or ``ui.scene.orthographic_camera``
:param on_click: callback to execute when a 3D object is clicked
:param fps: target frame rate for the scene in frames per second (default: 20)
:param show_stats: whether to show the frame rate, frame times and memory usage in the top left corner of the scene (default: False)
"""
super().__init__()
self._props['width'] = width
Expand All @@ -54,6 +58,8 @@ def __init__(self,
self._click_handlers = [on_click] if on_click else []
self.on('init', self._handle_init)
self.on('click3d', self._handle_click)
self._props['fps'] = fps
self._props['show_stats'] = show_stats

def on_click(self, callback: Handler[ClickEventArguments]) -> Self:
"""Add a callback to be invoked when a 3D object is clicked."""
Expand Down
2 changes: 2 additions & 0 deletions npm.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@
}
},
"three": {
"version": "0.168.0",
"destination": "nicegui/elements/lib",
"keep": [
"package/build/three\\.module\\.js",
Expand All @@ -111,6 +112,7 @@
"package/examples/jsm/loaders/STLLoader(\\.min)?\\.js",
"package/examples/jsm/loaders/GLTFLoader(\\.min)?\\.js",
"package/examples/jsm/libs/tween\\.module(\\.min)?\\.js",
"package/examples/jsm/libs/stats.module(\\.min)?\\.js",
"package/examples/jsm/utils/BufferGeometryUtils(\\.min)?\\.js"
],
"rename": {
Expand Down
20 changes: 19 additions & 1 deletion website/documentation/content/scene_documentation.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

@doc.demo(ui.scene)
def main_demo() -> None:
with ui.scene().classes('w-full h-64') as scene:
with ui.scene(fps=60, show_stats=True).classes('w-full h-64') as scene:
scene.axes_helper()
scene.sphere().material('#4488ff').move(2, 2)
scene.cylinder(1, 0.5, 2, 20).material('#ff8800', opacity=0.5).move(-2, 1)
Expand Down Expand Up @@ -90,6 +90,24 @@ def handle_click(e: events.SceneClickEventArguments) -> None:
scene.box().move(x=1, z=1).with_name('box')


@doc.demo('FPS configuration', '''
You can configure the target frames per second (FPS) of the scene using the `fps` argument.
The default value is 20, but you can set it to a higher value for smoother animations, or a lower value to save resources.

If you turn on the `show_stats` argument, the FPS will be displayed in the top left corner of the scene.
You will notice that the FPS is generally lower than the target frame rate, because the browser also takes some time to render the scene.

This also applies to `ui.scene_view`
''')
def fps_configuration() -> None:
ui.label('Higher frame rate instead of default 20 FPS for the movable view')
with ui.scene(fps=60, show_stats=True).classes('w-full h-32') as scene:
scene.sphere()
ui.label('Lower frame rate instead of default 20 FPS for the static view')
with ui.scene_view(scene, fps=5, show_stats=True).classes('w-full h-32') as scene_view:
scene_view.move_camera(x=1, y=-3, z=5)


@doc.demo('Draggable objects', '''
You can make objects draggable using the `.draggable` method.
There is an optional `on_drag_start` and `on_drag_end` argument to `ui.scene` to handle drag events.
Expand Down