Python에서 3D 모델 로드하는 방법

Python에서 3D 모델 로드하는 방법

Aspose.3D FOSS for Python은 네이티브 종속성 없이 3D 파일을 열 수 있는 간단한 API를 제공합니다. 파일을 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

모든 public 클래스는 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 (binary 및 ASCII), glTF 2.0 / GLB, COLLADA (DAE), 및 3MF 파일을 지원합니다.


4단계: 씬 노드 순회

로드된 씬은 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는 0개 이상의 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_pointsVector4 객체들의 리스트이며; 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 참조를 참조하십시오.


일반적인 문제 및 해결 방법

Scene.from_file() 호출 시 FileNotFoundError

경로는 절대 경로이거나 런타임 시 작업 디렉터리에 대해 올바른 상대 경로여야 합니다. pathlib.Path을 사용하여 신뢰할 수 있는 경로를 구축하십시오:

from pathlib import Path
from aspose.threed import Scene

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

STL 파일을 로드한 후 mesh.polygons가 비어 있습니다

STL 파일은 삼각형을 원시 면으로 저장하며, 인덱스된 메시는 사용하지 않습니다. 로드한 후에는 해당 면들로부터 다각형이 합성됩니다. polygons이 비어 있으면, len(mesh.control_points)을 확인하십시오; 카운트가 3의 배수이면 기하학이 인덱스되지 않은 형태로 저장된 것이며, 연속된 세 개의 정점이 하나의 삼각형을 형성합니다.

좌표계 불일치 (모델이 회전되거나 반전된 것으로 보임)

다른 도구들은 서로 다른 규칙(Y-up vs Z-up, left-hand vs right-hand)을 사용합니다. 로드 후 ObjLoadOptions.flip_coordinate_system = True를 설정하거나 루트 노드의 Transform에 회전을 적용하십시오.

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

노드의 엔터티 목록에는 메시가 아닌 엔터티(카메라, 조명)가 포함될 수 있습니다. 캐스팅하기 전에 항상 isinstance(entity, Mesh) 로 보호하십시오.


자주 묻는 질문 (FAQ)

어떤 3D 형식을 로드할 수 있나요?

OBJ (Wavefront), STL (binary and ASCII), glTF 2.0 / GLB, COLLADA (DAE), 및 3MF. FBX 파일 토큰화는 부분적으로 지원되지만 전체 파싱은 아직 완료되지 않았습니다.

OBJ 파일을 로드하면 .mtl 재질도 로드됩니까?

예, ObjLoadOptions.enable_materials = True(기본값)일 때입니다. 라이브러리는 .obj 파일과 동일한 디렉터리에서 .mtl 파일을 찾습니다. .mtl이 없으면, 기하학은 여전히 로드되고 경고가 발생합니다.

경로 대신 바이트 스트림에서 파일을 로드할 수 있나요?

예. scene.open()은 파일 경로 문자열 외에 .read() 메서드를 가진 파일과 유사한 객체를 모두 허용합니다. 열린 바이너리 스트림(예: io.BytesIO)을 직접 전달하십시오. Scene.from_file()은 파일 경로 문자열만 허용합니다.

표면 법선은 어떻게 얻나요?

로드한 후, mesh.get_element(VertexElementType.NORMAL)를 확인하십시오. 이것은 VertexElementNormal를 반환하며, 그 data 목록에는 각 참조마다 하나의 정상 벡터가 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를 공유하지 않는 한 안전합니다.

 한국어