วิธีการเดินผ่านกราฟฉาก 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.