如何在 Python 中加载 3D 模型

如何在 Python 中加载 3D 模型

Aspose.3D FOSS for Python 提供了一个直接的 API,用于在没有任何本地依赖的情况下打开 3D 文件。将文件加载到一个 Scene 对象后,您可以遍历节点层次结构,并读取场景中每个网格的原始几何数据。.

分步指南

步骤 1:安装包

从 PyPI 安装 Aspose.3D FOSS。无需额外的系统库。.

pip install aspose-3d-foss

支持的 Python 版本:3.7、3.8、3.9、3.10、3.11、3.12。.


步骤 2:导入 Scene 类

Scene 类是所有 3D 数据的顶层容器。请将其与您需要的任何加载选项类一起导入。.

from aspose.threed import Scene
from aspose.threed.formats import ObjLoadOptions

所有公共类位于 aspose.threed 或其子包(aspose.threed.entities, aspose.threed.formats, aspose.threed.utilities).


步骤 3:加载文件

使用静态 Scene.from_file() 方法打开任何受支持的格式。库会自动根据文件扩展名检测格式。.

##Automatic format detection
scene = Scene.from_file("model.obj")

或者,创建一个 Scene 实例并调用 open();;当您想显式传递加载选项或处理错误时,这很有用::

scene = Scene()
scene.open("model.obj")

这两种方法都支持 OBJ、STL(二进制和 ASCII)、glTF 2.0 / GLB、COLLADA(DAE)和 3MF 文件。.


步骤 4:遍历 Scene 节点

已加载的场景是一个由 Node 对象组成的树,根位于 scene.root_node.。递归遍历以查找所有节点::

from aspose.threed import Scene, Node

scene = Scene.from_file("model.obj")

def walk(node: Node, depth: int = 0) -> None:
    indent = "  " * depth
    print(f"{indent}Node: {node.name!r}")
    for child in node.child_nodes:
        walk(child, depth + 1)

walk(scene.root_node)

每个 Node 可以携带零个或多个 Entity 对象(网格、相机、灯光)。检查 node.entities 查看已附加的内容。.


步骤 5:访问顶点和多边形数据

将节点的实体强制转换为 Mesh 并读取其控制点(顶点位置)和多边形(面索引列表)::

from aspose.threed import Scene
from aspose.threed.entities import Mesh

scene = Scene.from_file("model.obj")

for node in scene.root_node.child_nodes:
    for entity in node.entities:
        if isinstance(entity, Mesh):
            mesh: Mesh = entity
            print(f"Mesh '{node.name}': "
                  f"{len(mesh.control_points)} vertices, "
                  f"{len(mesh.polygons)} polygons")

            # First vertex position
            if mesh.control_points:
                v = mesh.control_points[0]
                print(f"  First vertex: ({v.x:.4f}, {v.y:.4f}, {v.z:.4f})")

            # First polygon face (list of control-point indices)
            if mesh.polygons:
                print(f"  First polygon: {mesh.polygons[0]}")

mesh.control_points 是一个列表 Vector4 对象;; x, y, z 携带位置和 w 是齐次坐标(通常为 1.0)。.

mesh.polygons 是一个整数列表的列表,其中每个内部列表是对应于一个面的控制点索引的有序集合。.


步骤 6:应用特定格式的加载选项

若要对 OBJ 文件的解释进行细粒度控制,请传入一个 ObjLoadOptions 实例传递给 scene.open():

from aspose.threed import Scene
from aspose.threed.formats import ObjLoadOptions

options = ObjLoadOptions()
options.flip_coordinate_system = True   # Convert right-hand Y-up to Z-up
options.scale = 0.01                    # Convert centimetres to metres
options.enable_materials = True         # Load .mtl material file
options.normalize_normal = True         # Normalize all normals to unit length

scene = Scene()
scene.open("model.obj", options)

对于 STL 文件,等价的类是 StlLoadOptions.。对于 glTF,请使用 GltfLoadOptions.。请参阅 API 参考 以获取完整列表。.


常见问题及解决方案

调用时出现 FileNotFoundError Scene.from_file()

路径必须是绝对路径或在运行时相对于工作目录的正确相对路径。请使用 pathlib.Path 来构建可靠的路径::

from pathlib import Path
from aspose.threed import Scene

path = Path(__file__).parent / "assets" / "model.obj"
scene = Scene.from_file(str(path))

mesh.polygons 在加载 STL 文件后为空

STL 文件将三角形存储为原始面片,而不是索引网格。加载后,多边形是从这些面片合成的。如果 polygons 显示为空,请检查 len(mesh.control_points); 如果计数是 3 的倍数,则几何体以非索引形式存储,每三个连续的顶点构成一个三角形。.

坐标系不匹配(模型出现旋转或镜像)

不同的工具使用不同的约定(Y 向上 vs Z 向上,左手坐标系 vs 右手坐标系)。设置 ObjLoadOptions.flip_coordinate_system = True 或对根节点进行旋转 Transform 加载后。.

AttributeError: 'NoneType' object has no attribute 'polygons'

节点的实体列表可能包含非网格实体(相机、灯光)。始终使用 isinstance(entity, Mesh) 在强制转换之前。.


常见问题 (FAQ)

我可以加载哪些 3D 格式??

OBJ(Wavefront)、STL(二进制和 ASCII)、glTF 2.0 / GLB、COLLADA(DAE)以及 3MF。FBX 文件的标记化部分支持,但完整解析尚未完成。.

加载 OBJ 文件时是否也会加载 .mtl 材质??

是的,当 ObjLoadOptions.enable_materials = True (默认)。库会在 .mtl 文件所在的同一目录中查找 .obj 文件。如果 .mtl 缺失,几何体仍会被加载并发出警告。.

我可以从字节流而不是路径加载文件吗??

是的。. scene.open() 接受任何具有 .read() 方法的类文件对象,除了文件路径字符串之外。传入一个打开的二进制流(例如,., io.BytesIO)直接。. Scene.from_file() 仅接受文件路径字符串。.

如何获取表面法线??

加载后,检查 mesh.get_element(VertexElementType.NORMAL).。这将返回一个 VertexElementNormaldata 列表包含每个参考的一个法向量,映射依据 mapping_modereference_mode.

from aspose.threed.entities import Mesh, VertexElementType

normals = mesh.get_element(VertexElementType.NORMAL)
if normals:
    print(normals.data[0])  # First normal vector

该库在并发加载多个文件时是否线程安全??

每个 Scene 对象是独立的。将不同的文件加载到不同的 Scene 实例(来自不同线程)是安全的,只要你不共享单个 Scene 跨线程而不进行外部锁定。.

 中文