วิธีการเดินผ่านกราฟฉาก 3D ใน Python

วิธีการเดินผ่านกราฟฉาก 3D ใน Python

กราฟฉากใน Aspose.3D FOSS เป็นต้นไม้ของ Node วัตถุที่มีรากอยู่ที่ scene.root_node. ทุกไฟล์ 3D ไม่ว่าจะโหลดจาก OBJ, glTF, STL, COLLADA หรือ 3MF จะสร้างโครงสร้างต้นไม้เดียวกัน การรู้วิธีการเดินทางผ่านมันทำให้คุณสามารถตรวจสอบเรขาคณิต, นับโพลิกอน, กรองวัตถุตามประเภท, และประมวลผลส่วนเฉพาะของฉากที่ซับซ้อนได้.

คู่มือแบบขั้นตอนต่อขั้นตอน

ขั้นตอนที่ 1: ติดตั้งและนำเข้า

ติดตั้ง Aspose.3D FOSS จาก PyPI:

pip install aspose-3d-foss

นำเข้าคลาสที่คุณต้องการ:

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

คลาสสาธารณะทั้งหมดอยู่ภายใต้ aspose.threed หรือในแพ็กเกจย่อยของมัน (aspose.threed.entities, aspose.threed.utilities).


ขั้นตอนที่ 2: โหลดฉากจากไฟล์

ใช้ static Scene.from_file() เมธอดเพื่อเปิดรูปแบบที่รองรับใด ๆ รูปแบบจะถูกตรวจจับโดยอัตโนมัติจากส่วนขยายของไฟล์:

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

คุณยังสามารถเปิดด้วยตัวเลือกการโหลดที่ระบุอย่างชัดเจนได้:

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

options = ObjLoadOptions()
options.enable_materials = True

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

หลังจากโหลด, scene.root_node เป็นรากของต้นไม้ ทุกโหนดที่นำเข้าจะเป็นลูกหรือทายาทของโหนดนี้.


ขั้นตอนที่ 3: เขียนฟังก์ชันการเดินทางแบบเรียกซ้ำ

การเดินทางที่ง่ายที่สุดจะเยี่ยมชมทุกโหนดตามลำดับแบบลึกก่อน:

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)

ตัวอย่างผลลัพธ์:

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

หมายเหตุ: โหนดรากมีชื่อว่าง (""), ดังนั้นบรรทัดแรกจะแสดง [-] โดยไม่มีชื่อต่อท้าย.

node.child_nodes คืนค่าเด็กตามลำดับการแทรก (ลำดับที่ผู้นำเข้าหรือผู้ใช้เพิ่มเข้าไป).


ขั้นตอนที่ 4: เข้าถึงคุณสมบัติ Entity บนแต่ละโหนด

ใช้ node.entity เพื่อรับเอนทิตีแรกที่แนบกับโหนด, หรือ node.entities เพื่อวนซ้ำทั้งหมด ตรวจสอบประเภทก่อนเข้าถึงคุณสมบัติที่เฉพาะของรูปแบบ:

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, และ node.transform.scaling ให้การแปลงในพื้นที่ท้องถิ่น. node.global_transform ให้ผลลัพธ์ที่ประเมินในพื้นที่โลก (กับ global_transform.scale สำหรับสเกลในพื้นที่โลก).


ขั้นตอนที่ 5: กรองโหนดตามประเภทเอนทิตี้

เพื่อทำงานเฉพาะกับประเภทเอนทิตี้ที่กำหนด, เพิ่ม isinstance การตรวจสอบภายในการเดินทาง:

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

รูปแบบนี้มีประโยชน์สำหรับการดำเนินการเป็นกลุ่ม: การใช้การแปลงกับทุกโหนดเมช, การแทนที่วัสดุ, หรือการส่งออกต้นไม้ย่อย.


ขั้นตอนที่ 6: รวบรวมเมชทั้งหมดและพิมพ์จำนวนเวอร์เท็กซ์

รวบรวมสถิติเมชในหนึ่งรอบการทำงาน:

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

ปัญหาทั่วไป

ปัญหาวิธีแก้ไข
AttributeError: 'NoneType' object has no attribute 'polygons'ตรวจสอบด้วย if node.entity is not None หรือ isinstance(node.entity, Mesh) ก่อนเข้าถึงคุณสมบัติของเมช. โหนดที่ไม่มีเอนทิตี้จะคืนค่า None.
การเดินทางหยุดก่อนตรวจสอบให้การเรียกซ้ำเข้าถึง node.child_nodes. หากคุณวนซ้ำเฉพาะ scene.root_node.child_nodes (ไม่ทำซ้ำ), คุณจะพลาดทายาททั้งหมด.
เมชหายจากผลลัพธ์ที่รวบรวมตรวจสอบว่าไฟล์ฟอร์แมตรักษาโครงสร้างลำดับชั้นไว้. OBJ จะทำให้เรขาคณิตทั้งหมดแบนเป็นระดับโหนดเดียว. ใช้ glTF หรือ COLLADA สำหรับโครงสร้างลำดับชั้นที่ลึก.
node.entity คืนค่าเฉพาะเอนทิตี้แรกใช้ node.entities (รายการเต็ม) เมื่อโหนดมีหลายเอนทิตี้. node.entity เป็นรูปแบบย่อของ node.entities[0] เมื่อ entities ไม่ว่างเปล่า.
collect_meshes คืนค่า 0 ผลลัพธ์สำหรับ STL ที่โหลดแล้วไฟล์ STL โดยทั่วไปจะสร้างโหนดแบนเดียวที่มีเอนทิตีเมชหนึ่งตัวอยู่โดยตรงภายใต้ root_node. ตรวจสอบ root_node.child_nodes[0].entity โดยตรง.
ชื่อโหนดเป็นสตริงว่างรูปแบบบางอย่าง (binary STL, ไฟล์ OBJ บางไฟล์) ไม่ได้เก็บชื่อวัตถุ โหนดจะมีสตริงว่าง name สตริง; ใช้ดัชนีเพื่อระบุตัวตนแทน.

คำถามที่พบบ่อย

กราฟฉากสามารถลึกได้มากแค่ไหน?

ไม่มีขีดจำกัดที่แน่นอน ค่าเริ่มต้นของการจำกัดการเรียกซ้ำของ Python (1000 เฟรม) ใช้กับฟังก์ชันการเดินผ่านแบบเรียกซ้ำ สำหรับโครงสร้างลึกมาก ให้แปลงการเรียกซ้ำเป็นสแตกที่ชัดเจน:

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

ฉันสามารถแก้ไขต้นไม้ขณะกำลังเดินผ่านได้หรือไม่?

ห้ามเพิ่มหรือเอาโหนดออกจาก child_nodes ขณะวนซ้ำ อย่าลบหรือเพิ่มโหนดในขณะวนซ้ำ เก็บโหนดที่ต้องการแก้ไขในรอบแรก แล้วจึงทำการเปลี่ยนแปลงในรอบที่สอง.

ฉันจะค้นหาโหนดเฉพาะโดยชื่อได้อย่างไร?

ใช้ node.get_child(name) เพื่อค้นหาเด็กโดยตรงตามชื่อ สำหรับการค้นหาเชิงลึก ให้เดินทางผ่านต้นไม้ด้วยตัวกรองชื่อ:

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

ทำ node.entity เสมอคืนค่า Mesh หรือไม่?

ไม่. โหนดสามารถเก็บประเภทเอนทิตีใดก็ได้: Mesh, Camera, Light, หรือเอนทิตีที่กำหนดเอง. ตรวจสอบเสมอด้วย isinstance(node.entity, Mesh) ก่อนใช้คุณสมบัติเฉพาะของเมช.

ฉันจะรับตำแหน่งในพื้นที่โลกของโหนดได้อย่างไร?

อ่าน node.global_transform.translation. นี่คือตำแหน่งที่ประเมินในพื้นที่โลก โดยคำนึงถึงการแปลงของบรรพบุรุษทั้งหมด มันเป็นแบบอ่านอย่างเดียว; แก้ไข node.transform.translation เพื่อเปลี่ยนตำแหน่งของโหนด.

ฉันสามารถนับจำนวน polygons ทั้งหมดในฉากโดยไม่ต้องเขียนการวนรอบได้หรือไม่?

ไม่สามารถทำได้โดยตรงผ่าน API; ไม่มี scene.total_polygon_count คุณสมบัติ. ใช้ collect_meshes และผลรวม mesh.polygon_count ทั่วผลลัพธ์, ตามที่แสดงในขั้นตอนที่ 6.

 ภาษาไทย