چگونه یک گراف صحنه 3D را در Python پیمایش کنیم

چگونه یک گراف صحنه 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 در سراسر نتایج، همان‌طور که در گام ۶ نشان داده شده است.

 فارسی