Szerző: raville (ragdavill kukac yahoo.fr)
Utolsó módosítás dátuma: 2006/08/09 03:00
Python&Blender
Titkos üzenet, és amit tudunk róla
Ddd Max üzenetet küldött neked, amiért Blendert használsz. Üzenetével csak egy baj van, hogy valamilyen fura oknál foga elkódolta. Ránk vár a feladat, hogy kibontsuk a levelet (ami egy blend fájl) és megfejtsük tartalmát...
Az üzenetben egy scene (szín) van, és rengeteg hasonló objektum. Néhány közülük valószínűleg az üzenet darabjainak hordozója, a nagy többség viszont csak szemét. A szakértők azt állítják, hogy ki lehet szűrni azokat az objektumokat, amelyekre az üzenet megfejtéshez szükség van. Néhány test mintha el lenne forgatva, meg sorszámozva is vannak...
1. Így néz ki az üzenet
Jó ronda, mi? Amíg a kutatók a további információkat és a köztük lévő kapcsolatokat keresik, nekünk az a feladatunk, hogy felkészüljünk a legrosszabbra és megtanuljuk, hogyan lehet Blender szkriptet írni, mert bizonyára szükségünk lesz erre a tudásunkra a probléma megoldásához.
Először nézzünk át egy-két dolgot, hogy python ismereteink elegendőek legyenek a feladathoz.
Szekvencia típusok: indexelhetőség, szeletelés
A szekvencia alapú típusok (lista, szöveg, tuple,..) rendelkeznek néhány hasznos funkcióval (valójában ettől lesznek szekvencia alapúak..), pl. az elemek indexelhetőségével. Ezt talán már említettem korábban, de többet érdemel annál, minthogy csak lábjegyzetben hivatkozzam rá.
>>> l=["Már","megint","itt","van","a","szerelem"] >>> print l[0] #0., azaz legelso elem kiiratása Már >>> print l[len(l)-1] #utolso elem kiiratása szerelem >>> l[2]="hol" #2. elem csereje >>> l[5]+="?" #5. elem csereje (uj szoveg kepzesevel) >>> mondat=''.join(l) #a lista reszeit ertlemes szovegge rakjuk ossze, a join a szoveg tipus metodusa >>> print mondat Már megint hol van a szerelem?
1. példa
Tehát az egyes elemeket direkten is elérhetjük, ha ismerjük a pontos pozíciójukat a listán belül. Figyeljünk oda, hogy az legelső elem listabeli pozíciója a 0, és nem az 1. A lista mutable típus, ezért az indexelve hivatkozott elemeken változtathatunk is. Ez szövegek (és más immutable, szekvencia típusok) esetén nem működik.
>>> s="Működni fog?" >>> print s[11] ? >>> s[11]="!" #kerdojel csereje Traceback (most recent call last): File "", line 1, in ? TypeError: object doesn't support item assignment
2. példa
Hiába erőlködünk, ez nem fog menni.
A másik hasznos funkció, a szelet-képzés (slicing) arra jó, hogy kijelölve az adatsorozat egy részét, csak azon végezzünk el valamilyen műveletet.
>>> l=['a','b','c','d','e'] >>> print s[:1] #az elejetől az 1 indexu elemig ['a','b'] >>> print s[2:] #a 2 indexu elemtol a vegeig ['c','d','e'] >>> a,b = 2,3 #a=2 és b=3 >>> print s[a:b] #a 2 indexu elemtol a 3 indexuig ['c','d'] >>> print s[-2:] #az utolso ket elem ['d','e']
3. példa
A kettőspont elé a részlista kezdő, mögé pedig utolsó elemének indexét kell írni. A negatív értékek hátulról számítanak (de a 0 ugyanaz, mint a -0). Néha igen kényelmesé válik szeletek használatával a programozás.
És még egy trükk, miképpen tudunk egyszerűen listákat másolni.
>>> l1=['a','b'] >>> l2=l1[:] #ez valodi masolas, nem pedig referencia-atadas.. (lasd ertekadas fejezet) >>> l2.append('c') >>> print l1, l2 #l2 más tartalmaz, mint l1 ['a','b'] ['a','b','c']
4. példa
Lista típus: metódusok
Valószínűleg már érezzük, listák nélkül nem élhetünk meg python környezetben. Fussunk gyorsan végig a típushoz tartozó metódusokon. Némelyiket már ismerjük:
lista.append(elem): a megadott objektumot a lista végére rakja
lista.count(elem): visszatérési értéke az elem előfordulásainak száma a listában
lista.extend(seqtipus): elemek listához való hozzáfűzése bármely más szekvencia (lista,tuple..) típusból
lista.index(elem): a visszatérési érték a megadott objektum első előfordulásának indexe, azaz pozíciója a listában
lista.insert(index, elem): az elem megadott index pozíció elé való beillesztése
lista.pop(n): az n. elem kivétele, ha nem adunk meg paramétert, akkor az utolsót veszi ki
lista.remove(elem): elem eltávolítása a listából (csak az első előfordulását törli)
lista.reverese(): lista megfordítása, így lesznek az elsőkből utolsók
lista.sort(): lista rendezése, legkisebbek előre, nagyok hátra
Nem sokára használnunk is fogjuk a legfontosabbakat.
tuple adattípus
A tuple, habár immutable típus, sokban hasonlít a listákhoz. Bármit belerakhatunk, elemein végiglépdelhetünk, lekérdezhetjük őket indexük alapján, és szeletelhetünk is kedvünkre. Ellenben, ha egyszer létrehoztunk egy tuple-t, többé nem változtathatunk az elemszámán, és a bele helyezett objektumokat sem cserélhetjük le! E megszorítások jótékony hatása, hogy az adatstruktúra erőforráskímélőbb és gyorsabban működik, mint a lista típus.
Gömbölyű zárójelek jelzik, ha ilyen típussal van dolgunk. Néhány példa:
>>> a=(1,3,"Halál a májra!") >>> print a (1,3,"Halál a májra!") >>> print type(a)>>> print a[1] 3 >>> a[1]=4.567 #probaljunk meg egy elemet lecserelni File "", line 1, in ? TypeError: object doesn't support item assignment
5. példa
Az utolsó próbálkozás csúfos kudarcot vallott. Szerencsére, ha már magán a tuple-n nem is változtathatunk, az elemein viszont igen, de csak akkor, ha azok mutable típusúak:
>>> a=([1,2],[3,4]) #a lista mutable >>> a[0].append("Coco Jumbo, ajajajj..") #az elso elem modositasa >>> print a ([1,2,"Coco Jumbo, ajajajj.."],[3,4])
6. példa
Továbbá, rendelkezik az packing/unpacking (csomagolás/kibontás) képességgel is.
>>> b=1, 3, "Paris" #packing, egy tuple definialasa, zarojelek nelkul >>> print type(b)>>> x,y,z=b #unpacking >>> print z,x,y #a bal oldali valtozokba kerult a három tuple-beli ertek Paris 1 3
7. példa
Listák esetén is működik a kibontás, próbáld ki. A csomagolás viszont mindig tuple típust eredményez. Bizonyos feladatokhoz csak tuple-kat használhatunk (pl. szövegek formázása, lásd később).
#min/max függvények
A min beépített függvény a paraméterei között megadott értékek közül a legkisebbet adja vissza. Ez számoknál egyértelmű, viszont más adattípusokra is működik.
>>> print min(1,2,3,4,5) 1 >>> print min("a","b","c","d") #szovegek esetén is ertelmezheto a >>> print min("a","A") #a nagy betuk elorebb vannak A >>> print min("ab","aB") #tobb betut tartalmazó szovegek eseten... aB >>> print min("aba","aBw") #az elso kulonbozo betu számit... aBw
8. példa
Szövegek, listák, tuple-k esetén is használhatjuk a függvényt. Ilyenkor a legkisebb elem értékével tér vissza.
>>> print min( "bcdefga" ) #szoveg a >>> print min( [1,2,3,4,5] ) #lista 1 >>> print min( ("a","b","c","d") ) #tuple a >>> print min( [["a","A"], ["a","B"]] ) #egy listaban ket lista ['a', 'A']
9. példa
Mi van akkor, ha különböző típusokat adunk meg, vajon melyik lesz kisebb? Vizsgáljuk meg ezt a kérdést egy rövid kód segítségével..
1. def mi_a_sorrend(a): #lista a bemeno parameter 2. while a: #amig van elem a-ban 3. b=min(a) 4. print b 5. a.remove(b) #toroljuk a listabol b-t 6. #fv vege 7. 8. mi_a_sorrend( [1 , "a" ,"A" , 0.2 , 1.2, None, ["majom", 234, [] ] , (0.0, 0.11)] )
10. példa
A függvény egy ciklust tartalmaz, amely a paramétereként megadott listából kiválasztja a legkisebb elemet, kiírja, majd törli is belőle. Előbb-utóbb elfogynak az elemek, és a ciklus leáll, hiszen az üres lista False értékű, így a while feltétele hamissá válik.
Az eredmény:
None 0.2 1 1.2 ['majom', 234, []] A a (0.0, 0.11)
Hmm, nehéz lett volna kitalálnunk magunktól, azt hiszem. =]
A max hasonlóan működik, csak nem a legkisebb, hanem a legnagyobb értéket választja ki.
Blender előkészítése
Eljött az idő, vegyük végre birtokba a felületet, amit használni fogunk. Úgy kell indítanunk a Blendert, hogy később láthassuk az üzeneteket, amelyeket az alapértelmezett kimenetre küld. Indításnál általában egy konzol ablak is nyílik a főablak mellett, erre állandóan szükségünk lesz (ha virtuális terminálból indítod, akkor oda írja a dolgokat).
Megjegyzés: Létezik egy fejlesztés, amely megpróbálja a Blenderen belül megjeleníteni a kimenetet. Én nem próbáltam, de állítólag még vannak vele problémák. Remélhetőleg hamar elkészül, és használhatjuk, mert szükség lenne rá.
Ha 2.40-es, vagy annál frissebb Blender verziót használsz az alapértelmezett screen beállításokkal, akkor a leggyorsabb módja annak, hogy egy szövegszerkesztő ablak elé kerülj, hogy beállítod az utolsó munkaképernyőt (a screen helyett ezt a szót fogom használni). A ++ billentyű kombináció háromszori lenyomása egyből odarepít minket (kivéve, ha egy gonosz ablakkezelő ezt megakadályozza), de a User Preferences ablak fejlécén (alapesetben ez a Blender legfelső sávja) is kiválaszthatjuk a kívánt munkafelületet az első legördülő listából.
2. Screen váltó legördűlő doboz
Ha minden kötél szakad, felezzük meg a képernyőt, és nyissunk egy új Text Editor ablakot.
A szövegszerkesztő ablak elég egyszerű, de a legfontosabb képességekkel (talán) rendelkezik. A láblécén lévő ikonokkal ki-be kapcsolhatjuk a szintaxis kiemelést és a sorok számozását.
3. Szintaxis kiemelés és sorok számozása gombok
Szerintem kapcsoljuk be mindkettőt, ugyanis hasznos dolgok.
Teszteljük le a rendszert! Írassunk ki valamit, legyen egy egyszerű "Hello Blender".
1. print "\nHello Blender"
11. példa
Futtassuk az egy soros szkriptet, azaz nyomjuk meg az + gyorsbillentyűt. Ez csak akkor működik, ha a éppen a szerkesztőablak áll fókuszban (az egér felette van)! Egyébként az ablak File menüjében találjuk a Run Python script parancsot.
Keressük meg, hova írta ki a program a szöveget. Ha megtaláltuk, akkor tovább is léphetünk.
Felfedező körút, az Object almodul
Minden Blender szkript általában a Blender python moduljainak betöltésével kezdődik. Ezek teszik lehetővé, hogy szkriptjeink kommunikálni tudjanak a programmal. A fő modult (valójában ez egy package, de mindegy) Blender-nek hívják. Ezt mindig be kell töltenünk, ezen keresztül érhetjük az almodulokat. Almodulból rengeteg van, ez az elején kicsit rémisztő lehet.
1. import Blender #Blender package betoltese, csak Blenderen belul erhető el! 2. 3. print "\n",dir(Blender)
12. példa
Eredményként a Blender package azonosítóit, az almodulok listáját kapjuk, ha futtatjuk a kódot. A Blender python API referenciában (v2.42) minden modul leírását megtaláljuk. Sokat kell majd forgatni a lapjait, de minden benne van, aminek egy referenciában benne kell lennie. Ezen kívül a meglévő szkriptek olvasgatása is elég hasznos a tanulás szempontjából, rengeteget találhattok az interneten (is).
Egyelőre csak az Object almodult (ref) vegyük szemügyre. Az Object-en keresztül a térben lévő objektumokhoz nyerhetünk hozzáférést. Kérdezzük le, milyen objektumok ácsorognak a virtuális térben (bármelyik színen)!
1. import Blender 2. from Blender import Object #az almodul nevterenek beemelese 3. 4. objektumok=Object.Get() 5. print objektumok
13. példa
Természetesen, a szkript csak akkor fog kiírni valamit, ha van objektum valamelyik színen! Nálam a következő eredménye lett a futtatásnak:
[[Object "Camera"], [Object "Cube"], [Object "Lamp"]]
Az Object almodul Get (ref) függvénye listázza a színen lévő objektumokat. A kimeneten láthatjuk, hogy a Blender objektumok is szögletes zárójelben jelennek meg. A fenti eredmény tehát egy objektumokat tartalmazó listát, nem pedig három, listába ágyazott listát jelent!
A második sorban látható paranccsal lehet egy almodult betölteni egy package-ből. Ha egy sima python modulból akarunk egy azonosítót átemelni a globális névtérbe, akkor is ezt a szintaxist kell használnunk. Ez legtöbbször csak arra jó, hogy megkönnyítsük a dolgunkat, és ne kelljen annyit írni. Példák:
1. import Blender 2. print Blender.Object.Get() #igy tul hosszan kell ra hivatkozni... 3. 4. from Blender import Object #az Object azonosito (jelen esetben almodul) nevterbe emelese 5. print Object.Get() #mar ez is mukodik, az Object azonosito ismert 6. 7. from math import sqrt #a gyokvonás fuggvény beemelese 8. print sqrt(4) #mar nem kell megjelolnunk, hogy a math nevterben van 9. 10. from math import * #minden azonositot beemel a math-bol 11. print pow(3,2) #3 masodik hatvanya = 9, már a math-ban levo pow is ismert azonosito
14. példa
Object osztály
Korábban már pedzegettem, hogy minden python objektumhoz tartozik egy osztály. Minden osztály egy adattípust határoz meg. Az osztály definíciója azt írja le, hogy az adott adattípus mire képes, milyen adatmezői, metódusai, azaz attribútumai vannak. E definíció alapján jönnek létre az objektumok, amelyek végül az tényleges adatokat reprezentálják a memóriában.
Például, egy egész számot reprezentáló objektummal mindig ugyanazokat a műveleteket végezhetjük el, bármi is legyen konkrétan az általa képviselt szám (jó, nullával osztani tudjuk, hogy nem lehet, de megpróbálni azért igen =]). Ugyanez vonatkozik a blenderen belüli objektumokra is. Például minden 3D alakzat rendelkezik pozícióval, elforgatható a térben és anyagtulajdonság kapcsolható hozzá. Ezen képességek és funkciók leírását mind az őket meghatározó osztályok tartalmazzák.
A térbe kirakható objektumok mind egy közös osztály reprezentánsai, az Object osztályé (ref). Ennek definícióját is az Object modul tartalmazza (igen, kicsit sok az Object, de majd megszokod). A referenciában megtalálod az összes metódust és adatmezőt, ami hozzá tartozik, szépen sorban. Kicsit hosszú a lista, de előbb-utóbb mindenki ráérez, hogy miképpen keressen benne, csak egy kis gyakorlat és türelem kell hozzá.
4. Object osztály referencia részlet
A fenti képernyőképen az Object osztály dokumentációjának egy részlete látható. Olvassuk el, mire jó a loc adatmező! Állítólag az objektum pozícióját tartalmazza (vektor formájában). Egy teszt nem árt, hátha félreértünk valamit (fránya idegen nyelvek).. írassuk ki ezt az értéket minden létező objektum esetén!
1. import Blender 2. from Blender import Object 3. 4. print "\n" 5. for ob in Object.Get(): 6. print "Name: ", ob.name ,"\t Pos: %.2f %.2f %.2f" % ob.loc #nev es pozicio kiiratasa 7. #cikl vege
15. példa
Name: Camera Pos: 7.48 -6.51 5.34 Name: Cube Pos: 0.00 0.00 0.00 Name: Lamp Pos: 4.08 1.01 5.90
Könnyen ellenőrizheted az adatokat a Transform Properties panelen, de azt hiszem, nincs rá szükség ;)
Csupán a 6. sor érdemel némi magyarázatot a szkript forráskódjában. Az ob.name attribútum az objektum nevét tartalmazza, a \t pedig egy egyszerű tabulátor jel, a kimenet formázása kedvéért (így a Pos: feliratok egy oszlopba kerülnek). Amit utána láttok, az szintén a formázás kedvéért van.
Egy térbeli pozíció három koordinátából áll, az X/Y/Z komponensekből. Ezeket általában egybe kezelik, vektorként. Az ob.loc egy tuple típusú érték, benne található a három valós (float típusú) szám, amely az objektum pozícióját leírja. A futás során az interpreter behelyettesíti a %.2f formázó kódok helyére ezt a három értékét.
Formázó kódokkal befolyásolhatjuk az adatok szöveges típusra való konvertálását. Jelen esetben azt adtuk meg, hogy csak két tizedes jegyig írja ki a valós számokat (mivel egy sor túl hosszú lenne, nem férne el egy sorban). A konvertálandó értékeket a szöveg végére kell rakni, egy százalékjel mögé. Több adat esetén gömbölyű zárójelet kell használnunk*, amelyben az értékek vesszővel vannak elválasztva (azaz egy tuple-t). Az interpreter a megadott sorrendben helyettesíti be ezeket az értékeket.
* Vagy kapcsos zárójeleket, leképzés esetén. Egyszer biztos lesz róla szó...
>>> s="%f + %f = %.3f" % (3.14,3.15,6.29) >>> print s '3.140000 + 3.150000 = 6.290'
16. példa
Jegyezd meg, hogy a formázásnál nem használhatunk tuple helyett listát (sajnos)!
Ha majd igény lesz rá, akkor bővítjük az ismereteinket a formázott szövegek témakörében is, ez most csak egy rövid kitekintés volt.
Térjünk vissza a blenderes példára, konkrétan az ob.loc-ra. A loc tuple minősége természetesen nem azt jelenti, hogy egy objektum pozícióját nem tudjuk megváltoztatni. A Blender útjai elég kacskaringósak, és bizonyára sok redundancia előfordul bennük. A setLocation metódus és a LocX/LocY/LocZ attribútumok mind használhatóak pozíció módosítása céljából, viszont lekérdezni gyorsabb a loc-ot.
Függvények, attribútumok az Object modulban és osztályban
Van még egy rakás egyéb attribútum is a loc-on kívül. Ahogy szükséged lesz rájuk, úgy fogod őket megismerni. Bizonyára a legtöbbel soha nem fogsz találkozni, ezért felesleges külön végigvenni őket. A következő felsorolásban bemutatok néhányat, mind a modulból, mind pedig az osztályból választottam egy-két metódust.
Object modul:
Get(): meglévő objektumok lekérdezése
GetSelected(): kijelölt objektumok lekérdezése
New(): új objektum létrehozása
Object osztály:
getLocation() / setLocation(): pozíció lekérdezése, módosítása
getEuler() / setEuler(): elforgatottság lekérdezése, módosítása (hogy miért Euler, arra majd visszatérünk)
getSize() / setSize(): méret lekérdezése, módosítása
getName() / setName(): objektum nevének lekérdezése, módosítása
isSeleted() / select(): objektum kijelöltségének lekérdezése, módosítása
getType(): objektumhoz kapcsolt adatblokk (blenderbeli) típusának lekérdezése (Mesh, Lamp, stb..)
Megvan a titok kulcsa
Miközben ismereteinket bővítettük, a laborban megfejtették, hogyan lehet kikódolni az üzenetet! Az üzenetet tartalmazó fájlt innen töltheted le.
Kiderült, hogy az objektumok nevei tartalmazzák az információt, az üzenet szövegét. Minden objektumban pontosan egy betűt találunk. A nevek valahogy így néznek ki: bob_szám_betű. De az is kiderült, hogy nem mindegyik objektum tartalmaz valódi üzenetrészletet, hanem csak azok, amelyek el lettek forgatva!
Önálló feladat (1)
A feladat tehát adott, fejtsük meg az üzenetet. Az objektumok neveit (nev=obj.name) egy (for) ciklusban kérdezzük le, daraboljuk fel (nev.split("_")), és közben a testek elforgatottságát is ellenőrizni tudjuk. Ha elforgatott testtel állunk szemben, akkor megjegyezzük a betűt, amit a nevében hordoz. Úgy néz ki, hogy az objektumok sorrendje pont jó, azaz ha egyszerűen végigfutunk rajtuk, akkor a jó sorrendben kapjuk meg a betűket.
A getEuler metódus (ref) egy vektort ad vissza. A vektor három komponense az objektum elfotgatottságának mértékét mutatja a három koordinátatengely körül, szögekben. Ha ezek valamelyike nem zérus, az objektum elforgatott állapotban van.
Önálló (és nehezebb) feladat (2)
Írd meg úgy az szkriptet, hogy nem hagyatkozol az objektumok eredendően jó sorrendjére, hanem a nevekben található számok alapján rakod össze az üzenetet! Minél kisebb egy szám, annál előrébb van a hozzá tartozó betű az üzenetben.
Egy megoldási vázlat:
Az első ciklusban külön listába gyűjtjük az üzenet betűit és a hozzá tartozó számokat (a eredeti, rossz sorrendet mindkét listában megtartva; ha az append metódust használjuk, nem lesz gond). Mind a két lista ugyanolyan hosszú lesz. A második ciklus minden lépésében kiválasztjuk a legkisebb számot a sorszámokat tartalmazó listából (elem=min(lista))), majd megkeressük az így kiválasztott elem indexét (index=lista.index(elem)), és a másik lista megfelelő pozícióján csücsülő betűt hozzárakjuk a változóhoz, amelyikben az üzenetet építjük. Végül, a ciklus utolsó műveleteként, mind a két listából eltávolítjuk az adott indexen lévő elemet (lista.pop(index)).
A listák elemei előbb-utóbb elfogynak, és a második ciklus végeztével összeáll az üzenet.
Megjegyzés: Később, ha már többet tudunk, egyszerűbb lesz ennek a feladatnak a megoldása is. Viszont, ha van csak listákat használó, de egyszerűbb ;) megoldásod, ne fogd vissza magad, és oszd meg velünk... biztos, hogy van.
Happy scripting!
Ha kérdésed van, gyere a fórumba. Ha úgy érzed, hogy valamelyik feladatot nem megy (esetleg nem találod a módját, miképpen kezdj hozzá), akkor mindenképpen gyere, és segítünk, ami egyenlő azzal, hogy bebizonyítjuk, magadtól is meg tudod csinálni. ;)