做数据科学的人经常遇到一个问题:计算结果需要 3D 可视化,但 Python 现有的可视化工具处理复杂 3D 场景时要么功能有限,要么交互体验差。如果想要 WebGL 级别的渲染质量和流畅的鼠标交互,通常得另起一套 JavaScript 前端,数据还得想办法从 Python 传过去,工作流被打断。
pythreejs 想解决的就是这个问题。它是 Jupyter Widgets 团队维护的扩展,把 Three.js 的 API 完整映射成 Python 类,让开发者在 Jupyter Notebook 里直接用 Python 描述 3D 场景——渲染还是走浏览器的 WebGL,但不需要写一行 JavaScript,也不需要搭独立前端。
Three.js 是前端的工具,Jupyter Notebook 是 Python 数据科学的工具,两个本来没什么交集。pythreejs 做的事是把这两个接起来。目前 989 Star,最新版本 2.4.1。
Python 对象怎么变成 Three.js 场景
先看一个最基础的例子,在 Jupyter 里创建一个红色球体:
from pythreejs import *from IPython.display import displaysphere = Mesh( SphereGeometry(radius=1, widthSegments=32, heightSegments=24), MeshLambertMaterial(color='red'), position=[2, 1, 0])camera = PerspectiveCamera(position=[0, 5, 5], up=[0, 1, 0])scene = Scene(children=[ sphere, camera, DirectionalLight(color='white', position=[3, 5, 1], intensity=0.5), AmbientLight(color='#777777')])renderer = Renderer(camera=camera, scene=scene, controls=[OrbitControls(controlling=camera)])display(renderer)
执行这段代码,Jupyter 单元格里直接出现一个可以鼠标旋转的 3D 球体。
这里的 Mesh、SphereGeometry、MeshLambertMaterial、PerspectiveCamera、Scene 和 Three.js 里的对象是一一对应的:
| |
|---|
Scene | THREE.Scene |
Mesh | THREE.Mesh |
PerspectiveCamera | THREE.PerspectiveCamera |
SphereGeometry | THREE.SphereGeometry |
MeshLambertMaterial | THREE.MeshLambertMaterial |
DirectionalLight | THREE.DirectionalLight |
OrbitControls | |
通信机制是 Jupyter Widgets 框架:Python 对象的属性变更会序列化成 JSON,通过 WebSocket 传给浏览器侧的 JavaScript,由 JavaScript 创建或更新对应的 Three.js 对象,最终在 WebGL 里渲染。渲染本身还是在浏览器里跑,Python 那边负责描述场景结构。
BufferGeometry + NumPy:给大数据集用
如果数据来自 NumPy 数组(比如点云、科学模拟结果),可以用 BufferGeometry 直接把数组传进去:
import numpy as npfrom pythreejs import *# 生成随机点云n = 5000positions = np.random.uniform(-5, 5, (n, 3)).astype(np.float32)colors = np.random.uniform(0, 1, (n, 3)).astype(np.float32)geometry = BufferGeometry(attributes={'position': BufferAttribute(array=positions),'color': BufferAttribute(array=colors),})material = PointsMaterial(vertexColors='VertexColors', size=0.1)points = Points(geometry=geometry, material=material)camera = PerspectiveCamera(position=[0, 0, 15])scene = Scene(children=[points, camera, AmbientLight()])renderer = Renderer(camera=camera, scene=scene, controls=[OrbitControls(controlling=camera)])display(renderer)
NumPy 数组直接作为 BufferAttribute 传入,不需要手动把数据转成 JavaScript 格式。pythreejs 内部处理了 NumPy 到浏览器端 TypedArray 的序列化。对于百万级顶点的数据集,这是实际可用的路径。
动态更新场景
Python 对象和浏览器里的 Three.js 对象保持同步,修改 Python 对象的属性会实时反映到场景里:
# 改变球体位置sphere.position = [0, 2, 0]# 改变材质颜色sphere.material.color = 'blue'# 改变相机位置camera.position = [10, 10, 10]
直接赋值,Jupyter 里的 3D 场景立刻更新。这个特性让在 Jupyter 里做交互式参数调试比较方便,比如配合 ipywidgets 的滑块来实时调整几何体参数。
有一个限制要提前知道
不是所有 Three.js 方法都可以直接在 Python 里调用。Three.js 对象上的方法(比如 geometry.rotateX())没有对应的 Python 方法,需要通过 exec_three_obj_method 来执行:
from pythreejs import exec_three_obj_methodimport math# 无法直接调用:geometry.rotateX(Math.PI / 2)# 需要用这个方式:exec_three_obj_method(geometry, 'rotateX', math.pi / 2)
这是 Python/JavaScript 桥接的固有限制。pythreejs 的设计目标是让属性可读写,但方法调用需要走这个中间层。对于大部分静态场景构建没有影响,但如果需要复杂的动画逻辑或频繁调用 Three.js 方法,这个限制会比较明显。
另外需要注意版本:最新版本 2.4.1 发布于 2022 年,Three.js 本身的 API 这两年有一些变化,部分新特性(比如新的材质类型)在 pythreejs 里还没有对应的 Python 类。
安装
pip install pythreejs# 或conda install -c conda-forge pythreejs
JupyterLab 3.0 及以上版本安装后可以直接用,不需要额外配置。旧版 JupyterLab 需要手动启用扩展:
jupyter nbextension install --py --symlink --sys-prefix pythreejsjupyter nbextension enable --py --sys-prefix pythreejs
写在最后
pythreejs 解决的问题很具体:在 Python/Jupyter 环境里需要 3D 可视化,又不想维护一套独立的 JavaScript 前端。NumPy 数组直接输入几何体、Python 对象属性变更实时同步到场景,这两点对数据科学工作流来说比较实用。
它不是要替代直接写 Three.js,两者的使用场景基本不重叠:需要部署成 Web 应用、需要完整的动画控制、需要自定义 Shader,还是得写 JavaScript。但如果工作流本身就在 Jupyter 里,需要把计算结果快速可视化成 3D 场景,pythreejs 是一个不需要切换技术栈的选项。
GitHub:https://github.com/jupyter-widgets/pythreejs
关注公众号,添加好友领1000+Three.js+Cesium项目案例合集
扫码直达 ⤵️ 或加微信 sanmu1598
