"Hordó modellezése és Textúrázása"
Részletek a hírekben!
 
   
 
 
Naprakész hírek...                                         240 fórumozó...                                         Számos magyar oktatóanyag...                                         Videóanyagok magyarul is...                                         Letölthető fájlok...                                         Textúrák...                                         Összegyűjtött linkek...
 
  Rólunk
Hírek
Fórum
Kihívás
Kapcsolatok
Galéria
Aktuális, 2015
2014, 2013
2012, 2011
2010, 2009
Leírások
Kezdő leckék
Haladó leckék
Egyéb leírás
Billentyűk
Videóanyagok
Kezdő videók
Haladó videók
Egyéb videók
Tippek, trükkök
Letöltés
BLEND fájlok
Textúrák
Linkajánló
Archivum
Projektek
BlendRace SokoTruck
Flipper MB game
 
 

Blender leírások és oktató anyagok

 

Blender szkriptelés alapok


Gondoltam írok egy rövid anyagot a Blender szkripteléséről. Ezt csupán bevezetőnek szánom, senki nem lesz tőle programozó. De úgy tűnik, hogy nagy az érdeklődés és a homály is ezen a téren. Természetesen lehetetlen nagy részletességgel leírni mindent, mert az hatalmas anyag lenne. Inkább csak az irányt segítek meghetározni.

Kikerülhetetlen, hogy pár szót szóljak a python nyelvről. Rendkívül elterjedt, és rengeteg helyen utána olvashatsz. Olvass is (http://docs.python.org/tutorial/index.html)! A Linux rendszerbeállító programjainak nagy része ma már ezen a nyelven íródik. Egy rendkívül rugalmas, magas szintű, szkript nyelvről van szó.
A szintaktikája vegyes, szinte minden nyelvből találunk benne elemeket. Kezel objektumokat, öröklődést és alapvetően mindent, amit egy mai nyelvnek tudni kell.
Ok, hagyjuk a rizsát nézzük az alapokat...
(A programozóktól itt kérek elnézést, de úgy fogok írni, hogy a "laikusok" is értsék, akkor is, ha ez némi pontatlanságot
jelent helyenként. A szakmai kérdéseket írjátok a fórumba, és ott úgy fogok válaszolni.)

Változók


A változók neve egy szöveg, ami nem lehet azonos a nyelv utasításaival. A Python a változóknak azt a típust adja, amit bele teszel. A szövegeket idézőjelek, vagy aposztrófok közé tesszük. A számokat csak kiírjuk, a nem egész számokba mindenképp teszünk tizedes pontot.

alma = "Ez egy szöveg."
# Az alma változó a
dio = 2564
# A dio változóban egy egész szám van
korte = 100.0
# A korte változóban egy tizedestörtként kezelt szám van

(A kettőskeresztutáni szövegek megjegyzések a nyelvben.)
Mi a külömbség a 10 és a 10.0 között? Sok. 10/3, az 3, mert egész számokkal egész-osztást végez a nyelv, de 10.0/3, az már 3.33333333333!
És ez nagy különbség...

A változókat át lehet alakítani, néhány függvénnyel.

a = 5          # a értéke az 5 egész
b = str(a)     # b értéke "5" lett, ami szöveg
c = "12"       # c értéke szöveg
d = int(c)     # d értéke 12 egész
e = float("4") # e értéke 4.0 lett

Használhatunk matematikai műveleteket. (* (szorzás), / (osztás), - (kivonás), + (összeadás), ** (hatványozás), % (egészosztás maradéka))
A print utasítással írhatunk a konzolra.

print (2 ** 3 + 2) / 3

Az eredmény: 3

Ha nem hiszed járj utána... de tényleg. Itt az ideje egy kis programozásnak. Ha Blenderben Python-t akarsz használni, akkor telepítened kell. (Windows-hoz töltsd le a python.org-ról, Linuxhoz megteszi egy "sudo apt-get install python" is.)
Parancssorba írd be, hogy "python"! Ennek hatására el is indul az ételmező ésvárja az utasításokat. Írd be szépen a fenti példákat!
Tudni kell, hogy a python nyelv érzékeny a kis és nagybetűkre, azaz nem mindegy, hogy print vagy Print!

Kicsit lépjünk tovább. A változókba több adatot is tehetsz.

tomb = ["alma", 4, 5, "valami", 2.5, 14]   # Ez egy 6 elemű tömb
teszt = []                                 # Ez egy üres tömb

Az ilyen változók egy konkrét elemére a sorszámával lehet hivatkozni, ami 0-tól kezdődik.

print tomb[3]         # Ez "valami"-t ír ki

Használhatunk olyan tömböket, aminek minden eleme nevet kap (ennek fontos szerepe lesz a Blender programozásában (is)). Az ilyen tömbök elemeire a nevével hivatkozunk.

semmi = {}     # Ez egy üres tömb, de később adhatunk hozzá elemeket a nevükkel
telefon = { "Zoli": 3612345678, "Peti": "0036-30-234-5678", "Jocó": 0 }    # Ez egy 3 elemű tömb


A telefon változó "Zoli" elemének értéke 3612345678 (egész), a "Peti" elemének "0036-30-234-5678" (szöveg). Így hivatkozunk rájuk:

print telefon["Jocó"]      # 0-t ír ki

Ez a változó típus azért fontos, mert a blender az objektumokat, textúrákat, és még sok mást is a nevük alapján ("OB:" mező) ilyen tömbökben tárolja.

Rendben. Nézzük most ezt a Blenderben. Lépj ki az interpreterből az exit() utasítással, és indítsd el a blendert a parancssorból! (Azért innen és nem a menüből, mert a Blenner ide írja a python üzeneteket, amit majd el szeretnénk olvasni.)

Nyiss a Blenderben egy Text Editor keretet, + -nel hozz létre egy üres lapot, kicsit lökd odébb az alap kockát, -nel nyisd meg az objektum tulajdonságokat, és nyiss egy Outliner keretet is, majd a Text Editorba írd be a képen látható szöveget! Ha kész, futtasd az + -vel!

01

Hmm, akkor most mit is csináltunk?
Az import bpy azt mondja a pythonnak, hogy használja a Blender eléréséhez szükséges modulokat.
A print -tel azt mondjuk, hogy írja ki az utána következő változó értékét. A változo pedig: bpy, ez az előbb betöltött modul, a data a Blender adatainak összessége a modulban, az objects, az objektumok az adatok közül, ezek közül is az "Cube" azonosítójú, és ennek kérjük a LocX nevű paraméterét.
Oké, de hova is írta akkor? A konzolra. Ezért indítottuk parancssorból. Húzd félre a Blender ablakot, és ahol elindítottad ott fogod olvasni a kiírásokat is.

Tehát ha a Lamp objektum Y koordinátája kell, akkor így írhatjuk ki: print dpy.data.objects["Lamp"].LocY
Az objektumokat az Outliner keretből kipuskázhatjuk, az alap változókat pedig a Properties ablakból.

A dolog fordítva kezd mérsékelten érdekessé válni... Azaz, ha nem kiolvassuk egy változó értékét, hanem felülírjuk.
Pl.:

dpy.data.objects["Cube"].LocX = 0.0
dpy.data.objects["Cube"].LocY = 0.0
dpy.data.objects["Cube"].LocZ = 0.0

Ha tényleg próbálod, amit olvasol, akkor fel fog tűnni, hogy NEM MENT a kocka az origóba. Nos, de igen. Csakhogy a képernyő nem frissült. Kattints a bal gombbal a képre a kurzor áthelyezéséhez. Ekkor a kurzorral együtt a kocka is frissül, és mostmár az origóban van.
Ez így elég bugyuta automatikusan kellene frissíteni (a játékban frissül is, mert ott folyamatos a frissítés, de most még nem vagyunk a játékban). Megoldható. Ehhez be kell töltenünk mégegy modult: a Blender szerkesztő felületét.

import Blender

Hát ez elég hosszú mindig bepötyögni (bár nem veszélyes), úgyhogy adjunk neki egy másik nevet:

import Bender as B

így már csak B -ként fogunk rá hivatkozni. (Ne felejtsd el, hogy ha itt nagybetűvel írod, később is azzal kell!)
Akkor most hozzuk egy magasságba a lámpánkkal:

import dpy
import Blender as B

dpy.data.objects["Cube"].LocZ = dpy.data.objects["Lamp"].LocZ
B.Redraw()

A Redraw() a Blender modul egy függvénye (később beszélek róluk), itt elég annyit tudni, hogy ez újrarajzolja a szerkesztő kereteinek tartalmát.
Most tegyük fél egységgel magasabbra a lámpát a jelenleginél!

dpy.data.objects["Lamp"].LocZ = dpy.data.objects["Lamp"].LocZ + .5

A .5 az 0.5 csak rövidebben ;)
Ezt rövidebben így is lehet (illik, mert így olvashatóbb) írni:

dpy.data.objects["Lamp"].LocZ += .5

Természetesen használható a -= a csökkentéshez, a *= és /= a szorzás, osztáshoz (és még van sok más is).

Ilyen módon nem csak az objektumok adatait tudjuk beállítani, lekérdezni, hanem a színek, anyagok, textúrák, stb. adatait is. ( lásd: http://www.blender.org/documentation/250PythonDoc/bpy.types.Main.html#bpy.types.Main)

Kísérletezz egy kicsit az objektumok módosítgatásával!

Csináljunk most valami mást...
Gyorsan rajzolok egy puritán órát. Egy cilinder a számlap, két megnyomozított cube a mutató. Egymás fölé rendezem, de mindegyik objektum középpontja (értsd: rózsaszín pötty) a Z tengelyen van rajta (az egyiket "Kismutato"-nak, a másikat "Nagymutato"-nak nevezem el (pl. az "OB:" mezőben)).
Most állítsuk be rajta a pontos időt!

02

(Jelzem, nálam ennyi az idő, és nem délben!)

A time a python modulja, nincs köze a Blenderhez, csak azért kell, hogy megkérdezzem tőle az órátés percet.
Most az + -vel beállítja  a pontos időt a script, de már nincs messze, hogy az óránkat hagyjuk magától járni...

Elágazások, ciklusok

A python nyelvnek van egy egészen sajátos tulajdonsága: az utasítások összefogására nem határoló jeleket (vagy szavakat) használ, hanem a sor kezdetének betolását. Van aki szerint ez jó ötlet, mert a kód jól olvasható, van aki szerint (ezek többnyire programozók) meg nem. De az tény, hogy kellemetlen. Figyeljünk oda, hogy egy sor ugyanannyi szóközzel/tabulátorral van-e betolva, mint a követkető, mert nem biztos, hogy ugyanazok a jelek vannak előtte, még ha egyvonalban lévőnek lászanak is. És ha nem egyeznek, akkor fordítási hibát kapunk, és a program el sem indul (a konzolon ott lesz a hibaüzenet, és benne, hogy melyik sorra panaszkodik).

A programot tehát a sorok szélén kell kezdeni, és a sorok azonos behúrása határoz meg egy program blokkot.
Nézzük az if utasítást! Ezzel feltételhez köthetjük egy kódrész végrehajtását. Az if után kell egy feltétel, ami ha igaz, akkor végrehajtódik az utána azonos mértékben beljebb tolt rész.

if dpy.data.objects["Lamp"].LocZ > dpy.data.objects["Cube"].LocZ:
    dpy.data.objects["Lamp"].LocX = dpy.data.objects["Cube"].LocX + 2.0
    dpy.data.objects["Lamp"].LocY = dpy.data.objects["Cube"].LocY + 2.0
    dpy.data.objects["Lamp"].LocZ = dpy.data.objects["Cube"].LocZ
print "Kész"
B.Redraw()

Ha a lámpa magasabban van a kockánál, akkor egy szintre hozzuk őket, a lámpát egy adott irányban mellé téve.
Ha nincs magasabban, akkor nem mozgatjuk (azaz, ha a feltétel nem teljesül, akkor a 3 betolt sor nem hajtódik végre).
A print és a Redraw sorok mindenképp végrehajtódnak, mert azok nem a feltétel függvéyei, hanem a felételes rész utáni következő utasítások.

Az else utasítással megadhatsz egy másik utasítássort is, ami akkor hajtódik végre, ha a feltétel nem igaz. Azaz a feltétel alapján vagy ez, vagy az a rész hajtódik végre.

if dpy.data.objects["Lamp"].LocZ > dpy.data.objects["Cube"].LocZ:
    dpy.data.objects["Lamp"].LocX = dpy.data.objects["Cube"].LocX + 2.0
    dpy.data.objects["Lamp"].LocY = dpy.data.objects["Cube"].LocY + 2.0
    dpy.data.objects["Lamp"].LocZ = dpy.data.objects["Cube"].LocZ
else:
    print "Nem csináltam semmit, mert a lámpa nincs magasabban!"
print "Kész"
B.Redraw()

Ciklust, azaz egy utasítássor többszöri végrehajtását hasonlóképpen gyárthatunk. A while utasítás utáni betolt sorok addig hajtódnak végre, amíg a while utáni feltérel igaz. Tehát ez is feltételes rész, de nem egyszer hajtódik gégre, hanem mindaddig, amíg a feltétel igaz. (Vigyázz, mert ha mindig igaz lesz a program sosem áll le, kvázi megfagy!)

where dpy.data.objects["Lamp"].LocZ > dpy.data.objects["Cube"].LocZ:
    dpy.data.objects["Lamp"].LocZ -= .2
    B.Redraw()
print "Kész"

Addig hozzuk lejjeb a lámpát, amíg a kockánál magasabban van, és ki is zajzoljuk, ahogy jön lefelé. (Sok értelme nincs...)

Használhatunk olyan ciklusokat, amik pontosan N-szer hajtódnak végre. Ehhez a for utasítást használjuk. A for után meg kell adni egy változot, ami az alatta betolt utasítások minden egyes végrehajtásakor új értéket kap, majd az in szavacska után meg kell adni egy tömböt, amely elemeinek értékét fogja a változó egymás után felvenni.
Nézzünk egy példát, és tisztább lesz:

listam = [ "egy", "kettő", "három" ]
for alma in listam:
    print alma
    print "Következő"

A betolt részt háromszor hajtja végre. Az elsőben az alma értéke "egy", a másodikban "kettő", a harmadikban "három". Így amit kiír, az ez lesz:

egy
Következő
kettő
Következő
három
Következő

Oké, nem mondtam hogy értelme is lesz...
Nézzük akkor egy fokkal hasznosabban (most nem veszek fel külön változót, hanem a tömböt egyből az in után írom):

for elem in [ "Lamp", "Cube" ]:
    dpy.data.objects[elem].LocZ += 2.0
B.Redraw()

Így a lámpát és a kockát is feljebb raktam, sőt, ha további objektum neveket sorolnék fel, azok is feljebb kerülnének.

Ha már nekiálltunk pakolni, ne kispályázzunk, pakoljukónk fel mindent!
Ehhez nem kell az objeltum neveket sem tudnunk, elég azt mondanunk, hogy a ciklus az összes objektum értékét vegye fel, mert a Blenderben az is egy (nevesített) tömb.

for elem in dpy.data.objects:
    elem.LocZ += 2.0
B.Redraw()

Vegyük észre, hogy a behúzott rész nagyot változott. Ennek az az oka, hogy fentebb a neveken mentünk végig, így meg kellett adni, hogy az melyik objektum neve, és annak az értékét változtattuk. Most viszont a for ciklus az objektumokat magukat veszi sorra, és tölti az elem változóba, így már csak azt kell megmondanunk, hogy ennek a változónak melyik property-jét írjuk át.

A range függvény sorszám tömböket generál. Így ha valamit négyszer (vagy százszor) akarsz végrehajtani, akkor nem kell beírni, hogy for x in [0,1,2,3], hanem használhatod a for x in range(4) kifejezést. A hatása ugyanaz lesz.

Most, hogy kiokosodtunk a ciklusokból is érdemes elővennünk az óránkat. Tegyünk rá mégegy mutatót, nevezzük el "Masodperc"-nek. Hozzáadjuk a kódját a meglévőhoz, majd az egészet betesszük egy for ciklusba:

import bpy
import Blender as B
import time
 
for x in range(200):
 ora = int(time.strftime("%H"))   # %H - Hours
 perc = int(time.strftime("%M"))  # %M - Minutes
 mp = int(time.strftime("%S"))    # %S - Seconds
 
 bpy.data.objects["Masodperc"].RotZ = -6.28 * mp/60
 bpy.data.objects["Nagymutato"].RotZ = -6.28 * perc/60
 bpy.data.objects["Kismutato"].RotZ = -6.28 * ora/12
 
 B.Redraw()
 
A ciklus 200-szor fut le, azaz, amíg a Blender 200-szor nem frissíti a képet, addig az óra "járni" fog.
(Ez nem 200 másodperc, hanem tényleg 200 frissítés. Vagyis egy gyors gépen rövidebb, egy lassún hosszabb idő.)
Vigyázz, ne állíts be nagy intervallumot, mert ezt a programot nem lehet szabályosan megszakítani, ki kell várni a lefutását! Én először 1000 ciklust adtam be bízva a gépemben... hiba volt... ez egy elég gagyi cucc.

03

Ha egy kicsit pontosabban szeretnénk tudni, hogy meddig jár az óránk, akkor ne bízzuk a véletlenre. Frissítsük le az állapotot, és várjunk 1 másodpercet a követkető frissítésig. Így jó közelítéssel annyi másodpercig jár az óránk, ahány elemű a tömb a for sorában (a példában a range egy 60 elemű tömböt hoz létre, azaz az óránk egy percig jár).

for x in range(60):
    bpy.data.objects["Masodperc"].RotZ = -6.28 * int(time.strftime("%S"))/60
    bpy.data.objects["Nagymutato"].RotZ = -6.28 * int(time.strftime("%M"))/60
    bpy.data.objects["Kismutato"].RotZ = -6.28 * int(time.strftime("%H"))/12
    B.Redraw()
    time.sleep(1)

Hát... a teljesség nem volt igény, így ezt is kivégeztük.

Függvények

A függvények olyan összetett utasítássorok, amik valamilyen művelelt hajtanak végre, vagy valamilyen értéket adnak vissza (vagy mindkettő :) ).
Ilyen volt a Redraw() függvény is. A függvények a nevük után zárójelben kapják meg a funkció végrehajtásához szükséges adatokat. Ha nincs szükségük adatra, a zárójelet akkor is ki kell tenni.
(A Python 3.0 -tól a print utasítás megszűnik, helyette print() függvény lesz, azaz akkor már print("Kész") lesz a helyes forma.)

A föggvényekről részletesebben a következő részben szeretnékk beszélni, noha már itt is belefutottunk párba.

Te magad is tudsz függvényt definiálni azzal, hogy a szükséges utasításokat, függvényeket összefogod. Ehhez a def utasításta van szükséged.

def emeles_eggyel( nev ):
    dpy.data.objects[ nev ].LocZ += 1.0
    B.Redraw()

Ezek után(!) bármikor haszálhatod az emeles_eggyel("Cube"), vagy emeles_eggyel("Lamp") saját függvényt az objektumok feljebb emelésére.

A függvények leírását itt találod: http://www.blender.org/documentation/250PythonDoc/bpy.ops.html
(Sajnos angol, de ha konkrétan keresel valamit, segítünk a fórumon.)

Hogy ne maradjon elvarratlan szál, csináljuk meg azt az órát rendesen...
Ami fent csináltunk az jópofa, látványos, tanuláshoz kiváló és hülyeség. Ki akarja, hogy az órága egy szerkesztő ablakában zakatoljon, ráadásul meghatározott ideig. Azért csak jobb lenne képernyőkínélőként, nem?

Ehhez a következőket kell elkövetni: Állítsd az összes mutatódat alapállapotba ( majd a RotZ -hez írj 0 -t). Rögzítsd mindegyiknek külön a szögét ( majd Rot). Lépj a 15. képkockára és mindet forgasd el 90 fokot jobbra ( majd a RotZ -hez írj -90 -et), mindnek rögzítsd a szögét. 30. kozkánál 180 fok ( majd a RotZ -hez írj -180 -at), 45. kozkhoz balra 90 fok ( majd a RotZ -hez írj -270 -et), majd alapállapot ( majd a RotZ -hez írj -360 -at). Természetesen minden mutató minden kulcskockáját rögzítsd ( majd Rot). Ha jól csináltad, akkor + -ra az összes mutatónak együtt körbe kell fordunia. Azaz mindnek van egy IPO görbéje, ami 1-től 60-ig a képkockákon az adott percre mutat (van benne némi számítási hiba, mert nem a 0. képkockával kezdtük, de ezzel hadd ne foglalkozzak).

Mostmár csak szépen, az aktuális időnek megfelelően végig kell vezetni a mutatókat ezeken a görbéken, hogy mindíg jó helyre mutassanak. Igen, egyszerűbb lenne, ha a szöget állítanánk, mint feljebb, csakhogy a Game Engine kissé fapados, és ilyet nem tud :o ...

Lássuk akkor:

04

Mindegyik mutatónál átlépünk a PacMan figurára kattintba a GE panelra. Az Add Property-vel hozzáadunk egy változót. A képen az "mp"-t. Hozzáadunk két szenzort. Nem azért, mert kell, hanem mert muszáj. A GE-ben ha egy szenzor nem vált ki eseményt, akkor nem történik semmi. Mindkét szencorunk folyamatosan kivált eseményeket (Always), ami azt jelenti, hogy folyamatosan futtatni fogja a hozzá kötött Controllereket.
Két kontrollert veszünk fel. Az egyik a szkript (válasszuk a Python-t és írjuk a Text Editor ablakban lévő szkipt nevét a Script melleti mezőbe), amit most tanulunk, hogy minden képfrissítésnél fusson le, a másik ismét egy alapvetően felesleges, de kötelező elem, egy AND kapu. Ez akkor aktíválja a hozzá kötött Actuator-t (esetményt), ha a bemenetére kötött összes szenzor aktív. Hát most egyet kötöttünk rá, és az folyamatosan aktív, azaz ő is folamatosan aktíválja a kiváltott eseményt, vagyis csak továbbadja a jelet és kész. Azért nem hagyható ki, mert a szenzorokat (Sensor) nem lehet egyből az eseményekhez (Actuator) kötni, az esetmény meg nem történik meg egy szenzor hívása nélkül.
Mi is ez az esemény, amit szeretnénk kiváltani?
Hozzá adunk egy Actuator-t és IPO-ra állítjuk, ez azt jelenti, hogy az objektum (jelen esetben a másodperc mutató) melyik képkozkának megfelelő szögben álljon. Ki kell választani a Property módot, vagyis vagy a képkocka számát egy property határozza meg. Micsoda véletlen, hogy épp az imént vettünk fel egyet, a "prop:" mezőbe beírjuk, hogy "mp". Ez azt jelenti, hogy az "mp" változónak megfelelő képkozkán áll majd a megjelenítés, de a képkockák meg pont a megfelelő másodpercre mutatnak. Vagyis az "mp" változóba írt számnak megfelelő másodpercre fog mutatni a mutató.

Nincs tehát más dolgunk, minthogy ebbe a változóba (Property-be) betöltsük az aktuális másodpercet.
Ehhez egy rövid python szkriptre van szükség. Íme:

import time        # ez egy python modul az idő lekérdezéséhez
import GameLogic   # Ez a modul a GE eléréséhez kell
 
aktualis_mp = int(time.strftime("%S"))   # Lekérdezzük az aktuális másodperc értéket
cont = GameLogic.getCurrentController()  # Lekérjük az aktuális kontrollert (a másodpercmutatóét)
cont.owner["mp"] = aktualis_mp          # Az "mp" változóba beletesszük a rendszertől kapott másodperc értéket
 
Az utolsó sorban töltjük fel a hozzáadott "mp" property-t értékkel.
Mivel ezt minden képkockánál megtesszük, és minden kockánál ehhez az értékhez igazítjuk az IPO fázist, az óránk járni fog. (Elindítás , leálítás ESC.)

Ezt csináljuk meg minden mutatóra, és kész az óra. Feltöltöttem egy demo blend fájlt hozzá. Lekötelezne, ha valaki egy "rendes" órával is megcsinálná...


[MsV]