Как конвертировать 3D модели в Python

Как конвертировать 3D модели в Python

Преобразование форматов с Aspose.3D FOSS для 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 обрабатывает геометрию в памяти. Для файлов с миллионами полигонов обеспечьте достаточный объём ОЗУ. В настоящее время нет 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, сохраняют полную иерархию.

 Русский