چگونه یک گراف صحنه 3D را در Python پیمایش کنیم
گراف صحنه در Aspose.3D FOSS یک درخت از Node اشیاء ریشهدار در scene.root_node. هر فایل 3D، چه از OBJ، glTF، STL، COLLADA یا 3MF بارگذاری شود، همان ساختار درختی را تولید میکند. دانستن نحوه پیمایش آن به شما امکان میدهد هندسه را بررسی کنید، چندضلعیها را شمارش کنید، اشیاء را بر اساس نوع فیلتر کنید و بخشهای خاصی از یک صحنه پیچیده را پردازش کنید.
راهنمای گام به گام
مرحله ۱: نصب و وارد کردن
نصب 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).
مرحله ۲: بارگذاری یک صحنه از فایل
از متد استاتیک 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 ریشه درخت است. تمام گرههای وارد شده فرزندان یا نوادگان این گره هستند.
مرحله ۳: نوشتن یک تابع پیمایش بازگشتی
سادهترین پیمایش، هر گره را به ترتیب عمق‑اول بازدید میکند:
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=“کپی کد”
<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 فرزندان را به ترتیب درج برمیگرداند (ترتیبی که واردکننده یا کاربر آنها را اضافه کرده است).
مرحله ۴: دسترسی به ویژگیهای موجودیت در هر گره
از 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 برای مقیاس فضای جهان).
مرحلهٔ ۵: فیلتر کردن گرهها بر اساس نوع موجودیت
برای کار فقط بر روی انواع خاصی از موجودیتها، یک 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}")این الگو برای عملیاتهای دستهای مفید است: اعمال یک تبدیل به هر گرهٔ مش، جایگزینی مواد، یا استخراج زیر‑درختها.
مرحلهٔ ۶: جمعآوری تمام مشها و چاپ تعداد رئوس
آمار مشها را در یک عبور جمعآوری کنید:
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 (بهصورت بازگشتی نیست)، تمام نوادگان را از دست میدهید. |
| Mesh گم شده از نتایج جمعآوری شده | اطمینان حاصل کنید که قالب فایل ساختار سلسلهمراتبی را حفظ میکند. OBJ تمام هندسه را به یک سطح گرهٔ واحد مسطح میکند. برای سلسلهمراتب عمیق از glTF یا COLLADA استفاده کنید. |
node.entity فقط اولین موجودیت را برمیگرداند | استفاده کنید node.entities (فهرست کامل) وقتی یک گره چند موجودیت دارد. node.entity یک مخفف برای است node.entities[0] زمانی که entities خالی نیست. |
collect_meshes برای یک STL بارگذاریشده 0 نتیجه برمیگرداند | فایلهای STL معمولاً یک گرهٔ صاف واحد با یک موجودیت مش بهصورت مستقیم زیر آن تولید میکنند root_node. بررسی کنید root_node.child_nodes[0].entity بهصورت مستقیم. |
| نامهای گره رشتههای خالی هستند | برخی فرمتها (binary STL، برخی فایلهای OBJ) نام اشیاء را ذخیره نمیکنند. گرهها رشتههای خالی خواهند داشت name رشتهها؛ بهجای آن از ایندکس برای شناسایی استفاده کنید. |
سوالات متداول
یک گراف صحنه تا چه عمقی میتواند باشد؟?
هیچ محدودیت سختی وجود ندارد. محدودیت پیشفرض بازگشت Python (۱۰۰۰ فریم) بر روی توابع عبور بازگشتی اعمال میشود. برای سلسلهمراتبهای بسیار عمیق، بازگشت را به یک پشتهٔ صریح تبدیل کنید:
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) قبل از استفاده از ویژگیهای خاص Mesh.
چگونه میتوانم موقعیت فضای جهانی یک گره را به دست آورم؟?
خواندن node.global_transform.translation. این موقعیت ارزیابیشده در فضای جهان است که تمام تبدیلات اجداد را در نظر میگیرد. این فقط‑خواندنی است؛ تغییر دهید node.transform.translation برای جابجایی گره.
آیا میتوانم تعداد کل چندضلعیها را در یک صحنه بدون نوشتن یک traversal شمارش کنم؟?
بهصورت مستقیم از طریق API امکانپذیر نیست؛ هیچ scene.total_polygon_count ویژگی. استفاده کنید collect_meshes و جمع کنید mesh.polygon_count در سراسر نتایج، همانطور که در گام ۶ نشان داده شده است.