كيفية استعراض مخطط المشهد ثلاثي الأبعاد في بايثون

كيفية استعراض مخطط المشهد ثلاثي الأبعاد في بايثون

مخطط المشهد في Aspose.3D FOSS هو شجرة من كائنات Node ذات جذر scene.root_node. كل ملف ثلاثي الأبعاد، سواء تم تحميله من 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: تحميل مشهد من ملف

استخدم الطريقة الساكنة 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: الوصول إلى خصائص الكيان على كل عقدة

استخدم 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")

المشكلات الشائعة

IssueResolution
AttributeError: 'NoneType' object has no attribute 'polygons'احمِ باستخدام if node.entity is not None أو isinstance(node.entity, Mesh) قبل الوصول إلى خصائص الشبكة. العقد التي لا تحتوي على كيانات تُعيد None.
Traversal stops earlyتأكد من أن العودية تصل إلى node.child_nodes. إذا قمت بالتكرار فقط على scene.root_node.child_nodes (دون العودية)، فإنك تفوت جميع السلالات.
Mesh missing from collected resultsتحقق من أن تنسيق الملف يحافظ على التسلسل الهرمي. يقوم OBJ بتسطيح جميع الهندسة إلى مستوى عقدة واحد. استخدم glTF أو COLLADA للتسلسلات الهرمية العميقة.
node.entity returns only the first entityاستخدم node.entities (القائمة الكاملة) عندما تحمل العقدة كيانات متعددة. node.entity هو اختصار لـ node.entities[0] عندما يكون entities غير فارغ.
collect_meshes returns 0 results for a loaded STLعادةً ما تُنتج ملفات STL عقدة مسطحة واحدة بكيان شبكة واحد مباشرةً تحت root_node. تحقق من root_node.child_nodes[0].entity مباشرةً.
Node names are empty stringsبعض التنسيقات (STL الثنائي، بعض ملفات OBJ) لا تخزن أسماء الكائنات. ستحصل العقد على سلاسل name فارغة؛ استخدم الفهرس للتعريف بدلاً من ذلك.

الأسئلة المتكررة

ما هو أقصى عمق يمكن أن يصل إليه مخطط المشهد؟

لا يوجد حد ثابت. الحد الافتراضي للتكرار في بايثون (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 لإعادة تموضع العقدة.

هل يمكنني حساب إجمالي المضلعات في المشهد دون كتابة استعراض؟

ليس مباشرةً عبر الـ API؛ لا توجد خاصية scene.total_polygon_count. استخدم collect_meshes واجمع mesh.polygon_count عبر النتائج، كما هو موضح في الخطوة 6.

 العربية