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단계: 출력 확인

저장한 후, 출력 파일이 존재하고 크기가 0이 아닌지 확인하십시오. 보다 철저한 검사를 위해 파일을 다시 로드하고 메시 개수를 비교하십시오:

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")

일반적인 문제 및 해결책

출력 파일이 생성되었지만 기하가 포함되어 있지 않습니다

소스 파일이 메쉬가 0개인 상태로 로드되었을 수 있습니다. 저장하기 전에 단계 3의 검사 단계를 추가하십시오. 또한 파일 확장자가 실제 형식과 일치하는지 확인하십시오; Aspose.3D는 확장자를 사용하여 파서를 선택합니다.

glTF 출력에 텍스처가 누락되었습니다

Aspose.3D FOSS는 변환 과정에서 기하학 및 재질 속성을 유지합니다. 소스 OBJ가 .mtl에 외부 이미지 파일을 참조하는 경우, 해당 이미지 파일은 .gltf와 함께 자동으로 복사되지 않습니다. 저장 후 텍스처 이미지를 출력 디렉터리로 수동으로 복사하십시오.

STL 출력이 안쪽을 향함 (면 법선이 뒤집힘)

STL은 winding-order 메타데이터를 포함하지 않습니다. 출력 노멀의 방향이 반전된 경우, 가능한 경우 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-to-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와 같은 형식은 전체 계층 구조를 유지합니다.

 한국어