3D 场景#

使用 three.js显示一个3D场景。目前NiceGUI支持盒子、球体、圆柱/圆锥、挤压、直线、曲线和纹理网格对象。对象可以进行平移、旋转,并以不同的颜色、不透明度或线框形式显示。它们也可以分组以应用联合运动。

  • width: 画布的宽度

  • height: 画布的高度

  • grid: 是否显示网格

  • on_click: 当点击3D对象时执行的回调函数

  • on_drag_start: 当拖动3D对象时执行的回调函数

  • on_drag_end: 当放下3D对象时执行的回调函数

  • drag_constraints: 用于限制拖动对象位置的逗号分隔的JavaScript表达式(例如'x = 0, z = y / 2'

from nicegui import ui

with ui.scene().classes('w-full h-64') as scene:
    scene.sphere().material('#4488ff')
    scene.cylinder(1, 0.5, 2, 20).material('#ff8800', opacity=0.5).move(-2, 1)
    scene.extrusion([[0, 0], [0, 1], [1, 0.5]], 0.1).material('#ff8888').move(2, -1)

    with scene.group().move(z=2):
        scene.box().move(x=2)
        scene.box().move(y=2).rotate(0.25, 0.5, 0.75)
        scene.box(wireframe=True).material('#888888').move(x=2, y=2)

    scene.line([-4, 0, 0], [-4, 2, 0]).material('#ff0000')
    scene.curve([-4, 0, 0], [-4, -1, 0], [-3, -1, 0], [-3, 0, 0]).material('#008800')

    logo = 'https://avatars.githubusercontent.com/u/2843826'
    scene.texture(logo, [[[0.5, 2, 0], [2.5, 2, 0]],
                         [[0.5, 0, 0], [2.5, 0, 0]]]).move(1, -3)

    teapot = 'https://upload.wikimedia.org/wikipedia/commons/9/93/Utah_teapot_(solid).stl'
    scene.stl(teapot).scale(0.2).move(-3, 4)

    avocado = 'https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Assets/main/Models/Avocado/glTF-Binary/Avocado.glb'
    scene.gltf(avocado).scale(40).move(-2, -3, 0.5)

    scene.text('2D', 'background: rgba(0, 0, 0, 0.2); border-radius: 5px; padding: 5px').move(z=2)
    scene.text3d('3D', 'background: rgba(0, 0, 0, 0.2); border-radius: 5px; padding: 5px').move(y=-2).scale(.05)

# ui.run()

处理点击事件#

您可以使用 ui.sceneon_click 参数来处理点击事件。回调函数会接收一个 SceneClickEventArguments 对象,该对象具有以下属性:

  • click_type: 点击类型("click""dblclick")。

  • button: 被点击的按钮(123)。

  • alt, ctrl, meta, shift: altctrlmetashift键是否被按下。

  • hits: 按距离摄像机的距离排序的SceneClickEventHit对象列表。

SceneClickEventHit对象具有以下属性:

  • object_id: 被点击对象的id。

  • object_name: 被点击对象的名称。

  • x, y, z: 点击的xyz坐标。

from nicegui import events, ui

def handle_click(e: events.SceneClickEventArguments):
    hit = e.hits[0]
    name = hit.object_name or hit.object_id
    ui.notify(f'You clicked on the {name} at ({hit.x:.2f}, {hit.y:.2f}, {hit.z:.2f})')

with ui.scene(width=285, height=220, on_click=handle_click) as scene:
    scene.sphere().move(x=-1, z=1).with_name('sphere')
    scene.box().move(x=1, z=1).with_name('box')

# ui.run()

可拖曳对象#

您可以使用.draggable方法使对象可拖动。ui.scene有一个可选的on_drag_starton_drag_end参数来处理拖动事件。回调函数会接收一个SceneDragEventArguments对象,该对象具有以下属性:

  • type: 拖动事件的类型(“dragstart”或”dragend”)。

  • object_id: 被拖动对象的id

  • object_name: 被拖动对象的名称。

  • x, y, z: 被拖动对象的xyz坐标。

您还可以使用drag_constraints参数设置逗号分隔的JavaScript表达式,用于限制被拖动对象的位置。

from nicegui import events, ui

def handle_drag(e: events.SceneDragEventArguments):
    ui.notify(f'You dropped the sphere at ({e.x:.2f}, {e.y:.2f}, {e.z:.2f})')

with ui.scene(width=285, height=220,
              drag_constraints='z = 1', on_drag_end=handle_drag) as scene:
    sphere = scene.sphere().move(z=1).draggable()

ui.switch('draggable sphere',
          value=sphere.draggable_,
          on_change=lambda e: sphere.draggable(e.value))

# ui.run()
<nicegui.elements.switch.Switch at 0x7ff4a8a21690>

渲染点云#

您可以使用 point_cloud 方法渲染点云。points 参数是一个点坐标的列表,colors参数是 RGB 颜色(0…1)的列表。

import numpy as np
from nicegui import ui

with ui.scene().classes('w-full h-64') as scene:
    x, y = np.meshgrid(np.linspace(-3, 3), np.linspace(-3, 3))
    z = np.sin(x) * np.cos(y) + 1
    points = np.dstack([x, y, z]).reshape(-1, 3)
    scene.point_cloud(points=points, colors=points, point_size=0.1)

# ui.run()

等待场景初始化#

您可以使用 initialized 方法等待场景初始化。这个演示在场景完全加载后动画化相机移动。

from nicegui import ui

with ui.scene(width=285, height=220) as scene:
    scene.sphere()
    await scene.initialized()
    scene.move_camera(x=1, y=-1, z=1.5, duration=2)

ui.run()