Đồ thị Cảnh

scene graph là mô hình dữ liệu nền tảng của Aspose.3D FOSS for Python. Mỗi tệp 3D, dù được tải từ đĩa hay được tạo trong bộ nhớ, đều được biểu diễn dưới dạng cây các đối tượng Node có gốc tại Scene.root_node. Mỗi nút có thể chứa các nút con và một hoặc nhiều đối tượng Entity (meshes, cameras, lights). Hiểu biết về scene graph cho phép bạn truy cập trực tiếp vào hình học, vật liệu và các phép biến đổi không gian của mọi đối tượng trong một cảnh.

Cài đặt và Thiết lập

Cài đặt thư viện từ PyPI:

pip install aspose-3d-foss

Không cần các tiện mở rộng gốc, trình biên dịch hoặc các gói hệ thống bổ sung. Để biết hướng dẫn cài đặt đầy đủ, xem Installation Guide.


Tổng quan: Khái niệm Đồ thị Cảnh

Đồ thị cảnh trong Aspose.3D FOSS tuân theo một hệ thống chứa đựng đơn giản:

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
SceneBộ chứa cấp cao nhất. Chứa root_node, asset_info, animation_clipssub_scenes.
NodeNút cây có tên. Có một nút cha, không hoặc nhiều nút con, không hoặc nhiều thực thể, và một Transform cục bộ.
EntityHình học hoặc đối tượng cảnh được gắn vào một nút. Các loại thực thể phổ biến: Mesh, Camera, Light.
TransformVị trí, quay và tỉ lệ trong không gian cục bộ cho một nút. Kết quả trong không gian thế giới được đọc từ global_transform.

Bước từng bước: Xây dựng Đồ thị Cảnh một cách lập trình

Bước 1: Tạo một Cảnh

Một Scene mới luôn bắt đầu với một root_node trống:

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 là điểm vào cho mọi thứ: tải tệp, lưu tệp, tạo clip hoạt hình và truy cập cây nút.


Bước 2: Tạo nút con

Sử dụng create_child_node(name) để thêm các nút có tên vào cây:

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

Ngoài ra, tạo một Node độc lập và đính kèm nó một cách rõ ràng:

from aspose.threed import Scene, Node

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

Cả hai cách tiếp cận đều cho ra cùng một kết quả. create_child_node ngắn gọn hơn cho việc xây dựng nội tuyến.


Bước 3: Tạo Thực thể Lưới và Gắn Nó

Một Mesh lưu trữ dữ liệu đỉnh (control_points) và cấu trúc mặt (polygons). Tạo một, thêm hình học, sau đó gắn nó vào một nút:

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) đại diện cho một tọa độ đồng nhất. Sử dụng w=1 cho các vị trí điểm thường.

create_polygon(*indices) chấp nhận các chỉ số đỉnh và đăng ký một mặt trong danh sách đa giác. Cung cấp ba chỉ số cho một tam giác, bốn chỉ số cho một tứ giác.


Bước 4: Đặt biến đổi nút

Mỗi nút có một Transform điều khiển vị trí, hướng và kích thước của nó trong không gian cục bộ:

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)

Các phép biến đổi là tích lũy: vị trí không gian thế giới của nút con là sự kết hợp của phép biến đổi riêng của nó với tất cả các phép biến đổi của tổ tiên. Đọc kết quả không gian thế giới đã được đánh giá từ node.global_transform (bất biến, chỉ đọc).


Bước 5: Duyệt Đồ thị Cảnh Đệ quy

Duyệt toàn bộ cây bằng cách đệ quy qua 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)

Ví dụ đầu ra cho cảnh được xây dựng ở trên (tên nút gốc là một chuỗi rỗng):

 [None]
  parent [None]
    child [Mesh]

Đối với các cảnh có nhiều thực thể trên mỗi nút, lặp lại node.entities thay vì 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)

Bước 6: Lưu cảnh

Cung cấp một đường dẫn tệp cho scene.save(). Định dạng được suy ra từ phần mở rộng của tệp:

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

Đối với các tùy chọn riêng cho định dạng, truyền một đối tượng save-options làm đối số thứ hai:

from aspose.threed.formats import GltfSaveOptions

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

Mẹo và Thực tiễn tốt nhất

  • Đặt tên cho mọi nút. Việc đặt tên có ý nghĩa cho các nút giúp việc gỡ lỗi các lần duyệt dễ dàng hơn rất nhiều và đảm bảo tên được giữ lại trong tệp xuất.
  • Một lưới mỗi nút. Giữ thực thể 1:1 với các nút làm đơn giản hoá việc biến đổi và các truy vấn va chạm.
  • Sử dụng create_child_node thay vì gắn thủ công. Nó tự động thiết lập tham chiếu cha và ít gây lỗi hơn.
  • Đọc global_transform sau khi xây dựng cây phân cấp. Kết quả trong không gian thế giới chỉ ổn định khi tất cả các biến đổi của tổ tiên đã được thiết lập.
  • Không thay đổi cây trong quá trình duyệt. Thêm hoặc xóa các nút con khi lặp child_nodes sẽ gây ra hành vi không thể đoán trước. Hãy thu thập các nút trước, sau đó mới sửa đổi.
  • Các điểm điều khiển sử dụng Vector4, không phải Vector3. Luôn truyền w=1 cho vị trí đỉnh thông thường; w=0 đại diện cho một vector hướng (không phải một điểm).
  • mesh.control_points trả về một bản sao. Thuộc tính control_points trả về list(self._control_points) — việc thêm vào danh sách trả về không thay đổi lưới. Luôn thêm vào mesh._control_points trực tiếp khi xây dựng hình học bằng chương trình. Đây là một hạn chế đã biết của thư viện; API sửa đổi công khai vẫn chưa tồn tại.

Các vấn đề thường gặp

IssueResolution
AttributeError: 'NoneType' object has no attribute 'polygons'Kiểm tra với if node.entity is not None trước khi truy cập các thuộc tính thực thể. Một nút không có thực thể sẽ có entity = None.
Mesh appears at the origin despite setting translationtransform.translation áp dụng một độ dịch cục bộ. Nếu nút cha tự nó có phép biến đổi không phải là đồng nhất, vị trí thế giới có thể khác. Kiểm tra global_transform.
Child nodes missing after scene.save() / reloadMột số định dạng (OBJ) làm phẳng cấu trúc cây. Sử dụng glTF hoặc COLLADA để giữ nguyên cây nút đầy đủ.
polygon_count is 0 after mesh.create_polygon(...)Xác minh rằng các chỉ số đỉnh được truyền cho create_polygon nằm trong phạm vi (0 đến len(control_points) - 1).
Node.get_child(name) returns NoneTên là phân biệt chữ hoa chữ thường. Xác nhận chuỗi tên chính xác đã dùng khi tạo.
Traversal visits nodes in unexpected orderchild_nodes trả về các nút con theo thứ tự chèn (theo thứ tự add_child_node / create_child_node đã được gọi).
 Tiếng Việt