گراف صحنه

یک گراف صحنه مدل دادهٔ بنیادی Aspose.3D FOSS برای Python است. هر فایل 3D، چه از دیسک بارگذاری شود و چه در حافظه ساخته شود، به‌صورت درختی از Node اشیائی که ریشه در Scene.root_node. هر گره می‌تواند گره‌های فرزند و یک یا چند Entity اشیاء (مش‌ها، دوربین‌ها، نورها). درک گراف صحنه به شما دسترسی مستقیم به هندسه، مواد و تبدیلات فضایی هر شیء در یک صحنه می‌دهد.

نصب و راه‌اندازی

کتابخانه را از PyPI نصب کنید:

pip install aspose-3d-foss

هیچ افزونهٔ بومی، کامپایلر یا بستهٔ سیستمی اضافی لازم نیست. برای دستورالعمل‌های کامل نصب، به راهنمای نصب.


بررسی کلی: مفاهیم گراف صحنه

گراف صحنه در Aspose.3D FOSS از یک سلسله‌مراتب سادهٔ دربرگیری پیروی می‌کند:

Scene
└── root_node  (Node)
    ├── child_node_A  (Node)
    │   ├── entity: Mesh
    │   └── transform: translation, rotation, scale
    ├── child_node_B  (Node)
    │   └── child_node_C  (Node)
    │       └── entity: Mesh
    └── ...
شیءنقش
Sceneمحفظه سطح بالا. شامل root_node, asset_info, animation_clips, و sub_scenes.
Nodeگره درختی نام‌گذاری‌شده. دارای والد، صفر یا بیشتر فرزند، صفر یا بیشتر موجودیت، و یک Transform.
Entityهندسه یا شیء صحنه‌ای که به گره متصل است. انواع رایج موجودیت: Mesh, Camera, Light.
Transformموقعیت، چرخش و مقیاس در فضای محلی برای یک گره. نتیجه در فضای جهانی از این خوانده می‌شود global_transform.

گام به گام: ساخت گراف صحنه به‌صورت برنامه‌نویسی

گام ۱: ایجاد یک صحنه

یک جدید Scene همیشه با یک خالی شروع می‌شود root_node:

from aspose.threed import Scene

scene = Scene()
print(scene.root_node.name)   # "" (empty string — root node has no name by default)
print(len(scene.root_node.child_nodes))  # 0

Scene نقطه ورودی برای همه چیز است: بارگذاری فایل‌ها، ذخیره‌سازی فایل‌ها، ایجاد کلیپ‌های انیمیشن، و دسترسی به درخت گره‌ها.


گام ۲: ایجاد گره‌های فرزند

از create_child_node(name) برای افزودن گره‌های نام‌گذاری‌شده به درخت استفاده کنید:

from aspose.threed import Scene

scene = Scene()

parent = scene.root_node.create_child_node("parent")
child  = parent.create_child_node("child")

print(parent.name)                          # "parent"
print(child.parent_node.name)               # "parent"
print(len(scene.root_node.child_nodes))     # 1

به‌جای آن، یک شیء مستقل ایجاد کنید Node و آن را به‌صورت صریح پیوست کنید:

from aspose.threed import Scene, Node

scene = Scene()
node = Node("standalone")
scene.root_node.add_child_node(node)

هر دو روش همان نتیجه را تولید می‌کنند. create_child_node برای ساخت درون‌خطی، مختصرتر است.


گام ۳: ایجاد یک موجودیت مش و اتصال آن

یک Mesh داده‌های راس را ذخیره می‌کند (control_points) و توپولوژی سطح (polygons). یک مورد ایجاد کنید، هندسه را اضافه کنید، سپس آن را به یک گره پیوست کنید:

from aspose.threed import Scene, Node
from aspose.threed.entities import Mesh
from aspose.threed.utilities import Vector3, Vector4

scene = Scene()
parent = scene.root_node.create_child_node("parent")
child  = parent.create_child_node("child")

##Create a quad mesh (four vertices, one polygon)
# Note: control_points returns a copy of the internal list; append to
# _control_points directly to actually add vertices. This is a known
# library limitation — a public add_control_point() API is not yet available.
mesh = Mesh("cube")
mesh._control_points.append(Vector4(0, 0, 0, 1))
mesh._control_points.append(Vector4(1, 0, 0, 1))
mesh._control_points.append(Vector4(1, 1, 0, 1))
mesh._control_points.append(Vector4(0, 1, 0, 1))
mesh.create_polygon(0, 1, 2, 3)

child.add_entity(mesh)

print(f"Mesh name:      {mesh.name}")
print(f"Vertex count:   {len(mesh.control_points)}")
print(f"Polygon count:  {mesh.polygon_count}")

Vector4(x, y, z, w) یک مختصات همگن را نشان می‌دهد. از w=1 برای موقعیت‌های نقطه‌ای معمولی.

create_polygon(*indices) شاخص‌های راس را می‌پذیرد و یک وجه را در فهرست چندضلعی‌ها ثبت می‌کند. برای یک مثلث سه شاخص، برای یک چهارضلعی چهار شاخص بدهید.


مرحله ۴: تنظیم تبدیلات گره

هر گره دارای یک Transform که موقعیت، جهت‌گیری و اندازهٔ آن را در فضای محلی کنترل می‌کند:

from aspose.threed.utilities import Vector3, Quaternion

##Translate the node 2 units along the X axis
child.transform.translation = Vector3(2.0, 0.0, 0.0)

##Scale the node to half its natural size
child.transform.scaling = Vector3(0.5, 0.5, 0.5)

##Rotate 45 degrees around the Y axis using Euler angles
child.transform.euler_angles = Vector3(0.0, 45.0, 0.0)

تبدیل‌ها تجمعی هستند: موقعیت فضای جهانی یک گره فرزند ترکیبی از تبدیل خود آن با تمام تبدیل‌های اجدادی است. نتیجهٔ ارزیابی‌شدهٔ فضای جهانی را از node.global_transform (غیرقابل تغییر، فقط‑خواندنی).


مرحله ۵: پیمایش بازگشتی گراف صحنه

کل درخت را با بازگشت از طریق node.child_nodes:

def traverse(node, depth=0):
    indent = "  " * depth
    entity_type = type(node.entity).__name__ if node.entity else "None"
    print(f"{indent}{node.name} [{entity_type}]")
    for child in node.child_nodes:
        traverse(child, depth + 1)

traverse(scene.root_node)

خروجی نمونه برای صحنه ساخته‌شده در بالا (نام گره ریشه یک رشته خالی است):

 [None]
  parent [None]
    child [Mesh]

برای صحنه‌هایی که در هر گره چندین موجودیت دارند، تکرار کنید node.entities به‌جای node.entity:

def traverse_full(node, depth=0):
    indent = "  " * depth
    entity_names = [type(e).__name__ for e in node.entities] or ["None"]
    print(f"{indent}{node.name} [{', '.join(entity_names)}]")
    for child in node.child_nodes:
        traverse_full(child, depth + 1)

traverse_full(scene.root_node)

مرحله ۶: ذخیره صحنه

یک مسیر فایل به scene.save(). قالب از پسوند فایل استخراج می‌شود:

scene.save("scene.gltf")   # JSON glTF 2.0
scene.save("scene.glb")    # Binary GLB container
scene.save("scene.obj")    # Wavefront OBJ
scene.save("scene.stl")    # STL

برای گزینه‌های مخصوص قالب، یک شیء گزینه‌های ذخیره را به عنوان آرگومان دوم بدهید:

from aspose.threed.formats import GltfSaveOptions

opts = GltfSaveOptions()
scene.save("scene.gltf", opts)

نکات و بهترین شیوه‌ها

  • به هر گره نام بدهید. به گره‌ها نام‌های معنادار دادن، اشکال‌زدایی عبورها را بسیار آسان‌تر می‌کند و اطمینان می‌دهد که نام‌ها در فایل صادر شده حفظ می‌شوند.
  • یک مش برای هر گره. نگه داشتن موجودیت‌ها به صورت 1:1 با گره‌ها، تبدیل‌ها و پرس‌و‌جوهای برخورد را ساده می‌کند.
  • استفاده کنید create_child_node به‌جای اتصال دستی. به‌طور خودکار مرجع والد را تنظیم می‌کند و خطا‌پذیری کمتری دارد.
  • خواندن global_transform پس از ساخت سلسله‌مراتب. نتیجه در فضای جهان تنها زمانی پایدار می‌شود که تمام تبدیل‌های اجداد تنظیم شوند.
  • در طول پیمایش، درخت را تغییر ندهید. افزودن یا حذف گره‌های فرزند هنگام تکرار child_nodes منجر به رفتار غیرقابل پیش‌بینی می‌شود. ابتدا گره‌ها را جمع‌آوری کنید، سپس تغییر دهید.
  • نقاط کنترل استفاده می‌کنند Vector4,، نه Vector3. همیشه پاس کنید w=1 برای موقعیت‌های رأس عادی؛; w=0 یک بردار جهت را نشان می‌دهد (نه یک نقطه).
  • mesh.control_points یک نسخه باز می‌گرداند. این control_points ویژگی مقدار را برمی‌گرداند list(self._control_points) — افزودن به لیست بازگردانده‌شده مش را تغییر نمی‌دهد. همیشه به mesh._control_points مستقیم هنگام ساخت هندسه به‌صورت برنامه‌نویسی. این یک محدودیت شناخته‌شده کتابخانه است؛ هنوز API عمومی برای تغییر وجود ندارد.

مشکلات رایج

مسئلهراه‌حل
AttributeError: 'NoneType' object has no attribute 'polygons'با Guard if node.entity is not None قبل از دسترسی به ویژگی‌های موجودیت. گره‌ای بدون موجودیت‌ها entity = None.
مش در مبدأ ظاهر می‌شود علیرغم تنظیم translationtransform.translation یک جابجایی محلی اعمال می‌کند. اگر گره والد خودش تبدیل غیرهویت داشته باشد، موقعیت جهانی ممکن است متفاوت باشد. بررسی کنید global_transform.
گره‌های فرزند پس از scene.save() / بارگذاری‌مجددبرخی فرمت‌ها (OBJ) ساختار سلسله‌مراتبی را صاف می‌کنند. برای حفظ درخت گره کامل از glTF یا COLLADA استفاده کنید.
polygon_count بعد از 0 است mesh.create_polygon(...)اطمینان حاصل کنید که شاخص‌های راس به create_polygon در محدوده (0 به len(control_points) - 1).
Node.get_child(name) باز می‌گرداند Noneنام به حروف بزرگ/کوچک حساس است. رشته نام دقیق استفاده شده در زمان ایجاد را تأیید کنید.
پیمایش گره‌ها را به ترتیب غیرمنتظره بازدید می‌کندchild_nodes فرزندان را به ترتیب درج (the order add_child_node / create_child_node نامیده شد).
 فارسی