씬 그래프
scene graph는 Aspose.3D FOSS for Python의 기본 데이터 모델입니다. 디스크에서 로드되든 메모리에서 구성되든 모든 3D 파일은 Node 객체들의 트리이며, 루트는 Scene.root_node입니다. 각 노드는 자식 노드와 하나 이상의 Entity 객체(메시, 카메라, 조명)를 보유할 수 있습니다. scene graph를 이해하면 씬에 있는 모든 객체의 기하학, 재료 및 공간 변환에 직접 접근할 수 있습니다.
설치 및 설정
PyPI에서 라이브러리를 설치하세요:
pip install aspose-3d-foss네이티브 확장, 컴파일러 또는 추가 시스템 패키지가 필요하지 않습니다. 전체 설치 지침은 Installation Guide를 참조하십시오.
개요: 씬 그래프 개념
Aspose.3D FOSS의 씬 그래프는 간단한 포함 계층 구조를 따릅니다:
Scene
└── root_node (Node)
├── child_node_A (Node)
│ ├── entity: Mesh
│ └── transform: translation, rotation, scale
├── child_node_B (Node)
│ └── child_node_C (Node)
│ └── entity: Mesh
└── ...| Object | Role |
|---|---|
Scene | 최상위 컨테이너. root_node, asset_info, animation_clips 및 sub_scenes을 포함합니다. |
Node | 이름이 지정된 트리 노드. 부모와 0개 이상의 자식, 0개 이상의 엔터티, 그리고 로컬 Transform을 가집니다. |
Entity | 노드에 연결된 기하학 또는 씬 객체. 일반적인 엔터티 유형: Mesh, Camera, Light. |
Transform | 노드의 로컬 공간 위치, 회전 및 스케일. 월드 공간 결과는 global_transform에서 읽습니다. |
단계별: 프로그래밍 방식으로 씬 그래프 구축하기
1단계: 장면 만들기
새로운 Scene은 항상 빈 root_node으로 시작합니다:
from aspose.threed import Scene
scene = Scene()
print(scene.root_node.name) # "" (empty string — root node has no name by default)
print(len(scene.root_node.child_nodes)) # 0Scene은 모든 작업의 진입점입니다: 파일 로드, 파일 저장, 애니메이션 클립 생성, 그리고 노드 트리 접근.
2단계: 자식 노드 만들기
create_child_node(name)을 사용하여 트리에 명명된 노드를 추가합니다:
from aspose.threed import Scene
scene = Scene()
parent = scene.root_node.create_child_node("parent")
child = parent.create_child_node("child")
print(parent.name) # "parent"
print(child.parent_node.name) # "parent"
print(len(scene.root_node.child_nodes)) # 1또는 독립형 Node를 생성하고 명시적으로 첨부하십시오:
from aspose.threed import Scene, Node
scene = Scene()
node = Node("standalone")
scene.root_node.add_child_node(node)두 접근 방식 모두 동일한 결과를 생성합니다. create_child_node은 인라인 구성에 더 간결합니다.
3단계: Mesh Entity 생성 및 첨부
Mesh는 정점 데이터 (control_points)와 면 토폴로지 (polygons)를 저장합니다. 하나를 생성하고, 기하를 추가한 다음, 노드에 연결하십시오:
from aspose.threed import Scene, Node
from aspose.threed.entities import Mesh
from aspose.threed.utilities import Vector3, Vector4
scene = Scene()
parent = scene.root_node.create_child_node("parent")
child = parent.create_child_node("child")
##Create a quad mesh (four vertices, one polygon)
# Note: control_points returns a copy of the internal list; append to
# _control_points directly to actually add vertices. This is a known
# library limitation — a public add_control_point() API is not yet available.
mesh = Mesh("cube")
mesh._control_points.append(Vector4(0, 0, 0, 1))
mesh._control_points.append(Vector4(1, 0, 0, 1))
mesh._control_points.append(Vector4(1, 1, 0, 1))
mesh._control_points.append(Vector4(0, 1, 0, 1))
mesh.create_polygon(0, 1, 2, 3)
child.add_entity(mesh)
print(f"Mesh name: {mesh.name}")
print(f"Vertex count: {len(mesh.control_points)}")
print(f"Polygon count: {mesh.polygon_count}")Vector4(x, y, z, w)은 동차 좌표를 나타냅니다. 일반적인 점 위치에는 w=1을 사용하십시오.
create_polygon(*indices)는 정점 인덱스를 받아 폴리곤 목록에 하나의 면을 등록합니다. 삼각형의 경우 인덱스 세 개를, 사각형의 경우 네 개를 전달하십시오.
4단계: 노드 변환 설정
각 노드에는 로컬 공간에서 위치, 방향 및 크기를 제어하는 Transform이 있습니다.
from aspose.threed.utilities import Vector3, Quaternion
##Translate the node 2 units along the X axis
child.transform.translation = Vector3(2.0, 0.0, 0.0)
##Scale the node to half its natural size
child.transform.scaling = Vector3(0.5, 0.5, 0.5)
##Rotate 45 degrees around the Y axis using Euler angles
child.transform.euler_angles = Vector3(0.0, 45.0, 0.0)변환은 누적됩니다: 자식 노드의 월드‑스페이스 위치는 자체 변환과 모든 조상 변환의 조합입니다. node.global_transform(불변, 읽기 전용)에서 평가된 월드‑스페이스 결과를 읽으십시오.
5단계: 씬 그래프를 재귀적으로 순회하기
node.child_nodes를 재귀적으로 순회하여 전체 트리를 탐색합니다:
def traverse(node, depth=0):
indent = " " * depth
entity_type = type(node.entity).__name__ if node.entity else "None"
print(f"{indent}{node.name} [{entity_type}]")
for child in node.child_nodes:
traverse(child, depth + 1)
traverse(scene.root_node)위에서 만든 씬에 대한 예시 출력 (루트 노드 이름은 빈 문자열입니다):
[None]
parent [None]
child [Mesh]노드당 여러 엔터티가 있는 씬에서는 node.entities을 node.entity 대신 반복하십시오:
def traverse_full(node, depth=0):
indent = " " * depth
entity_names = [type(e).__name__ for e in node.entities] or ["None"]
print(f"{indent}{node.name} [{', '.join(entity_names)}]")
for child in node.child_nodes:
traverse_full(child, depth + 1)
traverse_full(scene.root_node)Step 6: 씬 저장
scene.save()에 파일 경로를 전달하십시오. 형식은 파일 확장자를 기반으로 추론됩니다:
scene.save("scene.gltf") # JSON glTF 2.0
scene.save("scene.glb") # Binary GLB container
scene.save("scene.obj") # Wavefront OBJ
scene.save("scene.stl") # STL형식별 옵션의 경우, 두 번째 인수로 save-options 객체를 전달하십시오:
from aspose.threed.formats import GltfSaveOptions
opts = GltfSaveOptions()
scene.save("scene.gltf", opts)팁 및 모범 사례
- 모든 노드에 이름을 지정하세요. 노드에 의미 있는 이름을 부여하면 트래버설 디버깅이 훨씬 쉬워지고, 내보낸 파일에서 이름이 보존됩니다.
- 노드당 하나의 메시. 엔티티를 노드와 1:1로 유지하면 변환 및 충돌 쿼리가 간단해집니다.
- 수동 연결보다
create_child_node을 사용하세요. 부모 참조를 자동으로 설정하고 오류 발생 가능성이 적습니다. - 계층 구조를 만든 후
global_transform을 읽으세요. 모든 조상 변환이 설정된 후에만 월드 스페이스 결과가 안정됩니다. - 트래버설 중에 트리를 변형하지 마세요.
child_nodes을 반복하면서 자식 노드를 추가하거나 제거하면 예측할 수 없는 동작이 발생합니다. 먼저 노드를 수집한 뒤 수정하세요. - 제어점에는
Vector4을 사용하고,Vector3은 사용하지 마세요. 일반 정점 위치에는 항상w=1을 전달하세요;w=0는 방향 벡터를 나타냅니다(점이 아님). mesh.control_points은 복사본을 반환합니다.control_points속성은list(self._control_points)을 반환합니다 — 반환된 리스트에 추가해도 메시가 수정되지 않습니다. 프로그램matically 기하를 구축할 때는 항상mesh._control_points에 직접 추가하세요. 이는 알려진 라이브러리 제한 사항이며, 공개 변이 API는 아직 존재하지 않습니다.
일반적인 문제
| Issue | Resolution |
|---|---|
AttributeError: 'NoneType' object has no attribute 'polygons' | 엔터티 속성에 접근하기 전에 if node.entity is not None 로 보호하십시오. 엔터티가 없는 노드는 entity = None 를 가집니다. |
Mesh appears at the origin despite setting translation | transform.translation 은 로컬 오프셋을 적용합니다. 부모 노드 자체에 비정규 변환이 있으면 월드 위치가 달라질 수 있습니다. global_transform 를 확인하십시오. |
Child nodes missing after scene.save() / reload | 일부 포맷(OBJ)은 계층 구조를 평탄화합니다. 전체 노드 트리를 보존하려면 glTF 또는 COLLADA를 사용하십시오. |
polygon_count is 0 after mesh.create_polygon(...) | create_polygon 에 전달된 정점 인덱스가 범위(0~len(control_points) - 1) 내에 있는지 확인하십시오. |
Node.get_child(name) returns None | 이름은 대소문자를 구분합니다. 생성 시 사용된 정확한 이름 문자열을 확인하십시오. |
| Traversal visits nodes in unexpected order | child_nodes 은 삽입 순서대로 자식을 반환합니다(add_child_node / create_child_node 가 호출된 순서). |