Cách Duyệt Đồ Thị Cảnh 3D trong Python

Cách Duyệt Đồ Thị Cảnh 3D trong Python

Đồ thị cảnh trong Aspose.3D FOSS là một cây của Node các đối tượng có gốc tại scene.root_node. Mỗi tệp 3D, dù được tải từ OBJ, glTF, STL, COLLADA, hoặc 3MF, đều tạo ra cùng một cấu trúc cây. Biết cách duyệt qua nó cho phép bạn kiểm tra hình học, đếm đa giác, lọc các đối tượng theo loại, và xử lý các phần cụ thể của một cảnh phức tạp.

Hướng Dẫn Từng Bước

Bước 1: Cài đặt và Nhập khẩu

Cài đặt Aspose.3D FOSS từ PyPI:

pip install aspose-3d-foss

Nhập các lớp bạn cần:

from aspose.threed import Scene
from aspose.threed.entities import Mesh

Tất cả các lớp công cộng nằm dưới aspose.threed hoặc các gói con của nó (aspose.threed.entities, aspose.threed.utilities).


Bước 2: Tải một Cảnh từ Tệp

Sử dụng phương thức tĩnh Scene.from_file() để mở bất kỳ định dạng nào được hỗ trợ. Định dạng được tự động phát hiện từ phần mở rộng tệp:

scene = Scene.from_file("model.gltf")

Bạn cũng có thể mở với các tùy chọn tải rõ ràng:

from aspose.threed import Scene
from aspose.threed.formats import ObjLoadOptions

options = ObjLoadOptions()
options.enable_materials = True

scene = Scene()
scene.open("model.obj", options)

Sau khi tải, scene.root_node là gốc của cây. Tất cả các nút được nhập đều là con hoặc hậu duệ của nút này.


Bước 3: Viết một Hàm Duyệt Đệ quy

Cách duyệt đơn giản nhất là thăm mọi nút theo thứ tự sâu trước:

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)

Kết quả ví dụ:

[-]
  [-] 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>

Lưu ý: Nút gốc có tên trống (""), vì vậy dòng đầu tiên hiển thị [-] không có tên phía sau.

node.child_nodes trả về các nút con theo thứ tự chèn (thứ tự mà trình nhập hoặc người dùng đã thêm chúng).


Bước 4: Truy cập Thuộc tính Thực thể trên Mỗi Nút

Sử dụng node.entity để lấy thực thể đầu tiên được gắn vào một nút, hoặc node.entities để lặp qua tất cả chúng. Kiểm tra kiểu trước khi truy cập các thuộc tính đặc thù của định dạng:

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, và node.transform.scaling trả về phép biến đổi không gian cục bộ. node.global_transform trả về kết quả không gian thế giới đã được đánh giá (với global_transform.scale cho tỷ lệ không gian thế giới).


Bước 5: Lọc các nút theo loại thực thể

Để chỉ hoạt động trên các loại thực thể cụ thể, hãy thêm một isinstance guard trong quá trình duyệt:

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}")

Mẫu này hữu ích cho các thao tác hàng loạt: áp dụng phép biến đổi cho mọi nút mesh, thay thế vật liệu, hoặc xuất các cây con.


Bước 6: Thu thập tất cả các Mesh và in số lượng đỉnh

Tổng hợp thống kê mesh trong một lần duyệt:

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")

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

Vấn đềGiải pháp
AttributeError: 'NoneType' object has no attribute 'polygons'Guard với if node.entity is not None hoặc isinstance(node.entity, Mesh) trước khi truy cập các thuộc tính lưới. Các nút không có thực thể sẽ trả về None.
Quá trình duyệt dừng sớmĐảm bảo đệ quy đi sâu vào node.child_nodes. Nếu bạn chỉ lặp lại scene.root_node.child_nodes (không đệ quy), bạn sẽ bỏ lỡ tất cả các phần tử con.
Lưới bị thiếu trong kết quả đã thu thậpKiểm tra định dạng tệp có giữ nguyên cấu trúc phân cấp hay không. OBJ làm phẳng toàn bộ hình học thành một cấp độ nút duy nhất. Sử dụng glTF hoặc COLLADA cho các cấu trúc phân cấp sâu.
node.entity chỉ trả về thực thể đầu tiênSử dụng node.entities (danh sách đầy đủ) khi một nút mang nhiều thực thể. node.entity là viết tắt của node.entities[0] khi entities không rỗng.
collect_meshes trả về 0 kết quả cho một STL đã được tảiCác tệp STL thường tạo ra một nút phẳng duy nhất với một thực thể mesh ngay bên dưới root_node. Kiểm tra root_node.child_nodes[0].entity trực tiếp.
Tên nút là chuỗi rỗngMột số định dạng (binary STL, một số tệp OBJ) không lưu trữ tên đối tượng. Các nút sẽ có chuỗi rỗng name ; thay vào đó hãy sử dụng chỉ mục để nhận dạng.

Câu hỏi thường gặp

Cây đồ họa cảnh có thể sâu bao nhiêu?

Không có giới hạn cứng. Giới hạn đệ quy mặc định của Python (1000 khung) áp dụng cho các hàm duyệt đệ quy. Đối với các cây phân cấp rất sâu, chuyển đệ quy thành một ngăn xếp rõ ràng:

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))

Tôi có thể sửa đổi cây trong khi đang duyệt không?

Không được thêm hoặc xóa nút khỏi child_nodes trong khi lặp qua nó. Thu thập các nút cần sửa đổi trong một lượt đầu, sau đó áp dụng các thay đổi trong lượt thứ hai.

Làm sao tôi tìm một nút cụ thể theo tên?

Sử dụng node.get_child(name) để tìm một nút con trực tiếp theo tên. Đối với tìm kiếm sâu, duyệt cây với bộ lọc tên:

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")

Liệu node.entity luôn trả về một Mesh không?

Không. Một nút có thể chứa bất kỳ loại thực thể nào: Mesh, Camera, Light, hoặc các thực thể tùy chỉnh. Luôn kiểm tra với isinstance(node.entity, Mesh) trước khi sử dụng các thuộc tính đặc thù của mesh.

Làm sao tôi lấy vị trí trong không gian toàn cục của một nút?

Đọc node.global_transform.translation. Đây là vị trí đã được tính toán trong không gian thế giới, tính đến tất cả các biến đổi của tổ tiên. Nó chỉ đọc; sửa đổi node.transform.translation để thay đổi vị trí của nút.

Tôi có thể đếm tổng số đa giác trong một cảnh mà không cần viết một vòng duyệt không?

Không thể trực tiếp qua API; không có scene.total_polygon_count thuộc tính. Sử dụng collect_meshes và tổng mesh.polygon_count trên các kết quả, như được hiển thị trong Bước 6.

 Tiếng Việt