씬 그래프

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
    └── ...
ObjectRole
Scene최상위 컨테이너. root_node, asset_info, animation_clipssub_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))  # 0

Scene은 모든 작업의 진입점입니다: 파일 로드, 파일 저장, 애니메이션 클립 생성, 그리고 노드 트리 접근.


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.entitiesnode.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는 아직 존재하지 않습니다.

일반적인 문제

IssueResolution
AttributeError: 'NoneType' object has no attribute 'polygons'엔터티 속성에 접근하기 전에 if node.entity is not None 로 보호하십시오. 엔터티가 없는 노드는 entity = None 를 가집니다.
Mesh appears at the origin despite setting translationtransform.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 orderchild_nodes 은 삽입 순서대로 자식을 반환합니다(add_child_node / create_child_node 가 호출된 순서).
 한국어