Graf Adegan

A grafik adegan adalah model data dasar dari Aspose.3D FOSS untuk Python. Setiap file 3D, baik yang dimuat dari disk maupun yang dibangun di memori, direpresentasikan sebagai pohon dari Node objek yang berakar pada Scene.root_node. Setiap node dapat menampung node anak dan satu atau lebih Entity objek (meshes, cameras, lights). Memahami scene graph memberi Anda akses langsung ke geometri, material, dan transformasi spasial setiap objek dalam sebuah adegan.

Instalasi dan Penyiapan

Instal perpustakaan dari PyPI:

pip install aspose-3d-foss

Tidak diperlukan ekstensi native, kompiler, atau paket sistem tambahan. Untuk petunjuk instalasi lengkap, lihat Panduan Instalasi.


Gambaran Umum: Konsep Graf Adegan

Graf adegan dalam Aspose.3D FOSS mengikuti hierarki kontainmen yang sederhana:

Scene
└── root_node  (Node)
    ├── child_node_A  (Node)
    │   ├── entity: Mesh
    │   └── transform: translation, rotation, scale
    ├── child_node_B  (Node)
    │   └── child_node_C  (Node)
    │       └── entity: Mesh
    └── ...
ObjekPeran
SceneKontainer tingkat atas. Menampung root_node, asset_info, animation_clips, dan sub_scenes.
NodeNode pohon bernama. Memiliki induk, nol atau lebih anak, nol atau lebih entitas, dan sebuah Transform.
EntityGeometri atau objek adegan yang terlampir pada sebuah node. Jenis entitas umum: Mesh, Camera, Light.
TransformPosisi, rotasi, dan skala ruang lokal untuk sebuah node. Hasil ruang dunia dibaca dari global_transform.

Langkah demi Langkah: Membangun Graf Adegan secara Programatik

Langkah 1: Buat sebuah Scene

Sebuah baru Scene selalu dimulai dengan kosong 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 adalah titik masuk untuk segala hal: memuat file, menyimpan file, membuat klip animasi, dan mengakses pohon node.


Langkah 2: Buat Node Anak

Gunakan create_child_node(name) untuk menambahkan node bernama ke pohon:

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

Sebagai alternatif, buat sebuah standalone Node dan lampirkan secara eksplisit:

from aspose.threed import Scene, Node

scene = Scene()
node = Node("standalone")
scene.root_node.add_child_node(node)

Kedua pendekatan menghasilkan hasil yang sama. create_child_node lebih ringkas untuk konstruksi inline.


Langkah 3: Buat Entitas Mesh dan Lampirkan

Sebuah Mesh menyimpan data vertex (control_points) dan topologi wajah (polygons). Buat satu, tambahkan geometri, lalu lampirkan ke node:

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) mewakili koordinat homogen. Gunakan w=1 untuk posisi titik biasa.

create_polygon(*indices) menerima indeks vertex dan mendaftarkan satu wajah dalam daftar poligon. Berikan tiga indeks untuk segitiga, empat untuk kuad.


Langkah 4: Atur Transformasi Node

Setiap node memiliki sebuah Transform yang mengontrol posisi, orientasi, dan ukuran dalam ruang lokal:

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)

Transformasi bersifat kumulatif: posisi ruang-dunia node anak adalah komposisi transformasinya sendiri dengan semua transformasi leluhur. Baca hasil ruang-dunia yang telah dievaluasi dari node.global_transform (tidak dapat diubah, hanya-baca).


Langkah 5: Jelajahi Graf Adegan Secara Rekursif

Jelajahi seluruh pohon dengan merekursi melalui 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)

Contoh output untuk adegan yang dibangun di atas (nama node akar adalah string kosong):

 [None]
  parent [None]
    child [Mesh]

Untuk adegan dengan beberapa entitas per node, lakukan iterasi node.entities daripada 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)

Langkah 6: Simpan Adegan

Berikan jalur file ke scene.save(). Formatnya ditentukan dari ekstensi file:

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

Untuk opsi khusus format, berikan objek save-options sebagai argumen kedua:

from aspose.threed.formats import GltfSaveOptions

opts = GltfSaveOptions()
scene.save("scene.gltf", opts)

Tips dan Praktik Terbaik

  • Beri nama setiap node. Memberi node nama yang bermakna membuat debugging traversal jauh lebih mudah dan memastikan nama tetap dipertahankan dalam file yang diekspor.
  • Satu mesh per node. Menjaga entitas 1:1 dengan node menyederhanakan transformasi dan kueri tabrakan.
  • Gunakan create_child_node daripada penempelan manual. Ini mengatur referensi induk secara otomatis dan lebih sedikit rawan kesalahan.
  • Baca global_transform setelah membangun hierarki. Hasil ruang-dunia hanya stabil setelah semua transformasi nenek moyang diatur.
  • Jangan mengubah pohon selama traversal. Menambahkan atau menghapus node anak saat iterasi child_nodes akan menghasilkan perilaku yang tidak dapat diprediksi. Kumpulkan node terlebih dahulu, kemudian modifikasi.
  • Titik kontrol menggunakan Vector4, bukan Vector3. Selalu berikan w=1 untuk posisi vertex biasa; w=0 mewakili vektor arah (bukan titik).
  • mesh.control_points mengembalikan salinan. The control_points properti mengembalikan list(self._control_points) — menambahkan ke daftar yang dikembalikan tidak mengubah mesh. Selalu tambahkan ke mesh._control_points langsung saat membangun geometri secara programatik. Ini adalah keterbatasan perpustakaan yang diketahui; API mutasi publik belum tersedia.

Masalah Umum

MasalahSolusi
AttributeError: 'NoneType' object has no attribute 'polygons'Lindungi dengan if node.entity is not None sebelum mengakses properti entitas. Sebuah node tanpa entitas memiliki entity = None.
Mesh muncul di asal meskipun telah mengatur translationtransform.translation menerapkan offset lokal. Jika node induk sendiri memiliki transformasi bukan identitas, posisi dunia mungkin berbeda. Periksa global_transform.
Node anak hilang setelah scene.save() / muat ulangBeberapa format (OBJ) meratakan hierarki. Gunakan glTF atau COLLADA untuk mempertahankan seluruh pohon node.
polygon_count adalah 0 setelah mesh.create_polygon(...)Verifikasi bahwa indeks vertex yang diberikan ke create_polygon berada dalam rentang (0 ke len(control_points) - 1).
Node.get_child(name) mengembalikan NoneNama bersifat case-sensitive. Konfirmasikan string nama yang tepat yang digunakan pada saat pembuatan.
Traversal mengunjungi node dalam urutan yang tidak terdugachild_nodes mengembalikan anak dalam urutan penyisipan (urutan add_child_node / create_child_node dipanggil).
 Bahasa Indonesia