Python에서 3D 씬 그래프를 탐색하는 방법
Aspose.3D FOSS의 씬 그래프는 트리 구조입니다 Node 다음에 루트된 객체 scene.root_node. OBJ, glTF, STL, COLLADA, 또는 3MF에서 로드되든 모든 3D 파일은 동일한 트리 구조를 생성합니다. 이를 순회하는 방법을 알면 기하학을 검사하고, 폴리곤을 셀 수 있으며, 유형별로 객체를 필터링하고, 복잡한 씬의 특정 부분을 처리할 수 있습니다.
단계별 가이드
1단계: 설치 및 임포트
PyPI에서 Aspose.3D FOSS를 설치합니다:
pip install aspose-3d-foss필요한 클래스를 임포트합니다:
from aspose.threed import Scene
from aspose.threed.entities import Mesh모든 public 클래스는 aspose.threed 또는 그 하위 패키지 (aspose.threed.entities, aspose.threed.utilities).
2단계: 파일에서 씬 로드
static 메서드를 사용하세요 Scene.from_file() 메서드를 사용하여 지원되는 모든 포맷을 열 수 있습니다. 포맷은 파일 확장자를 통해 자동으로 감지됩니다:
scene = Scene.from_file("model.gltf")명시적인 로드 옵션을 사용하여 열 수도 있습니다:
from aspose.threed import Scene
from aspose.threed.formats import ObjLoadOptions
options = ObjLoadOptions()
options.enable_materials = True
scene = Scene()
scene.open("model.obj", options)로드 후, scene.root_node 는 트리의 루트입니다. 모든 가져온 노드는 이 노드의 자식 또는 하위 노드입니다.
3단계: 재귀 순회 함수 작성
가장 간단한 순회는 깊이 우선 순서로 모든 노드를 방문합니다:
from aspose.threed import Scene
from aspose.threed.entities import Mesh
def traverse(node, depth=0):
prefix = " " * depth
entity_name = type(node.entity).__name__ if node.entity else "-"
print(f"{prefix}[{entity_name}] {node.name}")
for child in node.child_nodes:
traverse(child, depth + 1)
scene = Scene.from_file("model.gltf")
traverse(scene.root_node)예시 출력:
[-]
[-] Armature
[Mesh] Body
[Mesh] Eyes
[-] Ground
[Mesh] Plane<button class=“hextra-code-copy-btn hx-group/copybtn hx-transition-all active:hx-opacity-50 hx-bg-primary-700/5 hx-border hx-border-black/5 hx-text-gray-600 hover:hx-text-gray-900 hx-rounded-md hx-p-1.5 dark:hx-bg-primary-300/10 dark:hx-border-white/10 dark:hx-text-gray-400 dark:hover:hx-text-gray-50” title=“Copy code”
<div class="copy-icon group-[.copied]/copybtn:hx-hidden hx-pointer-events-none hx-h-4 hx-w-4"></div>
<div class="success-icon hx-hidden group-[.copied]/copybtn:hx-block hx-pointer-events-none hx-h-4 hx-w-4"></div>
참고: 루트 노드의 이름은 비어 있습니다 (""), 따라서 첫 번째 줄에 [-] 이름이 뒤에 없습니다.
node.child_nodes 삽입 순서대로 자식을 반환합니다 (importer 또는 사용자가 추가한 순서).
4단계: 각 노드에서 엔티티 속성에 접근
사용하세요 node.entity 노드에 연결된 첫 번째 엔티티를 가져오려면, 또는 node.entities 모두를 반복하려면. 형식별 속성에 접근하기 전에 타입을 확인하세요:
from aspose.threed import Scene
from aspose.threed.entities import Mesh
def print_entity_details(node, depth=0):
indent = " " * depth
for entity in node.entities:
if isinstance(entity, Mesh):
mesh = entity
print(f"{indent}Mesh '{node.name}':")
print(f"{indent} vertices : {len(mesh.control_points)}")
print(f"{indent} polygons : {mesh.polygon_count}")
print(f"{indent} cast_shadows : {mesh.cast_shadows}")
print(f"{indent} receive_shadows: {mesh.receive_shadows}")
for child in node.child_nodes:
print_entity_details(child, depth + 1)
scene = Scene.from_file("model.gltf")
print_entity_details(scene.root_node)node.transform.translation, node.transform.rotation, 그리고 node.transform.scaling 로컬 공간 변환을 제공합니다. node.global_transform 평가된 월드 공간 결과를 제공합니다 (with global_transform.scale 월드 스페이스 스케일에 대해).
5단계: 엔티티 유형별로 노드 필터링
특정 엔티티 유형에만 작동하려면, isinstance 트래버설 내부에 가드를 추가하십시오:
from aspose.threed import Scene
from aspose.threed.entities import Mesh
def find_mesh_nodes(node, results=None):
if results is None:
results = []
for entity in node.entities:
if isinstance(entity, Mesh):
results.append(node)
break # One match per node is enough
for child in node.child_nodes:
find_mesh_nodes(child, results)
return results
scene = Scene.from_file("model.gltf")
mesh_nodes = find_mesh_nodes(scene.root_node)
print(f"Found {len(mesh_nodes)} mesh node(s)")
for n in mesh_nodes:
print(f" {n.name}")이 패턴은 대량 작업에 유용합니다: 모든 메시 노드에 변환 적용, 재질 교체, 또는 서브 트리 내보내기.
6단계: 모든 메쉬를 수집하고 정점 수 출력
한 번의 패스로 메쉬 통계를 집계합니다:
from aspose.threed import Scene
from aspose.threed.entities import Mesh
def collect_meshes(node, results=None):
if results is None:
results = []
if isinstance(node.entity, Mesh):
results.append((node.name, node.entity))
for child in node.child_nodes:
collect_meshes(child, results)
return results
scene = Scene.from_file("model.gltf")
meshes = collect_meshes(scene.root_node)
print(f"Total meshes: {len(meshes)}")
total_verts = 0
total_polys = 0
for name, mesh in meshes:
verts = len(mesh.control_points)
polys = mesh.polygon_count
total_verts += verts
total_polys += polys
print(f" {name}: {verts} vertices, {polys} polygons")
print(f"Scene totals: {total_verts} vertices, {total_polys} polygons")일반적인 문제
| 문제 | 해결책 |
|---|---|
AttributeError: 'NoneType' object has no attribute 'polygons' | 다음으로 가드 if node.entity is not None 또는 isinstance(node.entity, Mesh) 메시 속성에 접근하기 전에. 엔티티가 없는 노드는 반환합니다 None. |
| 트래버설이 조기에 중단됩니다 | 재귀가 다음까지 도달하도록 보장하십시오 node.child_nodes. 만약 반복을 오직 scene.root_node.child_nodes (재귀적으로 하지 않으면), 모든 하위 요소를 놓칩니다. |
| 수집된 결과에서 메시가 누락됨 | 파일 형식이 계층 구조를 보존하는지 확인하십시오. OBJ는 모든 기하학을 단일 노드 레벨로 평탄화합니다. 깊은 계층 구조를 위해서는 glTF 또는 COLLADA를 사용하십시오. |
node.entity 첫 번째 엔티티만 반환합니다 | 사용 node.entities (전체 목록) 노드가 여러 엔터티를 보유할 때. node.entity 은(는) 약식 표현이다 node.entities[0] 때 entities 은 비어 있지 않다. |
collect_meshes 로드된 STL에 대해 0개의 결과를 반환합니다 | STL 파일은 일반적으로 하나의 평면 노드와 그 바로 아래에 하나의 mesh 엔터티를 생성합니다 root_node. 확인 root_node.child_nodes[0].entity 직접. |
| 노드 이름이 빈 문자열입니다 | 일부 포맷(바이너리 STL, 일부 OBJ 파일)은 객체 이름을 저장하지 않습니다. 노드는 빈 name 문자열; 대신 인덱스를 사용하여 식별하십시오. |
자주 묻는 질문
씬 그래프는 얼마나 깊을 수 있나요?
엄격한 제한은 없습니다. Python의 기본 재귀 제한(1000 프레임)이 재귀 순회 함수에 적용됩니다. 매우 깊은 계층 구조의 경우, 재귀를 명시적인 스택으로 변환하세요:
from collections import deque
from aspose.threed import Scene
scene = Scene.from_file("deep.gltf")
queue = deque([(scene.root_node, 0)])
while queue:
node, depth = queue.popleft()
print(" " * depth + node.name)
for child in node.child_nodes:
queue.append((child, depth + 1))순회 중에 트리를 수정할 수 있나요?
노드를 추가하거나 제거하지 마세요 child_nodes 반복하면서는 하지 마세요. 첫 번째 패스에서 수정할 노드를 수집하고, 두 번째 패스에서 변경을 적용하세요.
이름으로 특정 노드를 찾으려면 어떻게 해야 하나요?
사용 node.get_child(name) 이름으로 직접 자식을 찾습니다. 깊은 검색을 위해서는 이름 필터를 사용해 트리를 순회하세요:
def find_by_name(root, name):
if root.name == name:
return root
for child in root.child_nodes:
result = find_by_name(child, name)
if result:
return result
return None
target = find_by_name(scene.root_node, "Wheel_FL")Does node.entity 항상 Mesh를 반환합니까?
아니요. 노드는 어떤 엔터티 타입도 가질 수 있습니다: Mesh, Camera, Light, 혹은 사용자 정의 엔터티. 항상 다음으로 확인하세요 isinstance(node.entity, Mesh) mesh 전용 속성을 사용하기 전에.
노드의 월드 공간 위치를 어떻게 얻나요?
읽기 node.global_transform.translation. 이것은 모든 상위 변환을 고려한 월드 공간에서 평가된 위치입니다. 읽기 전용; 수정 node.transform.translation 노드의 위치를 재배치하려면.
장면에서 전체 폴리곤 수를 트래버설을 작성하지 않고 셀 수 있나요?
API를 통해 직접적으로는 할 수 없으며; 없습니다 scene.total_polygon_count 속성. 사용 collect_meshes 그리고 합계 mesh.polygon_count 결과 전반에 걸쳐, 단계 6에 표시된 대로.