如何在 Python 中转换 3D 模型

如何在 Python 中转换 3D 模型

使用 Aspose.3D FOSS for Python 进行格式转换是一个两步过程:加载到 Scene 对象中,然后保存为所需的输出格式。由于所有几何体都保存在通用的内存表示中,无需格式特定的中间步骤。下面的各节展示了最常见的转换及其工作代码。

分步指南

步骤 1:安装软件包

pip install aspose-3d-foss

不需要系统库、编译器或额外的运行时依赖。


步骤 2:加载源模型

使用 Scene.from_file() 处理最简单的情况:格式会根据文件扩展名自动检测:

from aspose.threed import Scene

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

对于需要控制坐标系或材质加载的 OBJ 文件,请使用 scene.open()ObjLoadOptions

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

options = ObjLoadOptions()
options.flip_coordinate_system = True  # Convert to Z-up if needed
options.enable_materials = True        # Load the accompanying .mtl file
options.normalize_normal = True

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

两种方法都会生成相同的 Scene 对象,用于后续的保存步骤。


步骤 3:检查已加载的场景

在进行转换之前,值得检查几何体是否正确加载。缺少文件、不受支持的 FBX 功能,或 .mtl 文件的路径问题,都可能导致场景为空。

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

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

mesh_count = 0
total_vertices = 0

def count_meshes(node) -> None:
    global mesh_count, total_vertices
    for entity in node.entities:
        if isinstance(entity, Mesh):
            mesh_count += 1
            total_vertices += len(entity.control_points)
    for child in node.child_nodes:
        count_meshes(child)

count_meshes(scene.root_node)
print(f"Loaded {mesh_count} mesh(es), {total_vertices} total vertices")

if mesh_count == 0:
    raise ValueError("Scene contains no geometry: check the source file path and format")

步骤 4:保存为目标格式

使用输出路径调用 scene.save()。传递特定格式的保存选项对象,以控制二进制与 ASCII 输出、坐标轴以及压缩。

OBJ 转 STL (二进制)

from aspose.threed import Scene
from aspose.threed.formats import StlSaveOptions

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

save_opts = StlSaveOptions()
##StlSaveOptions defaults to binary output, which is more compact.

scene.save("model.stl", save_opts)
print("Saved model.stl")

OBJ 转 glTF 2.0

from aspose.threed import Scene
from aspose.threed.formats import GltfSaveOptions

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

save_opts = GltfSaveOptions()

scene.save("model.gltf", save_opts)
print("Saved model.gltf")

要将保存方式改为自包含的 GLB 二进制,而不是 .gltf + 外部缓冲区,请将输出扩展名更改为 .glb

scene.save("model.glb", save_opts)

OBJ 转 3MF

from aspose.threed import Scene
from aspose.threed.formats import ThreeMfSaveOptions

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

save_opts = ThreeMfSaveOptions()

scene.save("model.3mf", save_opts)
print("Saved model.3mf")

STL 转 glTF 2.0

相同的模式适用于所有源格式:

from aspose.threed import Scene
from aspose.threed.formats import GltfSaveOptions

scene = Scene.from_file("input.stl")
scene.save("output.gltf", GltfSaveOptions())
print("Saved output.gltf")

步骤 5:验证输出

保存后,确认输出文件存在且大小非零。为了进行更彻底的检查,重新加载文件并比较网格计数:

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

output_path = "model.stl"

##Basic file-system check
size = os.path.getsize(output_path)
print(f"Output file size: {size} bytes")
if size == 0:
    raise RuntimeError("Output file is empty: save may have failed silently")

##Round-trip verification: reload and count geometry
def _iter_nodes(node):
    yield node
    for child in node.child_nodes:
        yield from _iter_nodes(child)

reloaded = Scene.from_file(output_path)
mesh_count = sum(
    1
    for node in _iter_nodes(reloaded.root_node)
    for entity in node.entities
    if isinstance(entity, Mesh)
)
print(f"Round-trip check: {mesh_count} mesh(es) in output")

常见问题及解决方案

输出文件已创建,但不包含几何体

源文件可能已加载为零网格。在保存之前,添加第 3 步中的检查步骤。同时确认文件扩展名与实际格式匹配;Aspose.3D 使用扩展名来选择解析器。

glTF 输出缺少纹理

Aspose.3D FOSS 在转换过程中会保留几何体和材质属性。如果源 OBJ 在 .mtl 中引用了外部图像文件,这些图像文件不会自动复制到 .gltf。保存后请手动将纹理图像复制到输出目录。

STL 输出看起来是内部翻转的(面法线翻转)

STL 不携带绕序元数据。如果输出法线被反转,请在可用时设置 StlSaveOptions 选项,或在加载时翻转坐标系:ObjLoadOptions.flip_coordinate_system = True

ValueError: unsupported format 保存时

检查输出文件扩展名是否为 .obj.stl.gltf.glb.dae.3mf之一。扩展名在 Linux 上区分大小写。

非常大的文件会导致转换缓慢

Aspose.3D FOSS 在内存中处理几何体。对于包含数百万多边形的文件,请确保有足够的 RAM。目前没有流式写入 API。


常见问题 (FAQ)

我可以在不先写入磁盘的情况下转换文件吗?

是的。scene.open()scene.save() 都接受二进制文件对象,除了文件路径之外。传入任何实现了 read() 用于加载或实现了 write() 用于保存的对象:

import io
from aspose.threed import Scene

# Load from an in-memory buffer
data = open('model.obj', 'rb').read()
scene = Scene()
scene.open(io.BytesIO(data))

# Save to an in-memory buffer
buf = io.BytesIO()
scene.save(buf)

FBX 是否支持作为转换的源格式?

FBX 标记化已部分实现,但解析器尚未完成。FBX 输入可能会生成不完整的场景。请使用 OBJ、STL、glTF、COLLADA 或 3MF 作为可靠的源格式。

材料在 OBJ 到 glTF 的转换中会保留下来吗?

基本的 Phong/Lambert 材质属性(漫反射颜色)通过 Scene 模型传递,并写入 glTF 材质块。无法在 glTF 材质模型中表达的程序化或自定义着色器参数将被舍弃。

我可以在循环中转换多个文件吗?

是的。每个 Scene.from_file() 调用都会创建一个独立的对象,因此对路径列表进行循环非常直接:

from pathlib import Path
from aspose.threed import Scene
from aspose.threed.formats import StlSaveOptions

source_dir = Path("input")
output_dir = Path("output")
output_dir.mkdir(exist_ok=True)

opts = StlSaveOptions()
for obj_file in source_dir.glob("*.obj"):
    scene = Scene.from_file(str(obj_file))
    out_path = output_dir / obj_file.with_suffix(".stl").name
    scene.save(str(out_path), opts)
    print(f"Converted {obj_file.name} -> {out_path.name}")

转换是否保留场景层次结构(父/子节点)?

是的。只要目标格式允许,节点树会被保留。STL 等格式仅存储平面几何,没有节点结构;在保存时层次结构会被扁平化。glTF 和 COLLADA 等格式则保留完整的层次结构。

 中文