io_export_ogreDotScene.py 295 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389
  1. # Copyright (C) 2010 Brett Hartshorn
  2. #
  3. # This library is free software; you can redistribute it and/or
  4. # modify it under the terms of the GNU Lesser General Public
  5. # License as published by the Free Software Foundation; either
  6. # version 2.1 of the License, or (at your option) any later version.
  7. #
  8. # This library is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  11. # Lesser General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU Lesser General Public
  14. # License along with this library; if not, write to the Free Software
  15. # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  16. VERSION = '0.6.0'
  17. '''
  18. CHANGELOG
  19. 0.6.0
  20. * patched to work with 2.66.
  21. 0.5.9
  22. * apply patch from Thomas for Blender 2.6x support
  23. 0.5.8
  24. * Clean all names that will be used as filenames on disk. Adjust all places
  25. that use these names for refs instead of ob.name/ob.data.name. Replaced chars
  26. are \, /, :, *, ?, ", <, >, | and spaces. Tested on work with ogre
  27. material, mesh and skeleton writing/refs inside the files and txml refs.
  28. Shows warning at final report if we had to resort to the renaming so user
  29. can possibly rename the object.
  30. * Added silent auto update checks if blender2ogre was installed using
  31. the .exe installer. This will keep people up to date when new versions are out.
  32. * Fix tracker issue 48: Needs to check if outputting to /tmp or
  33. ~/.wine/drive_c/tmp on Linux. Thanks to vax456 for providing the patch,
  34. added him to contributors. Preview mesh's are now placed under /tmp
  35. on Linux systems if the OgreMeshy executable ends with .exe
  36. * Fix tracker issue 46: add operationtype to <submesh>
  37. * Implement a modal dialog that reports if material names have invalid
  38. characters and cant be saved on disk. This small popup will show until
  39. user presses left or right mouse (anywhere).
  40. * Fix tracker issue 44: XML Attributes not properly escaped in .scene file
  41. * Implemented reading OgreXmlConverter path from windows registry.
  42. The .exe installer will ship with certain tools so we can stop guessing
  43. and making the user install tools separately and setting up paths.
  44. * Fix bug that .mesh files were not generated while doing a .txml export.
  45. This was result of the late 2.63 mods that forgot to update object
  46. facecount before determining if mesh should be exported.
  47. * Fix bug that changed settings in the export dialog were forgotten when you
  48. re-exported without closing blender. Now settings should persist always
  49. from the last export. They are also stored to disk so the same settings
  50. are in use when if you restart Blender.
  51. * Fix bug that once you did a export, the next time the export location was
  52. forgotten. Now on sequential exports, the last export path is remembered in
  53. the export dialog.
  54. * Remove all local:// from asset refs and make them relative in .txml export.
  55. Having relative refs is the best for local preview and importing the txml
  56. to existing scenes.
  57. * Make .material generate what version of this plugins was used to generate
  58. the material file. Will be helpful in production to catch things.
  59. Added pretty printing line endings so the raw .material data is easier to read.
  60. * Improve console logging for the export stages. Run Blender from
  61. cmd prompt to see this information.
  62. * Clean/fix documentation in code for future development
  63. * Add todo to code for future development
  64. * Restructure/move code for easier readability
  65. * Remove extra white spaces and convert tabs to space
  66. 0.5.7
  67. * Update to Blender 2.6.3.
  68. * Fixed xz-y Skeleton rotation (again)
  69. * Added additional Keyframe at the end of each animation to prevent
  70. ogre from interpolating back to the start
  71. * Added option to ignore non-deformable bones
  72. * Added option to export nla-strips independently from each other
  73. TODO
  74. * Remove this section and integrate below with code :)
  75. * Fix terrain collision offset bug
  76. * Add realtime transform (rotation is missing)
  77. * Fix camera rotated -90 ogre-dot-scene
  78. * Set description field for all pyRNA
  79. '''
  80. bl_info = {
  81. "name": "OGRE Exporter (.scene, .mesh, .skeleton) and RealXtend (.txml)",
  82. "author": "Brett, S.Rombauts, F00bar, Waruck, Mind Calamity, Mr.Magne, Jonne Nauha, vax456",
  83. "version": (0, 6, 0),
  84. "blender": (2, 6, 6),
  85. "location": "File > Export...",
  86. "description": "Export to Ogre xml and binary formats",
  87. "wiki_url": "http://code.google.com/p/blender2ogre/w/list",
  88. "tracker_url": "http://code.google.com/p/blender2ogre/issues/list",
  89. "category": "Import-Export"
  90. }
  91. ## Public API
  92. ## Search for "Public API" to find more
  93. UI_CLASSES = []
  94. def UI(cls):
  95. ''' Toggles the Ogre interface panels '''
  96. if cls not in UI_CLASSES:
  97. UI_CLASSES.append(cls)
  98. return cls
  99. def hide_user_interface():
  100. for cls in UI_CLASSES:
  101. bpy.utils.unregister_class( cls )
  102. def uid(ob):
  103. if ob.uid == 0:
  104. high = 0
  105. multires = 0
  106. for o in bpy.data.objects:
  107. if o.uid > high: high = o.uid
  108. if o.use_multires_lod: multires += 1
  109. high += 1 + (multires*10)
  110. if high < 100: high = 100 # start at 100
  111. ob.uid = high
  112. return ob.uid
  113. ## Imports
  114. import os, sys, time, array, ctypes, math
  115. try:
  116. # Inside blender
  117. import bpy, mathutils
  118. from bpy.props import *
  119. except ImportError:
  120. # If we end up here we are outside blender (compile optional C module)
  121. assert __name__ == '__main__'
  122. print('Trying to compile Rpython C-library')
  123. assert sys.version_info.major == 2 # rpython only works from Python2
  124. print('...searching for rpythonic...')
  125. sys.path.append('../rpythonic')
  126. import rpythonic
  127. rpythonic.set_pypy_root( '../pypy' )
  128. import pypy.rpython.lltypesystem.rffi as rffi
  129. from pypy.rlib import streamio
  130. rpy = rpythonic.RPython( 'blender2ogre' )
  131. @rpy.bind(
  132. path=str,
  133. facesAddr=int,
  134. facesSmoothAddr=int,
  135. facesMatAddr=int,
  136. vertsPosAddr=int,
  137. vertsNorAddr=int,
  138. numFaces=int,
  139. numVerts=int,
  140. materialNames=str, # [str] is too tricky to convert py-list to rpy-list
  141. )
  142. def dotmesh( path, facesAddr, facesSmoothAddr, facesMatAddr, vertsPosAddr, vertsNorAddr, numFaces, numVerts, materialNames ):
  143. print('PATH----------------', path)
  144. materials = []
  145. for matname in materialNames.split(';'):
  146. print( 'Material Name: %s' %matname )
  147. materials.append( matname )
  148. file = streamio.open_file_as_stream( path, 'w')
  149. faces = rffi.cast( rffi.UINTP, facesAddr ) # face vertex indices
  150. facesSmooth = rffi.cast( rffi.CCHARP, facesSmoothAddr )
  151. facesMat = rffi.cast( rffi.USHORTP, facesMatAddr )
  152. vertsPos = rffi.cast( rffi.FLOATP, vertsPosAddr )
  153. vertsNor = rffi.cast( rffi.FLOATP, vertsNorAddr )
  154. VB = [
  155. '<sharedgeometry>',
  156. '<vertexbuffer positions="true" normals="true">'
  157. ]
  158. fastlookup = {}
  159. ogre_vert_index = 0
  160. triangles = []
  161. for fidx in range( numFaces ):
  162. smooth = ord( facesSmooth[ fidx ] ) # ctypes.c_bool > char > int
  163. matidx = facesMat[ fidx ]
  164. i = fidx*4
  165. ai = faces[ i ]; bi = faces[ i+1 ]
  166. ci = faces[ i+2 ]; di = faces[ i+3 ]
  167. triangle = []
  168. for J in [ai, bi, ci]:
  169. i = J*3
  170. x = rffi.cast( rffi.DOUBLE, vertsPos[ i ] )
  171. y = rffi.cast( rffi.DOUBLE, vertsPos[ i+1 ] )
  172. z = rffi.cast( rffi.DOUBLE, vertsPos[ i+2 ] )
  173. pos = (x,y,z)
  174. #if smooth:
  175. x = rffi.cast( rffi.DOUBLE, vertsNor[ i ] )
  176. y = rffi.cast( rffi.DOUBLE, vertsNor[ i+1 ] )
  177. z = rffi.cast( rffi.DOUBLE, vertsNor[ i+2 ] )
  178. nor = (x,y,z)
  179. SIG = (pos,nor)#, matidx)
  180. skip = False
  181. if J in fastlookup:
  182. for otherSIG in fastlookup[ J ]:
  183. if SIG == otherSIG:
  184. triangle.append( fastlookup[J][otherSIG] )
  185. skip = True
  186. break
  187. if not skip:
  188. triangle.append( ogre_vert_index )
  189. fastlookup[ J ][ SIG ] = ogre_vert_index
  190. else:
  191. triangle.append( ogre_vert_index )
  192. fastlookup[ J ] = { SIG : ogre_vert_index }
  193. if skip: continue
  194. xml = [
  195. '<vertex>',
  196. '<position x="%s" y="%s" z="%s" />' %pos, # funny that tuple is valid here
  197. '<normal x="%s" y="%s" z="%s" />' %nor,
  198. '</vertex>'
  199. ]
  200. VB.append( '\n'.join(xml) )
  201. ogre_vert_index += 1
  202. triangles.append( triangle )
  203. VB.append( '</vertexbuffer>' )
  204. VB.append( '</sharedgeometry>' )
  205. file.write( '\n'.join(VB) )
  206. del VB # free memory
  207. SMS = ['<submeshes>']
  208. #for matidx, matname in ...:
  209. SM = [
  210. '<submesh usesharedvertices="true" use32bitindexes="true" material="%s" operationtype="triangle_list">' % 'somemat',
  211. '<faces count="%s">' %'100',
  212. ]
  213. for tri in triangles:
  214. #x,y,z = tri # rpython bug, when in a new 'block' need to unpack/repack tuple
  215. #s = '<face v1="%s" v2="%s" v3="%s" />' %(x,y,z)
  216. assert isinstance(tri,tuple) #and len(tri)==3 # this also works
  217. s = '<face v1="%s" v2="%s" v3="%s" />' %tri # but tuple is not valid here
  218. SM.append( s )
  219. SM.append( '</faces>' )
  220. SM.append( '</submesh>' )
  221. file.write( '\n'.join(SM) )
  222. file.close()
  223. rpy.cache(refresh=1)
  224. sys.exit('OK: module compiled and cached')
  225. ## More imports now that Blender is imported
  226. import hashlib, getpass, tempfile, configparser, subprocess, pickle
  227. from xml.sax.saxutils import XMLGenerator, quoteattr
  228. class CMesh(object):
  229. def __init__(self, data):
  230. self.numVerts = N = len( data.vertices )
  231. self.numFaces = Nfaces = len(data.tessfaces)
  232. self.vertex_positions = (ctypes.c_float * (N * 3))()
  233. data.vertices.foreach_get( 'co', self.vertex_positions )
  234. v = self.vertex_positions
  235. self.vertex_normals = (ctypes.c_float * (N * 3))()
  236. data.vertices.foreach_get( 'normal', self.vertex_normals )
  237. self.faces = (ctypes.c_uint * (Nfaces * 4))()
  238. data.tessfaces.foreach_get( 'vertices_raw', self.faces )
  239. self.faces_normals = (ctypes.c_float * (Nfaces * 3))()
  240. data.tessfaces.foreach_get( 'normal', self.faces_normals )
  241. self.faces_smooth = (ctypes.c_bool * Nfaces)()
  242. data.tessfaces.foreach_get( 'use_smooth', self.faces_smooth )
  243. self.faces_material_index = (ctypes.c_ushort * Nfaces)()
  244. data.tessfaces.foreach_get( 'material_index', self.faces_material_index )
  245. self.vertex_colors = []
  246. if len( data.vertex_colors ):
  247. vc = data.vertex_colors[0]
  248. n = len(vc.data)
  249. # no colors_raw !!?
  250. self.vcolors1 = (ctypes.c_float * (n * 3))() # face1
  251. vc.data.foreach_get( 'color1', self.vcolors1 )
  252. self.vertex_colors.append( self.vcolors1 )
  253. self.vcolors2 = (ctypes.c_float * (n * 3))() # face2
  254. vc.data.foreach_get( 'color2', self.vcolors2 )
  255. self.vertex_colors.append( self.vcolors2 )
  256. self.vcolors3 = (ctypes.c_float * (n * 3))() # face3
  257. vc.data.foreach_get( 'color3', self.vcolors3 )
  258. self.vertex_colors.append( self.vcolors3 )
  259. self.vcolors4 = (ctypes.c_float * (n * 3))() # face4
  260. vc.data.foreach_get( 'color4', self.vcolors4 )
  261. self.vertex_colors.append( self.vcolors4 )
  262. self.uv_textures = []
  263. if data.uv_textures.active:
  264. for layer in data.uv_textures:
  265. n = len(layer.data) * 8
  266. a = (ctypes.c_float * n)()
  267. layer.data.foreach_get( 'uv_raw', a ) # 4 faces
  268. self.uv_textures.append( a )
  269. def save( blenderobject, path ):
  270. cmesh = Mesh( blenderobject.data )
  271. start = time.time()
  272. dotmesh(
  273. path,
  274. ctypes.addressof( cmesh.faces ),
  275. ctypes.addressof( cmesh.faces_smooth ),
  276. ctypes.addressof( cmesh.faces_material_index ),
  277. ctypes.addressof( cmesh.vertex_positions ),
  278. ctypes.addressof( cmesh.vertex_normals ),
  279. cmesh.numFaces,
  280. cmesh.numVerts,
  281. )
  282. print('Mesh dumped in %s seconds' % (time.time()-start))
  283. ## Make sure we can import from same directory
  284. SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
  285. if SCRIPT_DIR not in sys.path:
  286. sys.path.append( SCRIPT_DIR )
  287. ## Avatar
  288. bpy.types.Object.use_avatar = BoolProperty(
  289. name='enable avatar',
  290. description='enables EC_Avatar',
  291. default=False)
  292. bpy.types.Object.avatar_reference = StringProperty(
  293. name='avatar reference',
  294. description='sets avatar reference URL',
  295. maxlen=128,
  296. default='')
  297. BoolProperty( name='enable avatar', description='enables EC_Avatar', default=False) # todo: is this used?
  298. # Tundra IDs
  299. bpy.types.Object.uid = IntProperty(
  300. name="unique ID",
  301. description="unique ID for Tundra",
  302. default=0, min=0, max=2**14)
  303. # Rendering
  304. bpy.types.Object.use_draw_distance = BoolProperty(
  305. name='enable draw distance',
  306. description='use LOD draw distance',
  307. default=False)
  308. bpy.types.Object.draw_distance = FloatProperty(
  309. name='draw distance',
  310. description='distance at which to begin drawing object',
  311. default=0.0, min=0.0, max=10000.0)
  312. bpy.types.Object.cast_shadows = BoolProperty(
  313. name='cast shadows',
  314. description='cast shadows',
  315. default=False)
  316. bpy.types.Object.use_multires_lod = BoolProperty(
  317. name='Enable Multires LOD',
  318. description='enables multires LOD',
  319. default=False)
  320. bpy.types.Object.multires_lod_range = FloatProperty(
  321. name='multires LOD range',
  322. description='far distance at which multires is set to base level',
  323. default=30.0, min=0.0, max=10000.0)
  324. ## Physics
  325. _physics_modes = [
  326. ('NONE', 'NONE', 'no physics'),
  327. ('RIGID_BODY', 'RIGID_BODY', 'rigid body'),
  328. ('SOFT_BODY', 'SOFT_BODY', 'soft body'),
  329. ]
  330. _collision_modes = [
  331. ('NONE', 'NONE', 'no collision'),
  332. ('PRIMITIVE', 'PRIMITIVE', 'primitive collision type'),
  333. ('MESH', 'MESH', 'triangle-mesh or convex-hull collision type'),
  334. ('DECIMATED', 'DECIMATED', 'auto-decimated collision type'),
  335. ('COMPOUND', 'COMPOUND', 'children primitive compound collision type'),
  336. ('TERRAIN', 'TERRAIN', 'terrain (height map) collision type'),
  337. ]
  338. bpy.types.Object.physics_mode = EnumProperty(
  339. items = _physics_modes,
  340. name = 'physics mode',
  341. description='physics mode',
  342. default='NONE')
  343. bpy.types.Object.physics_friction = FloatProperty(
  344. name='Simple Friction',
  345. description='physics friction',
  346. default=0.1, min=0.0, max=1.0)
  347. bpy.types.Object.physics_bounce = FloatProperty(
  348. name='Simple Bounce',
  349. description='physics bounce',
  350. default=0.01, min=0.0, max=1.0)
  351. bpy.types.Object.collision_terrain_x_steps = IntProperty(
  352. name="Ogre Terrain: x samples",
  353. description="resolution in X of height map",
  354. default=64, min=4, max=8192)
  355. bpy.types.Object.collision_terrain_y_steps = IntProperty(
  356. name="Ogre Terrain: y samples",
  357. description="resolution in Y of height map",
  358. default=64, min=4, max=8192)
  359. bpy.types.Object.collision_mode = EnumProperty(
  360. items = _collision_modes,
  361. name = 'primary collision mode',
  362. description='collision mode',
  363. default='NONE')
  364. bpy.types.Object.subcollision = BoolProperty(
  365. name="collision compound",
  366. description="member of a collision compound",
  367. default=False)
  368. ## Sound
  369. bpy.types.Speaker.play_on_load = BoolProperty(
  370. name='play on load',
  371. default=False)
  372. bpy.types.Speaker.loop = BoolProperty(
  373. name='loop sound',
  374. default=False)
  375. bpy.types.Speaker.use_spatial = BoolProperty(
  376. name='3D spatial sound',
  377. default=True)
  378. ## ImageMagick
  379. _IMAGE_FORMATS = [
  380. ('NONE','NONE', 'do not convert image'),
  381. ('bmp', 'bmp', 'bitmap format'),
  382. ('jpg', 'jpg', 'jpeg format'),
  383. ('gif', 'gif', 'gif format'),
  384. ('png', 'png', 'png format'),
  385. ('tga', 'tga', 'targa format'),
  386. ('dds', 'dds', 'nvidia dds format'),
  387. ]
  388. bpy.types.Image.use_convert_format = BoolProperty(
  389. name='use convert format',
  390. default=False
  391. )
  392. bpy.types.Image.convert_format = EnumProperty(
  393. name='convert to format',
  394. description='converts to image format using imagemagick',
  395. items=_IMAGE_FORMATS,
  396. default='NONE')
  397. bpy.types.Image.jpeg_quality = IntProperty(
  398. name="jpeg quality",
  399. description="quality of jpeg",
  400. default=80, min=0, max=100)
  401. bpy.types.Image.use_color_quantize = BoolProperty(
  402. name='use color quantize',
  403. default=False)
  404. bpy.types.Image.use_color_quantize_dither = BoolProperty(
  405. name='use color quantize dither',
  406. default=True)
  407. bpy.types.Image.color_quantize = IntProperty(
  408. name="color quantize",
  409. description="reduce to N colors (requires ImageMagick)",
  410. default=32, min=2, max=256)
  411. bpy.types.Image.use_resize_half = BoolProperty(
  412. name='resize by 1/2',
  413. default=False)
  414. bpy.types.Image.use_resize_absolute = BoolProperty(
  415. name='force image resize',
  416. default=False)
  417. bpy.types.Image.resize_x = IntProperty(
  418. name='resize X',
  419. description='only if image is larger than defined, use ImageMagick to resize it down',
  420. default=256, min=2, max=4096)
  421. bpy.types.Image.resize_y = IntProperty(
  422. name='resize Y',
  423. description='only if image is larger than defined, use ImageMagick to resize it down',
  424. default=256, min=2, max=4096)
  425. # Materials
  426. bpy.types.Material.ogre_depth_write = BoolProperty(
  427. # Material.ogre_depth_write = AUTO|ON|OFF
  428. name='depth write',
  429. default=True)
  430. bpy.types.Material.ogre_depth_check = BoolProperty(
  431. # If depth-buffer checking is on, whenever a pixel is about to be written to
  432. # the frame buffer the depth buffer is checked to see if the pixel is in front
  433. # of all other pixels written at that point. If not, the pixel is not written.
  434. # If depth checking is off, pixels are written no matter what has been rendered before.
  435. name='depth check',
  436. default=True)
  437. bpy.types.Material.ogre_alpha_to_coverage = BoolProperty(
  438. # Sets whether this pass will use 'alpha to coverage', a way to multisample alpha
  439. # texture edges so they blend more seamlessly with the background. This facility
  440. # is typically only available on cards from around 2006 onwards, but it is safe to
  441. # enable it anyway - Ogre will just ignore it if the hardware does not support it.
  442. # The common use for alpha to coverage is foliage rendering and chain-link fence style textures.
  443. name='multisample alpha edges',
  444. default=False)
  445. bpy.types.Material.ogre_light_scissor = BoolProperty(
  446. # This option is usually only useful if this pass is an additive lighting pass, and is
  447. # at least the second one in the technique. Ie areas which are not affected by the current
  448. # light(s) will never need to be rendered. If there is more than one light being passed to
  449. # the pass, then the scissor is defined to be the rectangle which covers all lights in screen-space.
  450. # Directional lights are ignored since they are infinite. This option does not need to be specified
  451. # if you are using a standard additive shadow mode, i.e. SHADOWTYPE_STENCIL_ADDITIVE or
  452. # SHADOWTYPE_TEXTURE_ADDITIVE, since it is the default behaviour to use a scissor for each additive
  453. # shadow pass. However, if you're not using shadows, or you're using Integrated Texture Shadows
  454. # where passes are specified in a custom manner, then this could be of use to you.
  455. name='light scissor',
  456. default=False)
  457. bpy.types.Material.ogre_light_clip_planes = BoolProperty(
  458. name='light clip planes',
  459. default=False)
  460. bpy.types.Material.ogre_normalise_normals = BoolProperty(
  461. name='normalise normals',
  462. default=False,
  463. description="Scaling objects causes normals to also change magnitude, which can throw off your lighting calculations. By default, the SceneManager detects this and will automatically re-normalise normals for any scaled object, but this has a cost. If you'd prefer to control this manually, call SceneManager::setNormaliseNormalsOnScale(false) and then use this option on materials which are sensitive to normals being resized.")
  464. bpy.types.Material.ogre_lighting = BoolProperty(
  465. # Sets whether or not dynamic lighting is turned on for this pass or not. If lighting is turned off,
  466. # all objects rendered using the pass will be fully lit. This attribute has no effect if a vertex program is used.
  467. name='dynamic lighting',
  468. default=True)
  469. bpy.types.Material.ogre_colour_write = BoolProperty(
  470. # If colour writing is off no visible pixels are written to the screen during this pass. You might think
  471. # this is useless, but if you render with colour writing off, and with very minimal other settings,
  472. # you can use this pass to initialise the depth buffer before subsequently rendering other passes which
  473. # fill in the colour data. This can give you significant performance boosts on some newer cards, especially
  474. # when using complex fragment programs, because if the depth check fails then the fragment program is never run.
  475. name='color-write',
  476. default=True)
  477. bpy.types.Material.use_fixed_pipeline = BoolProperty(
  478. # Fixed pipeline is oldschool
  479. # todo: whats the meaning of this?
  480. name='fixed pipeline',
  481. default=True)
  482. bpy.types.Material.use_material_passes = BoolProperty(
  483. # hidden option - gets turned on by operator
  484. # todo: What is a hidden option, is this needed?
  485. name='use ogre extra material passes (layers)',
  486. default=False)
  487. bpy.types.Material.use_in_ogre_material_pass = BoolProperty(
  488. name='Layer Toggle',
  489. default=True)
  490. bpy.types.Material.use_ogre_advanced_options = BoolProperty(
  491. name='Show Advanced Options',
  492. default=False)
  493. bpy.types.Material.use_ogre_parent_material = BoolProperty(
  494. name='Use Script Inheritance',
  495. default=False)
  496. bpy.types.Material.ogre_parent_material = EnumProperty(
  497. name="Script Inheritence",
  498. description='ogre parent material class', #default='NONE',
  499. items=[])
  500. bpy.types.Material.ogre_polygon_mode = EnumProperty(
  501. name='faces draw type',
  502. description="ogre face draw mode",
  503. items=[ ('solid', 'solid', 'SOLID'),
  504. ('wireframe', 'wireframe', 'WIREFRAME'),
  505. ('points', 'points', 'POINTS') ],
  506. default='solid')
  507. bpy.types.Material.ogre_shading = EnumProperty(
  508. name='hardware shading',
  509. description="Sets the kind of shading which should be used for representing dynamic lighting for this pass.",
  510. items=[ ('flat', 'flat', 'FLAT'),
  511. ('gouraud', 'gouraud', 'GOURAUD'),
  512. ('phong', 'phong', 'PHONG') ],
  513. default='gouraud')
  514. bpy.types.Material.ogre_cull_hardware = EnumProperty(
  515. name='hardware culling',
  516. description="If the option 'cull_hardware clockwise' is set, all triangles whose vertices are viewed in clockwise order from the camera will be culled by the hardware.",
  517. items=[ ('clockwise', 'clockwise', 'CLOCKWISE'),
  518. ('anticlockwise', 'anticlockwise', 'COUNTER CLOCKWISE'),
  519. ('none', 'none', 'NONE') ],
  520. default='clockwise')
  521. bpy.types.Material.ogre_transparent_sorting = EnumProperty(
  522. name='transparent sorting',
  523. description="By default all transparent materials are sorted such that renderables furthest away from the camera are rendered first. This is usually the desired behaviour but in certain cases this depth sorting may be unnecessary and undesirable. If for example it is necessary to ensure the rendering order does not change from one frame to the next. In this case you could set the value to 'off' to prevent sorting.",
  524. items=[ ('on', 'on', 'ON'),
  525. ('off', 'off', 'OFF'),
  526. ('force', 'force', 'FORCE ON') ],
  527. default='on')
  528. bpy.types.Material.ogre_illumination_stage = EnumProperty(
  529. name='illumination stage',
  530. description='When using an additive lighting mode (SHADOWTYPE_STENCIL_ADDITIVE or SHADOWTYPE_TEXTURE_ADDITIVE), the scene is rendered in 3 discrete stages, ambient (or pre-lighting), per-light (once per light, with shadowing) and decal (or post-lighting). Usually OGRE figures out how to categorise your passes automatically, but there are some effects you cannot achieve without manually controlling the illumination.',
  531. items=[ ('', '', 'autodetect'),
  532. ('ambient', 'ambient', 'ambient'),
  533. ('per_light', 'per_light', 'lights'),
  534. ('decal', 'decal', 'decal') ],
  535. default=''
  536. )
  537. _ogre_depth_func = [
  538. ('less_equal', 'less_equal', '<='),
  539. ('less', 'less', '<'),
  540. ('equal', 'equal', '=='),
  541. ('not_equal', 'not_equal', '!='),
  542. ('greater_equal', 'greater_equal', '>='),
  543. ('greater', 'greater', '>'),
  544. ('always_fail', 'always_fail', 'false'),
  545. ('always_pass', 'always_pass', 'true'),
  546. ]
  547. bpy.types.Material.ogre_depth_func = EnumProperty(
  548. items=_ogre_depth_func,
  549. name='depth buffer function',
  550. description='If depth checking is enabled (see depth_check) a comparison occurs between the depth value of the pixel to be written and the current contents of the buffer. This comparison is normally less_equal, i.e. the pixel is written if it is closer (or at the same distance) than the current contents',
  551. default='less_equal')
  552. _ogre_scene_blend_ops = [
  553. ('add', 'add', 'DEFAULT'),
  554. ('subtract', 'subtract', 'SUBTRACT'),
  555. ('reverse_subtract', 'reverse_subtract', 'REVERSE SUBTRACT'),
  556. ('min', 'min', 'MIN'),
  557. ('max', 'max', 'MAX'),
  558. ]
  559. bpy.types.Material.ogre_scene_blend_op = EnumProperty(
  560. items=_ogre_scene_blend_ops,
  561. name='scene blending operation',
  562. description='This directive changes the operation which is applied between the two components of the scene blending equation',
  563. default='add')
  564. _ogre_scene_blend_types = [
  565. ('one zero', 'one zero', 'DEFAULT'),
  566. ('alpha_blend', 'alpha_blend', "The alpha value of the rendering output is used as a mask. Equivalent to 'scene_blend src_alpha one_minus_src_alpha'"),
  567. ('add', 'add', "The colour of the rendering output is added to the scene. Good for explosions, flares, lights, ghosts etc. Equivalent to 'scene_blend one one'."),
  568. ('modulate', 'modulate', "The colour of the rendering output is multiplied with the scene contents. Generally colours and darkens the scene, good for smoked glass, semi-transparent objects etc. Equivalent to 'scene_blend dest_colour zero'"),
  569. ('colour_blend', 'colour_blend', 'Colour the scene based on the brightness of the input colours, but dont darken. Equivalent to "scene_blend src_colour one_minus_src_colour"'),
  570. ]
  571. for mode in 'dest_colour src_colour one_minus_dest_colour dest_alpha src_alpha one_minus_dest_alpha one_minus_src_alpha'.split():
  572. _ogre_scene_blend_types.append( ('one %s'%mode, 'one %s'%mode, '') )
  573. del mode
  574. bpy.types.Material.ogre_scene_blend = EnumProperty(
  575. items=_ogre_scene_blend_types,
  576. name='scene blend',
  577. description='blending operation of material to scene',
  578. default='one zero')
  579. ## FAQ
  580. _faq_ = '''
  581. Q: I have hundres of objects, is there a way i can merge them on export only?
  582. A: Yes, just add them to a group named starting with "merge", or link the group.
  583. Q: Can i use subsurf or multi-res on a mesh with an armature?
  584. A: Yes.
  585. Q: Can i use subsurf or multi-res on a mesh with shape animation?
  586. A: No.
  587. Q: I don't see any objects when i export?
  588. A: You must select the objects you wish to export.
  589. Q: I don't see my animations when exported?
  590. A: Make sure you created an NLA strip on the armature.
  591. Q: Do i need to bake my IK and other constraints into FK on my armature before export?
  592. A: No.
  593. '''
  594. ## DOCUMENTATION
  595. ''' todo: Update the nonsense C:\Tundra2 paths from defaul config and fix this doc.
  596. Additionally point to some doc how to build opengl only version on windows if that really is needed and
  597. remove the old Tundra 7z link. '''
  598. _doc_installing_ = '''
  599. Installing:
  600. Installing the Addon:
  601. You can simply copy io_export_ogreDotScene.py to your blender installation under blender/2.6x/scripts/addons/
  602. and enable it in the user-prefs interface (CTRL+ALT+U)
  603. Or you can use blenders interface, under user-prefs, click addons, and click 'install-addon'
  604. (its a good idea to delete the old version first)
  605. Required:
  606. 1. Blender 2.63
  607. 2. Install Ogre Command Line tools to the default path: C:\\OgreCommandLineTools from http://www.ogre3d.org/download/tools
  608. * These tools are used to create the binary Mesh from the .xml mesh generated by this plugin.
  609. * Linux users may use above and Wine, or install from source, or install via apt-get install ogre-tools.
  610. Optional:
  611. 3. Install NVIDIA DDS Legacy Utilities - Install them to default path.
  612. * http://developer.nvidia.com/object/dds_utilities_legacy.html
  613. * Linux users will need to use Wine.
  614. 4. Install Image Magick
  615. * http://www.imagemagick.org
  616. 5. Copy OgreMeshy to C:\\OgreMeshy
  617. * If your using 64bit Windows, you may need to download a 64bit OgreMeshy
  618. * Linux copy to your home folder.
  619. 6. realXtend Tundra
  620. * For latest Tundra releases see http://code.google.com/p/realxtend-naali/downloads/list
  621. - You may need to tweak the config to tell your Tundra path or install to C:\Tundra2
  622. * Old OpenGL only build can be found from http://blender2ogre.googlecode.com/files/realxtend-Tundra-2.1.2-OpenGL.7z
  623. - Windows: extract to C:\Tundra2
  624. - Linux: extract to ~/Tundra2
  625. '''
  626. ## Options
  627. AXIS_MODES = [
  628. ('xyz', 'xyz', 'no swapping'),
  629. ('xz-y', 'xz-y', 'ogre standard'),
  630. ('-xzy', '-xzy', 'non standard'),
  631. ]
  632. def swap(vec):
  633. if CONFIG['SWAP_AXIS'] == 'xyz': return vec
  634. elif CONFIG['SWAP_AXIS'] == 'xzy':
  635. if len(vec) == 3: return mathutils.Vector( [vec.x, vec.z, vec.y] )
  636. elif len(vec) == 4: return mathutils.Quaternion( [ vec.w, vec.x, vec.z, vec.y] )
  637. elif CONFIG['SWAP_AXIS'] == '-xzy':
  638. if len(vec) == 3: return mathutils.Vector( [-vec.x, vec.z, vec.y] )
  639. elif len(vec) == 4: return mathutils.Quaternion( [ vec.w, -vec.x, vec.z, vec.y] )
  640. elif CONFIG['SWAP_AXIS'] == 'xz-y':
  641. if len(vec) == 3: return mathutils.Vector( [vec.x, vec.z, -vec.y] )
  642. elif len(vec) == 4: return mathutils.Quaternion( [ vec.w, vec.x, vec.z, -vec.y] )
  643. else:
  644. print( 'unknown swap axis mode', CONFIG['SWAP_AXIS'] )
  645. assert 0
  646. ## Config
  647. CONFIG_PATH = bpy.utils.user_resource('CONFIG', path='scripts', create=True)
  648. CONFIG_FILENAME = 'blender2ogre.pickle'
  649. CONFIG_FILEPATH = os.path.join(CONFIG_PATH, CONFIG_FILENAME)
  650. _CONFIG_DEFAULTS_ALL = {
  651. 'TUNDRA_STREAMING' : True,
  652. 'COPY_SHADER_PROGRAMS' : True,
  653. 'MAX_TEXTURE_SIZE' : 4096,
  654. 'SWAP_AXIS' : 'xz-y', # ogre standard
  655. 'ONLY_ANIMATED_BONES' : False,
  656. 'ONLY_DEFORMABLE_BONES' : False,
  657. 'INDEPENDENT_ANIM' : False,
  658. 'FORCE_IMAGE_FORMAT' : 'NONE',
  659. 'TOUCH_TEXTURES' : True,
  660. 'SEP_MATS' : True,
  661. 'SCENE' : True,
  662. 'SELONLY' : True,
  663. 'FORCE_CAMERA' : True,
  664. 'FORCE_LAMPS' : True,
  665. 'MESH' : True,
  666. 'MESH_OVERWRITE' : True,
  667. 'ARM_ANIM' : True,
  668. 'SHAPE_ANIM' : True,
  669. 'ARRAY' : True,
  670. 'MATERIALS' : True,
  671. 'DDS_MIPS' : True,
  672. 'TRIM_BONE_WEIGHTS' : 0.01,
  673. 'lodLevels' : 0,
  674. 'lodDistance' : 300,
  675. 'lodPercent' : 40,
  676. 'nuextremityPoints' : 0,
  677. 'generateEdgeLists' : False,
  678. 'generateTangents' : True, # this is now safe - ignored if mesh is missing UVs
  679. 'tangentSemantic' : 'uvw',
  680. 'tangentUseParity' : 4,
  681. 'tangentSplitMirrored' : False,
  682. 'tangentSplitRotated' : False,
  683. 'reorganiseBuffers' : True,
  684. 'optimiseAnimations' : True,
  685. }
  686. _CONFIG_TAGS_ = 'OGRETOOLS_XML_CONVERTER OGRETOOLS_MESH_MAGICK TUNDRA_ROOT OGRE_MESHY IMAGE_MAGICK_CONVERT NVCOMPRESS NVIDIATOOLS_EXE USER_MATERIALS SHADER_PROGRAMS TUNDRA_STREAMING'.split()
  687. ''' todo: Change pretty much all of these windows ones. Make a smarter way of detecting
  688. Ogre tools and Tundra from various default folders. Also consider making a installer that
  689. ships Ogre cmd line tools to ease the setup steps for end users. '''
  690. _CONFIG_DEFAULTS_WINDOWS = {
  691. 'OGRETOOLS_XML_CONVERTER' : 'C:\\OgreCommandLineTools\\OgreXmlConverter.exe',
  692. 'OGRETOOLS_MESH_MAGICK' : 'C:\\OgreCommandLineTools\\MeshMagick.exe',
  693. 'TUNDRA_ROOT' : 'C:\\Tundra2',
  694. 'OGRE_MESHY' : 'C:\\OgreMeshy\\Ogre Meshy.exe',
  695. 'IMAGE_MAGICK_CONVERT' : 'C:\\Program Files\\ImageMagick\\convert.exe',
  696. 'NVIDIATOOLS_EXE' : 'C:\\Program Files\\NVIDIA Corporation\\DDS Utilities\\nvdxt.exe',
  697. 'USER_MATERIALS' : 'C:\\Tundra2\\media\\materials',
  698. 'SHADER_PROGRAMS' : 'C:\\Tundra2\\media\\materials\\programs',
  699. 'NVCOMPRESS' : 'C:\\nvcompress.exe'
  700. }
  701. _CONFIG_DEFAULTS_UNIX = {
  702. 'OGRETOOLS_XML_CONVERTER' : '/usr/local/bin/OgreXMLConverter', # source build is better
  703. 'OGRETOOLS_MESH_MAGICK' : '/usr/local/bin/MeshMagick',
  704. 'TUNDRA_ROOT' : '~/Tundra2',
  705. 'OGRE_MESHY' : '~/OgreMeshy/Ogre Meshy.exe',
  706. 'IMAGE_MAGICK_CONVERT' : '/usr/bin/convert',
  707. 'NVIDIATOOLS_EXE' : '~/.wine/drive_c/Program Files/NVIDIA Corporation/DDS Utilities',
  708. 'USER_MATERIALS' : '~/Tundra2/media/materials',
  709. 'SHADER_PROGRAMS' : '~/Tundra2/media/materials/programs',
  710. #'USER_MATERIALS' : '~/ogre_src_v1-7-3/Samples/Media/materials',
  711. #'SHADER_PROGRAMS' : '~/ogre_src_v1-7-3/Samples/Media/materials/programs',
  712. 'NVCOMPRESS' : '/usr/local/bin/nvcompress'
  713. }
  714. # Unix: Replace ~ with absolute home dir path
  715. if sys.platform.startswith('linux') or sys.platform.startswith('darwin') or sys.platform.startswith('freebsd'):
  716. for tag in _CONFIG_DEFAULTS_UNIX:
  717. path = _CONFIG_DEFAULTS_UNIX[ tag ]
  718. if path.startswith('~'):
  719. _CONFIG_DEFAULTS_UNIX[ tag ] = os.path.expanduser( path )
  720. elif tag.startswith('OGRETOOLS') and not os.path.isfile( path ):
  721. _CONFIG_DEFAULTS_UNIX[ tag ] = os.path.join( '/usr/bin', os.path.split( path )[-1] )
  722. del tag
  723. del path
  724. ## PUBLIC API continues
  725. CONFIG = {}
  726. def load_config():
  727. global CONFIG
  728. if os.path.isfile( CONFIG_FILEPATH ):
  729. try:
  730. with open( CONFIG_FILEPATH, 'rb' ) as f:
  731. CONFIG = pickle.load( f )
  732. except:
  733. print('[ERROR]: Can not read config from %s' %CONFIG_FILEPATH)
  734. for tag in _CONFIG_DEFAULTS_ALL:
  735. if tag not in CONFIG:
  736. CONFIG[ tag ] = _CONFIG_DEFAULTS_ALL[ tag ]
  737. for tag in _CONFIG_TAGS_:
  738. if tag not in CONFIG:
  739. if sys.platform.startswith('win'):
  740. CONFIG[ tag ] = _CONFIG_DEFAULTS_WINDOWS[ tag ]
  741. elif sys.platform.startswith('linux') or sys.platform.startswith('darwin') or sys.platform.startswith('freebsd'):
  742. CONFIG[ tag ] = _CONFIG_DEFAULTS_UNIX[ tag ]
  743. else:
  744. print( 'ERROR: unknown platform' )
  745. assert 0
  746. try:
  747. if sys.platform.startswith('win'):
  748. import winreg
  749. # Find the blender2ogre install path from windows registry
  750. registry_key = winreg.OpenKey(winreg.HKEY_CLASSES_ROOT, r'Software\blender2ogre', 0, winreg.KEY_READ)
  751. exe_install_dir = winreg.QueryValueEx(registry_key, "Path")[0]
  752. if exe_install_dir != "":
  753. # OgreXmlConverter
  754. if os.path.isfile(exe_install_dir + "OgreXmlConverter.exe"):
  755. print ("Using OgreXmlConverter from install path:", exe_install_dir + "OgreXmlConverter.exe")
  756. CONFIG['OGRETOOLS_XML_CONVERTER'] = exe_install_dir + "OgreXmlConverter.exe"
  757. # Run auto updater as silent. Notifies user if there is a new version out.
  758. # This will not show any UI if there are no update and will go to network
  759. # only once per 2 days so it wont be spending much resources either.
  760. # todo: Move this to a more appropriate place than load_config()
  761. if os.path.isfile(exe_install_dir + "check-for-updates.exe"):
  762. subprocess.Popen([exe_install_dir + "check-for-updates.exe", "/silent"])
  763. except Exception as e:
  764. print("Exception while reading windows registry:", e)
  765. # Setup temp hidden RNA to expose the file paths
  766. for tag in _CONFIG_TAGS_:
  767. default = CONFIG[ tag ]
  768. func = eval( 'lambda self,con: CONFIG.update( {"%s" : self.%s} )' %(tag,tag) )
  769. if type(default) is bool:
  770. prop = BoolProperty(
  771. name=tag, description='updates bool setting', default=default,
  772. options={'SKIP_SAVE'}, update=func
  773. )
  774. else:
  775. prop = StringProperty(
  776. name=tag, description='updates path setting', maxlen=128, default=default,
  777. options={'SKIP_SAVE'}, update=func
  778. )
  779. setattr( bpy.types.WindowManager, tag, prop )
  780. return CONFIG
  781. CONFIG = load_config()
  782. def save_config():
  783. #for key in CONFIG: print( '%s = %s' %(key, CONFIG[key]) )
  784. if os.path.isdir( CONFIG_PATH ):
  785. try:
  786. with open( CONFIG_FILEPATH, 'wb' ) as f:
  787. pickle.dump( CONFIG, f, -1 )
  788. except:
  789. print('[ERROR]: Can not write to %s' %CONFIG_FILEPATH)
  790. else:
  791. print('[ERROR:] Config directory does not exist %s' %CONFIG_PATH)
  792. class Blender2Ogre_ConfigOp(bpy.types.Operator):
  793. '''operator: saves current b2ogre configuration'''
  794. bl_idname = "ogre.save_config"
  795. bl_label = "save config file"
  796. bl_options = {'REGISTER'}
  797. @classmethod
  798. def poll(cls, context):
  799. return True
  800. def invoke(self, context, event):
  801. save_config()
  802. Report.reset()
  803. Report.messages.append('SAVED %s' %CONFIG_FILEPATH)
  804. Report.show()
  805. return {'FINISHED'}
  806. # Make default material for missing materials:
  807. # * Red flags for users so they can quickly see what they forgot to assign a material to.
  808. # * Do not crash if no material on object - thats annoying for the user.
  809. MISSING_MATERIAL = '''
  810. material _missing_material_
  811. {
  812. receive_shadows off
  813. technique
  814. {
  815. pass
  816. {
  817. ambient 0.1 0.1 0.1 1.0
  818. diffuse 0.8 0.0 0.0 1.0
  819. specular 0.5 0.5 0.5 1.0 12.5
  820. emissive 0.3 0.3 0.3 1.0
  821. }
  822. }
  823. }
  824. '''
  825. ## Helper functions
  826. def timer_diff_str(start):
  827. return "%0.2f" % (time.time()-start)
  828. def find_bone_index( ob, arm, groupidx): # sometimes the groups are out of order, this finds the right index.
  829. if groupidx < len(ob.vertex_groups): # reported by Slacker
  830. vg = ob.vertex_groups[ groupidx ]
  831. j = 0
  832. for i,bone in enumerate(arm.pose.bones):
  833. if not bone.bone.use_deform and CONFIG['ONLY_DEFORMABLE_BONES']:
  834. j+=1 # if we skip bones we need to adjust the id
  835. if bone.name == vg.name:
  836. return i-j
  837. else:
  838. print('WARNING: object vertex groups not in sync with armature', ob, arm, groupidx)
  839. def mesh_is_smooth( mesh ):
  840. for face in mesh.tessfaces:
  841. if face.use_smooth: return True
  842. def find_uv_layer_index( uvname, material=None ):
  843. # This breaks if users have uv layers with same name with different indices over different objects
  844. idx = 0
  845. for mesh in bpy.data.meshes:
  846. if material is None or material.name in mesh.materials:
  847. if mesh.uv_textures:
  848. names = [ uv.name for uv in mesh.uv_textures ]
  849. if uvname in names:
  850. idx = names.index( uvname )
  851. break # should we check all objects using material and enforce the same index?
  852. return idx
  853. def has_custom_property( a, name ):
  854. for prop in a.items():
  855. n,val = prop
  856. if n == name:
  857. return True
  858. def is_strictly_simple_terrain( ob ):
  859. # A default plane, with simple-subsurf and displace modifier on Z
  860. if len(ob.data.vertices) != 4 and len(ob.data.tessfaces) != 1:
  861. return False
  862. elif len(ob.modifiers) < 2:
  863. return False
  864. elif ob.modifiers[0].type != 'SUBSURF' or ob.modifiers[1].type != 'DISPLACE':
  865. return False
  866. elif ob.modifiers[0].subdivision_type != 'SIMPLE':
  867. return False
  868. elif ob.modifiers[1].direction != 'Z':
  869. return False # disallow NORMAL and other modes
  870. else:
  871. return True
  872. def get_image_textures( mat ):
  873. r = []
  874. for s in mat.texture_slots:
  875. if s and s.texture.type == 'IMAGE':
  876. r.append( s )
  877. return r
  878. def indent( level, *args ):
  879. if not args:
  880. return ' ' * level
  881. else:
  882. a = ''
  883. for line in args:
  884. a += ' ' * level
  885. a += line
  886. a += '\n'
  887. return a
  888. def gather_instances():
  889. instances = {}
  890. for ob in bpy.context.scene.objects:
  891. if ob.data and ob.data.users > 1:
  892. if ob.data not in instances:
  893. instances[ ob.data ] = []
  894. instances[ ob.data ].append( ob )
  895. return instances
  896. def select_instances( context, name ):
  897. for ob in bpy.context.scene.objects:
  898. ob.select = False
  899. ob = bpy.context.scene.objects[ name ]
  900. if ob.data:
  901. inst = gather_instances()
  902. for ob in inst[ ob.data ]: ob.select = True
  903. bpy.context.scene.objects.active = ob
  904. def select_group( context, name, options={} ):
  905. for ob in bpy.context.scene.objects:
  906. ob.select = False
  907. for grp in bpy.data.groups:
  908. if grp.name == name:
  909. # context.scene.objects.active = grp.objects
  910. # Note that the context is read-only. These values cannot be modified directly,
  911. # though they may be changed by running API functions or by using the data API.
  912. # So bpy.context.object = obj will raise an error. But bpy.context.scene.objects.active = obj
  913. # will work as expected. - http://wiki.blender.org/index.php?title=Dev:2.5/Py/API/Intro&useskin=monobook
  914. bpy.context.scene.objects.active = grp.objects[0]
  915. for ob in grp.objects:
  916. ob.select = True
  917. else:
  918. pass
  919. def get_objects_using_materials( mats ):
  920. obs = []
  921. for ob in bpy.data.objects:
  922. if ob.type == 'MESH':
  923. for mat in ob.data.materials:
  924. if mat in mats:
  925. if ob not in obs:
  926. obs.append( ob )
  927. break
  928. return obs
  929. def get_materials_using_image( img ):
  930. mats = []
  931. for mat in bpy.data.materials:
  932. for slot in get_image_textures( mat ):
  933. if slot.texture.image == img:
  934. if mat not in mats:
  935. mats.append( mat )
  936. return mats
  937. def get_parent_matrix( ob, objects ):
  938. if not ob.parent:
  939. return mathutils.Matrix(((1,0,0,0),(0,1,0,0),(0,0,1,0),(0,0,0,1))) # Requiered for Blender SVN > 2.56
  940. else:
  941. if ob.parent in objects:
  942. return ob.parent.matrix_world.copy()
  943. else:
  944. return get_parent_matrix(ob.parent, objects)
  945. def merge_group( group ):
  946. print('--------------- merge group ->', group )
  947. copies = []
  948. for ob in group.objects:
  949. if ob.type == 'MESH':
  950. print( '\t group member', ob.name )
  951. o2 = ob.copy(); copies.append( o2 )
  952. o2.data = o2.to_mesh(bpy.context.scene, True, "PREVIEW") # collaspe modifiers
  953. while o2.modifiers:
  954. o2.modifiers.remove( o2.modifiers[0] )
  955. bpy.context.scene.objects.link( o2 ) #; o2.select = True
  956. merged = merge( copies )
  957. merged.name = group.name
  958. merged.data.name = group.name
  959. return merged
  960. def merge_objects( objects, name='_temp_', transform=None ):
  961. assert objects
  962. copies = []
  963. for ob in objects:
  964. ob.select = False
  965. if ob.type == 'MESH':
  966. o2 = ob.copy(); copies.append( o2 )
  967. o2.data = o2.to_mesh(bpy.context.scene, True, "PREVIEW") # collaspe modifiers
  968. while o2.modifiers:
  969. o2.modifiers.remove( o2.modifiers[0] )
  970. if transform:
  971. o2.matrix_world = transform * o2.matrix_local
  972. bpy.context.scene.objects.link( o2 ) #; o2.select = True
  973. merged = merge( copies )
  974. merged.name = name
  975. merged.data.name = name
  976. return merged
  977. def merge( objects ):
  978. print('MERGE', objects)
  979. for ob in bpy.context.selected_objects:
  980. ob.select = False
  981. for ob in objects:
  982. print('\t'+ob.name)
  983. ob.select = True
  984. assert not ob.library
  985. bpy.context.scene.objects.active = ob
  986. bpy.ops.object.join()
  987. return bpy.context.active_object
  988. def get_merge_group( ob, prefix='merge' ):
  989. m = []
  990. for grp in ob.users_group:
  991. if grp.name.lower().startswith(prefix): m.append( grp )
  992. if len(m)==1:
  993. #if ob.data.users != 1:
  994. # print( 'WARNING: an instance can not be in a merge group' )
  995. # return
  996. return m[0]
  997. elif m:
  998. print('WARNING: an object can not be in two merge groups at the same time', ob)
  999. return
  1000. def wordwrap( txt ):
  1001. r = ['']
  1002. for word in txt.split(' '): # do not split on tabs
  1003. word = word.replace('\t', ' '*3)
  1004. r[-1] += word + ' '
  1005. if len(r[-1]) > 90:
  1006. r.append( '' )
  1007. return r
  1008. ## RPython xml dom
  1009. class RElement(object):
  1010. def appendChild( self, child ):
  1011. self.childNodes.append( child )
  1012. def setAttribute( self, name, value ):
  1013. self.attributes[name]=value
  1014. def __init__(self, tag):
  1015. self.tagName = tag
  1016. self.childNodes = []
  1017. self.attributes = {}
  1018. def toprettyxml(self, lines, indent ):
  1019. s = '<%s ' % self.tagName
  1020. for name in self.attributes:
  1021. value = self.attributes[name]
  1022. if not isinstance(value, str):
  1023. value = str(value)
  1024. s += '%s=%s ' % (name, quoteattr(value))
  1025. if not self.childNodes:
  1026. s += '/>'; lines.append( (' '*indent)+s )
  1027. else:
  1028. s += '>'; lines.append( (' '*indent)+s )
  1029. indent += 1
  1030. for child in self.childNodes:
  1031. child.toprettyxml( lines, indent )
  1032. indent -= 1
  1033. lines.append((' '*indent) + '</%s>' % self.tagName )
  1034. class RDocument(object):
  1035. def __init__(self):
  1036. self.documentElement = None
  1037. def appendChild(self, root):
  1038. self.documentElement = root
  1039. def createElement(self, tag):
  1040. e = RElement(tag)
  1041. e.document = self
  1042. return e
  1043. def toprettyxml(self):
  1044. indent = 0
  1045. lines = []
  1046. self.documentElement.toprettyxml(lines, indent)
  1047. return '\n'.join(lines)
  1048. class SimpleSaxWriter():
  1049. def __init__(self, output, encoding, top_level_tag, attrs):
  1050. xml_writer = XMLGenerator(output, encoding, True)
  1051. xml_writer.startDocument()
  1052. xml_writer.startElement(top_level_tag, attrs)
  1053. self._xml_writer = xml_writer
  1054. self.top_level_tag = top_level_tag
  1055. self.ident=4
  1056. self._xml_writer.characters('\n')
  1057. def start_tag(self, name, attrs):
  1058. self._xml_writer.characters(" " * self.ident)
  1059. self._xml_writer.startElement(name, attrs)
  1060. self.ident += 4
  1061. self._xml_writer.characters('\n')
  1062. def end_tag(self, name):
  1063. self.ident -= 4
  1064. self._xml_writer.characters(" " * self.ident)
  1065. self._xml_writer.endElement(name)
  1066. self._xml_writer.characters('\n')
  1067. def leaf_tag(self, name, attrs):
  1068. self._xml_writer.characters(" " * self.ident)
  1069. self._xml_writer.startElement(name, attrs)
  1070. self._xml_writer.endElement(name)
  1071. self._xml_writer.characters('\n')
  1072. def close(self):
  1073. self._xml_writer.endElement(self.top_level_tag)
  1074. self._xml_writer.endDocument()
  1075. ## Report Hack
  1076. class ReportSingleton(object):
  1077. def __init__(self):
  1078. self.reset()
  1079. def show(self):
  1080. bpy.ops.wm.call_menu( name='MiniReport' )
  1081. def reset(self):
  1082. self.materials = []
  1083. self.meshes = []
  1084. self.lights = []
  1085. self.cameras = []
  1086. self.armatures = []
  1087. self.armature_animations = []
  1088. self.shape_animations = []
  1089. self.textures = []
  1090. self.vertices = 0
  1091. self.orig_vertices = 0
  1092. self.faces = 0
  1093. self.triangles = 0
  1094. self.warnings = []
  1095. self.errors = []
  1096. self.messages = []
  1097. self.paths = []
  1098. def report(self):
  1099. r = ['Report:']
  1100. ex = ['Extended Report:']
  1101. if self.errors:
  1102. r.append( ' ERRORS:' )
  1103. for a in self.errors: r.append( ' - %s' %a )
  1104. #if not bpy.context.selected_objects:
  1105. # self.warnings.append('YOU DID NOT SELECT ANYTHING TO EXPORT')
  1106. if self.warnings:
  1107. r.append( ' WARNINGS:' )
  1108. for a in self.warnings: r.append( ' - %s' %a )
  1109. if self.messages:
  1110. r.append( ' MESSAGES:' )
  1111. for a in self.messages: r.append( ' - %s' %a )
  1112. if self.paths:
  1113. r.append( ' PATHS:' )
  1114. for a in self.paths: r.append( ' - %s' %a )
  1115. if self.vertices:
  1116. r.append( ' Original Vertices: %s' %self.orig_vertices)
  1117. r.append( ' Exported Vertices: %s' %self.vertices )
  1118. r.append( ' Original Faces: %s' %self.faces )
  1119. r.append( ' Exported Triangles: %s' %self.triangles )
  1120. ## TODO report file sizes, meshes and textures
  1121. for tag in 'meshes lights cameras armatures armature_animations shape_animations materials textures'.split():
  1122. attr = getattr(self, tag)
  1123. if attr:
  1124. name = tag.replace('_',' ').upper()
  1125. r.append( ' %s: %s' %(name, len(attr)) )
  1126. if attr:
  1127. ex.append( ' %s:' %name )
  1128. for a in attr: ex.append( ' . %s' %a )
  1129. txt = '\n'.join( r )
  1130. ex = '\n'.join( ex ) # console only - extended report
  1131. print('_'*80)
  1132. print(txt)
  1133. print(ex)
  1134. print('_'*80)
  1135. return txt
  1136. Report = ReportSingleton()
  1137. class MiniReport(bpy.types.Menu):
  1138. bl_label = "Mini-Report | (see console for full report)"
  1139. def draw(self, context):
  1140. layout = self.layout
  1141. txt = Report.report()
  1142. for line in txt.splitlines():
  1143. layout.label(text=line)
  1144. ## RPython xml dom ends
  1145. def _get_proxy_decimate_mod( ob ):
  1146. proxy = None
  1147. for child in ob.children:
  1148. if child.subcollision and child.name.startswith('DECIMATED'):
  1149. for mod in child.modifiers:
  1150. if mod.type == 'DECIMATE':
  1151. return mod
  1152. def bake_terrain( ob, normalize=True ):
  1153. assert ob.collision_mode == 'TERRAIN'
  1154. terrain = None
  1155. for child in ob.children:
  1156. if child.subcollision and child.name.startswith('TERRAIN'):
  1157. terrain = child
  1158. break
  1159. assert terrain
  1160. data = terrain.to_mesh(bpy.context.scene, True, "PREVIEW")
  1161. raw = [ v.co.z for v in data.vertices ]
  1162. Zmin = min( raw )
  1163. Zmax = max( raw )
  1164. depth = Zmax-Zmin
  1165. m = 1.0 / depth
  1166. rows = []
  1167. i = 0
  1168. for x in range( ob.collision_terrain_x_steps ):
  1169. row = []
  1170. for y in range( ob.collision_terrain_y_steps ):
  1171. v = data.vertices[ i ]
  1172. if normalize:
  1173. z = (v.co.z - Zmin) * m
  1174. else:
  1175. z = v.co.z
  1176. row.append( z )
  1177. i += 1
  1178. if x%2:
  1179. row.reverse() # blender grid prim zig-zags
  1180. rows.append( row )
  1181. return {'data':rows, 'min':Zmin, 'max':Zmax, 'depth':depth}
  1182. def save_terrain_as_NTF( path, ob ): # Tundra format - hardcoded 16x16 patch format
  1183. info = bake_terrain( ob )
  1184. url = os.path.join( path, '%s.ntf' % clean_object_name(ob.data.name) )
  1185. f = open(url, "wb")
  1186. # Header
  1187. buf = array.array("I")
  1188. xs = ob.collision_terrain_x_steps
  1189. ys = ob.collision_terrain_y_steps
  1190. xpatches = int(xs/16)
  1191. ypatches = int(ys/16)
  1192. header = [ xpatches, ypatches ]
  1193. buf.fromlist( header )
  1194. buf.tofile(f)
  1195. # Body
  1196. rows = info['data']
  1197. for x in range( xpatches ):
  1198. for y in range( ypatches ):
  1199. patch = []
  1200. for i in range(16):
  1201. for j in range(16):
  1202. v = rows[ (x*16)+i ][ (y*16)+j ]
  1203. patch.append( v )
  1204. buf = array.array("f")
  1205. buf.fromlist( patch )
  1206. buf.tofile(f)
  1207. f.close()
  1208. path,name = os.path.split(url)
  1209. R = {
  1210. 'url':url, 'min':info['min'], 'max':info['max'], 'path':path, 'name':name,
  1211. 'xpatches': xpatches, 'ypatches': ypatches,
  1212. 'depth':info['depth'],
  1213. }
  1214. return R
  1215. class OgreCollisionOp(bpy.types.Operator):
  1216. '''Ogre Collision'''
  1217. bl_idname = "ogre.set_collision"
  1218. bl_label = "modify collision"
  1219. bl_options = {'REGISTER'}
  1220. MODE = StringProperty(name="toggle mode", maxlen=32, default="disable")
  1221. @classmethod
  1222. def poll(cls, context):
  1223. if context.active_object and context.active_object.type == 'MESH':
  1224. return True
  1225. def get_subcollisions( self, ob, create=True ):
  1226. r = get_subcollisions( ob )
  1227. if not r and create:
  1228. method = getattr(self, 'create_%s'%ob.collision_mode)
  1229. p = method(ob)
  1230. p.name = '%s.%s' %(ob.collision_mode, ob.name)
  1231. p.subcollision = True
  1232. r.append( p )
  1233. return r
  1234. def create_DECIMATED(self, ob):
  1235. child = ob.copy()
  1236. bpy.context.scene.objects.link( child )
  1237. child.matrix_local = mathutils.Matrix()
  1238. child.parent = ob
  1239. child.hide_select = True
  1240. child.draw_type = 'WIRE'
  1241. #child.select = False
  1242. child.lock_location = [True]*3
  1243. child.lock_rotation = [True]*3
  1244. child.lock_scale = [True]*3
  1245. decmod = child.modifiers.new('proxy', type='DECIMATE')
  1246. decmod.ratio = 0.5
  1247. return child
  1248. def create_TERRAIN(self, ob):
  1249. x = ob.collision_terrain_x_steps
  1250. y = ob.collision_terrain_y_steps
  1251. #################################
  1252. #pos = ob.matrix_world.to_translation()
  1253. bpy.ops.mesh.primitive_grid_add(
  1254. x_subdivisions=x,
  1255. y_subdivisions=y,
  1256. size=1.0 ) #, location=pos )
  1257. grid = bpy.context.active_object
  1258. assert grid.name.startswith('Grid')
  1259. grid.collision_terrain_x_steps = x
  1260. grid.collision_terrain_y_steps = y
  1261. #############################
  1262. x,y,z = ob.dimensions
  1263. sx,sy,sz = ob.scale
  1264. x *= 1.0/sx
  1265. y *= 1.0/sy
  1266. z *= 1.0/sz
  1267. grid.scale.x = x/2
  1268. grid.scale.y = y/2
  1269. grid.location.z -= z/2
  1270. grid.data.show_all_edges = True
  1271. grid.draw_type = 'WIRE'
  1272. grid.hide_select = True
  1273. #grid.select = False
  1274. grid.lock_location = [True]*3
  1275. grid.lock_rotation = [True]*3
  1276. grid.lock_scale = [True]*3
  1277. grid.parent = ob
  1278. bpy.context.scene.objects.active = ob
  1279. mod = grid.modifiers.new(name='temp', type='SHRINKWRAP')
  1280. mod.wrap_method = 'PROJECT'
  1281. mod.use_project_z = True
  1282. mod.target = ob
  1283. mod.cull_face = 'FRONT'
  1284. return grid
  1285. def invoke(self, context, event):
  1286. ob = context.active_object
  1287. game = ob.game
  1288. subtype = None
  1289. if ':' in self.MODE:
  1290. mode, subtype = self.MODE.split(':')
  1291. ##BLENDERBUG##ob.game.collision_bounds_type = subtype # BUG this can not come before
  1292. if subtype in 'BOX SPHERE CYLINDER CONE CAPSULE'.split():
  1293. ob.draw_bounds_type = subtype
  1294. else:
  1295. ob.draw_bounds_type = 'POLYHEDRON'
  1296. ob.game.collision_bounds_type = subtype # BLENDERBUG - this must come after draw_bounds_type assignment
  1297. else:
  1298. mode = self.MODE
  1299. ob.collision_mode = mode
  1300. if ob.data.show_all_edges:
  1301. ob.data.show_all_edges = False
  1302. if ob.show_texture_space:
  1303. ob.show_texture_space = False
  1304. if ob.show_bounds:
  1305. ob.show_bounds = False
  1306. if ob.show_wire:
  1307. ob.show_wire = False
  1308. for child in ob.children:
  1309. if child.subcollision and not child.hide:
  1310. child.hide = True
  1311. if mode == 'NONE':
  1312. game.use_ghost = True
  1313. game.use_collision_bounds = False
  1314. elif mode == 'PRIMITIVE':
  1315. game.use_ghost = False
  1316. game.use_collision_bounds = True
  1317. ob.show_bounds = True
  1318. elif mode == 'MESH':
  1319. game.use_ghost = False
  1320. game.use_collision_bounds = True
  1321. ob.show_wire = True
  1322. if game.collision_bounds_type == 'CONVEX_HULL':
  1323. ob.show_texture_space = True
  1324. else:
  1325. ob.data.show_all_edges = True
  1326. elif mode == 'DECIMATED':
  1327. game.use_ghost = True
  1328. game.use_collision_bounds = False
  1329. game.use_collision_compound = True
  1330. proxy = self.get_subcollisions(ob)[0]
  1331. if proxy.hide: proxy.hide = False
  1332. ob.game.use_collision_compound = True # proxy
  1333. mod = _get_proxy_decimate_mod( ob )
  1334. mod.show_viewport = True
  1335. if not proxy.select: # ugly (but works)
  1336. proxy.hide_select = False
  1337. proxy.select = True
  1338. proxy.hide_select = True
  1339. if game.collision_bounds_type == 'CONVEX_HULL':
  1340. ob.show_texture_space = True
  1341. elif mode == 'TERRAIN':
  1342. game.use_ghost = True
  1343. game.use_collision_bounds = False
  1344. game.use_collision_compound = True
  1345. proxy = self.get_subcollisions(ob)[0]
  1346. if proxy.hide:
  1347. proxy.hide = False
  1348. elif mode == 'COMPOUND':
  1349. game.use_ghost = True
  1350. game.use_collision_bounds = False
  1351. game.use_collision_compound = True
  1352. else:
  1353. assert 0 # unknown mode
  1354. return {'FINISHED'}
  1355. # UI panels
  1356. @UI
  1357. class PANEL_Physics(bpy.types.Panel):
  1358. bl_space_type = 'VIEW_3D'
  1359. bl_region_type = 'UI'
  1360. bl_label = "Physics"
  1361. @classmethod
  1362. def poll(cls, context):
  1363. if context.active_object:
  1364. return True
  1365. else:
  1366. return False
  1367. def draw(self, context):
  1368. layout = self.layout
  1369. ob = context.active_object
  1370. game = ob.game
  1371. if ob.type != 'MESH':
  1372. return
  1373. elif ob.subcollision == True:
  1374. box = layout.box()
  1375. if ob.parent:
  1376. box.label(text='object is a collision proxy for: %s' %ob.parent.name)
  1377. else:
  1378. box.label(text='WARNING: collision proxy missing parent')
  1379. return
  1380. box = layout.box()
  1381. box.prop(ob, 'physics_mode')
  1382. if ob.physics_mode != 'NONE':
  1383. box.prop(game, 'mass', text='Mass')
  1384. box.prop(ob, 'physics_friction', text='Friction', slider=True)
  1385. box.prop(ob, 'physics_bounce', text='Bounce', slider=True)
  1386. box.label(text="Damping:")
  1387. box.prop(game, 'damping', text='Translation', slider=True)
  1388. box.prop(game, 'rotation_damping', text='Rotation', slider=True)
  1389. box.label(text="Velocity:")
  1390. box.prop(game, "velocity_min", text="Minimum")
  1391. box.prop(game, "velocity_max", text="Maximum")
  1392. @UI
  1393. class PANEL_Collision(bpy.types.Panel):
  1394. bl_space_type = 'VIEW_3D'
  1395. bl_region_type = 'UI'
  1396. bl_label = "Collision"
  1397. @classmethod
  1398. def poll(cls, context):
  1399. if context.active_object:
  1400. return True
  1401. else:
  1402. return False
  1403. def draw(self, context):
  1404. layout = self.layout
  1405. ob = context.active_object
  1406. game = ob.game
  1407. if ob.type != 'MESH':
  1408. return
  1409. elif ob.subcollision == True:
  1410. box = layout.box()
  1411. if ob.parent:
  1412. box.label(text='object is a collision proxy for: %s' %ob.parent.name)
  1413. else:
  1414. box.label(text='WARNING: collision proxy missing parent')
  1415. return
  1416. mode = ob.collision_mode
  1417. if mode == 'NONE':
  1418. box = layout.box()
  1419. op = box.operator( 'ogre.set_collision', text='Enable Collision', icon='PHYSICS' )
  1420. op.MODE = 'PRIMITIVE:%s' %game.collision_bounds_type
  1421. else:
  1422. prim = game.collision_bounds_type
  1423. box = layout.box()
  1424. op = box.operator( 'ogre.set_collision', text='Disable Collision', icon='X' )
  1425. op.MODE = 'NONE'
  1426. box.prop(game, "collision_margin", text="Collision Margin", slider=True)
  1427. box = layout.box()
  1428. if mode == 'PRIMITIVE':
  1429. box.label(text='Primitive: %s' %prim)
  1430. else:
  1431. box.label(text='Primitive')
  1432. row = box.row()
  1433. _icons = {
  1434. 'BOX':'MESH_CUBE', 'SPHERE':'MESH_UVSPHERE', 'CYLINDER':'MESH_CYLINDER',
  1435. 'CONE':'MESH_CONE', 'CAPSULE':'META_CAPSULE'}
  1436. for a in 'BOX SPHERE CYLINDER CONE CAPSULE'.split():
  1437. if prim == a and mode == 'PRIMITIVE':
  1438. op = row.operator( 'ogre.set_collision', text='', icon=_icons[a], emboss=True )
  1439. op.MODE = 'PRIMITIVE:%s' %a
  1440. else:
  1441. op = row.operator( 'ogre.set_collision', text='', icon=_icons[a], emboss=False )
  1442. op.MODE = 'PRIMITIVE:%s' %a
  1443. box = layout.box()
  1444. if mode == 'MESH': box.label(text='Mesh: %s' %prim.split('_')[0] )
  1445. else: box.label(text='Mesh')
  1446. row = box.row()
  1447. row.label(text='- - - - - - - - - - - - - -')
  1448. _icons = {'TRIANGLE_MESH':'MESH_ICOSPHERE', 'CONVEX_HULL':'SURFACE_NCURVE'}
  1449. for a in 'TRIANGLE_MESH CONVEX_HULL'.split():
  1450. if prim == a and mode == 'MESH':
  1451. op = row.operator( 'ogre.set_collision', text='', icon=_icons[a], emboss=True )
  1452. op.MODE = 'MESH:%s' %a
  1453. else:
  1454. op = row.operator( 'ogre.set_collision', text='', icon=_icons[a], emboss=False )
  1455. op.MODE = 'MESH:%s' %a
  1456. box = layout.box()
  1457. if mode == 'DECIMATED':
  1458. box.label(text='Decimate: %s' %prim.split('_')[0] )
  1459. row = box.row()
  1460. mod = _get_proxy_decimate_mod( ob )
  1461. assert mod # decimate modifier is missing
  1462. row.label(text='Faces: %s' %mod.face_count )
  1463. box.prop( mod, 'ratio', text='' )
  1464. else:
  1465. box.label(text='Decimate')
  1466. row = box.row()
  1467. row.label(text='- - - - - - - - - - - - - -')
  1468. _icons = {'TRIANGLE_MESH':'MESH_ICOSPHERE', 'CONVEX_HULL':'SURFACE_NCURVE'}
  1469. for a in 'TRIANGLE_MESH CONVEX_HULL'.split():
  1470. if prim == a and mode == 'DECIMATED':
  1471. op = row.operator( 'ogre.set_collision', text='', icon=_icons[a], emboss=True )
  1472. op.MODE = 'DECIMATED:%s' %a
  1473. else:
  1474. op = row.operator( 'ogre.set_collision', text='', icon=_icons[a], emboss=False )
  1475. op.MODE = 'DECIMATED:%s' %a
  1476. box = layout.box()
  1477. if mode == 'TERRAIN':
  1478. terrain = get_subcollisions( ob )[0]
  1479. if ob.collision_terrain_x_steps != terrain.collision_terrain_x_steps or ob.collision_terrain_y_steps != terrain.collision_terrain_y_steps:
  1480. op = box.operator( 'ogre.set_collision', text='Rebuild Terrain', icon='MESH_GRID' )
  1481. op.MODE = 'TERRAIN'
  1482. else:
  1483. box.label(text='Terrain:')
  1484. row = box.row()
  1485. row.prop( ob, 'collision_terrain_x_steps', 'X' )
  1486. row.prop( ob, 'collision_terrain_y_steps', 'Y' )
  1487. #box.prop( terrain.modifiers[0], 'offset' ) # gets normalized away
  1488. box.prop( terrain.modifiers[0], 'cull_face', text='Cull' )
  1489. box.prop( terrain, 'location' ) # TODO hide X and Y
  1490. else:
  1491. op = box.operator( 'ogre.set_collision', text='Terrain Collision', icon='MESH_GRID' )
  1492. op.MODE = 'TERRAIN'
  1493. box = layout.box()
  1494. if mode == 'COMPOUND':
  1495. op = box.operator( 'ogre.set_collision', text='Compound Collision', icon='ROTATECOLLECTION' )
  1496. else:
  1497. op = box.operator( 'ogre.set_collision', text='Compound Collision', icon='ROTATECOLLECTION' )
  1498. op.MODE = 'COMPOUND'
  1499. @UI
  1500. class PANEL_Configure(bpy.types.Panel):
  1501. bl_space_type = 'PROPERTIES'
  1502. bl_region_type = 'WINDOW'
  1503. bl_context = "scene"
  1504. bl_label = "Ogre Configuration File"
  1505. def draw(self, context):
  1506. layout = self.layout
  1507. op = layout.operator( 'ogre.save_config', text='update config file', icon='FILE' )
  1508. for tag in _CONFIG_TAGS_:
  1509. layout.prop( context.window_manager, tag )
  1510. ## Pop up dialog for various info/error messages
  1511. popup_message = ""
  1512. class PopUpDialogOperator(bpy.types.Operator):
  1513. bl_idname = "object.popup_dialog_operator"
  1514. bl_label = "blender2ogre"
  1515. def __init__(self):
  1516. print("dialog Start")
  1517. def __del__(self):
  1518. print("dialog End")
  1519. def execute(self, context):
  1520. print ("execute")
  1521. return {'RUNNING_MODAL'}
  1522. def draw(self, context):
  1523. # todo: Make this bigger and center on screen.
  1524. # Blender UI stuff seems quite complex, would
  1525. # think that showing a dialog with a message thath
  1526. # does not hide when mouse is moved would be simpler!
  1527. global popup_message
  1528. layout = self.layout
  1529. col = layout.column()
  1530. col.label(popup_message, 'ERROR')
  1531. def invoke(self, context, event):
  1532. wm = context.window_manager
  1533. wm.invoke_popup(self)
  1534. wm.modal_handler_add(self)
  1535. return {'RUNNING_MODAL'}
  1536. def modal(self, context, event):
  1537. # Close
  1538. if event.type == 'LEFTMOUSE':
  1539. print ("Left mouse")
  1540. return {'FINISHED'}
  1541. # Close
  1542. elif event.type in ('RIGHTMOUSE', 'ESC'):
  1543. print ("right mouse")
  1544. return {'FINISHED'}
  1545. print("running modal")
  1546. return {'RUNNING_MODAL'}
  1547. def show_dialog(message):
  1548. global popup_message
  1549. popup_message = message
  1550. bpy.ops.object.popup_dialog_operator('INVOKE_DEFAULT')
  1551. ## Game Logic Documentation
  1552. _game_logic_intro_doc_ = '''
  1553. Hijacking the BGE
  1554. Blender contains a fully functional game engine (BGE) that is highly useful for learning the concepts of game programming by breaking it down into three simple parts: Sensor, Controller, and Actuator. An Ogre based game engine will likely have similar concepts in its internal API and game logic scripting. Without a custom interface to define game logic, very often game designers may have to resort to having programmers implement their ideas in purely handwritten script. This is prone to breakage because object names then end up being hard-coded. Not only does this lead to non-reusable code, its also a slow process. Why should we have to resort to this when Blender already contains a very rich interface for game logic? By hijacking a subset of the BGE interface we can make this workflow between game designer and game programmer much better.
  1555. The OgreDocScene format can easily be extened to include extra game logic data. While the BGE contains some features that can not be easily mapped to other game engines, there are many are highly useful generic features we can exploit, including many of the Sensors and Actuators. Blender uses the paradigm of: 1. Sensor -> 2. Controller -> 3. Actuator. In pseudo-code, this can be thought of as: 1. on-event -> 2. conditional logic -> 3. do-action. The designer is most often concerned with the on-events (the Sensors), and the do-actions (the Actuators); and the BGE interface provides a clear way for defining and editing those. Its a harder task to provide a good interface for the conditional logic (Controller), that is flexible enough to fit everyones different Ogre engine and requirements, so that is outside the scope of this exporter at this time. A programmer will still be required to fill the gap between Sensor and Actuator, but hopefully his work is greatly reduced and can write more generic/reuseable code.
  1556. The rules for which Sensors trigger which Actuators is left undefined, as explained above we are hijacking the BGE interface not trying to export and reimplement everything. BGE Controllers and all links are ignored by the exporter, so whats the best way to define Sensor/Actuator relationships? One convention that seems logical is to group Sensors and Actuators by name. More complex syntax could be used in Sensor/Actuators names, or they could be completely ignored and instead all the mapping is done by the game programmer using other rules. This issue is not easily solved so designers and the engine programmers will have to decide upon their own conventions, there is no one size fits all solution.
  1557. '''
  1558. _ogre_logic_types_doc_ = '''
  1559. Supported Sensors:
  1560. . Collision
  1561. . Near
  1562. . Radar
  1563. . Touching
  1564. . Raycast
  1565. . Message
  1566. Supported Actuators:
  1567. . Shape Action*
  1568. . Edit Object
  1569. . Camera
  1570. . Constraint
  1571. . Message
  1572. . Motion
  1573. . Sound
  1574. . Visibility
  1575. *note: Shape Action
  1576. The most common thing a designer will want to do is have an event trigger an animation. The BGE contains an Actuator called "Shape Action", with useful properties like: start/end frame, and blending. It also contains a property called "Action" but this is hidden because the exporter ignores action names and instead uses the names of NLA strips when exporting Ogre animation tracks. The current workaround is to hijack the "Frame Property" attribute and change its name to "animation". The designer can then simply type the name of the animation track (NLA strip). Any custom syntax could actually be implemented here for calling animations, its up to the engine programmer to define how this field will be used. For example: "*.explode" could be implemented to mean "on all objects" play the "explode" animation.
  1577. '''
  1578. class _WrapLogic(object):
  1579. SwapName = { 'frame_property' : 'animation' } # custom name hacks
  1580. def __init__(self, node):
  1581. self.node = node
  1582. self.name = node.name
  1583. self.type = node.type
  1584. def widget(self, layout):
  1585. box = layout.box()
  1586. row = box.row()
  1587. row.label( text=self.type )
  1588. row.separator()
  1589. row.prop( self.node, 'name', text='' )
  1590. if self.type in self.TYPES:
  1591. for name in self.TYPES[ self.type ]:
  1592. if name in self.SwapName:
  1593. box.prop( self.node, name, text=self.SwapName[name] )
  1594. else:
  1595. box.prop( self.node, name )
  1596. def xml( self, doc ):
  1597. g = doc.createElement( self.LogicType )
  1598. g.setAttribute('name', self.name)
  1599. g.setAttribute('type', self.type)
  1600. if self.type in self.TYPES:
  1601. for name in self.TYPES[ self.type ]:
  1602. attr = getattr( self.node, name )
  1603. if name in self.SwapName: name = self.SwapName[name]
  1604. a = doc.createElement( 'component' )
  1605. g.appendChild(a)
  1606. a.setAttribute('name', name)
  1607. if attr is None: a.setAttribute('type', 'POINTER' )
  1608. else: a.setAttribute('type', type(attr).__name__)
  1609. if type(attr) in (float, int, str, bool): a.setAttribute('value', str(attr))
  1610. elif not attr: a.setAttribute('value', '') # None case
  1611. elif hasattr(attr,'filepath'): a.setAttribute('value', attr.filepath)
  1612. elif hasattr(attr,'name'): a.setAttribute('value', attr.name)
  1613. elif hasattr(attr,'x') and hasattr(attr,'y') and hasattr(attr,'z'):
  1614. a.setAttribute('value', '%s %s %s' %(attr.x, attr.y, attr.z))
  1615. else:
  1616. print('ERROR: unknown type', attr)
  1617. return g
  1618. class WrapSensor( _WrapLogic ):
  1619. LogicType = 'sensor'
  1620. TYPES = {
  1621. 'COLLISION': ['property'],
  1622. 'MESSAGE' : ['subject'],
  1623. 'NEAR' : ['property', 'distance', 'reset_distance'],
  1624. 'RADAR' : ['property', 'axis', 'angle', 'distance' ],
  1625. 'RAY' : ['ray_type', 'property', 'material', 'axis', 'range', 'use_x_ray'],
  1626. 'TOUCH' : ['material'],
  1627. }
  1628. class WrapActuator( _WrapLogic ):
  1629. LogicType = 'actuator'
  1630. TYPES = {
  1631. 'CAMERA' : ['object', 'height', 'min', 'max', 'axis'],
  1632. 'CONSTRAINT' : ['mode', 'limit', 'limit_min', 'limit_max', 'damping'],
  1633. 'MESSAGE' : ['to_property', 'subject', 'body_message'], #skipping body_type
  1634. 'OBJECT' : 'damping derivate_coefficient force force_max_x force_max_y force_max_z force_min_x force_min_y force_min_z integral_coefficient linear_velocity mode offset_location offset_rotation proportional_coefficient reference_object torque use_local_location use_local_rotation use_local_torque use_servo_limit_x use_servo_limit_y use_servo_limit_z'.split(),
  1635. 'SOUND' : 'cone_inner_angle_3d cone_outer_angle_3d cone_outer_gain_3d distance_3d_max distance_3d_reference gain_3d_max gain_3d_min mode pitch rolloff_factor_3d sound use_sound_3d volume'.split(), # note .sound contains .filepath
  1636. 'VISIBILITY' : 'apply_to_children use_occlusion use_visible'.split(),
  1637. 'SHAPE_ACTION' : 'frame_blend_in frame_end frame_property frame_start mode property use_continue_last_frame'.split(),
  1638. 'EDIT_OBJECT' : 'dynamic_operation linear_velocity mass mesh mode object time track_object use_3d_tracking use_local_angular_velocity use_local_linear_velocity use_replace_display_mesh use_replace_physics_mesh'.split(),
  1639. }
  1640. class _OgreMatPass( object ):
  1641. bl_space_type = 'PROPERTIES'
  1642. bl_region_type = 'WINDOW'
  1643. bl_context = "material"
  1644. @classmethod
  1645. def poll(cls, context):
  1646. if context.active_object and context.active_object.active_material and context.active_object.active_material.use_material_passes:
  1647. return True
  1648. def draw(self, context):
  1649. if not hasattr(context, "material"):
  1650. return
  1651. if not context.active_object:
  1652. return
  1653. if not context.active_object.active_material:
  1654. return
  1655. mat = context.material
  1656. ob = context.object
  1657. slot = context.material_slot
  1658. layout = self.layout
  1659. #layout.label(text=str(self.INDEX))
  1660. if mat.use_material_passes:
  1661. db = layout.box()
  1662. nodes = bpyShaders.get_or_create_material_passes( mat )
  1663. node = nodes[ self.INDEX ]
  1664. split = db.row()
  1665. if node.material: split.prop( node.material, 'use_in_ogre_material_pass', text='' )
  1666. split.prop( node, 'material' )
  1667. if not node.material:
  1668. op = split.operator( 'ogre.helper_create_attach_material_layer', icon="PLUS", text='' )
  1669. op.INDEX = self.INDEX
  1670. if node.material and node.material.use_in_ogre_material_pass:
  1671. dbb = db.box()
  1672. ogre_material_panel( dbb, node.material, parent=mat )
  1673. ogre_material_panel_extra( dbb, node.material )
  1674. class _create_new_material_layer_helper(bpy.types.Operator):
  1675. '''helper to create new material layer'''
  1676. bl_idname = "ogre.helper_create_attach_material_layer"
  1677. bl_label = "creates and assigns new material to layer"
  1678. bl_options = {'REGISTER'}
  1679. INDEX = IntProperty(name="material layer index", description="index", default=0, min=0, max=8)
  1680. @classmethod
  1681. def poll(cls, context):
  1682. if context.active_object and context.active_object.active_material and context.active_object.active_material.use_material_passes:
  1683. return True
  1684. def execute(self, context):
  1685. mat = context.active_object.active_material
  1686. nodes = bpyShaders.get_or_create_material_passes( mat )
  1687. node = nodes[ self.INDEX ]
  1688. node.material = bpy.data.materials.new( name='%s.LAYER%s'%(mat.name,self.INDEX) )
  1689. node.material.use_fixed_pipeline = False
  1690. node.material.offset_z = (self.INDEX*2) + 2 # nudge each pass by 2
  1691. return {'FINISHED'}
  1692. # UI panels continues
  1693. @UI
  1694. class PANEL_properties_window_ogre_material( bpy.types.Panel ):
  1695. bl_space_type = 'PROPERTIES'
  1696. bl_region_type = 'WINDOW'
  1697. bl_context = "material"
  1698. bl_label = "Ogre Material (base pass)"
  1699. @classmethod
  1700. def poll( self, context ):
  1701. if not hasattr(context, "material"): return False
  1702. if not context.active_object: return False
  1703. if not context.active_object.active_material: return False
  1704. return True
  1705. def draw(self, context):
  1706. mat = context.material
  1707. ob = context.object
  1708. slot = context.material_slot
  1709. layout = self.layout
  1710. if not mat.use_material_passes:
  1711. box = layout.box()
  1712. box.operator( 'ogre.force_setup_material_passes', text="Ogre Material Layers", icon='SCENE_DATA' )
  1713. ogre_material_panel( layout, mat )
  1714. ogre_material_panel_extra( layout, mat )
  1715. @UI
  1716. class MatPass1( _OgreMatPass, bpy.types.Panel ): INDEX = 0; bl_label = "Ogre Material (pass%s)"%str(INDEX+1)
  1717. @UI
  1718. class MatPass2( _OgreMatPass, bpy.types.Panel ): INDEX = 1; bl_label = "Ogre Material (pass%s)"%str(INDEX+1)
  1719. @UI
  1720. class MatPass3( _OgreMatPass, bpy.types.Panel ): INDEX = 2; bl_label = "Ogre Material (pass%s)"%str(INDEX+1)
  1721. @UI
  1722. class MatPass4( _OgreMatPass, bpy.types.Panel ): INDEX = 3; bl_label = "Ogre Material (pass%s)"%str(INDEX+1)
  1723. @UI
  1724. class MatPass5( _OgreMatPass, bpy.types.Panel ): INDEX = 4; bl_label = "Ogre Material (pass%s)"%str(INDEX+1)
  1725. @UI
  1726. class MatPass6( _OgreMatPass, bpy.types.Panel ): INDEX = 5; bl_label = "Ogre Material (pass%s)"%str(INDEX+1)
  1727. @UI
  1728. class MatPass7( _OgreMatPass, bpy.types.Panel ): INDEX = 6; bl_label = "Ogre Material (pass%s)"%str(INDEX+1)
  1729. @UI
  1730. class MatPass8( _OgreMatPass, bpy.types.Panel ): INDEX = 7; bl_label = "Ogre Material (pass%s)"%str(INDEX+1)
  1731. @UI
  1732. class PANEL_Textures(bpy.types.Panel):
  1733. bl_space_type = 'PROPERTIES'
  1734. bl_region_type = 'WINDOW'
  1735. bl_context = "texture"
  1736. bl_label = "Ogre Texture"
  1737. @classmethod
  1738. def poll(cls, context):
  1739. if not hasattr(context, "texture_slot"):
  1740. return False
  1741. else: return True
  1742. def draw(self, context):
  1743. #if not hasattr(context, "texture_slot"):
  1744. # return False
  1745. layout = self.layout
  1746. #idblock = context_tex_datablock(context)
  1747. slot = context.texture_slot
  1748. if not slot or not slot.texture:
  1749. return
  1750. btype = slot.blend_type # todo: fix this hack if/when slots support pyRNA
  1751. ex = False; texop = None
  1752. if btype in TextureUnit.colour_op:
  1753. if btype=='MIX' and slot.use_map_alpha and not slot.use_stencil:
  1754. if slot.diffuse_color_factor >= 1.0:
  1755. texop = 'alpha_blend'
  1756. else:
  1757. texop = TextureUnit.colour_op_ex[ btype ]
  1758. ex = True
  1759. elif btype=='MIX' and slot.use_map_alpha and slot.use_stencil:
  1760. texop = 'blend_current_alpha'; ex=True
  1761. elif btype=='MIX' and not slot.use_map_alpha and slot.use_stencil:
  1762. texop = 'blend_texture_alpha'; ex=True
  1763. else:
  1764. texop = TextureUnit.colour_op[ btype ]
  1765. elif btype in TextureUnit.colour_op_ex:
  1766. texop = TextureUnit.colour_op_ex[ btype ]
  1767. ex = True
  1768. box = layout.box()
  1769. row = box.row()
  1770. if texop:
  1771. if ex:
  1772. row.prop(slot, "blend_type", text=texop, icon='NEW')
  1773. else:
  1774. row.prop(slot, "blend_type", text=texop)
  1775. else:
  1776. row.prop(slot, "blend_type", text='(invalid option)')
  1777. if btype == 'MIX':
  1778. row.prop(slot, "use_stencil", text="")
  1779. row.prop(slot, "use_map_alpha", text="")
  1780. if texop == 'blend_manual':
  1781. row = box.row()
  1782. row.label(text="Alpha:")
  1783. row.prop(slot, "diffuse_color_factor", text="")
  1784. if hasattr(slot.texture, 'image') and slot.texture.image:
  1785. row = box.row()
  1786. n = '(invalid option)'
  1787. if slot.texture.extension in TextureUnit.tex_address_mode:
  1788. n = TextureUnit.tex_address_mode[ slot.texture.extension ]
  1789. row.prop(slot.texture, "extension", text=n)
  1790. if slot.texture.extension == 'CLIP':
  1791. row.prop(slot, "color", text="Border Color")
  1792. row = box.row()
  1793. if slot.texture_coords == 'UV':
  1794. row.prop(slot, "texture_coords", text="", icon='GROUP_UVS')
  1795. row.prop(slot, "uv_layer", text='Layer')
  1796. elif slot.texture_coords == 'REFLECTION':
  1797. row.prop(slot, "texture_coords", text="", icon='MOD_UVPROJECT')
  1798. n = '(invalid option)'
  1799. if slot.mapping in 'FLAT SPHERE'.split(): n = ''
  1800. row.prop(slot, "mapping", text=n)
  1801. else:
  1802. row.prop(slot, "texture_coords", text="(invalid mapping option)")
  1803. # Animation and offset options
  1804. split = layout.row()
  1805. box = split.box()
  1806. box.prop(slot, "offset", text="XY=offset, Z=rotation")
  1807. box = split.box()
  1808. box.prop(slot, "scale", text="XY=scale (Z ignored)")
  1809. box = layout.box()
  1810. row = box.row()
  1811. row.label(text='scrolling animation')
  1812. # Can't use if its enabled by default row.prop(slot, "use_map_density", text="")
  1813. row.prop(slot, "use_map_scatter", text="")
  1814. row = box.row()
  1815. row.prop(slot, "density_factor", text="X")
  1816. row.prop(slot, "emission_factor", text="Y")
  1817. box = layout.box()
  1818. row = box.row()
  1819. row.label(text='rotation animation')
  1820. row.prop(slot, "emission_color_factor", text="")
  1821. row.prop(slot, "use_from_dupli", text="")
  1822. ## Image magick
  1823. if hasattr(slot.texture, 'image') and slot.texture.image:
  1824. img = slot.texture.image
  1825. box = layout.box()
  1826. row = box.row()
  1827. row.prop( img, 'use_convert_format' )
  1828. if img.use_convert_format:
  1829. row.prop( img, 'convert_format' )
  1830. if img.convert_format == 'jpg':
  1831. box.prop( img, 'jpeg_quality' )
  1832. row = box.row()
  1833. row.prop( img, 'use_color_quantize', text='Reduce Colors' )
  1834. if img.use_color_quantize:
  1835. row.prop( img, 'use_color_quantize_dither', text='dither' )
  1836. row.prop( img, 'color_quantize', text='colors' )
  1837. row = box.row()
  1838. row.prop( img, 'use_resize_half' )
  1839. if not img.use_resize_half:
  1840. row.prop( img, 'use_resize_absolute' )
  1841. if img.use_resize_absolute:
  1842. row = box.row()
  1843. row.prop( img, 'resize_x' )
  1844. row.prop( img, 'resize_y' )
  1845. ## OgreMeshy
  1846. class OgreMeshyPreviewOp(bpy.types.Operator):
  1847. '''helper to open ogremeshy'''
  1848. bl_idname = 'ogremeshy.preview'
  1849. bl_label = "opens ogremeshy in a subprocess"
  1850. bl_options = {'REGISTER'}
  1851. preview = BoolProperty(name="preview", description="fast preview", default=True)
  1852. groups = BoolProperty(name="preview merge groups", description="use merge groups", default=False)
  1853. mesh = BoolProperty(name="update mesh", description="update mesh (disable for fast material preview", default=True)
  1854. @classmethod
  1855. def poll(cls, context):
  1856. if context.active_object and context.active_object.type in ('MESH','EMPTY') and context.mode != 'EDIT_MESH':
  1857. if context.active_object.type == 'EMPTY' and context.active_object.dupli_type != 'GROUP':
  1858. return False
  1859. else:
  1860. return True
  1861. def execute(self, context):
  1862. Report.reset()
  1863. Report.messages.append('running %s' %CONFIG['OGRE_MESHY'])
  1864. if sys.platform.startswith('linux'):
  1865. # If OgreMeshy ends with .exe, set the path for preview meshes to
  1866. # the user's wine directory, otherwise to /tmp.
  1867. if CONFIG['OGRE_MESHY'].endswith('.exe'):
  1868. path = '%s/.wine/drive_c/tmp' % os.environ['HOME']
  1869. else:
  1870. path = '/tmp'
  1871. elif sys.platform.startswith('darwin') or sys.platform.startswith('freebsd'):
  1872. path = '/tmp'
  1873. else:
  1874. path = 'C:\\tmp'
  1875. mat = None
  1876. mgroup = merged = None
  1877. umaterials = []
  1878. if context.active_object.type == 'MESH':
  1879. mat = context.active_object.active_material
  1880. elif context.active_object.type == 'EMPTY': # assume group
  1881. obs = []
  1882. for e in context.selected_objects:
  1883. if e.type != 'EMPTY' and e.dupli_group: continue
  1884. grp = e.dupli_group
  1885. subs = []
  1886. for o in grp.objects:
  1887. if o.type=='MESH': subs.append( o )
  1888. if subs:
  1889. m = merge_objects( subs, transform=e.matrix_world )
  1890. obs.append( m )
  1891. if obs:
  1892. merged = merge_objects( obs )
  1893. umaterials = dot_mesh( merged, path=path, force_name='preview' )
  1894. for o in obs: context.scene.objects.unlink(o)
  1895. if not self.mesh:
  1896. for ob in context.selected_objects:
  1897. if ob.type == 'MESH':
  1898. for mat in ob.data.materials:
  1899. if mat and mat not in umaterials: umaterials.append( mat )
  1900. if not merged:
  1901. mgroup = MeshMagick.get_merge_group( context.active_object )
  1902. if not mgroup and self.groups:
  1903. group = get_merge_group( context.active_object )
  1904. if group:
  1905. print('--------------- has merge group ---------------' )
  1906. merged = merge_group( group )
  1907. else:
  1908. print('--------------- NO merge group ---------------' )
  1909. elif len(context.selected_objects)>1 and context.selected_objects:
  1910. merged = merge_objects( context.selected_objects )
  1911. if mgroup:
  1912. for ob in mgroup.objects:
  1913. nmats = dot_mesh( ob, path=path )
  1914. for m in nmats:
  1915. if m not in umaterials: umaterials.append( m )
  1916. MeshMagick.merge( mgroup, path=path, force_name='preview' )
  1917. elif merged:
  1918. umaterials = dot_mesh( merged, path=path, force_name='preview' )
  1919. else:
  1920. umaterials = dot_mesh( context.active_object, path=path, force_name='preview' )
  1921. if mat or umaterials:
  1922. #CONFIG['TOUCH_TEXTURES'] = True
  1923. #CONFIG['PATH'] = path # TODO deprecate
  1924. data = ''
  1925. for umat in umaterials:
  1926. data += generate_material( umat, path=path, copy_programs=True, touch_textures=True ) # copies shader programs to path
  1927. f=open( os.path.join( path, 'preview.material' ), 'wb' )
  1928. f.write( bytes(data,'utf-8') ); f.close()
  1929. if merged: context.scene.objects.unlink( merged )
  1930. if sys.platform.startswith('linux') or sys.platform.startswith('darwin') or sys.platform.startswith('freebsd'):
  1931. if CONFIG['OGRE_MESHY'].endswith('.exe'):
  1932. cmd = ['wine', CONFIG['OGRE_MESHY'], 'c:\\tmp\\preview.mesh' ]
  1933. else:
  1934. cmd = [CONFIG['OGRE_MESHY'], '/tmp/preview.mesh']
  1935. print( cmd )
  1936. #subprocess.call(cmd)
  1937. subprocess.Popen(cmd)
  1938. else:
  1939. #subprocess.call([CONFIG_OGRE_MESHY, 'C:\\tmp\\preview.mesh'])
  1940. subprocess.Popen( [CONFIG['OGRE_MESHY'], 'C:\\tmp\\preview.mesh'] )
  1941. Report.show()
  1942. return {'FINISHED'}
  1943. ## Ogre Documentation to UI
  1944. _OGRE_DOCS_ = []
  1945. def ogredoc( cls ):
  1946. tag = cls.__name__.split('_ogredoc_')[-1]
  1947. cls.bl_label = tag.replace('_', ' ')
  1948. _OGRE_DOCS_.append( cls )
  1949. return cls
  1950. class INFO_MT_ogre_helper(bpy.types.Menu):
  1951. bl_label = '_overloaded_'
  1952. def draw(self, context):
  1953. layout = self.layout
  1954. #row = self.layout.box().split(percentage=0.05)
  1955. #col = row.column(align=False)
  1956. #print(dir(col))
  1957. #row.scale_x = 0.1
  1958. #row.alignment = 'RIGHT'
  1959. for line in self.mydoc.splitlines():
  1960. if line.strip():
  1961. for ww in wordwrap( line ): layout.label(text=ww)
  1962. layout.separator()
  1963. class INFO_MT_ogre_docs(bpy.types.Menu):
  1964. bl_label = "Ogre Help"
  1965. def draw(self, context):
  1966. layout = self.layout
  1967. for cls in _OGRE_DOCS_:
  1968. layout.menu( cls.__name__ )
  1969. layout.separator()
  1970. layout.separator()
  1971. layout.label(text='bug reports to: [email protected]')
  1972. class INFO_MT_ogre_shader_pass_attributes(bpy.types.Menu):
  1973. bl_label = "Shader-Pass"
  1974. def draw(self, context):
  1975. layout = self.layout
  1976. for cls in _OGRE_SHADER_REF_:
  1977. layout.menu( cls.__name__ )
  1978. class INFO_MT_ogre_shader_texture_attributes(bpy.types.Menu):
  1979. bl_label = "Shader-Texture"
  1980. def draw(self, context):
  1981. layout = self.layout
  1982. for cls in _OGRE_SHADER_REF_TEX_:
  1983. layout.menu( cls.__name__ )
  1984. @ogredoc
  1985. class _ogredoc_Installing( INFO_MT_ogre_helper ):
  1986. mydoc = _doc_installing_
  1987. @ogredoc
  1988. class _ogredoc_FAQ( INFO_MT_ogre_helper ):
  1989. mydoc = _faq_
  1990. @ogredoc
  1991. class _ogredoc_Animation_System( INFO_MT_ogre_helper ):
  1992. mydoc = '''
  1993. Armature Animation System | OgreDotSkeleton
  1994. Quick Start:
  1995. 1. select your armature and set a single keyframe on the object (loc,rot, or scl)
  1996. . note, this step is just a hack for creating an action so you can then create an NLA track.
  1997. . do not key in pose mode, unless you want to only export animation on the keyed bones.
  1998. 2. open the NLA, and convert the action into an NLA strip
  1999. 3. name the NLA strip(s)
  2000. 4. set the in and out frames for each strip ( the strip name becomes the Ogre track name )
  2001. How it Works:
  2002. The NLA strips can be blank, they are only used to define Ogre track names, and in and out frame ranges. You are free to animate the armature with constraints (no baking required), or you can used baked animation and motion capture. Blending that is driven by the NLA is also supported, if you don't want blending, put space between each strip.
  2003. The OgreDotSkeleton (.skeleton) format supports multiple named tracks that can contain some or all of the bones of an armature. This feature can be exploited by a game engine for segmenting and animation blending. For example: lets say we want to animate the upper torso independently of the lower body while still using a single armature. This can be done by hijacking the NLA of the armature.
  2004. Advanced NLA Hijacking (selected-bones-animation):
  2005. . define an action and keyframe only the bones you want to 'group', ie. key all the upper torso bones
  2006. . import the action into the NLA
  2007. . name the strip (this becomes the track name in Ogre)
  2008. . adjust the start and end frames of each strip
  2009. ( you may use multiple NLA tracks, multiple strips per-track is ok, and strips may overlap in time )
  2010. '''
  2011. @ogredoc
  2012. class _ogredoc_Physics( INFO_MT_ogre_helper ):
  2013. mydoc = '''
  2014. Ogre Dot Scene + BGE Physics
  2015. extended format including external collision mesh, and BGE physics settings
  2016. <node name="...">
  2017. <entity name="..." meshFile="..." collisionFile="..." collisionPrim="..." [and all BGE physics attributes] />
  2018. </node>
  2019. collisionFile : sets path to .mesh that is used for collision (ignored if collisionPrim is set)
  2020. collisionPrim : sets optimal collision type [ cube, sphere, capsule, cylinder ]
  2021. *these collisions are static meshes, animated deforming meshes should give the user a warning that they have chosen a static mesh collision type with an object that has an armature
  2022. Blender Collision Setup:
  2023. 1. If a mesh object has a child mesh with a name starting with 'collision', then the child becomes the collision mesh for the parent mesh.
  2024. 2. If 'Collision Bounds' game option is checked, the bounds type [box, sphere, etc] is used. This will override above rule.
  2025. 3. Instances (and instances generated by optimal array modifier) will share the same collision type of the first instance, you DO NOT need to set the collision type for each instance.
  2026. '''
  2027. @ogredoc
  2028. class _ogredoc_Bugs( INFO_MT_ogre_helper ):
  2029. mydoc = '''
  2030. Known Issues:
  2031. . shape animation breaks when using modifiers that change the vertex count
  2032. (Any modifier that changes the vertex count is bad with shape anim or armature anim)
  2033. . never rename the nodes created by enabling Ogre-Material-Layers
  2034. . never rename collision proxy meshes created by the Collision Panel
  2035. . lighting in Tundra is not excatly the same as in Blender
  2036. Tundra Streaming:
  2037. . only supports streaming transform of up to 10 objects selected objects
  2038. . the 3D view must be shown at the time you open Tundra
  2039. . the same 3D view must be visible to stream data to Tundra
  2040. . only position and scale are updated, a bug on the Tundra side prevents rotation update
  2041. . animation playback is broken if you rename your NLA strips after opening Tundra
  2042. '''
  2043. # Ogre v1.7 Doc
  2044. def _mesh_entity_helper( doc, ob, o ):
  2045. ## extended format - BGE Physics ##
  2046. o.setAttribute('mass', str(ob.game.mass))
  2047. o.setAttribute('mass_radius', str(ob.game.radius))
  2048. o.setAttribute('physics_type', ob.game.physics_type)
  2049. o.setAttribute('actor', str(ob.game.use_actor))
  2050. o.setAttribute('ghost', str(ob.game.use_ghost))
  2051. o.setAttribute('velocity_min', str(ob.game.velocity_min))
  2052. o.setAttribute('velocity_max', str(ob.game.velocity_max))
  2053. o.setAttribute('lock_trans_x', str(ob.game.lock_location_x))
  2054. o.setAttribute('lock_trans_y', str(ob.game.lock_location_y))
  2055. o.setAttribute('lock_trans_z', str(ob.game.lock_location_z))
  2056. o.setAttribute('lock_rot_x', str(ob.game.lock_rotation_x))
  2057. o.setAttribute('lock_rot_y', str(ob.game.lock_rotation_y))
  2058. o.setAttribute('lock_rot_z', str(ob.game.lock_rotation_z))
  2059. o.setAttribute('anisotropic_friction', str(ob.game.use_anisotropic_friction))
  2060. x,y,z = ob.game.friction_coefficients
  2061. o.setAttribute('friction_x', str(x))
  2062. o.setAttribute('friction_y', str(y))
  2063. o.setAttribute('friction_z', str(z))
  2064. o.setAttribute('damping_trans', str(ob.game.damping))
  2065. o.setAttribute('damping_rot', str(ob.game.rotation_damping))
  2066. o.setAttribute('inertia_tensor', str(ob.game.form_factor))
  2067. mesh = ob.data
  2068. # custom user props
  2069. for prop in mesh.items():
  2070. propname, propvalue = prop
  2071. if not propname.startswith('_'):
  2072. user = doc.createElement('user_data')
  2073. o.appendChild( user )
  2074. user.setAttribute( 'name', propname )
  2075. user.setAttribute( 'value', str(propvalue) )
  2076. user.setAttribute( 'type', type(propvalue).__name__ )
  2077. #class _type(bpy.types.IDPropertyGroup):
  2078. # name = StringProperty(name="jpeg format", description="", maxlen=64, default="")
  2079. def get_lights_by_type( T ):
  2080. r = []
  2081. for ob in bpy.context.scene.objects:
  2082. if ob.type=='LAMP':
  2083. if ob.data.type==T: r.append( ob )
  2084. return r
  2085. class _TXML_(object):
  2086. '''
  2087. <component type="EC_Script" sync="1" name="myscript">
  2088. <attribute value="" name="Script ref"/>
  2089. <attribute value="false" name="Run on load"/>
  2090. <attribute value="0" name="Run mode"/>
  2091. <attribute value="" name="Script application name"/>
  2092. <attribute value="" name="Script class name"/>
  2093. </component>
  2094. '''
  2095. def create_tundra_document( self, context ):
  2096. # todo: Make a way in the gui to give prefix for the refs
  2097. # This can be very useful if you want to give deployment URL
  2098. # eg. "http://www.myassets.com/myscene/". By default this needs
  2099. # to be an empty string, it will operate best for local preview
  2100. # and importing the scene content to existing scenes with relative refs.
  2101. proto = ''
  2102. doc = RDocument()
  2103. scn = doc.createElement('scene')
  2104. doc.appendChild( scn )
  2105. # EC_Script
  2106. if 0: # todo: tundra bug (what does this mean?)
  2107. e = doc.createElement( 'entity' )
  2108. doc.documentElement.appendChild( e )
  2109. e.setAttribute('id', len(doc.documentElement.childNodes)+1 )
  2110. c = doc.createElement( 'component' ); e.appendChild( c )
  2111. c.setAttribute( 'type', 'EC_Script' )
  2112. c.setAttribute( 'sync', '1' )
  2113. c.setAttribute( 'name', 'myscript' )
  2114. a = doc.createElement('attribute'); c.appendChild( a )
  2115. a.setAttribute('name', 'Script ref')
  2116. #a.setAttribute('value', "%s%s"%(proto,TUNDRA_GEN_SCRIPT_PATH) )
  2117. a = doc.createElement('attribute'); c.appendChild( a )
  2118. a.setAttribute('name', 'Run on load')
  2119. a.setAttribute('value', 'true' )
  2120. a = doc.createElement('attribute'); c.appendChild( a )
  2121. a.setAttribute('name', 'Run mode')
  2122. a.setAttribute('value', '0' )
  2123. a = doc.createElement('attribute'); c.appendChild( a )
  2124. a.setAttribute('name', 'Script application name')
  2125. a.setAttribute('value', 'blender2ogre' )
  2126. # Check lighting settings
  2127. sun = hemi = None
  2128. if get_lights_by_type('SUN'):
  2129. sun = get_lights_by_type('SUN')[0]
  2130. if get_lights_by_type('HEMI'):
  2131. hemi = get_lights_by_type('HEMI')[0]
  2132. # Environment
  2133. if bpy.context.scene.world.mist_settings.use_mist or sun or hemi:
  2134. # Entity for environment components
  2135. e = doc.createElement( 'entity' )
  2136. doc.documentElement.appendChild( e )
  2137. e.setAttribute('id', len(doc.documentElement.childNodes)+1 )
  2138. # EC_Fog
  2139. c = doc.createElement( 'component' ); e.appendChild( c )
  2140. c.setAttribute( 'type', 'EC_Fog' )
  2141. c.setAttribute( 'sync', '1' )
  2142. c.setAttribute( 'name', 'Fog' )
  2143. a = doc.createElement('attribute'); c.appendChild( a )
  2144. a.setAttribute('name', 'Color')
  2145. if bpy.context.scene.world.mist_settings.use_mist:
  2146. A = bpy.context.scene.world.mist_settings.intensity
  2147. R,G,B = bpy.context.scene.world.horizon_color
  2148. a.setAttribute('value', '%s %s %s %s'%(R,G,B,A))
  2149. else:
  2150. a.setAttribute('value', '0.4 0.4 0.4 1.0')
  2151. if bpy.context.scene.world.mist_settings.use_mist:
  2152. mist = bpy.context.scene.world.mist_settings
  2153. a = doc.createElement('attribute'); c.appendChild( a )
  2154. a.setAttribute('name', 'Start distance')
  2155. a.setAttribute('value', mist.start)
  2156. a = doc.createElement('attribute'); c.appendChild( a )
  2157. a.setAttribute('name', 'End distance')
  2158. a.setAttribute('value', mist.start+mist.depth)
  2159. a = doc.createElement('attribute'); c.appendChild( a )
  2160. a.setAttribute('name', 'Exponential density')
  2161. a.setAttribute('value', 0.001)
  2162. # EC_EnvironmentLight
  2163. c = doc.createElement( 'component' ); e.appendChild( c )
  2164. c.setAttribute( 'type', 'EC_EnvironmentLight' )
  2165. c.setAttribute( 'sync', '1' )
  2166. c.setAttribute( 'name', 'Environment Light' )
  2167. a = doc.createElement('attribute'); c.appendChild( a )
  2168. a.setAttribute('name', 'Sunlight color')
  2169. if sun:
  2170. R,G,B = sun.data.color
  2171. a.setAttribute('value', '%s %s %s 1' %(R,G,B))
  2172. else:
  2173. a.setAttribute('value', '0 0 0 1')
  2174. a = doc.createElement('attribute'); c.appendChild( a )
  2175. a.setAttribute('name', 'Brightness') # brightness of sunlight
  2176. if sun:
  2177. a.setAttribute('value', sun.data.energy*10) # 10=magic
  2178. else:
  2179. a.setAttribute('value', '0')
  2180. a = doc.createElement('attribute'); c.appendChild( a )
  2181. a.setAttribute('name', 'Ambient light color')
  2182. if hemi:
  2183. R,G,B = hemi.data.color * hemi.data.energy * 3.0
  2184. if R>1.0: R=1.0
  2185. if G>1.0: G=1.0
  2186. if B>1.0: B=1.0
  2187. a.setAttribute('value', '%s %s %s 1' %(R,G,B))
  2188. else:
  2189. a.setAttribute('value', '0 0 0 1')
  2190. a = doc.createElement('attribute'); c.appendChild( a )
  2191. a.setAttribute('name', 'Sunlight direction vector')
  2192. a.setAttribute('value', '-0.25 -1.0 -0.25') # TODO, get the sun rotation from blender
  2193. a = doc.createElement('attribute'); c.appendChild( a )
  2194. a.setAttribute('name', 'Sunlight cast shadows')
  2195. a.setAttribute('value', 'true')
  2196. # EC_SkyX
  2197. if context.scene.world.ogre_skyX:
  2198. c = doc.createElement( 'component' ); e.appendChild( c )
  2199. c.setAttribute( 'type', 'EC_SkyX' )
  2200. c.setAttribute( 'sync', '1' )
  2201. c.setAttribute( 'name', 'SkyX' )
  2202. a = doc.createElement('attribute'); a.setAttribute('name', 'Weather (volumetric clouds only)')
  2203. den = (
  2204. context.scene.world.ogre_skyX_cloud_density_x,
  2205. context.scene.world.ogre_skyX_cloud_density_y
  2206. )
  2207. a.setAttribute('value', '%s %s' %den)
  2208. c.appendChild( a )
  2209. config = (
  2210. ('time', 'Time multiplier'),
  2211. ('volumetric_clouds','Volumetric clouds'),
  2212. ('wind','Wind direction'),
  2213. )
  2214. for bname, aname in config:
  2215. a = doc.createElement('attribute')
  2216. a.setAttribute('name', aname)
  2217. s = str( getattr(context.scene.world, 'ogre_skyX_'+bname) )
  2218. a.setAttribute('value', s.lower())
  2219. c.appendChild( a )
  2220. return doc
  2221. # Creates new Tundra entity
  2222. def tundra_entity( self, doc, ob, path='/tmp', collision_proxies=[], parent=None, matrix=None,visible=True ):
  2223. assert not ob.subcollision
  2224. # Tundra TRANSFORM
  2225. if not matrix:
  2226. matrix = ob.matrix_world.copy()
  2227. # todo: Make a way in the gui to give prefix for the refs
  2228. # This can be very useful if you want to give deployment URL
  2229. # eg. "http://www.myassets.com/myscene/". By default this needs
  2230. # to be an empty string, it will operate best for local preview
  2231. # and importing the scene content to existing scenes with relative refs.
  2232. proto = ''
  2233. # Entity
  2234. entityid = uid(ob)
  2235. objectname = clean_object_name(ob.name)
  2236. print(" Creating Tundra Enitity with ID", entityid)
  2237. e = doc.createElement( 'entity' )
  2238. doc.documentElement.appendChild( e )
  2239. e.setAttribute('id', entityid)
  2240. # EC_Name
  2241. print (" - EC_Name with", objectname)
  2242. c = doc.createElement('component'); e.appendChild( c )
  2243. c.setAttribute('type', "EC_Name")
  2244. c.setAttribute('sync', '1')
  2245. a = doc.createElement('attribute'); c.appendChild(a)
  2246. a.setAttribute('name', "name" )
  2247. a.setAttribute('value', objectname )
  2248. a = doc.createElement('attribute'); c.appendChild(a)
  2249. a.setAttribute('name', "description" )
  2250. a.setAttribute('value', "" )
  2251. # EC_Placeable
  2252. print (" - EC_Placeable ")
  2253. c = doc.createElement('component'); e.appendChild( c )
  2254. c.setAttribute('type', "EC_Placeable")
  2255. c.setAttribute('sync', '1')
  2256. a = doc.createElement('attribute'); c.appendChild(a)
  2257. a.setAttribute('name', "Transform" )
  2258. x,y,z = swap(matrix.to_translation())
  2259. loc = '%6f,%6f,%6f' %(x,y,z)
  2260. x,y,z = swap(matrix.to_euler())
  2261. x = math.degrees( x ); y = math.degrees( y ); z = math.degrees( z )
  2262. if ob.type == 'CAMERA':
  2263. x -= 90
  2264. elif ob.type == 'LAMP':
  2265. x += 90
  2266. rot = '%6f,%6f,%6f' %(x,y,z)
  2267. x,y,z = swap(matrix.to_scale())
  2268. scl = '%6f,%6f,%6f' %(abs(x),abs(y),abs(z)) # Tundra2 clamps any negative to zero
  2269. a.setAttribute('value', "%s,%s,%s" %(loc,rot,scl) )
  2270. a = doc.createElement('attribute'); c.appendChild(a)
  2271. a.setAttribute('name', "Show bounding box" )
  2272. a.setAttribute('value', "false" )
  2273. # Don't mark bounding boxes to show in Tundra!
  2274. #if ob.show_bounds or ob.type != 'MESH':
  2275. # a.setAttribute('value', "true" )
  2276. #else:
  2277. # a.setAttribute('value', "false" )
  2278. a = doc.createElement('attribute'); c.appendChild(a)
  2279. a.setAttribute('name', "Visible" )
  2280. if visible:
  2281. a.setAttribute('value', 'true') # overrides children's setting - not good!
  2282. else:
  2283. a.setAttribute('value', 'false')
  2284. a = doc.createElement('attribute'); c.appendChild(a)
  2285. a.setAttribute('name', "Selection layer" )
  2286. a.setAttribute('value', 1)
  2287. # Tundra parenting to EC_Placeable.
  2288. # todo: Verify this inserts correct ent name or id here.
  2289. # <attribute value="" name="Parent entity ref"/>
  2290. # <attribute value="" name="Parent bone name"/>
  2291. if parent:
  2292. a = doc.createElement('attribute'); c.appendChild(a)
  2293. a.setAttribute('name', "Parent entity ref" )
  2294. a.setAttribute('value', parent)
  2295. if ob.type != 'MESH':
  2296. c = doc.createElement('component'); e.appendChild( c )
  2297. c.setAttribute('type', 'EC_Name')
  2298. c.setAttribute('sync', '1')
  2299. a = doc.createElement('attribute'); c.appendChild(a)
  2300. a.setAttribute('name', "name" )
  2301. a.setAttribute('value', objectname)
  2302. # EC_Sound: Supports wav and ogg
  2303. if ob.type == 'SPEAKER':
  2304. print (" - EC_Sound")
  2305. c = doc.createElement('component'); e.appendChild( c )
  2306. c.setAttribute('type', 'EC_Sound')
  2307. c.setAttribute('sync', '1')
  2308. if ob.data.sound:
  2309. abspath = bpy.path.abspath( ob.data.sound.filepath )
  2310. soundpath, soundfile = os.path.split( abspath )
  2311. soundref = "%s%s" % (proto, soundfile)
  2312. print (" Sounds ref:", soundref)
  2313. a = doc.createElement('attribute'); c.appendChild(a)
  2314. a.setAttribute('name', 'Sound ref' )
  2315. a.setAttribute('value', soundref)
  2316. if not os.path.isfile( os.path.join(path,soundfile) ):
  2317. open( os.path.join(path,soundfile), 'wb' ).write( open(abspath,'rb').read() )
  2318. a = doc.createElement('attribute'); c.appendChild(a)
  2319. a.setAttribute('name', 'Sound radius inner' )
  2320. a.setAttribute('value', ob.data.cone_angle_inner)
  2321. a = doc.createElement('attribute'); c.appendChild(a)
  2322. a.setAttribute('name', 'Sound radius outer' )
  2323. a.setAttribute('value', ob.data.cone_angle_outer)
  2324. a = doc.createElement('attribute'); c.appendChild(a)
  2325. a.setAttribute('name', 'Sound gain' )
  2326. a.setAttribute('value', ob.data.volume)
  2327. a = doc.createElement('attribute'); c.appendChild(a)
  2328. a.setAttribute('name', 'Play on load' )
  2329. if ob.data.play_on_load:
  2330. a.setAttribute('value', 'true')
  2331. else:
  2332. a.setAttribute('value', 'false')
  2333. a = doc.createElement('attribute'); c.appendChild(a)
  2334. a.setAttribute('name', 'Loop sound' )
  2335. if ob.data.loop:
  2336. a.setAttribute('value', 'true')
  2337. else:
  2338. a.setAttribute('value', 'false')
  2339. a = doc.createElement('attribute'); c.appendChild(a)
  2340. a.setAttribute('name', 'Spatial' )
  2341. if ob.data.use_spatial:
  2342. a.setAttribute('value', 'true')
  2343. else:
  2344. a.setAttribute('value', 'false')
  2345. # EC_Camera
  2346. ''' todo: This is really not very helpful. Apps define
  2347. camera logic in Tundra. By default you will have
  2348. a freecamera to move around the scene etc. This created
  2349. camera wont be activated except if a script does so.
  2350. Best leave camera (creation) logic for the inworld apps.
  2351. At least remove the default "export cameras" for txml. '''
  2352. if ob.type == 'CAMERA':
  2353. print (" - EC_Camera")
  2354. c = doc.createElement('component'); e.appendChild( c )
  2355. c.setAttribute('type', 'EC_Camera')
  2356. c.setAttribute('sync', '1')
  2357. a = doc.createElement('attribute'); c.appendChild(a)
  2358. a.setAttribute('name', "Up vector" )
  2359. a.setAttribute('value', '0.0 1.0 0.0')
  2360. a = doc.createElement('attribute'); c.appendChild(a)
  2361. a.setAttribute('name', "Near plane" )
  2362. a.setAttribute('value', '0.01')
  2363. a = doc.createElement('attribute'); c.appendChild(a)
  2364. a.setAttribute('name', "Far plane" )
  2365. a.setAttribute('value', '2000')
  2366. a = doc.createElement('attribute'); c.appendChild(a)
  2367. a.setAttribute('name', "Vertical FOV" )
  2368. a.setAttribute('value', '45')
  2369. a = doc.createElement('attribute'); c.appendChild(a)
  2370. a.setAttribute('name', "Aspect ratio" )
  2371. a.setAttribute('value', '')
  2372. NTF = None
  2373. # EC_Rigidbody
  2374. # Any object can have physics, although it needs
  2375. # EC_Placeable to have position etc.
  2376. if ob.physics_mode != 'NONE' or ob.collision_mode != 'NONE':
  2377. TundraTypes = {
  2378. 'BOX' : 0,
  2379. 'SPHERE' : 1,
  2380. 'CYLINDER' : 2,
  2381. 'CONE' : 0, # Missing in Tundra
  2382. 'CAPSULE' : 3,
  2383. 'TRIANGLE_MESH' : 4,
  2384. #'HEIGHT_FIELD': 5, # Missing in Blender
  2385. 'CONVEX_HULL' : 6
  2386. }
  2387. com = doc.createElement('component'); e.appendChild( com )
  2388. com.setAttribute('type', 'EC_RigidBody')
  2389. com.setAttribute('sync', '1')
  2390. # Mass
  2391. # * Does not affect static collision types (TriMesh and ConvexHull)
  2392. # * You can have working collisions with mass 0
  2393. a = doc.createElement('attribute'); com.appendChild( a )
  2394. a.setAttribute('name', 'Mass')
  2395. if ob.physics_mode == 'RIGID_BODY':
  2396. a.setAttribute('value', ob.game.mass)
  2397. else:
  2398. a.setAttribute('value', '0.0')
  2399. SHAPE = a = doc.createElement('attribute'); com.appendChild( a )
  2400. a.setAttribute('name', 'Shape type')
  2401. a.setAttribute('value', TundraTypes[ ob.game.collision_bounds_type ] )
  2402. print (" - EC_RigidBody with shape type", TundraTypes[ob.game.collision_bounds_type])
  2403. M = ob.game.collision_margin
  2404. a = doc.createElement('attribute'); com.appendChild( a )
  2405. a.setAttribute('name', 'Size')
  2406. if ob.game.collision_bounds_type in 'TRIANGLE_MESH CONVEX_HULL'.split():
  2407. a.setAttribute('value', '%s %s %s' %(1.0+M, 1.0+M, 1.0+M) )
  2408. else:
  2409. #x,y,z = swap(ob.matrix_world.to_scale())
  2410. x,y,z = swap(ob.dimensions)
  2411. a.setAttribute('value', '%s %s %s' %(abs(x)+M,abs(y)+M,abs(z)+M) )
  2412. a = doc.createElement('attribute'); com.appendChild( a )
  2413. a.setAttribute('name', 'Collision mesh ref')
  2414. #if ob.game.use_collision_compound:
  2415. if ob.collision_mode == 'DECIMATED':
  2416. proxy = None
  2417. for child in ob.children:
  2418. if child.subcollision and child.name.startswith('DECIMATED'):
  2419. proxy = child; break
  2420. if proxy:
  2421. collisionref = "%s_collision_%s.mesh" % (proto, proxy.data.name)
  2422. a.setAttribute('value', collisionref)
  2423. if proxy not in collision_proxies:
  2424. collision_proxies.append( proxy )
  2425. else:
  2426. print('[WARNINIG]: Collision proxy mesh not found' )
  2427. assert 0
  2428. elif ob.collision_mode == 'TERRAIN':
  2429. NTF = save_terrain_as_NTF( path, ob )
  2430. SHAPE.setAttribute( 'value', '5' ) # HEIGHT_FIELD
  2431. elif ob.type == 'MESH':
  2432. # todo: Remove this. There is no need to set mesh collision ref
  2433. # if TriMesh or ConvexHull is used, it will be auto picked from EC_Mesh
  2434. # in the same Entity.
  2435. collisionref = "%s%s.mesh" % (proto, clean_object_name(ob.data.name))
  2436. a.setAttribute('value', collisionref)
  2437. a = doc.createElement('attribute'); com.appendChild( a )
  2438. a.setAttribute('name', 'Friction')
  2439. #avg = sum( ob.game.friction_coefficients ) / 3.0
  2440. a.setAttribute('value', ob.physics_friction)
  2441. a = doc.createElement('attribute'); com.appendChild( a )
  2442. a.setAttribute('name', 'Restitution')
  2443. a.setAttribute('value', ob.physics_bounce)
  2444. a = doc.createElement('attribute'); com.appendChild( a )
  2445. a.setAttribute('name', 'Linear damping')
  2446. a.setAttribute('value', ob.game.damping)
  2447. a = doc.createElement('attribute'); com.appendChild( a )
  2448. a.setAttribute('name', 'Angular damping')
  2449. a.setAttribute('value', ob.game.rotation_damping)
  2450. a = doc.createElement('attribute'); com.appendChild( a )
  2451. a.setAttribute('name', 'Linear factor')
  2452. a.setAttribute('value', '1.0 1.0 1.0')
  2453. a = doc.createElement('attribute'); com.appendChild( a )
  2454. a.setAttribute('name', 'Angular factor')
  2455. a.setAttribute('value', '1.0 1.0 1.0')
  2456. a = doc.createElement('attribute'); com.appendChild( a )
  2457. a.setAttribute('name', 'Kinematic')
  2458. a.setAttribute('value', 'false' )
  2459. # todo: Find out what Phantom actually means and if this
  2460. # needs to be set for NONE collision rigids. I don't actually
  2461. # see any reason to make EC_RigidBody if collision is NONE
  2462. a = doc.createElement('attribute'); com.appendChild( a )
  2463. a.setAttribute('name', 'Phantom')
  2464. if ob.collision_mode == 'NONE':
  2465. a.setAttribute('value', 'true' )
  2466. else:
  2467. a.setAttribute('value', 'false' )
  2468. a = doc.createElement('attribute'); com.appendChild( a )
  2469. a.setAttribute('name', 'Draw Debug')
  2470. a.setAttribute('value', 'false' )
  2471. # Never mark rigids to have draw debug, it can
  2472. # be toggled in tundra for visual debugging.
  2473. #if ob.collision_mode == 'NONE':
  2474. # a.setAttribute('value', 'false' )
  2475. #else:
  2476. # a.setAttribute('value', 'true' )
  2477. a = doc.createElement('attribute'); com.appendChild( a )
  2478. a.setAttribute('name', 'Linear velocity')
  2479. a.setAttribute('value', '0.0 0.0 0.0')
  2480. a = doc.createElement('attribute'); com.appendChild( a )
  2481. a.setAttribute('name', 'Angular velocity')
  2482. a.setAttribute('value', '0.0 0.0 0.0')
  2483. a = doc.createElement('attribute'); com.appendChild( a )
  2484. a.setAttribute('name', 'Collision Layer')
  2485. a.setAttribute('value', -1)
  2486. a = doc.createElement('attribute'); com.appendChild( a )
  2487. a.setAttribute('name', 'Collision Mask')
  2488. a.setAttribute('value', -1)
  2489. # EC_Terrain
  2490. if NTF:
  2491. xp = NTF['xpatches']
  2492. yp = NTF['ypatches']
  2493. depth = NTF['depth']
  2494. print (" - EC_Terrain")
  2495. com = doc.createElement('component'); e.appendChild( com )
  2496. com.setAttribute('type', 'EC_Terrain')
  2497. com.setAttribute('sync', '1')
  2498. a = doc.createElement('attribute'); com.appendChild( a )
  2499. a.setAttribute('name', 'Transform')
  2500. x,y,z = ob.dimensions
  2501. sx,sy,sz = ob.scale
  2502. x *= 1.0/sx
  2503. y *= 1.0/sy
  2504. z *= 1.0/sz
  2505. #trans = '%s,%s,%s,' %(-xp/4, -z/2, -yp/4)
  2506. trans = '%s,%s,%s,' %(-xp/4, -depth, -yp/4)
  2507. # scaling in Tundra happens after translation
  2508. nx = x/(xp*16)
  2509. ny = y/(yp*16)
  2510. trans += '0,0,0,%s,%s,%s' %(nx,depth, ny)
  2511. a.setAttribute('value', trans )
  2512. a = doc.createElement('attribute'); com.appendChild( a )
  2513. a.setAttribute('name', 'Grid Width')
  2514. a.setAttribute('value', xp)
  2515. a = doc.createElement('attribute'); com.appendChild( a )
  2516. a.setAttribute('name', 'Grid Height')
  2517. a.setAttribute('value', yp)
  2518. a = doc.createElement('attribute'); com.appendChild( a )
  2519. a.setAttribute('name', 'Tex. U scale')
  2520. a.setAttribute('value', 1.0)
  2521. a = doc.createElement('attribute'); com.appendChild( a )
  2522. a.setAttribute('name', 'Tex. V scale')
  2523. a.setAttribute('value', 1.0)
  2524. a = doc.createElement('attribute'); com.appendChild( a )
  2525. a.setAttribute('name', 'Material')
  2526. a.setAttribute('value', '')
  2527. for i in range(4):
  2528. a = doc.createElement('attribute'); com.appendChild( a )
  2529. a.setAttribute('name', 'Texture %s' %i)
  2530. a.setAttribute('value', '')
  2531. # todo: Check that NTF['name'] is the actual valid asset ref
  2532. # and not the disk path.
  2533. heightmapref = "%s%s" % (proto, NTF['name'])
  2534. print (" Heightmap ref:", heightmapref)
  2535. a = doc.createElement('attribute'); com.appendChild( a )
  2536. a.setAttribute('name', 'Heightmap')
  2537. a.setAttribute('value', heightmapref )
  2538. # Enitity XML generation done, return the element.
  2539. return e
  2540. # EC_Mesh
  2541. def tundra_mesh( self, e, ob, url, exported_meshes ):
  2542. # todo: Make a way in the gui to give prefix for the refs
  2543. # This can be very useful if you want to give deployment URL
  2544. # eg. "http://www.myassets.com/myscene/". By default this needs
  2545. # to be an empty string, it will operate best for local preview
  2546. # and importing the scene content to existing scenes with relative refs.
  2547. proto = ''
  2548. objectname = clean_object_name(ob.data.name)
  2549. meshname = "%s.mesh" % objectname
  2550. meshref = "%s%s.mesh" % (proto, objectname)
  2551. print (" - EC_Mesh")
  2552. print (" - Mesh ref:", meshref)
  2553. if self.EX_MESH:
  2554. murl = os.path.join( os.path.split(url)[0], meshname )
  2555. exists = os.path.isfile( murl )
  2556. if not exists or (exists and self.EX_MESH_OVERWRITE):
  2557. if meshname not in exported_meshes:
  2558. exported_meshes.append( meshname )
  2559. self.dot_mesh( ob, os.path.split(url)[0] )
  2560. doc = e.document
  2561. if ob.find_armature():
  2562. print (" - EC_AnimationController")
  2563. c = doc.createElement('component'); e.appendChild( c )
  2564. c.setAttribute('type', "EC_AnimationController")
  2565. c.setAttribute('sync', '1')
  2566. c = doc.createElement('component'); e.appendChild( c )
  2567. c.setAttribute('type', "EC_Mesh")
  2568. c.setAttribute('sync', '1')
  2569. a = doc.createElement('attribute'); c.appendChild(a)
  2570. a.setAttribute('name', "Mesh ref" )
  2571. a.setAttribute('value', meshref)
  2572. a = doc.createElement('attribute'); c.appendChild(a)
  2573. a.setAttribute('name', "Mesh materials" )
  2574. # Query object its materials and make a proper material ref string of it.
  2575. # note: We assume blindly here that the 'submesh' indexes are correct in the material list.
  2576. mymaterials = ob.data.materials
  2577. if mymaterials is not None and len(mymaterials) > 0:
  2578. mymatstring = '' # generate ; separated material list
  2579. for mymat in mymaterials:
  2580. if mymat is None:
  2581. continue
  2582. mymatstring += proto + material_name(mymat) + '.material;'
  2583. mymatstring = mymatstring[:-1] # strip ending ;
  2584. a.setAttribute('value', mymatstring )
  2585. else:
  2586. # default to nothing to avoid error prints in .txml import
  2587. a.setAttribute('value', "" )
  2588. if ob.find_armature():
  2589. skeletonref = "%s%s.skeleton" % (proto, clean_object_name(ob.data.name))
  2590. print (" Skeleton ref:", skeletonref)
  2591. a = doc.createElement('attribute'); c.appendChild(a)
  2592. a.setAttribute('name', "Skeleton ref" )
  2593. a.setAttribute('value', skeletonref)
  2594. a = doc.createElement('attribute'); c.appendChild(a)
  2595. a.setAttribute('name', "Draw distance" )
  2596. if ob.use_draw_distance:
  2597. a.setAttribute('value', ob.draw_distance )
  2598. else:
  2599. a.setAttribute('value', "0" )
  2600. a = doc.createElement('attribute'); c.appendChild(a)
  2601. a.setAttribute('name', 'Cast shadows' )
  2602. if ob.cast_shadows:
  2603. a.setAttribute('value', 'true' )
  2604. else:
  2605. a.setAttribute('value', 'false' )
  2606. # EC_Light
  2607. def tundra_light( self, e, ob ):
  2608. '''
  2609. <component type="EC_Light" sync="1">
  2610. <attribute value="1" name="light type"/>
  2611. <attribute value="1 1 1 1" name="diffuse color"/>
  2612. <attribute value="1 1 1 1" name="specular color"/>
  2613. <attribute value="true" name="cast shadows"/>
  2614. <attribute value="29.9999828" name="light range"/>
  2615. <attribute value="1" name="brightness"/>
  2616. <attribute value="0" name="constant atten"/>
  2617. <attribute value="1" name="linear atten"/>
  2618. <attribute value="0" name="quadratic atten"/>
  2619. <attribute value="30" name="light inner angle"/>
  2620. <attribute value="40" name="light outer angle"/>
  2621. </component>
  2622. '''
  2623. if ob.data.type not in 'POINT SPOT'.split():
  2624. return
  2625. doc = e.document
  2626. c = doc.createElement('component'); e.appendChild( c )
  2627. c.setAttribute('type', "EC_Light")
  2628. c.setAttribute('sync', '1')
  2629. a = doc.createElement('attribute'); c.appendChild(a)
  2630. a.setAttribute('name', 'light type' )
  2631. if ob.data.type=='POINT':
  2632. a.setAttribute('value', '0' )
  2633. elif ob.data.type=='SPOT':
  2634. a.setAttribute('value', '1' )
  2635. #2 = directional light. blender has no directional light?
  2636. R,G,B = ob.data.color
  2637. a = doc.createElement('attribute'); c.appendChild(a)
  2638. a.setAttribute('name', 'diffuse color' )
  2639. if ob.data.use_diffuse:
  2640. a.setAttribute('value', '%s %s %s 1' %(R,G,B) )
  2641. else:
  2642. a.setAttribute('value', '0 0 0 1' )
  2643. a = doc.createElement('attribute'); c.appendChild(a)
  2644. a.setAttribute('name', 'specular color' )
  2645. if ob.data.use_specular:
  2646. a.setAttribute('value', '%s %s %s 1' %(R,G,B) )
  2647. else:
  2648. a.setAttribute('value', '0 0 0 1' )
  2649. a = doc.createElement('attribute'); c.appendChild(a)
  2650. a.setAttribute('name', 'cast shadows' )
  2651. if ob.data.type=='HEMI':
  2652. a.setAttribute('value', 'false' ) # HEMI no .shadow_method
  2653. elif ob.data.shadow_method != 'NOSHADOW':
  2654. a.setAttribute('value', 'true' )
  2655. else:
  2656. a.setAttribute('value', 'false' )
  2657. a = doc.createElement('attribute'); c.appendChild(a)
  2658. a.setAttribute('name', 'light range' )
  2659. a.setAttribute('value', ob.data.distance*2 )
  2660. a = doc.createElement('attribute'); c.appendChild(a)
  2661. a.setAttribute('name', 'brightness' )
  2662. a.setAttribute('value', ob.data.energy )
  2663. a = doc.createElement('attribute'); c.appendChild(a)
  2664. a.setAttribute('name', 'constant atten' )
  2665. a.setAttribute('value', '0' )
  2666. a = doc.createElement('attribute'); c.appendChild(a)
  2667. a.setAttribute('name', 'linear atten' )
  2668. energy = ob.data.energy
  2669. if energy <= 0.0:
  2670. energy = 0.001
  2671. a.setAttribute('value', (1.0/energy)*0.25 )
  2672. a = doc.createElement('attribute'); c.appendChild(a)
  2673. a.setAttribute('name', 'quadratic atten' )
  2674. a.setAttribute('value', '0.0' )
  2675. if ob.data.type=='SPOT':
  2676. outer = math.degrees(ob.data.spot_size) / 2.0
  2677. inner = outer * (1.0-ob.data.spot_blend)
  2678. a = doc.createElement('attribute'); c.appendChild(a)
  2679. a.setAttribute('name', 'light inner angle' )
  2680. a.setAttribute('value', '%s'%inner )
  2681. a = doc.createElement('attribute'); c.appendChild(a)
  2682. a.setAttribute('name', 'light outer angle' )
  2683. a.setAttribute('value', '%s' %outer )
  2684. ## UI export panel
  2685. invalid_chars = '\/:*?"<>|'
  2686. def clean_object_name(value):
  2687. global invalid_chars
  2688. for invalid_char in invalid_chars:
  2689. value = value.replace(invalid_char, '_')
  2690. value = value.replace(' ', '_')
  2691. return value;
  2692. def clean_object_name_with_spaces(value):
  2693. global invalid_chars
  2694. for invalid_char in invalid_chars:
  2695. value = value.replace(invalid_char, '_')
  2696. return value;
  2697. last_export_filepath = ""
  2698. class _OgreCommonExport_(_TXML_):
  2699. @classmethod
  2700. def poll(cls, context):
  2701. if context.active_object and context.mode != 'EDIT_MESH':
  2702. return True
  2703. def invoke(self, context, event):
  2704. # Resolve path from opened .blend if available. It's not if
  2705. # blender normally was opened with "last open scene".
  2706. # After export is done once, remember that path when re-exporting.
  2707. global last_export_filepath
  2708. if last_export_filepath == "":
  2709. # First export during this blender run
  2710. if self.filepath == "" and context.blend_data.filepath != "":
  2711. path, name = os.path.split(context.blend_data.filepath)
  2712. self.filepath = os.path.join(path, name.split('.')[0])
  2713. if self.filepath == "":
  2714. self.filepath = "blender2ogre-export"
  2715. if self.EXPORT_TYPE == "OGRE":
  2716. self.filepath += ".scene"
  2717. elif self.EXPORT_TYPE == "REX":
  2718. self.filepath += ".txml"
  2719. else:
  2720. # Sequential export, use the previous path
  2721. self.filepath = last_export_filepath
  2722. # Replace file extension if we have swapped dialogs.
  2723. if self.EXPORT_TYPE == "OGRE":
  2724. self.filepath = self.filepath.replace(".txml", ".scene")
  2725. elif self.EXPORT_TYPE == "REX":
  2726. self.filepath = self.filepath.replace(".scene", ".txml")
  2727. # Update ui setting from the last export, or file config.
  2728. self.update_ui()
  2729. wm = context.window_manager
  2730. fs = wm.fileselect_add(self) # writes to filepath
  2731. return {'RUNNING_MODAL'}
  2732. def execute(self, context):
  2733. # Store this path for later re-export
  2734. global last_export_filepath
  2735. last_export_filepath = self.filepath
  2736. # Run the .scene or .txml export
  2737. self.ogre_export(self.filepath, context)
  2738. return {'FINISHED'}
  2739. def update_ui(self):
  2740. self.EX_SWAP_AXIS = CONFIG['SWAP_AXIS']
  2741. self.EX_SEP_MATS = CONFIG['SEP_MATS']
  2742. self.EX_ONLY_DEFORMABLE_BONES = CONFIG['ONLY_DEFORMABLE_BONES']
  2743. self.EX_ONLY_ANIMATED_BONES = CONFIG['ONLY_ANIMATED_BONES']
  2744. self.EX_SCENE = CONFIG['SCENE']
  2745. self.EX_SELONLY = CONFIG['SELONLY']
  2746. self.EX_FORCE_CAMERA = CONFIG['FORCE_CAMERA']
  2747. self.EX_FORCE_LAMPS = CONFIG['FORCE_LAMPS']
  2748. self.EX_MESH = CONFIG['MESH']
  2749. self.EX_MESH_OVERWRITE = CONFIG['MESH_OVERWRITE']
  2750. self.EX_ARM_ANIM = CONFIG['ARM_ANIM']
  2751. self.EX_SHAPE_ANIM = CONFIG['SHAPE_ANIM']
  2752. self.EX_INDEPENDENT_ANIM = CONFIG['INDEPENDENT_ANIM']
  2753. self.EX_TRIM_BONE_WEIGHTS = CONFIG['TRIM_BONE_WEIGHTS']
  2754. self.EX_ARRAY = CONFIG['ARRAY']
  2755. self.EX_MATERIALS = CONFIG['MATERIALS']
  2756. self.EX_FORCE_IMAGE_FORMAT = CONFIG['FORCE_IMAGE_FORMAT']
  2757. self.EX_DDS_MIPS = CONFIG['DDS_MIPS']
  2758. self.EX_COPY_SHADER_PROGRAMS = CONFIG['COPY_SHADER_PROGRAMS']
  2759. self.EX_lodLevels = CONFIG['lodLevels']
  2760. self.EX_lodDistance = CONFIG['lodDistance']
  2761. self.EX_lodPercent = CONFIG['lodPercent']
  2762. self.EX_nuextremityPoints = CONFIG['nuextremityPoints']
  2763. self.EX_generateEdgeLists = CONFIG['generateEdgeLists']
  2764. self.EX_generateTangents = CONFIG['generateTangents']
  2765. self.EX_tangentSemantic = CONFIG['tangentSemantic']
  2766. self.EX_tangentUseParity = CONFIG['tangentUseParity']
  2767. self.EX_tangentSplitMirrored = CONFIG['tangentSplitMirrored']
  2768. self.EX_tangentSplitRotated = CONFIG['tangentSplitRotated']
  2769. self.EX_reorganiseBuffers = CONFIG['reorganiseBuffers']
  2770. self.EX_optimiseAnimations = CONFIG['optimiseAnimations']
  2771. # Basic options
  2772. EX_SWAP_AXIS = EnumProperty(
  2773. items=AXIS_MODES,
  2774. name='swap axis',
  2775. description='axis swapping mode',
  2776. default= CONFIG['SWAP_AXIS'])
  2777. EX_SEP_MATS = BoolProperty(
  2778. name="Separate Materials",
  2779. description="exports a .material for each material (rather than putting all materials in a single .material file)",
  2780. default=CONFIG['SEP_MATS'])
  2781. EX_ONLY_DEFORMABLE_BONES = BoolProperty(
  2782. name="Only Deformable Bones",
  2783. description="only exports bones that are deformable. Useful for hiding IK-Bones used in Blender. Warning: Will cause trouble if a deformable bone has a non-deformable parent",
  2784. default=CONFIG['ONLY_DEFORMABLE_BONES'])
  2785. EX_ONLY_ANIMATED_BONES = BoolProperty(
  2786. name="Only Animated Bones",
  2787. description="only exports bones that have been keyframed, useful for run-time animation blending (example: upper/lower torso split)",
  2788. default=CONFIG['ONLY_ANIMATED_BONES'])
  2789. EX_SCENE = BoolProperty(
  2790. name="Export Scene",
  2791. description="export current scene (OgreDotScene xml)",
  2792. default=CONFIG['SCENE'])
  2793. EX_SELONLY = BoolProperty(
  2794. name="Export Selected Only",
  2795. description="export selected",
  2796. default=CONFIG['SELONLY'])
  2797. EX_FORCE_CAMERA = BoolProperty(
  2798. name="Force Camera",
  2799. description="export active camera",
  2800. default=CONFIG['FORCE_CAMERA'])
  2801. EX_FORCE_LAMPS = BoolProperty(
  2802. name="Force Lamps",
  2803. description="export all lamps",
  2804. default=CONFIG['FORCE_LAMPS'])
  2805. EX_MESH = BoolProperty(
  2806. name="Export Meshes",
  2807. description="export meshes",
  2808. default=CONFIG['MESH'])
  2809. EX_MESH_OVERWRITE = BoolProperty(
  2810. name="Export Meshes (overwrite)",
  2811. description="export meshes (overwrite existing files)",
  2812. default=CONFIG['MESH_OVERWRITE'])
  2813. EX_ARM_ANIM = BoolProperty(
  2814. name="Armature Animation",
  2815. description="export armature animations - updates the .skeleton file",
  2816. default=CONFIG['ARM_ANIM'])
  2817. EX_SHAPE_ANIM = BoolProperty(
  2818. name="Shape Animation",
  2819. description="export shape animations - updates the .mesh file",
  2820. default=CONFIG['SHAPE_ANIM'])
  2821. EX_INDEPENDENT_ANIM = BoolProperty(
  2822. name="Independent Animations",
  2823. description="Export each NLA-Strip independently. If unchecked NLA-Strips that overlap influence each other.",
  2824. default=CONFIG['INDEPENDENT_ANIM'])
  2825. EX_TRIM_BONE_WEIGHTS = FloatProperty(
  2826. name="Trim Weights",
  2827. description="ignore bone weights below this value (Ogre supports 4 bones per vertex)",
  2828. min=0.0, max=0.5, default=CONFIG['TRIM_BONE_WEIGHTS'] )
  2829. EX_ARRAY = BoolProperty(
  2830. name="Optimize Arrays",
  2831. description="optimize array modifiers as instances (constant offset only)",
  2832. default=CONFIG['ARRAY'])
  2833. EX_MATERIALS = BoolProperty(
  2834. name="Export Materials",
  2835. description="exports .material script",
  2836. default=CONFIG['MATERIALS'])
  2837. EX_FORCE_IMAGE_FORMAT = EnumProperty(
  2838. items=_IMAGE_FORMATS,
  2839. name='Convert Images',
  2840. description='convert all textures to format',
  2841. default=CONFIG['FORCE_IMAGE_FORMAT'] )
  2842. EX_DDS_MIPS = IntProperty(
  2843. name="DDS Mips",
  2844. description="number of mip maps (DDS)",
  2845. min=0, max=16,
  2846. default=CONFIG['DDS_MIPS'])
  2847. # Mesh options
  2848. EX_lodLevels = IntProperty(
  2849. name="LOD Levels",
  2850. description="MESH number of LOD levels",
  2851. min=0, max=32,
  2852. default=CONFIG['lodLevels'])
  2853. EX_lodDistance = IntProperty(
  2854. name="LOD Distance",
  2855. description="MESH distance increment to reduce LOD",
  2856. min=0, max=2000, default=CONFIG['lodDistance'])
  2857. EX_lodPercent = IntProperty(
  2858. name="LOD Percentage",
  2859. description="LOD percentage reduction",
  2860. min=0, max=99,
  2861. default=CONFIG['lodPercent'])
  2862. EX_nuextremityPoints = IntProperty(
  2863. name="Extremity Points",
  2864. description="MESH Extremity Points",
  2865. min=0, max=65536,
  2866. default=CONFIG['nuextremityPoints'])
  2867. EX_generateEdgeLists = BoolProperty(
  2868. name="Edge Lists",
  2869. description="MESH generate edge lists (for stencil shadows)",
  2870. default=CONFIG['generateEdgeLists'])
  2871. EX_generateTangents = BoolProperty(
  2872. name="Tangents",
  2873. description="MESH generate tangents",
  2874. default=CONFIG['generateTangents'])
  2875. EX_tangentSemantic = StringProperty(
  2876. name="Tangent Semantic",
  2877. description="MESH tangent semantic",
  2878. maxlen=3,
  2879. default=CONFIG['tangentSemantic'])
  2880. EX_tangentUseParity = IntProperty(
  2881. name="Tangent Parity",
  2882. description="MESH tangent use parity",
  2883. min=0, max=16,
  2884. default=CONFIG['tangentUseParity'])
  2885. EX_tangentSplitMirrored = BoolProperty(
  2886. name="Tangent Split Mirrored",
  2887. description="MESH split mirrored tangents",
  2888. default=CONFIG['tangentSplitMirrored'])
  2889. EX_tangentSplitRotated = BoolProperty(
  2890. name="Tangent Split Rotated",
  2891. description="MESH split rotated tangents",
  2892. default=CONFIG['tangentSplitRotated'])
  2893. EX_reorganiseBuffers = BoolProperty(
  2894. name="Reorganise Buffers",
  2895. description="MESH reorganise vertex buffers",
  2896. default=CONFIG['reorganiseBuffers'])
  2897. EX_optimiseAnimations = BoolProperty(
  2898. name="Optimize Animations",
  2899. description="MESH optimize animations",
  2900. default=CONFIG['optimiseAnimations'])
  2901. EX_COPY_SHADER_PROGRAMS = BoolProperty(
  2902. name="copy shader programs",
  2903. description="when using script inheritance copy the source shader programs to the output path",
  2904. default=CONFIG['COPY_SHADER_PROGRAMS'])
  2905. filepath_last = ""
  2906. filepath = StringProperty(
  2907. name="File Path",
  2908. description="Filepath used for exporting file",
  2909. maxlen=1024, default="",
  2910. subtype='FILE_PATH')
  2911. def dot_material( self, meshes, path='/tmp', mat_file_name='SceneMaterial'):
  2912. material_files = []
  2913. mats = []
  2914. for ob in meshes:
  2915. if len(ob.data.materials):
  2916. for mat in ob.data.materials:
  2917. if mat not in mats:
  2918. mats.append( mat )
  2919. if not mats:
  2920. print('WARNING: no materials, not writting .material script'); return []
  2921. M = MISSING_MATERIAL + '\n'
  2922. for mat in mats:
  2923. if mat is None:
  2924. continue
  2925. Report.materials.append( material_name(mat) )
  2926. if CONFIG['COPY_SHADER_PROGRAMS']:
  2927. data = generate_material( mat, path=path, copy_programs=True, touch_textures=CONFIG['TOUCH_TEXTURES'] )
  2928. else:
  2929. data = generate_material( mat, path=path, touch_textures=CONFIG['TOUCH_TEXTURES'] )
  2930. M += data
  2931. # Write own .material file per material
  2932. if self.EX_SEP_MATS:
  2933. url = self.dot_material_write_separate( mat, data, path )
  2934. material_files.append(url)
  2935. # Write one .material file for everything
  2936. if not self.EX_SEP_MATS:
  2937. try:
  2938. url = os.path.join(path, '%s.material' % mat_file_name)
  2939. f = open( url, 'wb' ); f.write( bytes(M,'utf-8') ); f.close()
  2940. print(' - Created material:', url)
  2941. material_files.append( url )
  2942. except Exception as e:
  2943. show_dialog("Invalid material object name: " + mat_file_name)
  2944. return material_files
  2945. def dot_material_write_separate( self, mat, data, path = '/tmp' ):
  2946. try:
  2947. clean_filename = clean_object_name(mat.name);
  2948. url = os.path.join(path, '%s.material' % clean_filename)
  2949. f = open(url, 'wb'); f.write( bytes(data,'utf-8') ); f.close()
  2950. print(' - Exported Material:', url)
  2951. return url
  2952. except Exception as e:
  2953. show_dialog("Invalid material object name: " + clean_filename)
  2954. return ""
  2955. def dot_mesh( self, ob, path='/tmp', force_name=None, ignore_shape_animation=False ):
  2956. dot_mesh( ob, path, force_name, ignore_shape_animation=False )
  2957. def ogre_export(self, url, context, force_material_update=[]):
  2958. print ("_"*80)
  2959. # Updating config to latest values?
  2960. global CONFIG
  2961. for name in dir(self):
  2962. if name.startswith('EX_'):
  2963. CONFIG[ name[3:] ] = getattr(self,name)
  2964. Report.reset()
  2965. print("Processing Scene")
  2966. prefix = url.split('.')[0]
  2967. path = os.path.split(url)[0]
  2968. # Nodes (objects) - gather because macros will change selection state
  2969. objects = []
  2970. linkedgroups = []
  2971. invalidnamewarnings = []
  2972. for ob in bpy.context.scene.objects:
  2973. if ob.subcollision:
  2974. continue
  2975. if self.EX_SELONLY and not ob.select:
  2976. if ob.type == 'CAMERA' and self.EX_FORCE_CAMERA:
  2977. pass
  2978. elif ob.type == 'LAMP' and self.EX_FORCE_LAMPS:
  2979. pass
  2980. else:
  2981. continue
  2982. if ob.type == 'EMPTY' and ob.dupli_group and ob.dupli_type == 'GROUP':
  2983. linkedgroups.append(ob)
  2984. else:
  2985. # Gather data of invalid names. Don't bother user with warnings on names
  2986. # that only get spaces converted to _, just do that automatically.
  2987. cleanname = clean_object_name(ob.name)
  2988. cleannamespaces = clean_object_name_with_spaces(ob.name)
  2989. if cleanname != ob.name:
  2990. if cleannamespaces != ob.name:
  2991. invalidnamewarnings.append(ob.name + " -> " + cleanname)
  2992. objects.append(ob)
  2993. # Print invalid obj names so user can go and fix them.
  2994. if len(invalidnamewarnings) > 0:
  2995. print ("[Warning]: Following object names have invalid characters for creating files. They will be automatically converted.")
  2996. for namewarning in invalidnamewarnings:
  2997. Report.warnings.append("Auto correcting object name: " + namewarning)
  2998. print (" - ", namewarning)
  2999. # Linked groups - allows 3 levels of nested blender library linking
  3000. temps = []
  3001. for e in linkedgroups:
  3002. grp = e.dupli_group
  3003. subs = []
  3004. for o in grp.objects:
  3005. if o.type=='MESH':
  3006. subs.append( o ) # TOP-LEVEL
  3007. elif o.type == 'EMPTY' and o.dupli_group and o.dupli_type == 'GROUP':
  3008. ss = [] # LEVEL2
  3009. for oo in o.dupli_group.objects:
  3010. if oo.type=='MESH':
  3011. ss.append( oo )
  3012. elif oo.type == 'EMPTY' and oo.dupli_group and oo.dupli_type == 'GROUP':
  3013. sss = [] # LEVEL3
  3014. for ooo in oo.dupli_group.objects:
  3015. if ooo.type=='MESH':
  3016. sss.append( ooo )
  3017. if sss:
  3018. m = merge_objects( sss, name=oo.name, transform=oo.matrix_world )
  3019. subs.append( m )
  3020. temps.append( m )
  3021. if ss:
  3022. m = merge_objects( ss, name=o.name, transform=o.matrix_world )
  3023. subs.append( m )
  3024. temps.append( m )
  3025. if subs:
  3026. m = merge_objects( subs, name=e.name, transform=e.matrix_world )
  3027. objects.append( m )
  3028. temps.append( m )
  3029. # Find merge groups
  3030. mgroups = []
  3031. mobjects = []
  3032. for ob in objects:
  3033. group = get_merge_group( ob )
  3034. if group:
  3035. for member in group.objects:
  3036. if member not in mobjects: mobjects.append( member )
  3037. if group not in mgroups: mgroups.append( group )
  3038. for rem in mobjects:
  3039. if rem in objects: objects.remove( rem )
  3040. for group in mgroups:
  3041. merged = merge_group( group )
  3042. objects.append( merged )
  3043. temps.append( merged )
  3044. # Gather roots because ogredotscene supports parents and children
  3045. def _flatten( _c, _f ):
  3046. if _c.parent in objects: _f.append( _c.parent )
  3047. if _c.parent: _flatten( _c.parent, _f )
  3048. else: _f.append( _c )
  3049. roots = []
  3050. meshes = []
  3051. for ob in objects:
  3052. flat = []
  3053. _flatten( ob, flat )
  3054. root = flat[-1]
  3055. if root not in roots:
  3056. roots.append(root)
  3057. if ob.type=='MESH':
  3058. meshes.append(ob)
  3059. mesh_collision_prims = {}
  3060. mesh_collision_files = {}
  3061. # Track that we don't export same data multiple times
  3062. exported_meshes = []
  3063. if self.EX_MATERIALS:
  3064. print (" Processing Materials")
  3065. material_file_name_base = os.path.split(url)[1].replace('.scene', '').replace('.txml', '')
  3066. material_files = self.dot_material(meshes + force_material_update, path, material_file_name_base)
  3067. else:
  3068. material_files = []
  3069. # realXtend Tundra .txml scene description export
  3070. if self.EXPORT_TYPE == 'REX':
  3071. rex = self.create_tundra_document(context)
  3072. proxies = []
  3073. for ob in objects:
  3074. print(" Processing %s [%s]" % (ob.name, ob.type))
  3075. # This seemingly needs to be done as its done in .scene
  3076. # export. Fixed a bug that no .meshes were exported when doing
  3077. # a Tundra export.
  3078. if ob.type == 'MESH':
  3079. ob.data.update(calc_tessface=True)
  3080. # EC_Light
  3081. if ob.type == 'LAMP':
  3082. TE = self.tundra_entity(rex, ob, path=path, collision_proxies=proxies)
  3083. self.tundra_light( TE, ob )
  3084. # EC_Sound
  3085. elif ob.type == 'SPEAKER':
  3086. TE = self.tundra_entity(rex, ob, path=path, collision_proxies=proxies)
  3087. # EC_Mesh
  3088. elif ob.type == 'MESH' and len(ob.data.tessfaces):
  3089. if ob.modifiers and ob.modifiers[0].type=='MULTIRES' and ob.use_multires_lod:
  3090. mod = ob.modifiers[0]
  3091. basename = ob.name
  3092. dataname = ob.data.name
  3093. ID = uid( ob ) # ensure uid
  3094. TE = self.tundra_entity(rex, ob, path=path, collision_proxies=proxies)
  3095. for level in range( mod.total_levels+1 ):
  3096. ob.uid += 1
  3097. mod.levels = level
  3098. ob.name = '%s.LOD%s' %(basename,level)
  3099. ob.data.name = '%s.LOD%s' %(dataname,level)
  3100. TE = self.tundra_entity(
  3101. rex, ob, path=path, collision_proxies=proxies, parent=basename,
  3102. matrix=mathutils.Matrix(), visible=False
  3103. )
  3104. self.tundra_mesh( TE, ob, url, exported_meshes )
  3105. ob.uid = ID
  3106. ob.name = basename
  3107. ob.data.name = dataname
  3108. else:
  3109. TE = self.tundra_entity( rex, ob, path=path, collision_proxies=proxies )
  3110. self.tundra_mesh( TE, ob, url, exported_meshes )
  3111. # EC_RigidBody separate collision meshes
  3112. for proxy in proxies:
  3113. self.dot_mesh(
  3114. proxy,
  3115. path=os.path.split(url)[0],
  3116. force_name='_collision_%s' %proxy.data.name
  3117. )
  3118. if self.EX_SCENE:
  3119. if not url.endswith('.txml'):
  3120. url += '.txml'
  3121. data = rex.toprettyxml()
  3122. f = open( url, 'wb' ); f.write( bytes(data,'utf-8') ); f.close()
  3123. print(' Exported Tundra Scene:', url)
  3124. # Ogre .scene scene description export
  3125. elif self.EXPORT_TYPE == 'OGRE':
  3126. doc = self.create_ogre_document( context, material_files )
  3127. for root in roots:
  3128. print(' - Exporting root node:', root.name)
  3129. self._node_export(
  3130. root,
  3131. url = url,
  3132. doc = doc,
  3133. exported_meshes = exported_meshes,
  3134. meshes = meshes,
  3135. mesh_collision_prims = mesh_collision_prims,
  3136. mesh_collision_files = mesh_collision_files,
  3137. prefix = prefix,
  3138. objects = objects,
  3139. xmlparent = doc._scene_nodes
  3140. )
  3141. if self.EX_SCENE:
  3142. if not url.endswith('.scene'):
  3143. url += '.scene'
  3144. data = doc.toprettyxml()
  3145. f = open( url, 'wb' ); f.write( bytes(data,'utf-8') ); f.close()
  3146. print(' Exported Ogre Scene:', url)
  3147. for ob in temps:
  3148. context.scene.objects.unlink( ob )
  3149. bpy.ops.wm.call_menu(name='MiniReport')
  3150. # Always save?
  3151. # todo: This does not seem to stick! It might save to disk
  3152. # but the old config defaults are read when this panel is opened!
  3153. save_config()
  3154. def create_ogre_document(self, context, material_files=[] ):
  3155. now = time.time()
  3156. doc = RDocument()
  3157. scn = doc.createElement('scene'); doc.appendChild( scn )
  3158. scn.setAttribute('export_time', str(now))
  3159. scn.setAttribute('formatVersion', '1.0.1')
  3160. bscn = bpy.context.scene
  3161. if '_previous_export_time_' in bscn.keys():
  3162. scn.setAttribute('previous_export_time', str(bscn['_previous_export_time_']))
  3163. else:
  3164. scn.setAttribute('previous_export_time', '0')
  3165. bscn[ '_previous_export_time_' ] = now
  3166. scn.setAttribute('exported_by', getpass.getuser())
  3167. nodes = doc.createElement('nodes')
  3168. doc._scene_nodes = nodes
  3169. extern = doc.createElement('externals')
  3170. environ = doc.createElement('environment')
  3171. for n in (nodes,extern,environ):
  3172. scn.appendChild( n )
  3173. # Extern files
  3174. for url in material_files:
  3175. item = doc.createElement('item'); extern.appendChild( item )
  3176. item.setAttribute('type','material')
  3177. a = doc.createElement('file'); item.appendChild( a )
  3178. a.setAttribute('name', url)
  3179. # Environ settings
  3180. world = context.scene.world
  3181. if world: # multiple scenes - other scenes may not have a world
  3182. _c = {'colourAmbient':world.ambient_color, 'colourBackground':world.horizon_color, 'colourDiffuse':world.horizon_color}
  3183. for ctag in _c:
  3184. a = doc.createElement(ctag); environ.appendChild( a )
  3185. color = _c[ctag]
  3186. a.setAttribute('r', '%s'%color.r)
  3187. a.setAttribute('g', '%s'%color.g)
  3188. a.setAttribute('b', '%s'%color.b)
  3189. if world and world.mist_settings.use_mist:
  3190. a = doc.createElement('fog'); environ.appendChild( a )
  3191. a.setAttribute('linearStart', '%s'%world.mist_settings.start )
  3192. mist_falloff = world.mist_settings.falloff
  3193. if mist_falloff == 'QUADRATIC': a.setAttribute('mode', 'exp') # on DTD spec (none | exp | exp2 | linear)
  3194. elif mist_falloff == 'LINEAR': a.setAttribute('mode', 'linear')
  3195. else: a.setAttribute('mode', 'exp2')
  3196. #a.setAttribute('mode', world.mist_settings.falloff.lower() ) # not on DTD spec
  3197. a.setAttribute('linearEnd', '%s' %(world.mist_settings.start+world.mist_settings.depth))
  3198. a.setAttribute('expDensity', world.mist_settings.intensity)
  3199. a.setAttribute('colourR', world.horizon_color.r)
  3200. a.setAttribute('colourG', world.horizon_color.g)
  3201. a.setAttribute('colourB', world.horizon_color.b)
  3202. return doc
  3203. # Recursive Node export
  3204. def _node_export( self, ob, url='', doc=None, rex=None, exported_meshes=[], meshes=[], mesh_collision_prims={}, mesh_collision_files={}, prefix='', objects=[], xmlparent=None ):
  3205. o = _ogre_node_helper( doc, ob, objects )
  3206. xmlparent.appendChild(o)
  3207. # Custom user props
  3208. for prop in ob.items():
  3209. propname, propvalue = prop
  3210. if not propname.startswith('_'):
  3211. user = doc.createElement('user_data')
  3212. o.appendChild( user )
  3213. user.setAttribute( 'name', propname )
  3214. user.setAttribute( 'value', str(propvalue) )
  3215. user.setAttribute( 'type', type(propvalue).__name__ )
  3216. # Custom user props from BGE props by Mind Calamity
  3217. for prop in ob.game.properties:
  3218. e = doc.createElement( 'user_data' )
  3219. o.appendChild( e )
  3220. e.setAttribute('name', prop.name)
  3221. e.setAttribute('value', str(prop.value))
  3222. e.setAttribute('type', type(prop.value).__name__)
  3223. # -- end of Mind Calamity patch
  3224. # BGE subset
  3225. game = doc.createElement('game')
  3226. o.appendChild( game )
  3227. sens = doc.createElement('sensors')
  3228. game.appendChild( sens )
  3229. acts = doc.createElement('actuators')
  3230. game.appendChild( acts )
  3231. for sen in ob.game.sensors:
  3232. sens.appendChild( WrapSensor(sen).xml(doc) )
  3233. for act in ob.game.actuators:
  3234. acts.appendChild( WrapActuator(act).xml(doc) )
  3235. if ob.type == 'MESH':
  3236. ob.data.update(calc_tessface=True)
  3237. if ob.type == 'MESH' and len(ob.data.tessfaces):
  3238. collisionFile = None
  3239. collisionPrim = None
  3240. if ob.data.name in mesh_collision_prims:
  3241. collisionPrim = mesh_collision_prims[ ob.data.name ]
  3242. if ob.data.name in mesh_collision_files:
  3243. collisionFile = mesh_collision_files[ ob.data.name ]
  3244. e = doc.createElement('entity')
  3245. o.appendChild(e); e.setAttribute('name', ob.data.name)
  3246. prefix = ''
  3247. e.setAttribute('meshFile', '%s%s.mesh' %(prefix,ob.data.name) )
  3248. if not collisionPrim and not collisionFile:
  3249. if ob.game.use_collision_bounds:
  3250. collisionPrim = ob.game.collision_bounds_type.lower()
  3251. mesh_collision_prims[ ob.data.name ] = collisionPrim
  3252. else:
  3253. for child in ob.children:
  3254. if child.subcollision and child.name.startswith('DECIMATE'):
  3255. collisionFile = '%s_collision_%s.mesh' %(prefix,ob.data.name)
  3256. break
  3257. if collisionFile:
  3258. mesh_collision_files[ ob.data.name ] = collisionFile
  3259. self.dot_mesh(
  3260. child,
  3261. path=os.path.split(url)[0],
  3262. force_name='_collision_%s' %ob.data.name
  3263. )
  3264. if collisionPrim:
  3265. e.setAttribute('collisionPrim', collisionPrim )
  3266. elif collisionFile:
  3267. e.setAttribute('collisionFile', collisionFile )
  3268. _mesh_entity_helper( doc, ob, e )
  3269. if self.EX_MESH:
  3270. murl = os.path.join( os.path.split(url)[0], '%s.mesh'%ob.data.name )
  3271. exists = os.path.isfile( murl )
  3272. if not exists or (exists and self.EX_MESH_OVERWRITE):
  3273. if ob.data.name not in exported_meshes:
  3274. exported_meshes.append( ob.data.name )
  3275. self.dot_mesh( ob, os.path.split(url)[0] )
  3276. # Deal with Array modifier
  3277. vecs = [ ob.matrix_world.to_translation() ]
  3278. for mod in ob.modifiers:
  3279. if mod.type == 'ARRAY':
  3280. if mod.fit_type != 'FIXED_COUNT':
  3281. print( 'WARNING: unsupport array-modifier type->', mod.fit_type )
  3282. continue
  3283. if not mod.use_constant_offset:
  3284. print( 'WARNING: unsupport array-modifier mode, must be "constant offset" type' )
  3285. continue
  3286. else:
  3287. #v = ob.matrix_world.to_translation()
  3288. newvecs = []
  3289. for prev in vecs:
  3290. for i in range( mod.count-1 ):
  3291. v = prev + mod.constant_offset_displace
  3292. newvecs.append( v )
  3293. ao = _ogre_node_helper( doc, ob, objects, prefix='_array_%s_'%len(vecs+newvecs), pos=v )
  3294. xmlparent.appendChild(ao)
  3295. e = doc.createElement('entity')
  3296. ao.appendChild(e); e.setAttribute('name', ob.data.name)
  3297. #if self.EX_MESH_SUBDIR: e.setAttribute('meshFile', 'meshes/%s.mesh' %ob.data.name)
  3298. #else:
  3299. e.setAttribute('meshFile', '%s.mesh' %ob.data.name)
  3300. if collisionPrim: e.setAttribute('collisionPrim', collisionPrim )
  3301. elif collisionFile: e.setAttribute('collisionFile', collisionFile )
  3302. vecs += newvecs
  3303. elif ob.type == 'CAMERA':
  3304. Report.cameras.append( ob.name )
  3305. c = doc.createElement('camera')
  3306. o.appendChild(c); c.setAttribute('name', ob.data.name)
  3307. aspx = bpy.context.scene.render.pixel_aspect_x
  3308. aspy = bpy.context.scene.render.pixel_aspect_y
  3309. sx = bpy.context.scene.render.resolution_x
  3310. sy = bpy.context.scene.render.resolution_y
  3311. fovY = 0.0
  3312. if (sx*aspx > sy*aspy):
  3313. fovY = 2*math.atan(sy*aspy*16.0/(ob.data.lens*sx*aspx))
  3314. else:
  3315. fovY = 2*math.atan(16.0/ob.data.lens)
  3316. # fov in radians - like OgreMax - requested by cyrfer
  3317. fov = math.radians( fovY*180.0/math.pi )
  3318. c.setAttribute('fov', '%s'%fov)
  3319. c.setAttribute('projectionType', "perspective")
  3320. a = doc.createElement('clipping'); c.appendChild( a )
  3321. a.setAttribute('nearPlaneDist', '%s' %ob.data.clip_start)
  3322. a.setAttribute('farPlaneDist', '%s' %ob.data.clip_end)
  3323. a.setAttribute('near', '%s' %ob.data.clip_start) # requested by cyrfer
  3324. a.setAttribute('far', '%s' %ob.data.clip_end)
  3325. elif ob.type == 'LAMP' and ob.data.type in 'POINT SPOT SUN'.split():
  3326. Report.lights.append( ob.name )
  3327. l = doc.createElement('light')
  3328. o.appendChild(l)
  3329. mat = get_parent_matrix(ob, objects).inverted() * ob.matrix_world
  3330. p = doc.createElement('position') # just to make sure we conform with the DTD
  3331. l.appendChild(p)
  3332. v = swap( ob.matrix_world.to_translation() )
  3333. p.setAttribute('x', '%6f'%v.x)
  3334. p.setAttribute('y', '%6f'%v.y)
  3335. p.setAttribute('z', '%6f'%v.z)
  3336. if ob.data.type == 'POINT':
  3337. l.setAttribute('type', 'point')
  3338. elif ob.data.type == 'SPOT':
  3339. l.setAttribute('type', 'spot')
  3340. elif ob.data.type == 'SUN':
  3341. l.setAttribute('type', 'directional')
  3342. l.setAttribute('name', ob.name )
  3343. l.setAttribute('powerScale', str(ob.data.energy))
  3344. a = doc.createElement('lightAttenuation'); l.appendChild( a )
  3345. a.setAttribute('range', '5000' ) # is this an Ogre constant?
  3346. a.setAttribute('constant', '1.0') # TODO support quadratic light
  3347. a.setAttribute('linear', '%s'%(1.0/ob.data.distance))
  3348. a.setAttribute('quadratic', '0.0')
  3349. if ob.data.type in ('SPOT', 'SUN'):
  3350. vector = swap(mathutils.Euler.to_matrix(ob.rotation_euler)[2])
  3351. a = doc.createElement('direction')
  3352. l.appendChild(a)
  3353. a.setAttribute('x',str(round(-vector[0],3)))
  3354. a.setAttribute('y',str(round(-vector[1],3)))
  3355. a.setAttribute('z',str(round(-vector[2],3)))
  3356. if ob.data.type == 'SPOT':
  3357. a = doc.createElement('spotLightRange')
  3358. l.appendChild(a)
  3359. a.setAttribute('inner',str( ob.data.spot_size*(1.0-ob.data.spot_blend) ))
  3360. a.setAttribute('outer',str(ob.data.spot_size))
  3361. a.setAttribute('falloff','1.0')
  3362. if ob.data.use_diffuse:
  3363. a = doc.createElement('colourDiffuse'); l.appendChild( a )
  3364. a.setAttribute('r', '%s'%ob.data.color.r)
  3365. a.setAttribute('g', '%s'%ob.data.color.g)
  3366. a.setAttribute('b', '%s'%ob.data.color.b)
  3367. if ob.data.use_specular:
  3368. a = doc.createElement('colourSpecular'); l.appendChild( a )
  3369. a.setAttribute('r', '%s'%ob.data.color.r)
  3370. a.setAttribute('g', '%s'%ob.data.color.g)
  3371. a.setAttribute('b', '%s'%ob.data.color.b)
  3372. if ob.data.type != 'HEMI': # colourShadow is extra, not part of Ogre DTD
  3373. if ob.data.shadow_method != 'NOSHADOW': # Hemi light has no shadow_method
  3374. a = doc.createElement('colourShadow');l.appendChild( a )
  3375. a.setAttribute('r', '%s'%ob.data.color.r)
  3376. a.setAttribute('g', '%s'%ob.data.color.g)
  3377. a.setAttribute('b', '%s'%ob.data.color.b)
  3378. l.setAttribute('shadow','true')
  3379. for child in ob.children:
  3380. self._node_export( child,
  3381. url = url, doc = doc, rex = rex,
  3382. exported_meshes = exported_meshes,
  3383. meshes = meshes,
  3384. mesh_collision_prims = mesh_collision_prims,
  3385. mesh_collision_files = mesh_collision_files,
  3386. prefix = prefix,
  3387. objects=objects,
  3388. xmlparent=o
  3389. )
  3390. ## UI panel Ogre export - Subclasses _OgreCommonExport_
  3391. class INFO_OT_createOgreExport(bpy.types.Operator, _OgreCommonExport_):
  3392. '''Export Ogre Scene'''
  3393. bl_idname = "ogre.export"
  3394. bl_label = "Export Ogre"
  3395. bl_options = {'REGISTER'}
  3396. # Basic options
  3397. EX_SWAP_AXIS = EnumProperty(
  3398. items=AXIS_MODES,
  3399. name='swap axis',
  3400. description='axis swapping mode',
  3401. default= CONFIG['SWAP_AXIS'])
  3402. EX_SEP_MATS = BoolProperty(
  3403. name="Separate Materials",
  3404. description="exports a .material for each material (rather than putting all materials in a single .material file)",
  3405. default=CONFIG['SEP_MATS'])
  3406. EX_ONLY_DEFORMABLE_BONES = BoolProperty(
  3407. name="Only Deformable Bones",
  3408. description="only exports bones that are deformable. Useful for hiding IK-Bones used in Blender. Warning: Will cause trouble if a deformable bone has a non-deformable parent",
  3409. default=CONFIG['ONLY_DEFORMABLE_BONES'])
  3410. EX_ONLY_ANIMATED_BONES = BoolProperty(
  3411. name="Only Animated Bones",
  3412. description="only exports bones that have been keyframed, useful for run-time animation blending (example: upper/lower torso split)",
  3413. default=CONFIG['ONLY_ANIMATED_BONES'])
  3414. EX_SCENE = BoolProperty(
  3415. name="Export Scene",
  3416. description="export current scene (OgreDotScene xml)",
  3417. default=CONFIG['SCENE'])
  3418. EX_SELONLY = BoolProperty(
  3419. name="Export Selected Only",
  3420. description="export selected",
  3421. default=CONFIG['SELONLY'])
  3422. EX_FORCE_CAMERA = BoolProperty(
  3423. name="Force Camera",
  3424. description="export active camera",
  3425. default=CONFIG['FORCE_CAMERA'])
  3426. EX_FORCE_LAMPS = BoolProperty(
  3427. name="Force Lamps",
  3428. description="export all lamps",
  3429. default=CONFIG['FORCE_LAMPS'])
  3430. EX_MESH = BoolProperty(
  3431. name="Export Meshes",
  3432. description="export meshes",
  3433. default=CONFIG['MESH'])
  3434. EX_MESH_OVERWRITE = BoolProperty(
  3435. name="Export Meshes (overwrite)",
  3436. description="export meshes (overwrite existing files)",
  3437. default=CONFIG['MESH_OVERWRITE'])
  3438. EX_ARM_ANIM = BoolProperty(
  3439. name="Armature Animation",
  3440. description="export armature animations - updates the .skeleton file",
  3441. default=CONFIG['ARM_ANIM'])
  3442. EX_SHAPE_ANIM = BoolProperty(
  3443. name="Shape Animation",
  3444. description="export shape animations - updates the .mesh file",
  3445. default=CONFIG['SHAPE_ANIM'])
  3446. EX_INDEPENDENT_ANIM = BoolProperty(
  3447. name="Independent Animations",
  3448. description="Export each NLA-Strip independently. If unchecked NLA-Strips that overlap influence each other.",
  3449. default=CONFIG['INDEPENDENT_ANIM'])
  3450. EX_TRIM_BONE_WEIGHTS = FloatProperty(
  3451. name="Trim Weights",
  3452. description="ignore bone weights below this value (Ogre supports 4 bones per vertex)",
  3453. min=0.0, max=0.5, default=CONFIG['TRIM_BONE_WEIGHTS'] )
  3454. EX_ARRAY = BoolProperty(
  3455. name="Optimize Arrays",
  3456. description="optimize array modifiers as instances (constant offset only)",
  3457. default=CONFIG['ARRAY'])
  3458. EX_MATERIALS = BoolProperty(
  3459. name="Export Materials",
  3460. description="exports .material script",
  3461. default=CONFIG['MATERIALS'])
  3462. EX_FORCE_IMAGE_FORMAT = EnumProperty(
  3463. items=_IMAGE_FORMATS,
  3464. name='Convert Images',
  3465. description='convert all textures to format',
  3466. default=CONFIG['FORCE_IMAGE_FORMAT'] )
  3467. EX_DDS_MIPS = IntProperty(
  3468. name="DDS Mips",
  3469. description="number of mip maps (DDS)",
  3470. min=0, max=16,
  3471. default=CONFIG['DDS_MIPS'])
  3472. # Mesh options
  3473. EX_lodLevels = IntProperty(
  3474. name="LOD Levels",
  3475. description="MESH number of LOD levels",
  3476. min=0, max=32,
  3477. default=CONFIG['lodLevels'])
  3478. EX_lodDistance = IntProperty(
  3479. name="LOD Distance",
  3480. description="MESH distance increment to reduce LOD",
  3481. min=0, max=2000,
  3482. default=CONFIG['lodDistance'])
  3483. EX_lodPercent = IntProperty(
  3484. name="LOD Percentage",
  3485. description="LOD percentage reduction",
  3486. min=0, max=99,
  3487. default=CONFIG['lodPercent'])
  3488. EX_nuextremityPoints = IntProperty(
  3489. name="Extremity Points",
  3490. description="MESH Extremity Points",
  3491. min=0, max=65536,
  3492. default=CONFIG['nuextremityPoints'])
  3493. EX_generateEdgeLists = BoolProperty(
  3494. name="Edge Lists",
  3495. description="MESH generate edge lists (for stencil shadows)",
  3496. default=CONFIG['generateEdgeLists'])
  3497. EX_generateTangents = BoolProperty(
  3498. name="Tangents",
  3499. description="MESH generate tangents",
  3500. default=CONFIG['generateTangents'])
  3501. EX_tangentSemantic = StringProperty(
  3502. name="Tangent Semantic",
  3503. description="MESH tangent semantic",
  3504. maxlen=3,
  3505. default=CONFIG['tangentSemantic'])
  3506. EX_tangentUseParity = IntProperty(
  3507. name="Tangent Parity",
  3508. description="MESH tangent use parity",
  3509. min=0, max=16,
  3510. default=CONFIG['tangentUseParity'])
  3511. EX_tangentSplitMirrored = BoolProperty(
  3512. name="Tangent Split Mirrored",
  3513. description="MESH split mirrored tangents",
  3514. default=CONFIG['tangentSplitMirrored'])
  3515. EX_tangentSplitRotated = BoolProperty(
  3516. name="Tangent Split Rotated",
  3517. description="MESH split rotated tangents",
  3518. default=CONFIG['tangentSplitRotated'])
  3519. EX_reorganiseBuffers = BoolProperty(
  3520. name="Reorganise Buffers",
  3521. description="MESH reorganise vertex buffers",
  3522. default=CONFIG['reorganiseBuffers'])
  3523. EX_optimiseAnimations = BoolProperty(
  3524. name="Optimize Animations",
  3525. description="MESH optimize animations",
  3526. default=CONFIG['optimiseAnimations'])
  3527. filepath= StringProperty(
  3528. name="File Path",
  3529. description="Filepath used for exporting Ogre .scene file",
  3530. maxlen=1024,
  3531. default="",
  3532. subtype='FILE_PATH')
  3533. EXPORT_TYPE = 'OGRE'
  3534. ## UI panel Tundra export - Subclasses _OgreCommonExport_
  3535. class INFO_OT_createRealxtendExport( bpy.types.Operator, _OgreCommonExport_):
  3536. '''Export RealXtend Scene'''
  3537. bl_idname = "ogre.export_realxtend"
  3538. bl_label = "Export RealXtend"
  3539. bl_options = {'REGISTER', 'UNDO'}
  3540. EX_SWAP_AXIS = EnumProperty(
  3541. items=AXIS_MODES,
  3542. name='swap axis',
  3543. description='axis swapping mode',
  3544. default= CONFIG['SWAP_AXIS']
  3545. )
  3546. # Basic options
  3547. EX_SEP_MATS = BoolProperty(
  3548. name="Separate Materials",
  3549. description="exports a .material for each material (rather than putting all materials in a single .material file)",
  3550. default=CONFIG['SEP_MATS'])
  3551. EX_ONLY_DEFORMABLE_BONES = BoolProperty(
  3552. name="Only Deformable Bones",
  3553. description="only exports bones that are deformable. Useful for hiding IK-Bones used in Blender. Warning: Will cause trouble if a deformable bone has a non-deformable parent",
  3554. default=CONFIG['ONLY_DEFORMABLE_BONES'])
  3555. EX_ONLY_ANIMATED_BONES = BoolProperty(
  3556. name="Only Animated Bones",
  3557. description="only exports bones that have been keyframed, useful for run-time animation blending (example: upper/lower torso split)",
  3558. default=CONFIG['ONLY_ANIMATED_BONES'])
  3559. EX_SCENE = BoolProperty(
  3560. name="Export Scene",
  3561. description="export current scene (OgreDotScene xml)",
  3562. default=CONFIG['SCENE'])
  3563. EX_SELONLY = BoolProperty(
  3564. name="Export Selected Only",
  3565. description="export selected",
  3566. default=CONFIG['SELONLY'])
  3567. EX_FORCE_CAMERA = BoolProperty(
  3568. name="Force Camera",
  3569. description="export active camera",
  3570. default=CONFIG['FORCE_CAMERA'])
  3571. EX_FORCE_LAMPS = BoolProperty(
  3572. name="Force Lamps",
  3573. description="export all lamps",
  3574. default=CONFIG['FORCE_LAMPS'])
  3575. EX_MESH = BoolProperty(
  3576. name="Export Meshes",
  3577. description="export meshes",
  3578. default=CONFIG['MESH'])
  3579. EX_MESH_OVERWRITE = BoolProperty(
  3580. name="Export Meshes (overwrite)",
  3581. description="export meshes (overwrite existing files)",
  3582. default=CONFIG['MESH_OVERWRITE'])
  3583. EX_ARM_ANIM = BoolProperty(
  3584. name="Armature Animation",
  3585. description="export armature animations - updates the .skeleton file",
  3586. default=CONFIG['ARM_ANIM'])
  3587. EX_SHAPE_ANIM = BoolProperty(
  3588. name="Shape Animation",
  3589. description="export shape animations - updates the .mesh file",
  3590. default=CONFIG['SHAPE_ANIM'])
  3591. EX_INDEPENDENT_ANIM = BoolProperty(
  3592. name="Independent Animations",
  3593. description="Export each NLA-Strip independently. If unchecked NLA-Strips that overlap influence each other.",
  3594. default=CONFIG['INDEPENDENT_ANIM'])
  3595. EX_TRIM_BONE_WEIGHTS = FloatProperty(
  3596. name="Trim Weights",
  3597. description="ignore bone weights below this value (Ogre supports 4 bones per vertex)",
  3598. min=0.0, max=0.5, default=CONFIG['TRIM_BONE_WEIGHTS'] )
  3599. EX_ARRAY = BoolProperty(
  3600. name="Optimize Arrays",
  3601. description="optimize array modifiers as instances (constant offset only)",
  3602. default=CONFIG['ARRAY'])
  3603. EX_MATERIALS = BoolProperty(
  3604. name="Export Materials",
  3605. description="exports .material script",
  3606. default=CONFIG['MATERIALS'])
  3607. EX_FORCE_IMAGE_FORMAT = EnumProperty(
  3608. name='Convert Images',
  3609. description='convert all textures to format',
  3610. items=_IMAGE_FORMATS,
  3611. default=CONFIG['FORCE_IMAGE_FORMAT'])
  3612. EX_DDS_MIPS = IntProperty(
  3613. name="DDS Mips",
  3614. description="number of mip maps (DDS)",
  3615. min=0, max=16,
  3616. default=CONFIG['DDS_MIPS'])
  3617. # Mesh options
  3618. EX_lodLevels = IntProperty(
  3619. name="LOD Levels",
  3620. description="MESH number of LOD levels",
  3621. min=0, max=32,
  3622. default=CONFIG['lodLevels'])
  3623. EX_lodDistance = IntProperty(
  3624. name="LOD Distance",
  3625. description="MESH distance increment to reduce LOD",
  3626. min=0, max=2000,
  3627. default=CONFIG['lodDistance'])
  3628. EX_lodPercent = IntProperty(
  3629. name="LOD Percentage",
  3630. description="LOD percentage reduction",
  3631. min=0, max=99,
  3632. default=CONFIG['lodPercent'])
  3633. EX_nuextremityPoints = IntProperty(
  3634. name="Extremity Points",
  3635. description="MESH Extremity Points",
  3636. min=0, max=65536,
  3637. default=CONFIG['nuextremityPoints'])
  3638. EX_generateEdgeLists = BoolProperty(
  3639. name="Edge Lists",
  3640. description="MESH generate edge lists (for stencil shadows)",
  3641. default=CONFIG['generateEdgeLists'])
  3642. EX_generateTangents = BoolProperty(
  3643. name="Tangents",
  3644. description="MESH generate tangents",
  3645. default=CONFIG['generateTangents'])
  3646. EX_tangentSemantic = StringProperty(
  3647. name="Tangent Semantic",
  3648. description="MESH tangent semantic",
  3649. maxlen=3,
  3650. default=CONFIG['tangentSemantic'])
  3651. EX_tangentUseParity = IntProperty(
  3652. name="Tangent Parity",
  3653. description="MESH tangent use parity",
  3654. min=0, max=16,
  3655. default=CONFIG['tangentUseParity'])
  3656. EX_tangentSplitMirrored = BoolProperty(
  3657. name="Tangent Split Mirrored",
  3658. description="MESH split mirrored tangents",
  3659. default=CONFIG['tangentSplitMirrored'])
  3660. EX_tangentSplitRotated = BoolProperty(
  3661. name="Tangent Split Rotated",
  3662. description="MESH split rotated tangents",
  3663. default=CONFIG['tangentSplitRotated'])
  3664. EX_reorganiseBuffers = BoolProperty(
  3665. name="Reorganise Buffers",
  3666. description="MESH reorganise vertex buffers",
  3667. default=CONFIG['reorganiseBuffers'])
  3668. EX_optimiseAnimations = BoolProperty(
  3669. name="Optimize Animations",
  3670. description="MESH optimize animations",
  3671. default=CONFIG['optimiseAnimations'])
  3672. filepath = StringProperty(
  3673. name="File Path",
  3674. description="Filepath used for exporting .txml file",
  3675. maxlen=1024,
  3676. default="",
  3677. subtype='FILE_PATH')
  3678. EXPORT_TYPE = 'REX'
  3679. ## Ogre node helper
  3680. def _ogre_node_helper( doc, ob, objects, prefix='', pos=None, rot=None, scl=None ):
  3681. # shouldn't this be matrix_local?
  3682. mat = get_parent_matrix(ob, objects).inverted() * ob.matrix_world
  3683. o = doc.createElement('node')
  3684. o.setAttribute('name',prefix+ob.name)
  3685. p = doc.createElement('position')
  3686. q = doc.createElement('rotation') #('quaternion')
  3687. s = doc.createElement('scale')
  3688. for n in (p,q,s):
  3689. o.appendChild(n)
  3690. if pos:
  3691. v = swap(pos)
  3692. else:
  3693. v = swap( mat.to_translation() )
  3694. p.setAttribute('x', '%6f'%v.x)
  3695. p.setAttribute('y', '%6f'%v.y)
  3696. p.setAttribute('z', '%6f'%v.z)
  3697. if rot:
  3698. v = swap(rot)
  3699. else:
  3700. v = swap( mat.to_quaternion() )
  3701. q.setAttribute('qx', '%6f'%v.x)
  3702. q.setAttribute('qy', '%6f'%v.y)
  3703. q.setAttribute('qz', '%6f'%v.z)
  3704. q.setAttribute('qw','%6f'%v.w)
  3705. if scl: # this should not be used
  3706. v = swap(scl)
  3707. x=abs(v.x); y=abs(v.y); z=abs(v.z)
  3708. s.setAttribute('x', '%6f'%x)
  3709. s.setAttribute('y', '%6f'%y)
  3710. s.setAttribute('z', '%6f'%z)
  3711. else: # scale is different in Ogre from blender - rotation is removed
  3712. ri = mat.to_quaternion().inverted().to_matrix()
  3713. scale = ri.to_4x4() * mat
  3714. v = swap( scale.to_scale() )
  3715. x=abs(v.x); y=abs(v.y); z=abs(v.z)
  3716. s.setAttribute('x', '%6f'%x)
  3717. s.setAttribute('y', '%6f'%y)
  3718. s.setAttribute('z', '%6f'%z)
  3719. return o
  3720. ## MeshMagick
  3721. class MeshMagick(object):
  3722. ''' Usage: MeshMagick [global_options] toolname [tool_options] infile(s) -- [outfile(s)]
  3723. Available Tools
  3724. ===============
  3725. info - print information about the mesh.
  3726. meshmerge - Merge multiple submeshes into a single mesh.
  3727. optimise - Optimise meshes and skeletons.
  3728. rename - Rename different elements of meshes and skeletons.
  3729. transform - Scale, rotate or otherwise transform a mesh.
  3730. '''
  3731. @staticmethod
  3732. def get_merge_group( ob ):
  3733. return get_merge_group( ob, prefix='magicmerge' )
  3734. @staticmethod
  3735. def merge( group, path='/tmp', force_name=None ):
  3736. print('-'*80)
  3737. print(' mesh magick - merge ')
  3738. exe = CONFIG['OGRETOOLS_MESH_MAGICK']
  3739. if not os.path.isfile( exe ):
  3740. print( 'ERROR: can not find MeshMagick.exe' )
  3741. print( exe )
  3742. return
  3743. files = []
  3744. for ob in group.objects:
  3745. if ob.data.users == 1: # single users only
  3746. files.append( os.path.join( path, ob.data.name+'.mesh' ) )
  3747. print( files[-1] )
  3748. opts = 'meshmerge'
  3749. if sys.platform == 'linux2': cmd = '/usr/bin/wine %s %s' %(exe, opts)
  3750. else: cmd = '%s %s' %(exe, opts)
  3751. if force_name: output = force_name + '.mesh'
  3752. else: output = '_%s_.mesh' %group.name
  3753. cmd = cmd.split() + files + ['--', os.path.join(path,output) ]
  3754. subprocess.call( cmd )
  3755. print(' mesh magick - complete ')
  3756. print('-'*80)
  3757. ## Ogre Command Line Tools Documentation
  3758. _ogre_command_line_tools_doc = '''
  3759. Usage: OgreXMLConverter [options] sourcefile [destfile]
  3760. Available options:
  3761. -i = interactive mode - prompt for options
  3762. (The next 4 options are only applicable when converting XML to Mesh)
  3763. -l lodlevels = number of LOD levels
  3764. -v lodvalue = value increment to reduce LOD
  3765. -s lodstrategy = LOD strategy to use for this mesh
  3766. -p lodpercent = Percentage triangle reduction amount per LOD
  3767. -f lodnumtris = Fixed vertex reduction per LOD
  3768. -e = DON'T generate edge lists (for stencil shadows)
  3769. -r = DON'T reorganise vertex buffers to OGRE recommended format.
  3770. -t = Generate tangents (for normal mapping)
  3771. -td [uvw|tangent]
  3772. = Tangent vertex semantic destination (default tangent)
  3773. -ts [3|4] = Tangent size (3 or 4 components, 4 includes parity, default 3)
  3774. -tm = Split tangent vertices at UV mirror points
  3775. -tr = Split tangent vertices where basis is rotated > 90 degrees
  3776. -o = DON'T optimise out redundant tracks & keyframes
  3777. -d3d = Prefer D3D packed colour formats (default on Windows)
  3778. -gl = Prefer GL packed colour formats (default on non-Windows)
  3779. -E endian = Set endian mode 'big' 'little' or 'native' (default)
  3780. -x num = Generate no more than num eXtremes for every submesh (default 0)
  3781. -q = Quiet mode, less output
  3782. -log filename = name of the log file (default: 'OgreXMLConverter.log')
  3783. sourcefile = name of file to convert
  3784. destfile = optional name of file to write to. If you don't
  3785. specify this OGRE works it out through the extension
  3786. and the XML contents if the source is XML. For example
  3787. test.mesh becomes test.xml, test.xml becomes test.mesh
  3788. if the XML document root is <mesh> etc.
  3789. '''
  3790. ## Ogre Command Line Tools
  3791. def OgreXMLConverter( infile, has_uvs=False ):
  3792. # todo: Show a UI dialog to show this error. It's pretty fatal for normal usage.
  3793. # We should show how to configure the converter location in config panel or tell the default path.
  3794. exe = CONFIG['OGRETOOLS_XML_CONVERTER']
  3795. if not os.path.isfile( exe ):
  3796. print( 'WARNING: can not find OgreXMLConverter (can not convert XXX.mesh.xml to XXX.mesh' )
  3797. return
  3798. basicArguments = ''
  3799. if CONFIG['lodLevels']:
  3800. basicArguments += ' -l %s -v %s -p %s' %(CONFIG['lodLevels'], CONFIG['lodDistance'], CONFIG['lodPercent'])
  3801. if CONFIG['nuextremityPoints'] > 0:
  3802. basicArguments += ' -x %s' %CONFIG['nuextremityPoints']
  3803. if not CONFIG['generateEdgeLists']:
  3804. basicArguments += ' -e'
  3805. # note: OgreXmlConverter fails to convert meshes without UVs
  3806. if CONFIG['generateTangents'] and has_uvs:
  3807. basicArguments += ' -t'
  3808. if CONFIG['tangentSemantic']:
  3809. basicArguments += ' -td %s' %CONFIG['tangentSemantic']
  3810. if CONFIG['tangentUseParity']:
  3811. basicArguments += ' -ts %s' %CONFIG['tangentUseParity']
  3812. if CONFIG['tangentSplitMirrored']:
  3813. basicArguments += ' -tm'
  3814. if CONFIG['tangentSplitRotated']:
  3815. basicArguments += ' -tr'
  3816. if not CONFIG['reorganiseBuffers']:
  3817. basicArguments += ' -r'
  3818. if not CONFIG['optimiseAnimations']:
  3819. basicArguments += ' -o'
  3820. # Make xml converter print less stuff, comment this if you want more debug info out
  3821. basicArguments += ' -q'
  3822. opts = '-log _ogre_debug.txt %s' %basicArguments
  3823. path,name = os.path.split( infile )
  3824. cmd = '%s %s' %(exe, opts)
  3825. cmd = cmd.split() + [infile]
  3826. subprocess.call( cmd )
  3827. ## Bone
  3828. class Bone(object):
  3829. def __init__(self, rbone, pbone, skeleton):
  3830. if CONFIG['SWAP_AXIS'] == 'xyz':
  3831. self.fixUpAxis = False
  3832. else:
  3833. self.fixUpAxis = True
  3834. if CONFIG['SWAP_AXIS'] == '-xzy': # Tundra 1.x
  3835. self.flipMat = mathutils.Matrix(((-1,0,0,0),(0,0,1,0),(0,1,0,0),(0,0,0,1)))
  3836. elif CONFIG['SWAP_AXIS'] == 'xz-y': # Tundra 2.x current generation
  3837. #self.flipMat = mathutils.Matrix(((1,0,0,0),(0,0,1,0),(0,1,0,0),(0,0,0,1)))
  3838. self.flipMat = mathutils.Matrix(((1,0,0,0),(0,0,1,0),(0,-1,0,0),(0,0,0,1))) # thanks to Waruck
  3839. else:
  3840. print( 'ERROR - TODO: axis swap mode not supported with armature animation' )
  3841. assert 0
  3842. self.skeleton = skeleton
  3843. self.name = pbone.name
  3844. self.matrix = rbone.matrix_local.copy() # armature space
  3845. #self.matrix_local = rbone.matrix.copy() # space?
  3846. self.bone = pbone # safe to hold pointer to pose bone, not edit bone!
  3847. if pbone.parent and not pbone.parent.bone.use_deform and CONFIG['ONLY_DEFORMABLE_BONES']:
  3848. print('warning: bone <%s> has non-deformable parent.' %self.name)
  3849. # todo: Test -> #if pbone.bone.use_inherit_scale: print('warning: bone <%s> is using inherit scaling, Ogre has no support for this' %self.name)
  3850. self.parent = pbone.parent
  3851. self.children = []
  3852. def update(self): # called on frame update
  3853. pose = self.bone.matrix.copy()
  3854. self._inverse_total_trans_pose = pose.inverted()
  3855. # calculate difference to parent bone
  3856. if self.parent:
  3857. pose = self.parent._inverse_total_trans_pose* pose
  3858. elif self.fixUpAxis:
  3859. pose = self.flipMat * pose
  3860. else:
  3861. pass
  3862. self.pose_location = pose.to_translation() - self.ogre_rest_matrix.to_translation()
  3863. pose = self.inverse_ogre_rest_matrix * pose
  3864. self.pose_rotation = pose.to_quaternion()
  3865. self.pose_scale = pose.to_scale()
  3866. for child in self.children:
  3867. child.update()
  3868. def rebuild_tree( self ): # called first on all bones
  3869. if self.parent:
  3870. self.parent = self.skeleton.get_bone( self.parent.name )
  3871. self.parent.children.append( self )
  3872. def compute_rest( self ): # called after rebuild_tree, recursive roots to leaves
  3873. if self.parent:
  3874. inverseParentMatrix = self.parent.inverse_total_trans
  3875. elif self.fixUpAxis:
  3876. inverseParentMatrix = self.flipMat
  3877. else:
  3878. inverseParentMatrix = mathutils.Matrix(((1,0,0,0),(0,1,0,0),(0,0,1,0),(0,0,0,1)))
  3879. #self.ogre_rest_matrix = self.skeleton.object_space_transformation * self.matrix # ALLOW ROTATION?
  3880. self.ogre_rest_matrix = self.matrix.copy()
  3881. # store total inverse transformation
  3882. self.inverse_total_trans = self.ogre_rest_matrix.inverted()
  3883. # relative to OGRE parent bone origin
  3884. self.ogre_rest_matrix = inverseParentMatrix * self.ogre_rest_matrix
  3885. self.inverse_ogre_rest_matrix = self.ogre_rest_matrix.inverted()
  3886. # recursion
  3887. for child in self.children:
  3888. child.compute_rest()
  3889. # Skeleton
  3890. class Skeleton(object):
  3891. def get_bone( self, name ):
  3892. for b in self.bones:
  3893. if b.name == name:
  3894. return b
  3895. def __init__(self, ob ):
  3896. if ob.location.x != 0 or ob.location.y != 0 or ob.location.z != 0:
  3897. Report.warnings.append('ERROR: Mesh (%s): is offset from Armature - zero transform is required' %ob.name)
  3898. if ob.scale.x != 1 or ob.scale.y != 1 or ob.scale.z != 1:
  3899. Report.warnings.append('ERROR: Mesh (%s): has been scaled - scale(1,1,1) is required' %ob.name)
  3900. self.object = ob
  3901. self.bones = []
  3902. mats = {}
  3903. self.arm = arm = ob.find_armature()
  3904. arm.hide = False
  3905. self._restore_layers = list(arm.layers)
  3906. #arm.layers = [True]*20 # can not have anything hidden - REQUIRED?
  3907. for pbone in arm.pose.bones:
  3908. if pbone.bone.use_deform or not CONFIG['ONLY_DEFORMABLE_BONES']:
  3909. mybone = Bone( arm.data.bones[pbone.name] ,pbone, self )
  3910. self.bones.append( mybone )
  3911. if arm.name not in Report.armatures:
  3912. Report.armatures.append( arm.name )
  3913. ## bad idea - allowing rotation of armature, means vertices must also be rotated,
  3914. ## also a bug with applying the rotation, the Z rotation is lost
  3915. #x,y,z = arm.matrix_local.copy().inverted().to_euler()
  3916. #e = mathutils.Euler( (x,z,y) )
  3917. #self.object_space_transformation = e.to_matrix().to_4x4()
  3918. x,y,z = arm.matrix_local.to_euler()
  3919. if x != 0 or y != 0 or z != 0:
  3920. Report.warnings.append('ERROR: Armature: %s is rotated - (rotation is ignored)' %arm.name)
  3921. ## setup bones for Ogre format ##
  3922. for b in self.bones: b.rebuild_tree()
  3923. ## walk bones, convert them ##
  3924. self.roots = []
  3925. ep = 0.0001
  3926. for b in self.bones:
  3927. if not b.parent:
  3928. b.compute_rest()
  3929. loc,rot,scl = b.ogre_rest_matrix.decompose()
  3930. #if loc.x or loc.y or loc.z:
  3931. # Report.warnings.append('ERROR: root bone has non-zero transform (location offset)')
  3932. #if rot.w > ep or rot.x > ep or rot.y > ep or rot.z < 1.0-ep:
  3933. # Report.warnings.append('ERROR: root bone has non-zero transform (rotation offset)')
  3934. self.roots.append( b )
  3935. def to_xml( self ):
  3936. _fps = float( bpy.context.scene.render.fps )
  3937. doc = RDocument()
  3938. root = doc.createElement('skeleton'); doc.appendChild( root )
  3939. bones = doc.createElement('bones'); root.appendChild( bones )
  3940. bh = doc.createElement('bonehierarchy'); root.appendChild( bh )
  3941. for i,bone in enumerate(self.bones):
  3942. b = doc.createElement('bone')
  3943. b.setAttribute('name', bone.name)
  3944. b.setAttribute('id', str(i) )
  3945. bones.appendChild( b )
  3946. mat = bone.ogre_rest_matrix.copy()
  3947. if bone.parent:
  3948. bp = doc.createElement('boneparent')
  3949. bp.setAttribute('bone', bone.name)
  3950. bp.setAttribute('parent', bone.parent.name)
  3951. bh.appendChild( bp )
  3952. pos = doc.createElement( 'position' ); b.appendChild( pos )
  3953. x,y,z = mat.to_translation()
  3954. pos.setAttribute('x', '%6f' %x )
  3955. pos.setAttribute('y', '%6f' %y )
  3956. pos.setAttribute('z', '%6f' %z )
  3957. rot = doc.createElement( 'rotation' ) # "rotation", not "rotate"
  3958. b.appendChild( rot )
  3959. q = mat.to_quaternion()
  3960. rot.setAttribute('angle', '%6f' %q.angle )
  3961. axis = doc.createElement('axis'); rot.appendChild( axis )
  3962. x,y,z = q.axis
  3963. axis.setAttribute('x', '%6f' %x )
  3964. axis.setAttribute('y', '%6f' %y )
  3965. axis.setAttribute('z', '%6f' %z )
  3966. # Ogre bones do not have initial scaling? ##
  3967. # note: Ogre bones by default do not pass down their scaling in animation,
  3968. # so in blender all bones are like 'do-not-inherit-scaling'
  3969. if 0:
  3970. scale = doc.createElement('scale'); b.appendChild( scale )
  3971. x,y,z = swap( mat.to_scale() )
  3972. scale.setAttribute('x', str(x))
  3973. scale.setAttribute('y', str(y))
  3974. scale.setAttribute('z', str(z))
  3975. arm = self.arm
  3976. if not arm.animation_data or (arm.animation_data and not arm.animation_data.nla_tracks): # assume animated via constraints and use blender timeline.
  3977. anims = doc.createElement('animations'); root.appendChild( anims )
  3978. anim = doc.createElement('animation'); anims.appendChild( anim )
  3979. tracks = doc.createElement('tracks'); anim.appendChild( tracks )
  3980. anim.setAttribute('name', 'my_animation')
  3981. start = bpy.context.scene.frame_start; end = bpy.context.scene.frame_end
  3982. anim.setAttribute('length', str( (end-start)/_fps ) )
  3983. _keyframes = {}
  3984. _bonenames_ = []
  3985. for bone in arm.pose.bones:
  3986. if self.get_bone(bone.name): #check if the bone was exported
  3987. _bonenames_.append( bone.name )
  3988. track = doc.createElement('track')
  3989. track.setAttribute('bone', bone.name)
  3990. tracks.appendChild( track )
  3991. keyframes = doc.createElement('keyframes')
  3992. track.appendChild( keyframes )
  3993. _keyframes[ bone.name ] = keyframes
  3994. for frame in range( int(start), int(end)+1, bpy.context.scene.frame_step):
  3995. bpy.context.scene.frame_set(frame)
  3996. for bone in self.roots: bone.update()
  3997. print('\t\t Frame:', frame)
  3998. for bonename in _bonenames_:
  3999. if self.get_bone(bonename): #check if the bone was exported
  4000. bone = self.get_bone( bonename )
  4001. _loc = bone.pose_location
  4002. _rot = bone.pose_rotation
  4003. _scl = bone.pose_scale
  4004. keyframe = doc.createElement('keyframe')
  4005. keyframe.setAttribute('time', str((frame-start)/_fps))
  4006. _keyframes[ bonename ].appendChild( keyframe )
  4007. trans = doc.createElement('translate')
  4008. keyframe.appendChild( trans )
  4009. x,y,z = _loc
  4010. trans.setAttribute('x', '%6f' %x)
  4011. trans.setAttribute('y', '%6f' %y)
  4012. trans.setAttribute('z', '%6f' %z)
  4013. rot = doc.createElement( 'rotate' )
  4014. keyframe.appendChild( rot )
  4015. q = _rot
  4016. rot.setAttribute('angle', '%6f' %q.angle )
  4017. axis = doc.createElement('axis'); rot.appendChild( axis )
  4018. x,y,z = q.axis
  4019. axis.setAttribute('x', '%6f' %x )
  4020. axis.setAttribute('y', '%6f' %y )
  4021. axis.setAttribute('z', '%6f' %z )
  4022. scale = doc.createElement('scale')
  4023. keyframe.appendChild( scale )
  4024. x,y,z = _scl
  4025. scale.setAttribute('x', '%6f' %x)
  4026. scale.setAttribute('y', '%6f' %y)
  4027. scale.setAttribute('z', '%6f' %z)
  4028. elif arm.animation_data:
  4029. anims = doc.createElement('animations'); root.appendChild( anims )
  4030. if not len( arm.animation_data.nla_tracks ):
  4031. Report.warnings.append('you must assign an NLA strip to armature (%s) that defines the start and end frames' %arm.name)
  4032. if CONFIG['INDEPENDENT_ANIM']:
  4033. for nla in arm.animation_data.nla_tracks:
  4034. nla.mute = True
  4035. for nla in arm.animation_data.nla_tracks: # NLA required, lone actions not supported
  4036. if not len(nla.strips): print( 'skipping empty NLA track: %s' %nla.name ); continue
  4037. print('NLA track:', nla.name)
  4038. if CONFIG['INDEPENDENT_ANIM']:
  4039. nla.mute = False
  4040. for strip in nla.strips:
  4041. strip.mute = True
  4042. for strip in nla.strips:
  4043. if CONFIG['INDEPENDENT_ANIM']: strip.mute = False
  4044. print(' strip name:', strip.name)
  4045. anim = doc.createElement('animation'); anims.appendChild( anim )
  4046. tracks = doc.createElement('tracks'); anim.appendChild( tracks )
  4047. Report.armature_animations.append( '%s : %s [start frame=%s end frame=%s]' %(arm.name, nla.name, strip.frame_start, strip.frame_end) )
  4048. anim.setAttribute('name', strip.name) # USE the strip name
  4049. anim.setAttribute('length', str( (strip.frame_end-strip.frame_start)/_fps ) )
  4050. stripbones = []
  4051. if CONFIG['ONLY_ANIMATED_BONES']:
  4052. for group in strip.action.groups: # check if the user has keyed only some of the bones (for anim blending)
  4053. if group.name in arm.pose.bones: stripbones.append( group.name )
  4054. if not stripbones: # otherwise we use all bones
  4055. stripbones = [ bone.name for bone in arm.pose.bones ]
  4056. else:
  4057. stripbones = [ bone.name for bone in arm.pose.bones ]
  4058. _keyframes = {}
  4059. for bonename in stripbones:
  4060. if self.get_bone(bonename): #check if the bone was exported
  4061. track = doc.createElement('track')
  4062. track.setAttribute('bone', bonename)
  4063. tracks.appendChild( track )
  4064. keyframes = doc.createElement('keyframes')
  4065. track.appendChild( keyframes )
  4066. _keyframes[ bonename ] = keyframes
  4067. for frame in range( int(strip.frame_start), int(strip.frame_end)+1, bpy.context.scene.frame_step):#thanks to Vesa
  4068. bpy.context.scene.frame_set(frame)
  4069. for bone in self.roots: bone.update()
  4070. for bonename in stripbones:
  4071. if self.get_bone( bonename ): #check if the bone was exported
  4072. bone = self.get_bone( bonename )
  4073. _loc = bone.pose_location
  4074. _rot = bone.pose_rotation
  4075. _scl = bone.pose_scale
  4076. keyframe = doc.createElement('keyframe')
  4077. keyframe.setAttribute('time', str((frame-strip.frame_start)/_fps))
  4078. _keyframes[ bonename ].appendChild( keyframe )
  4079. trans = doc.createElement('translate')
  4080. keyframe.appendChild( trans )
  4081. x,y,z = _loc
  4082. trans.setAttribute('x', '%6f' %x)
  4083. trans.setAttribute('y', '%6f' %y)
  4084. trans.setAttribute('z', '%6f' %z)
  4085. rot = doc.createElement( 'rotate' )
  4086. keyframe.appendChild( rot )
  4087. q = _rot
  4088. rot.setAttribute('angle', '%6f' %q.angle )
  4089. axis = doc.createElement('axis'); rot.appendChild( axis )
  4090. x,y,z = q.axis
  4091. axis.setAttribute('x', '%6f' %x )
  4092. axis.setAttribute('y', '%6f' %y )
  4093. axis.setAttribute('z', '%6f' %z )
  4094. scale = doc.createElement('scale')
  4095. keyframe.appendChild( scale )
  4096. x,y,z = _scl
  4097. scale.setAttribute('x', '%6f' %x)
  4098. scale.setAttribute('y', '%6f' %y)
  4099. scale.setAttribute('z', '%6f' %z)
  4100. if CONFIG['INDEPENDENT_ANIM']: strip.mute = True
  4101. if CONFIG['INDEPENDENT_ANIM']:
  4102. nla.mute = True
  4103. for strip in nla.strips:
  4104. strip.mute = False
  4105. if CONFIG['INDEPENDENT_ANIM']:
  4106. for nla in arm.animation_data.nla_tracks:
  4107. nla.mute = False
  4108. return doc.toprettyxml()
  4109. ## Selector extras
  4110. class INFO_MT_instances(bpy.types.Menu):
  4111. bl_label = "Instances"
  4112. def draw(self, context):
  4113. layout = self.layout
  4114. inst = gather_instances()
  4115. for data in inst:
  4116. ob = inst[data][0]
  4117. op = layout.operator(INFO_MT_instance.bl_idname, text=ob.name) # operator has no variable for button name?
  4118. op.mystring = ob.name
  4119. layout.separator()
  4120. class INFO_MT_instance(bpy.types.Operator):
  4121. '''select instance group'''
  4122. bl_idname = "ogre.select_instances"
  4123. bl_label = "Select Instance Group"
  4124. bl_options = {'REGISTER', 'UNDO'} # Options for this panel type
  4125. mystring= StringProperty(name="MyString", description="hidden string", maxlen=1024, default="my string")
  4126. @classmethod
  4127. def poll(cls, context):
  4128. return True
  4129. def invoke(self, context, event):
  4130. print( 'invoke select_instances op', event )
  4131. select_instances( context, self.mystring )
  4132. return {'FINISHED'}
  4133. class INFO_MT_groups(bpy.types.Menu):
  4134. bl_label = "Groups"
  4135. def draw(self, context):
  4136. layout = self.layout
  4137. for group in bpy.data.groups:
  4138. op = layout.operator(INFO_MT_group.bl_idname, text=group.name) # operator no variable for button name?
  4139. op.mystring = group.name
  4140. layout.separator()
  4141. class INFO_MT_group(bpy.types.Operator):
  4142. '''select group'''
  4143. bl_idname = "ogre.select_group"
  4144. bl_label = "Select Group"
  4145. bl_options = {'REGISTER'} # Options for this panel type
  4146. mystring= StringProperty(name="MyString", description="hidden string", maxlen=1024, default="my string")
  4147. @classmethod
  4148. def poll(cls, context):
  4149. return True
  4150. def invoke(self, context, event):
  4151. select_group( context, self.mystring )
  4152. return {'FINISHED'}
  4153. ## NVIDIA texture tool documentation
  4154. NVDXT_DOC = '''
  4155. Version 8.30
  4156. NVDXT
  4157. This program
  4158. compresses images
  4159. creates normal maps from color or alpha
  4160. creates DuDv map
  4161. creates cube maps
  4162. writes out .dds file
  4163. does batch processing
  4164. reads .tga, .bmp, .gif, .ppm, .jpg, .tif, .cel, .dds, .png, .psd, .rgb, *.bw and .rgba
  4165. filters MIP maps
  4166. Options:
  4167. -profile <profile name> : Read a profile created from the Photoshop plugin
  4168. -quick : use fast compression method
  4169. -quality_normal : normal quality compression
  4170. -quality_production : production quality compression
  4171. -quality_highest : highest quality compression (this can be very slow)
  4172. -rms_threshold <int> : quality RMS error. Above this, an extensive search is performed.
  4173. -prescale <int> <int>: rescale image to this size first
  4174. -rescale <nearest | hi | lo | next_lo>: rescale image to nearest, next highest or next lowest power of two
  4175. -rel_scale <float, float> : relative scale of original image. 0.5 is half size Default 1.0, 1.0
  4176. Optional Filtering for rescaling. Default cube filter:
  4177. -RescalePoint
  4178. -RescaleBox
  4179. -RescaleTriangle
  4180. -RescaleQuadratic
  4181. -RescaleCubic
  4182. -RescaleCatrom
  4183. -RescaleMitchell
  4184. -RescaleGaussian
  4185. -RescaleSinc
  4186. -RescaleBessel
  4187. -RescaleHanning
  4188. -RescaleHamming
  4189. -RescaleBlackman
  4190. -RescaleKaiser
  4191. -clamp <int, int> : maximum image size. image width and height are clamped
  4192. -clampScale <int, int> : maximum image size. image width and height are scaled
  4193. -window <left, top, right, bottom> : window of original window to compress
  4194. -nomipmap : don't generate MIP maps
  4195. -nmips <int> : specify the number of MIP maps to generate
  4196. -rgbe : Image is RGBE format
  4197. -dither : add dithering
  4198. -sharpenMethod <method>: sharpen method MIP maps
  4199. <method> is
  4200. None
  4201. Negative
  4202. Lighter
  4203. Darker
  4204. ContrastMore
  4205. ContrastLess
  4206. Smoothen
  4207. SharpenSoft
  4208. SharpenMedium
  4209. SharpenStrong
  4210. FindEdges
  4211. Contour
  4212. EdgeDetect
  4213. EdgeDetectSoft
  4214. Emboss
  4215. MeanRemoval
  4216. UnSharp <radius, amount, threshold>
  4217. XSharpen <xsharpen_strength, xsharpen_threshold>
  4218. Custom
  4219. -pause : wait for keyboard on error
  4220. -flip : flip top to bottom
  4221. -timestamp : Update only changed files
  4222. -list <filename> : list of files to convert
  4223. -cubeMap : create cube map .
  4224. Cube faces specified with individual files with -list option
  4225. positive x, negative x, positive y, negative y, positive z, negative z
  4226. Use -output option to specify filename
  4227. Cube faces specified in one file. Use -file to specify input filename
  4228. -volumeMap : create volume texture.
  4229. Volume slices specified with individual files with -list option
  4230. Use -output option to specify filename
  4231. Volume specified in one file. Use -file to specify input filename
  4232. -all : all image files in current directory
  4233. -outdir <directory>: output directory
  4234. -deep [directory]: include all subdirectories
  4235. -outsamedir : output directory same as input
  4236. -overwrite : if input is .dds file, overwrite old file
  4237. -forcewrite : write over readonly files
  4238. -file <filename> : input file to process. Accepts wild cards
  4239. -output <filename> : filename to write to [-outfile can also be specified]
  4240. -append <filename_append> : append this string to output filename
  4241. -8 <dxt1c | dxt1a | dxt3 | dxt5 | u1555 | u4444 | u565 | u8888 | u888 | u555 | L8 | A8> : compress 8 bit images with this format
  4242. -16 <dxt1c | dxt1a | dxt3 | dxt5 | u1555 | u4444 | u565 | u8888 | u888 | u555 | A8L8> : compress 16 bit images with this format
  4243. -24 <dxt1c | dxt1a | dxt3 | dxt5 | u1555 | u4444 | u565 | u8888 | u888 | u555> : compress 24 bit images with this format
  4244. -32 <dxt1c | dxt1a | dxt3 | dxt5 | u1555 | u4444 | u565 | u8888 | u888 | u555> : compress 32 bit images with this format
  4245. -swapRB : swap rb
  4246. -swapRG : swap rg
  4247. -gamma <float value>: gamma correcting during filtering
  4248. -outputScale <float, float, float, float>: scale the output by this (r,g,b,a)
  4249. -outputBias <float, float, float, float>: bias the output by this amount (r,g,b,a)
  4250. -outputWrap : wraps overflow values modulo the output format
  4251. -inputScale <float, float, float, float>: scale the inpput by this (r,g,b,a)
  4252. -inputBias <float, float, float, float>: bias the input by this amount (r,g,b,a)
  4253. -binaryalpha : treat alpha as 0 or 1
  4254. -alpha_threshold <byte>: [0-255] alpha reference value
  4255. -alphaborder : border images with alpha = 0
  4256. -alphaborderLeft : border images with alpha (left) = 0
  4257. -alphaborderRight : border images with alpha (right)= 0
  4258. -alphaborderTop : border images with alpha (top) = 0
  4259. -alphaborderBottom : border images with alpha (bottom)= 0
  4260. -fadeamount <int>: percentage to fade each MIP level. Default 15
  4261. -fadecolor : fade map (color, normal or DuDv) over MIP levels
  4262. -fadetocolor <hex color> : color to fade to
  4263. -custom_fade <n> <n fadeamounts> : set custom fade amount. n is number number of fade amounts. fadeamount are [0,1]
  4264. -fadealpha : fade alpha over MIP levels
  4265. -fadetoalpha <byte>: [0-255] alpha to fade to
  4266. -border : border images with color
  4267. -bordercolor <hex color> : color for border
  4268. -force4 : force DXT1c to use always four colors
  4269. -weight <float, float, float>: Compression weightings for R G and B
  4270. -luminance : convert color values to luminance for L8 formats
  4271. -greyScale : Convert to grey scale
  4272. -greyScaleWeights <float, float, float, float>: override greyscale conversion weights of (0.3086, 0.6094, 0.0820, 0)
  4273. -brightness <float, float, float, float>: per channel brightness. Default 0.0 usual range [0,1]
  4274. -contrast <float, float, float, float>: per channel contrast. Default 1.0 usual range [0.5, 1.5]
  4275. Texture Format Default DXT3:
  4276. -dxt1c : DXT1 (color only)
  4277. -dxt1a : DXT1 (one bit alpha)
  4278. -dxt3 : DXT3
  4279. -dxt5 : DXT5n
  4280. -u1555 : uncompressed 1:5:5:5
  4281. -u4444 : uncompressed 4:4:4:4
  4282. -u565 : uncompressed 5:6:5
  4283. -u8888 : uncompressed 8:8:8:8
  4284. -u888 : uncompressed 0:8:8:8
  4285. -u555 : uncompressed 0:5:5:5
  4286. -p8c : paletted 8 bit (256 colors)
  4287. -p8a : paletted 8 bit (256 colors with alpha)
  4288. -p4c : paletted 4 bit (16 colors)
  4289. -p4a : paletted 4 bit (16 colors with alpha)
  4290. -a8 : 8 bit alpha channel
  4291. -cxv8u8 : normal map format
  4292. -v8u8 : EMBM format (8, bit two component signed)
  4293. -v16u16 : EMBM format (16 bit, two component signed)
  4294. -A8L8 : 8 bit alpha channel, 8 bit luminance
  4295. -fp32x4 : fp32 four channels (A32B32G32R32F)
  4296. -fp32 : fp32 one channel (R32F)
  4297. -fp16x4 : fp16 four channels (A16B16G16R16F)
  4298. -dxt5nm : dxt5 style normal map
  4299. -3Dc : 3DC
  4300. -g16r16 : 16 bit in, two component
  4301. -g16r16f : 16 bit float, two components
  4302. Mip Map Filtering Options. Default box filter:
  4303. -Point
  4304. -Box
  4305. -Triangle
  4306. -Quadratic
  4307. -Cubic
  4308. -Catrom
  4309. -Mitchell
  4310. -Gaussian
  4311. -Sinc
  4312. -Bessel
  4313. -Hanning
  4314. -Hamming
  4315. -Blackman
  4316. -Kaiser
  4317. ***************************
  4318. To make a normal or dudv map, specify one of
  4319. -n4 : normal map 4 sample
  4320. -n3x3 : normal map 3x3 filter
  4321. -n5x5 : normal map 5x5 filter
  4322. -n7x7 : normal map 7x7 filter
  4323. -n9x9 : normal map 9x9 filter
  4324. -dudv : DuDv
  4325. and source of height info:
  4326. -alpha : alpha channel
  4327. -rgb : average rgb
  4328. -biased : average rgb biased
  4329. -red : red channel
  4330. -green : green channel
  4331. -blue : blue channel
  4332. -max : max of (r,g,b)
  4333. -colorspace : mix of r,g,b
  4334. -norm : normalize mip maps (source is a normal map)
  4335. -toHeight : create a height map (source is a normal map)
  4336. Normal/DuDv Map dxt:
  4337. -aheight : store calculated height in alpha field
  4338. -aclear : clear alpha channel
  4339. -awhite : set alpha channel = 1.0
  4340. -scale <float> : scale of height map. Default 1.0
  4341. -wrap : wrap texture around. Default off
  4342. -minz <int> : minimum value for up vector [0-255]. Default 0
  4343. ***************************
  4344. To make a depth sprite, specify:
  4345. -depth
  4346. and source of depth info:
  4347. -alpha : alpha channel
  4348. -rgb : average rgb (default)
  4349. -red : red channel
  4350. -green : green channel
  4351. -blue : blue channel
  4352. -max : max of (r,g,b)
  4353. -colorspace : mix of r,g,b
  4354. Depth Sprite dxt:
  4355. -aheight : store calculated depth in alpha channel
  4356. -aclear : store 0.0 in alpha channel
  4357. -awhite : store 1.0 in alpha channel
  4358. -scale <float> : scale of depth sprite (default 1.0)
  4359. -alpha_modulate : multiplies color by alpha during filtering
  4360. -pre_modulate : multiplies color by alpha before processing
  4361. Examples
  4362. nvdxt -cubeMap -list cubemapfile.lst -output cubemap.dds
  4363. nvdxt -cubeMap -file cubemapfile.tga
  4364. nvdxt -file test.tga -dxt1c
  4365. nvdxt -file *.tga
  4366. nvdxt -file c:\temp\*.tga
  4367. nvdxt -file temp\*.tga
  4368. nvdxt -file height_field_in_alpha.tga -n3x3 -alpha -scale 10 -wrap
  4369. nvdxt -file grey_scale_height_field.tga -n5x5 -rgb -scale 1.3
  4370. nvdxt -file normal_map.tga -norm
  4371. nvdxt -file image.tga -dudv -fade -fadeamount 10
  4372. nvdxt -all -dxt3 -gamma -outdir .\dds_dir -time
  4373. nvdxt -file *.tga -depth -max -scale 0.5
  4374. '''
  4375. try:
  4376. import io_export_rogremesh.rogremesh as Rmesh
  4377. except:
  4378. Rmesh = None
  4379. print( 'WARNING: "io_export_rogremesh" is missing' )
  4380. if Rmesh and Rmesh.rpy.load():
  4381. _USE_RPYTHON_ = True
  4382. else:
  4383. _USE_RPYTHON_ = False
  4384. print( 'Rpython module is not cached, you must exit Blender to compile the module:' )
  4385. print( 'cd io_export_rogremesh; python rogremesh.py' )
  4386. class VertexNoPos(object):
  4387. def __init__(self, ogre_vidx, nx,ny,nz, r,g,b,ra, vert_uvs):
  4388. self.ogre_vidx = ogre_vidx
  4389. self.nx = nx
  4390. self.ny = ny
  4391. self.nz = nz
  4392. self.r = r
  4393. self.g = g
  4394. self.b = b
  4395. self.ra = ra
  4396. self.vert_uvs = vert_uvs
  4397. '''does not compare ogre_vidx (and position at the moment) [ no need to compare position ]'''
  4398. def __eq__(self, o):
  4399. if self.nx != o.nx or self.ny != o.ny or self.nz != o.nz: return False
  4400. elif self.r != o.r or self.g != o.g or self.b != o.b or self.ra != o.ra: return False
  4401. elif len(self.vert_uvs) != len(o.vert_uvs): return False
  4402. elif self.vert_uvs:
  4403. for i, uv1 in enumerate( self.vert_uvs ):
  4404. uv2 = o.vert_uvs[ i ]
  4405. if uv1 != uv2: return False
  4406. return True
  4407. ## Creating .mesh
  4408. def dot_mesh( ob, path='/tmp', force_name=None, ignore_shape_animation=False, normals=True ):
  4409. start = time.time()
  4410. if not os.path.isdir( path ):
  4411. print('>> Creating working directory', path )
  4412. os.makedirs( path )
  4413. Report.meshes.append( ob.data.name )
  4414. Report.faces += len( ob.data.tessfaces )
  4415. Report.orig_vertices += len( ob.data.vertices )
  4416. cleanup = False
  4417. if ob.modifiers:
  4418. cleanup = True
  4419. copy = ob.copy()
  4420. #bpy.context.scene.objects.link(copy)
  4421. rem = []
  4422. for mod in copy.modifiers: # remove armature and array modifiers before collaspe
  4423. if mod.type in 'ARMATURE ARRAY'.split(): rem.append( mod )
  4424. for mod in rem: copy.modifiers.remove( mod )
  4425. # bake mesh
  4426. mesh = copy.to_mesh(bpy.context.scene, True, "PREVIEW") # collaspe
  4427. else:
  4428. copy = ob
  4429. mesh = ob.data
  4430. name = force_name or ob.data.name
  4431. name = clean_object_name(name)
  4432. xmlfile = os.path.join(path, '%s.mesh.xml' % name )
  4433. print(' - Generating:', '%s.mesh.xml' % name)
  4434. if _USE_RPYTHON_ and False:
  4435. Rmesh.save( ob, xmlfile )
  4436. else:
  4437. f = None
  4438. try:
  4439. f = open( xmlfile, 'w' )
  4440. except Exception as e:
  4441. show_dialog("Invalid mesh object name: " + name)
  4442. return
  4443. doc = SimpleSaxWriter(f, 'UTF-8', "mesh", {})
  4444. # Very ugly, have to replace number of vertices later
  4445. doc.start_tag('sharedgeometry', {'vertexcount' : '__TO_BE_REPLACED_VERTEX_COUNT__'})
  4446. print(' - Writing shared geometry')
  4447. doc.start_tag('vertexbuffer', {
  4448. 'positions':'true',
  4449. 'normals':'true',
  4450. 'colours_diffuse' : str(bool( mesh.vertex_colors )),
  4451. 'texture_coords' : '%s' % len(mesh.uv_textures) if mesh.uv_textures.active else '0'
  4452. })
  4453. # Vertex colors
  4454. vcolors = None
  4455. vcolors_alpha = None
  4456. if len( mesh.tessface_vertex_colors ):
  4457. vcolors = mesh.tessface_vertex_colors[0]
  4458. for bloc in mesh.tessface_vertex_colors:
  4459. if bloc.name.lower().startswith('alpha'):
  4460. vcolors_alpha = bloc; break
  4461. # Materials
  4462. materials = []
  4463. for mat in ob.data.materials:
  4464. if mat:
  4465. materials.append( mat )
  4466. else:
  4467. print('[WARNING:] Bad material data in', ob)
  4468. materials.append( '_missing_material_' ) # fixed dec22, keep proper index
  4469. if not materials:
  4470. materials.append( '_missing_material_' )
  4471. _sm_faces_ = []
  4472. for matidx, mat in enumerate( materials ):
  4473. _sm_faces_.append([])
  4474. # Textures
  4475. dotextures = False
  4476. uvcache = [] # should get a little speed boost by this cache
  4477. if mesh.tessface_uv_textures.active:
  4478. dotextures = True
  4479. for layer in mesh.tessface_uv_textures:
  4480. uvs = []; uvcache.append( uvs ) # layer contains: name, active, data
  4481. for uvface in layer.data:
  4482. uvs.append( (uvface.uv1, uvface.uv2, uvface.uv3, uvface.uv4) )
  4483. _sm_vertices_ = {}
  4484. _remap_verts_ = []
  4485. numverts = 0
  4486. for F in mesh.tessfaces:
  4487. smooth = F.use_smooth
  4488. faces = _sm_faces_[ F.material_index ]
  4489. # Ogre only supports triangles
  4490. tris = []
  4491. tris.append( (F.vertices[0], F.vertices[1], F.vertices[2]) )
  4492. if len(F.vertices) >= 4: tris.append( (F.vertices[0], F.vertices[2], F.vertices[3]) )
  4493. if dotextures:
  4494. a = []; b = []
  4495. uvtris = [ a, b ]
  4496. for layer in uvcache:
  4497. uv1, uv2, uv3, uv4 = layer[ F.index ]
  4498. a.append( (uv1, uv2, uv3) )
  4499. b.append( (uv1, uv3, uv4) )
  4500. for tidx, tri in enumerate(tris):
  4501. face = []
  4502. for vidx, idx in enumerate(tri):
  4503. v = mesh.vertices[ idx ]
  4504. if smooth:
  4505. nx,ny,nz = swap( v.normal ) # fixed june 17th 2011
  4506. else:
  4507. nx,ny,nz = swap( F.normal )
  4508. r = 1.0
  4509. g = 1.0
  4510. b = 1.0
  4511. ra = 1.0
  4512. if vcolors:
  4513. k = list(F.vertices).index(idx)
  4514. r,g,b = getattr( vcolors.data[ F.index ], 'color%s'%(k+1) )
  4515. if vcolors_alpha:
  4516. ra,ga,ba = getattr( vcolors_alpha.data[ F.index ], 'color%s'%(k+1) )
  4517. else:
  4518. ra = 1.0
  4519. # Texture maps
  4520. vert_uvs = []
  4521. if dotextures:
  4522. for layer in uvtris[ tidx ]:
  4523. vert_uvs.append(layer[ vidx ])
  4524. ''' Check if we already exported that vertex with same normal, do not export in that case,
  4525. (flat shading in blender seems to work with face normals, so we copy each flat face'
  4526. vertices, if this vertex with same normals was already exported,
  4527. todo: maybe not best solution, check other ways (let blender do all the work, or only
  4528. support smooth shading, what about seems, smoothing groups, materials, ...)
  4529. '''
  4530. vert = VertexNoPos(numverts, nx, ny, nz, r, g, b, ra, vert_uvs)
  4531. alreadyExported = False
  4532. if idx in _sm_vertices_:
  4533. for vert2 in _sm_vertices_[idx]:
  4534. #does not compare ogre_vidx (and position at the moment)
  4535. if vert == vert2:
  4536. face.append(vert2.ogre_vidx)
  4537. alreadyExported = True
  4538. #print(idx,numverts, nx,ny,nz, r,g,b,ra, vert_uvs, "already exported")
  4539. break
  4540. if not alreadyExported:
  4541. face.append(vert.ogre_vidx)
  4542. _sm_vertices_[idx].append(vert)
  4543. #print(numverts, nx,ny,nz, r,g,b,ra, vert_uvs, "appended")
  4544. else:
  4545. face.append(vert.ogre_vidx)
  4546. _sm_vertices_[idx] = [vert]
  4547. #print(idx, numverts, nx,ny,nz, r,g,b,ra, vert_uvs, "created")
  4548. if alreadyExported:
  4549. continue
  4550. numverts += 1
  4551. _remap_verts_.append( v )
  4552. x,y,z = swap(v.co) # xz-y is correct!
  4553. doc.start_tag('vertex', {})
  4554. doc.leaf_tag('position', {
  4555. 'x' : '%6f' % x,
  4556. 'y' : '%6f' % y,
  4557. 'z' : '%6f' % z
  4558. })
  4559. doc.leaf_tag('normal', {
  4560. 'x' : '%6f' % nx,
  4561. 'y' : '%6f' % ny,
  4562. 'z' : '%6f' % nz
  4563. })
  4564. if vcolors:
  4565. doc.leaf_tag('colour_diffuse', {'value' : '%6f %6f %6f %6f' % (r,g,b,ra)})
  4566. # Texture maps
  4567. if dotextures:
  4568. for uv in vert_uvs:
  4569. doc.leaf_tag('texcoord', {
  4570. 'u' : '%6f' % uv[0],
  4571. 'v' : '%6f' % (1.0-uv[1])
  4572. })
  4573. doc.end_tag('vertex')
  4574. faces.append( (face[0], face[1], face[2]) )
  4575. del(_sm_vertices_)
  4576. Report.vertices += numverts
  4577. doc.end_tag('vertexbuffer')
  4578. doc.end_tag('sharedgeometry')
  4579. print(' Done at', timer_diff_str(start), "seconds")
  4580. print(' - Writing submeshes')
  4581. doc.start_tag('submeshes', {})
  4582. for matidx, mat in enumerate( materials ):
  4583. if not len(_sm_faces_[matidx]):
  4584. Report.warnings.append( 'BAD SUBMESH "%s": material %r, has not been applied to any faces - not exporting as submesh.' % (ob.name, mat.name) )
  4585. continue # fixes corrupt unused materials
  4586. doc.start_tag('submesh', {
  4587. 'usesharedvertices' : 'true',
  4588. 'material' : material_name(mat),
  4589. # Maybe better look at index of all faces, if one over 65535 set to true;
  4590. # Problem: we know it too late, postprocessing of file needed
  4591. "use32bitindexes" : str(bool(numverts > 65535)),
  4592. "operationtype" : "triangle_list"
  4593. })
  4594. doc.start_tag('faces', {
  4595. 'count' : str(len(_sm_faces_[matidx]))
  4596. })
  4597. for fidx, (v1, v2, v3) in enumerate(_sm_faces_[matidx]):
  4598. doc.leaf_tag('face', {
  4599. 'v1' : str(v1),
  4600. 'v2' : str(v2),
  4601. 'v3' : str(v3)
  4602. })
  4603. doc.end_tag('faces')
  4604. doc.end_tag('submesh')
  4605. Report.triangles += len(_sm_faces_[matidx])
  4606. del(_sm_faces_)
  4607. doc.end_tag('submeshes')
  4608. ## by MrMagne
  4609. doc.start_tag('submeshnames', {})
  4610. for matidx, mat in enumerate( materials ):
  4611. doc.start_tag('submesh', {
  4612. 'name' : material_name(mat),
  4613. 'index' : str(matidx)
  4614. })
  4615. doc.end_tag('submesh')
  4616. doc.end_tag('submeshnames')
  4617. # -- end of MrMagne's patch
  4618. print(' Done at', timer_diff_str(start), "seconds")
  4619. arm = ob.find_armature()
  4620. if arm:
  4621. doc.leaf_tag('skeletonlink', {
  4622. 'name' : '%s.skeleton' % name
  4623. })
  4624. doc.start_tag('boneassignments', {})
  4625. badverts = 0
  4626. for vidx, v in enumerate(_remap_verts_):
  4627. check = 0
  4628. for vgroup in v.groups:
  4629. if vgroup.weight > CONFIG['TRIM_BONE_WEIGHTS']:
  4630. bnidx = find_bone_index(copy,arm,vgroup.group)
  4631. if bnidx is not None: # allows other vertex groups, not just armature vertex groups
  4632. doc.leaf_tag('vertexboneassignment', {
  4633. 'vertexindex' : str(vidx),
  4634. 'boneindex' : str(bnidx),
  4635. 'weight' : str(vgroup.weight)
  4636. })
  4637. check += 1
  4638. if check > 4:
  4639. badverts += 1
  4640. print('WARNING: vertex %s is in more than 4 vertex groups (bone weights)\n(this maybe Ogre incompatible)' %vidx)
  4641. if badverts:
  4642. Report.warnings.append( '%s has %s vertices weighted to too many bones (Ogre limits a vertex to 4 bones)\n[try increaseing the Trim-Weights threshold option]' %(mesh.name, badverts) )
  4643. doc.end_tag('boneassignments')
  4644. # Updated June3 2011 - shape animation works
  4645. if CONFIG['SHAPE_ANIM'] and ob.data.shape_keys and len(ob.data.shape_keys.key_blocks):
  4646. print(' - Writing shape keys')
  4647. doc.start_tag('poses', {})
  4648. for sidx, skey in enumerate(ob.data.shape_keys.key_blocks):
  4649. if sidx == 0: continue
  4650. if len(skey.data) != len( mesh.vertices ):
  4651. failure = 'FAILED to save shape animation - you can not use a modifier that changes the vertex count! '
  4652. failure += '[ mesh : %s ]' %mesh.name
  4653. Report.warnings.append( failure )
  4654. print( failure )
  4655. break
  4656. doc.start_tag('pose', {
  4657. 'name' : skey.name,
  4658. # If target is 'mesh', no index needed, if target is submesh then submesh identified by 'index'
  4659. #'index' : str(sidx-1),
  4660. #'index' : '0',
  4661. 'target' : 'mesh'
  4662. })
  4663. for vidx, v in enumerate(_remap_verts_):
  4664. pv = skey.data[ v.index ]
  4665. x,y,z = swap( pv.co - v.co )
  4666. #for i,p in enumerate( skey.data ):
  4667. #x,y,z = p.co - ob.data.vertices[i].co
  4668. #x,y,z = swap( ob.data.vertices[i].co - p.co )
  4669. #if x==.0 and y==.0 and z==.0: continue # the older exporter optimized this way, is it safe?
  4670. doc.leaf_tag('poseoffset', {
  4671. 'x' : '%6f' % x,
  4672. 'y' : '%6f' % y,
  4673. 'z' : '%6f' % z,
  4674. 'index' : str(vidx) # is this required?
  4675. })
  4676. doc.end_tag('pose')
  4677. doc.end_tag('poses')
  4678. print(' Done at', timer_diff_str(start), "seconds")
  4679. if ob.data.shape_keys.animation_data and len(ob.data.shape_keys.animation_data.nla_tracks):
  4680. print(' - Writing shape animations')
  4681. doc.start_tag('animations', {})
  4682. _fps = float( bpy.context.scene.render.fps )
  4683. for nla in ob.data.shape_keys.animation_data.nla_tracks:
  4684. for idx, strip in enumerate(nla.strips):
  4685. doc.start_tag('animation', {
  4686. 'name' : strip.name,
  4687. 'length' : str((strip.frame_end-strip.frame_start)/_fps)
  4688. })
  4689. doc.start_tag('tracks', {})
  4690. doc.start_tag('track', {
  4691. 'type' : 'pose',
  4692. 'target' : 'mesh'
  4693. # If target is 'mesh', no index needed, if target is submesh then submesh identified by 'index'
  4694. #'index' : str(idx)
  4695. #'index' : '0'
  4696. })
  4697. doc.start_tag('keyframes', {})
  4698. for frame in range( int(strip.frame_start), int(strip.frame_end)+1, bpy.context.scene.frame_step):#thanks to Vesa
  4699. bpy.context.scene.frame_set(frame)
  4700. doc.start_tag('keyframe', {
  4701. 'time' : str((frame-strip.frame_start)/_fps)
  4702. })
  4703. for sidx, skey in enumerate( ob.data.shape_keys.key_blocks ):
  4704. if sidx == 0: continue
  4705. doc.leaf_tag('poseref', {
  4706. 'poseindex' : str(sidx-1),
  4707. 'influence' : str(skey.value)
  4708. })
  4709. doc.end_tag('keyframe')
  4710. doc.end_tag('keyframes')
  4711. doc.end_tag('track')
  4712. doc.end_tag('tracks')
  4713. doc.end_tag('animation')
  4714. doc.end_tag('animations')
  4715. print(' Done at', timer_diff_str(start), "seconds")
  4716. ## Clean up and save
  4717. #bpy.context.scene.meshes.unlink(mesh)
  4718. if cleanup:
  4719. #bpy.context.scene.objects.unlink(copy)
  4720. copy.user_clear()
  4721. bpy.data.objects.remove(copy)
  4722. mesh.user_clear()
  4723. bpy.data.meshes.remove(mesh)
  4724. del copy
  4725. del mesh
  4726. del _remap_verts_
  4727. del uvcache
  4728. doc.close() # reported by Reyn
  4729. f.close()
  4730. print(' - Created .mesh.xml at', timer_diff_str(start), "seconds")
  4731. # todo: Very ugly, find better way
  4732. def replaceInplace(f,searchExp,replaceExp):
  4733. import fileinput
  4734. for line in fileinput.input(f, inplace=1):
  4735. if searchExp in line:
  4736. line = line.replace(searchExp,replaceExp)
  4737. sys.stdout.write(line)
  4738. fileinput.close() # reported by jakob
  4739. replaceInplace(xmlfile, '__TO_BE_REPLACED_VERTEX_COUNT__' + '"', str(numverts) + '"' )#+ ' ' * (ls - lr))
  4740. del(replaceInplace)
  4741. # Start .mesh.xml to .mesh convertion tool
  4742. OgreXMLConverter(xmlfile, has_uvs=dotextures)
  4743. if arm and CONFIG['ARM_ANIM']:
  4744. skel = Skeleton( ob )
  4745. data = skel.to_xml()
  4746. name = force_name or ob.data.name
  4747. name = clean_object_name(name)
  4748. xmlfile = os.path.join(path, '%s.skeleton.xml' % name)
  4749. f = open( xmlfile, 'wb' )
  4750. f.write( bytes(data,'utf-8') )
  4751. f.close()
  4752. OgreXMLConverter( xmlfile )
  4753. mats = []
  4754. for mat in materials:
  4755. if mat != '_missing_material_': mats.append( mat )
  4756. print(' - Created .mesh in total time', timer_diff_str(start), 'seconds')
  4757. return mats
  4758. ## Jmonkey preview
  4759. ## todo: remove jmonkey
  4760. class JmonkeyPreviewOp( _OgreCommonExport_, bpy.types.Operator ):
  4761. '''helper to open jMonkey (JME)'''
  4762. bl_idname = 'jmonkey.preview'
  4763. bl_label = "opens JMonkeyEngine in a non-blocking subprocess"
  4764. bl_options = {'REGISTER'}
  4765. filepath= StringProperty(name="File Path", description="Filepath used for exporting Jmonkey .scene file", maxlen=1024, default="/tmp/preview.txml", subtype='FILE_PATH')
  4766. EXPORT_TYPE = 'OGRE'
  4767. @classmethod
  4768. def poll(cls, context):
  4769. if context.active_object: return True
  4770. def invoke(self, context, event):
  4771. global TundraSingleton
  4772. path = '/tmp/preview.scene'
  4773. self.ogre_export( path, context )
  4774. JmonkeyPipe( path )
  4775. return {'FINISHED'}
  4776. def JmonkeyPipe( path ):
  4777. root = CONFIG[ 'JMONKEY_ROOT']
  4778. if sys.platform.startswith('win'):
  4779. cmd = [ os.path.join( os.path.join( root, 'bin' ), 'jmonkeyplatform.exe' ) ]
  4780. else:
  4781. cmd = [ os.path.join( os.path.join( root, 'bin' ), 'jmonkeyplatform' ) ]
  4782. cmd.append( '--nosplash' )
  4783. cmd.append( '--open' )
  4784. cmd.append( path )
  4785. proc = subprocess.Popen(cmd)#, stdin=subprocess.PIPE)
  4786. return proc
  4787. ## realXtend Tundra preview
  4788. ## todo: This only work if the custom py script is enabled in Tundra
  4789. ## It's nice when it works but PythonScriptModule is not part of the
  4790. ## default Tundra distro anymore, so this is atm kind of dead.
  4791. class TundraPreviewOp( _OgreCommonExport_, bpy.types.Operator ):
  4792. '''helper to open Tundra2 (realXtend)'''
  4793. bl_idname = 'tundra.preview'
  4794. bl_label = "opens Tundra2 in a non-blocking subprocess"
  4795. bl_options = {'REGISTER'}
  4796. EXPORT_TYPE = 'REX'
  4797. filepath= StringProperty(
  4798. name="File Path",
  4799. description="Filepath used for exporting Tundra .txml file",
  4800. maxlen=1024,
  4801. default="/tmp/preview.txml",
  4802. subtype='FILE_PATH')
  4803. EX_FORCE_CAMERA = BoolProperty(
  4804. name="Force Camera",
  4805. description="export active camera",
  4806. default=False)
  4807. EX_FORCE_LAMPS = BoolProperty(
  4808. name="Force Lamps",
  4809. description="export all lamps",
  4810. default=False)
  4811. @classmethod
  4812. def poll(cls, context):
  4813. if context.active_object and context.mode != 'EDIT_MESH':
  4814. return True
  4815. def invoke(self, context, event):
  4816. global TundraSingleton
  4817. syncmats = []
  4818. obs = []
  4819. if TundraSingleton:
  4820. actob = context.active_object
  4821. obs = TundraSingleton.deselect_previously_updated(context)
  4822. for ob in obs:
  4823. if ob.type=='MESH':
  4824. syncmats.append( ob )
  4825. if ob.name == actob.name:
  4826. export_mesh( ob, path='/tmp/rex' )
  4827. if not os.path.isdir( '/tmp/rex' ): os.makedirs( '/tmp/rex' )
  4828. path = '/tmp/rex/preview.txml'
  4829. self.ogre_export( path, context, force_material_update=syncmats )
  4830. if not TundraSingleton:
  4831. TundraSingleton = TundraPipe( context )
  4832. elif self.EX_SCENE:
  4833. TundraSingleton.load( context, path )
  4834. for ob in obs:
  4835. ob.select = True # restore selection
  4836. return {'FINISHED'}
  4837. TundraSingleton = None
  4838. class Tundra_StartPhysicsOp(bpy.types.Operator):
  4839. '''TundraSingleton helper'''
  4840. bl_idname = 'tundra.start_physics'
  4841. bl_label = "start physics"
  4842. bl_options = {'REGISTER'}
  4843. @classmethod
  4844. def poll(cls, context):
  4845. if TundraSingleton: return True
  4846. def invoke(self, context, event):
  4847. TundraSingleton.start()
  4848. return {'FINISHED'}
  4849. class Tundra_StopPhysicsOp(bpy.types.Operator):
  4850. '''TundraSingleton helper'''
  4851. bl_idname = 'tundra.stop_physics'
  4852. bl_label = "stop physics"
  4853. bl_options = {'REGISTER'}
  4854. @classmethod
  4855. def poll(cls, context):
  4856. if TundraSingleton: return True
  4857. def invoke(self, context, event):
  4858. TundraSingleton.stop()
  4859. return {'FINISHED'}
  4860. class Tundra_PhysicsDebugOp(bpy.types.Operator):
  4861. '''TundraSingleton helper'''
  4862. bl_idname = 'tundra.toggle_physics_debug'
  4863. bl_label = "stop physics"
  4864. bl_options = {'REGISTER'}
  4865. @classmethod
  4866. def poll(cls, context):
  4867. if TundraSingleton: return True
  4868. def invoke(self, context, event):
  4869. TundraSingleton.toggle_physics_debug()
  4870. return {'FINISHED'}
  4871. class Tundra_ExitOp(bpy.types.Operator):
  4872. '''TundraSingleton helper'''
  4873. bl_idname = 'tundra.exit'
  4874. bl_label = "quit tundra"
  4875. bl_options = {'REGISTER'}
  4876. @classmethod
  4877. def poll(cls, context):
  4878. if TundraSingleton: return True
  4879. def invoke(self, context, event):
  4880. TundraSingleton.exit()
  4881. return {'FINISHED'}
  4882. ## Server object to talk with realXtend Tundra with UDP
  4883. ## Requires Tundra to be running a py script.
  4884. class Server(object):
  4885. def stream( self, o ):
  4886. b = pickle.dumps( o, protocol=2 ) #protocol2 is python2 compatible
  4887. #print( 'streaming bytes', len(b) )
  4888. n = len( b ); d = STREAM_BUFFER_SIZE - n -4
  4889. if n > STREAM_BUFFER_SIZE:
  4890. print( 'ERROR: STREAM OVERFLOW:', n )
  4891. return
  4892. padding = b'#' * d
  4893. if n < 10: header = '000%s' %n
  4894. elif n < 100: header = '00%s' %n
  4895. elif n < 1000: header = '0%s' %n
  4896. else: header = '%s' %n
  4897. header = bytes( header, 'utf-8' )
  4898. assert len(header) == 4
  4899. w = header + b + padding
  4900. assert len(w) == STREAM_BUFFER_SIZE
  4901. self.buffer.insert(0, w )
  4902. return w
  4903. def multires_lod( self ):
  4904. '''
  4905. Ogre builtin LOD sucks for character animation
  4906. '''
  4907. ob = bpy.context.active_object
  4908. cam = bpy.context.scene.camera
  4909. if ob and cam and ob.type=='MESH' and ob.use_multires_lod:
  4910. delta = bpy.context.active_object.matrix_world.to_translation() - cam.matrix_world.to_translation()
  4911. dist = delta.length
  4912. #print( 'Distance', dist )
  4913. if ob.modifiers and ob.modifiers[0].type == 'MULTIRES' and ob.modifiers[0].total_levels > 1:
  4914. mod = ob.modifiers[0]
  4915. step = ob.multires_lod_range / mod.total_levels
  4916. level = mod.total_levels - int( dist / step )
  4917. if mod.levels != level: mod.levels = level
  4918. return level
  4919. def sync( self ): # 153 bytes per object + n bytes for animation names and weights
  4920. LOD = self.multires_lod()
  4921. p = STREAM_PROTO
  4922. i = 0; msg = []
  4923. for ob in bpy.context.selected_objects:
  4924. if ob.type not in ('MESH','LAMP','SPEAKER'): continue
  4925. loc, rot, scale = ob.matrix_world.decompose()
  4926. loc = swap(loc).to_tuple()
  4927. x,y,z = swap( rot.to_euler() )
  4928. rot = (x,y,z)
  4929. x,y,z = swap( scale )
  4930. scale = ( abs(x), abs(y), abs(z) )
  4931. d = { p['ID']:uid(ob), p['POSITION']:loc, p['ROTATION']:rot, p['SCALE']:scale, p['TYPE']:p[ob.type] }
  4932. msg.append( d )
  4933. if ob.name == bpy.context.active_object.name and LOD is not None:
  4934. d[ p['LOD'] ] = LOD
  4935. if ob.type == 'MESH':
  4936. arm = ob.find_armature()
  4937. if arm and arm.animation_data and arm.animation_data.nla_tracks:
  4938. anim = None
  4939. d[ p['ANIMATIONS'] ] = state = {} # animation-name : weight
  4940. for nla in arm.animation_data.nla_tracks:
  4941. for strip in nla.strips:
  4942. if strip.active: state[ strip.name ] = strip.influence
  4943. else: pass # armature without proper NLA setup
  4944. elif ob.type == 'LAMP':
  4945. d[ p['ENERGY'] ] = ob.data.energy
  4946. d[ p['DISTANCE'] ] = ob.data.distance
  4947. elif ob.type == 'SPEAKER':
  4948. d[ p['VOLUME'] ] = ob.data.volume
  4949. d[ p['MUTE'] ] = ob.data.muted
  4950. if i >= 10: break # max is 13 objects to stay under 2048 bytes
  4951. return msg
  4952. def __init__(self):
  4953. import socket
  4954. self.buffer = [] # cmd buffer
  4955. self.socket = sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # UDP
  4956. host='localhost'; port = 9420
  4957. sock.connect((host, port))
  4958. print('SERVER: socket connected', sock)
  4959. self._handle = None
  4960. self.setup_callback( bpy.context )
  4961. import threading
  4962. self.ready = threading._allocate_lock()
  4963. self.ID = threading._start_new_thread(
  4964. self.loop, (None,)
  4965. )
  4966. print( 'SERVER: thread started')
  4967. def loop(self, none):
  4968. self.active = True
  4969. prev = time.time()
  4970. while self.active:
  4971. if not self.ready.locked(): time.sleep(0.001) # not threadsafe
  4972. else: # threadsafe start
  4973. now = time.time()
  4974. if now - prev > 0.066: # don't flood Tundra
  4975. actob = None
  4976. try: actob = bpy.context.active_object
  4977. except: pass
  4978. if not actob: continue
  4979. prev = now
  4980. sel = bpy.context.active_object
  4981. msg = self.sync()
  4982. self.ready.release() # thread release
  4983. self.stream( msg ) # releases GIL?
  4984. if self.buffer:
  4985. bin = self.buffer.pop()
  4986. try:
  4987. self.socket.sendall( bin )
  4988. except:
  4989. print('SERVER: send data error')
  4990. time.sleep(0.5)
  4991. pass
  4992. else: print( 'SERVER: empty buffer' )
  4993. else:
  4994. self.ready.release()
  4995. print('SERVER: thread exit')
  4996. def threadsafe( self, reg ):
  4997. if not TundraSingleton: return
  4998. if not self.ready.locked():
  4999. self.ready.acquire()
  5000. time.sleep(0.0001)
  5001. while self.ready.locked(): # must block to be safe
  5002. time.sleep(0.0001) # wait for unlock
  5003. else: pass #time.sleep(0.033) dont block
  5004. _handle = None
  5005. def setup_callback( self, context ): # TODO replace with a proper frame update callback
  5006. print('SERVER: setup frame update callback')
  5007. if self._handle: return self._handle
  5008. for area in bpy.context.window.screen.areas:
  5009. if area.type == 'VIEW_3D':
  5010. for reg in area.regions:
  5011. if reg.type == 'WINDOW':
  5012. # PRE_VIEW, POST_VIEW, POST_PIXEL
  5013. self._handle = reg.callback_add(self.threadsafe, (reg,), 'PRE_VIEW' )
  5014. self._area = area
  5015. self._region = reg
  5016. break
  5017. if not self._handle:
  5018. print('SERVER: FAILED to setup frame update callback')
  5019. def _create_stream_proto():
  5020. proto = {}
  5021. tags = 'ID NAME POSITION ROTATION SCALE DATA SELECTED TYPE MESH LAMP CAMERA SPEAKER ANIMATIONS DISTANCE ENERGY VOLUME MUTE LOD'.split()
  5022. for i,tag in enumerate( tags ):
  5023. proto[ tag ] = chr(i) # up to 256
  5024. return proto
  5025. STREAM_PROTO = _create_stream_proto()
  5026. STREAM_BUFFER_SIZE = 2048
  5027. TUNDRA_SCRIPT = '''
  5028. # this file was generated by blender2ogre #
  5029. import tundra, socket, select, pickle
  5030. STREAM_BUFFER_SIZE = 2048
  5031. globals().update( %s )
  5032. E = {} # this is just for debugging from the pyconsole
  5033. def get_entity(ID):
  5034. scn = tundra.Scene().MainCameraScene()
  5035. return scn.GetEntityRaw( ID )
  5036. class Client(object):
  5037. def __init__(self):
  5038. self.socket = sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  5039. host='localhost'; port = 9420
  5040. sock.bind((host, port))
  5041. self._animated = {} # entity ID : { anim-name : weight }
  5042. def update(self, delay):
  5043. global E
  5044. sock = self.socket
  5045. poll = select.select( [ sock ], [], [], 0.01 )
  5046. if not poll[0]: return True
  5047. data = sock.recv( STREAM_BUFFER_SIZE )
  5048. assert len(data) == STREAM_BUFFER_SIZE
  5049. if not data:
  5050. print( 'blender crashed?' )
  5051. return
  5052. header = data[ : 4]
  5053. s = data[ 4 : int(header)+4 ]
  5054. objects = pickle.loads( s )
  5055. scn = tundra.Scene().MainCameraScene() # replaces GetDefaultScene()
  5056. for ob in objects:
  5057. e = scn.GetEntityRaw( ob[ID] )
  5058. if not e: continue
  5059. x,y,z = ob[POSITION]
  5060. e.placeable.SetPosition( x,y,z )
  5061. x,y,z = ob[SCALE]
  5062. e.placeable.SetScale( x,y,z )
  5063. #e.placeable.SetOrientation( ob[ROTATION] )
  5064. if ob[TYPE] == LAMP:
  5065. e.light.range = ob[ DISTANCE ]
  5066. e.light.brightness = ob[ ENERGY ]
  5067. #e.light.diffColor = !! not wrapped !!
  5068. #e.light.specColor = !! not wrapped !!
  5069. elif ob[TYPE] == SPEAKER:
  5070. e.sound.soundGain = ob[VOLUME]
  5071. #e.sound.soundInnerRadius =
  5072. #e.sound.soundOuterRadius =
  5073. if ob[MUTE]: e.sound.StopSound()
  5074. else: e.sound.PlaySound() # tundra API needs sound.IsPlaying()
  5075. if ANIMATIONS in ob:
  5076. self.update_animation( e, ob )
  5077. if LOD in ob:
  5078. #print( 'LOD', ob[LOD] )
  5079. index = e.id + ob[LOD] + 1
  5080. for i in range(1,9):
  5081. elod = get_entity( e.id + i )
  5082. if elod:
  5083. if elod.id == index and not elod.placeable.visible:
  5084. elod.placeable.visible = True
  5085. elif elod.id != index and elod.placeable.visible:
  5086. elod.placeable.visible = False
  5087. if ob[ID] not in E: E[ ob[ID] ] = e
  5088. def update_animation( self, e, ob ):
  5089. if ob[ID] not in self._animated:
  5090. self._animated[ ob[ID] ] = {}
  5091. state = self._animated[ ob[ID] ]
  5092. ac = e.animationcontroller
  5093. for aname in ob[ ANIMATIONS ]:
  5094. if aname not in state: # save weight of new animation
  5095. state[ aname ] = ob[ANIMATIONS][aname] # weight
  5096. for aname in state:
  5097. if aname not in ob[ANIMATIONS] and ac.IsAnimationActive( aname ):
  5098. ac.StopAnim( aname, '0.0' )
  5099. elif aname in ob[ANIMATIONS]:
  5100. weight = ob[ANIMATIONS][aname]
  5101. if ac.HasAnimationFinished( aname ):
  5102. ac.PlayLoopedAnim( aname, '1.0', 'false' ) # PlayAnim(...) TODO single playback
  5103. ok = ac.SetAnimationWeight( aname, weight )
  5104. state[ aname ] = weight
  5105. if weight != state[ aname ]:
  5106. ok = ac.SetAnimationWeight( aname, weight )
  5107. state[ aname ] = weight
  5108. client = Client()
  5109. tundra.Frame().connect( 'Updated(float)', client.update )
  5110. print('blender2ogre plugin ok')
  5111. ''' %STREAM_PROTO
  5112. class TundraPipe(object):
  5113. CONFIG_PATH = '/tmp/rex/plugins.xml'
  5114. TUNDRA_SCRIPT_PATH = '/tmp/rex/b2ogre_plugin.py'
  5115. CONFIG_XML = '''<?xml version="1.0"?>
  5116. <Tundra>
  5117. <!-- plugins.xml is hardcoded to be the default configuration file to load if another file is not specified on the command line with the "config filename.xml" parameter. -->
  5118. <plugin path="OgreRenderingModule" />
  5119. <plugin path="EnvironmentModule" /> <!-- EnvironmentModule depends on OgreRenderingModule -->
  5120. <plugin path="PhysicsModule" /> <!-- PhysicsModule depends on OgreRenderingModule and EnvironmentModule -->
  5121. <plugin path="TundraProtocolModule" /> <!-- TundraProtocolModule depends on OgreRenderingModule -->
  5122. <plugin path="JavascriptModule" /> <!-- JavascriptModule depends on TundraProtocolModule -->
  5123. <plugin path="AssetModule" /> <!-- AssetModule depends on TundraProtocolModule -->
  5124. <plugin path="AvatarModule" /> <!-- AvatarModule depends on AssetModule and OgreRenderingModule -->
  5125. <plugin path="ECEditorModule" /> <!-- ECEditorModule depends on OgreRenderingModule, TundraProtocolModule, OgreRenderingModule and AssetModule -->
  5126. <plugin path="SkyXHydrax" /> <!-- SkyXHydrax depends on OgreRenderingModule -->
  5127. <plugin path="OgreAssetEditorModule" /> <!-- OgreAssetEditorModule depends on OgreRenderingModule -->
  5128. <plugin path="DebugStatsModule" /> <!-- DebugStatsModule depends on OgreRenderingModule, EnvironmentModule and AssetModule -->
  5129. <plugin path="SceneWidgetComponents" /> <!-- SceneWidgetComponents depends on OgreRenderingModule and TundraProtocolModule -->
  5130. <plugin path="PythonScriptModule" />
  5131. <!-- TODO: Currently the above <plugin> items are loaded in the order they are specified, but below, the jsplugin items are loaded in an undefined order. Use the order specified here as the load order. -->
  5132. <!-- NOTE: The startup .js scripts are specified only by base name of the file. Don's specify a path here. Place the startup .js scripts to /bin/jsmodules/startup/. -->
  5133. <!-- Important: The file names specified here are case sensitive even on Windows! -->
  5134. <jsplugin path="cameraapplication.js" />
  5135. <jsplugin path="FirstPersonMouseLook.js" />
  5136. <jsplugin path="MenuBar.js" />
  5137. <!-- Python plugins -->
  5138. <!-- <pyplugin path="lib/apitests.py" /> --> <!-- Runs framework api tests -->
  5139. <pyplugin path="%s" /> <!-- shows qt py console. could enable by default when add to menu etc. for controls, now just shows directly when is enabled here -->
  5140. <option name="--accept_unknown_http_sources" />
  5141. <option name="--accept_unknown_local_sources" />
  5142. <option name="--fpslimit" value="60" />
  5143. <!-- AssetAPI's file system watcher currently disabled because LocalAssetProvider implements
  5144. the same functionality for LocalAssetStorages and HTTPAssetProviders do not yet support live-update. -->
  5145. <option name="--nofilewatcher" />
  5146. </Tundra>''' %TUNDRA_SCRIPT_PATH
  5147. def __init__(self, context, debug=False):
  5148. self._physics_debug = True
  5149. self._objects = []
  5150. self.proc = None
  5151. exe = None
  5152. if 'Tundra.exe' in os.listdir( CONFIG['TUNDRA_ROOT'] ):
  5153. exe = os.path.join( CONFIG['TUNDRA_ROOT'], 'Tundra.exe' )
  5154. elif 'Tundra' in os.listdir( CONFIG['TUNDRA_ROOT'] ):
  5155. exe = os.path.join( CONFIG['TUNDRA_ROOT'], 'Tundra' )
  5156. cmd = []
  5157. if not exe:
  5158. print('ERROR: failed to find Tundra executable')
  5159. assert 0
  5160. elif sys.platform.startswith('win'):
  5161. cmd.append(exe)
  5162. else:
  5163. if exe.endswith('.exe'): cmd.append('wine') # assume user has Wine
  5164. cmd.append( exe )
  5165. if debug:
  5166. cmd.append('--loglevel')
  5167. cmd.append('debug')
  5168. if CONFIG['TUNDRA_STREAMING']:
  5169. cmd.append( '--config' )
  5170. cmd.append( self.CONFIG_PATH )
  5171. with open( self.CONFIG_PATH, 'wb' ) as fp: fp.write( bytes(self.CONFIG_XML,'utf-8') )
  5172. with open( self.TUNDRA_SCRIPT_PATH, 'wb' ) as fp: fp.write( bytes(TUNDRA_SCRIPT,'utf-8') )
  5173. self.server = Server()
  5174. #cmd += ['--file', '/tmp/rex/preview.txml'] # tundra2.1.2 bug loading from --file ignores entity ID's
  5175. cmd.append( '--storage' )
  5176. if sys.platform.startswith('win'): cmd.append( 'C:\\tmp\\rex' )
  5177. else: cmd.append( '/tmp/rex' )
  5178. self.proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, cwd=CONFIG['TUNDRA_ROOT'])
  5179. self.physics = True
  5180. if self.proc:
  5181. time.sleep(0.1)
  5182. self.load( context, '/tmp/rex/preview.txml' )
  5183. self.stop()
  5184. def deselect_previously_updated(self, context):
  5185. r = []
  5186. for ob in context.selected_objects:
  5187. if ob.name in self._objects: ob.select = False; r.append( ob )
  5188. return r
  5189. def load( self, context, url, clear=False ):
  5190. self._objects += [ob.name for ob in context.selected_objects]
  5191. if clear:
  5192. self.proc.stdin.write( b'loadscene(/tmp/rex/preview.txml,true,true)\n')
  5193. else:
  5194. self.proc.stdin.write( b'loadscene(/tmp/rex/preview.txml,false,true)\n')
  5195. try:
  5196. self.proc.stdin.flush()
  5197. except:
  5198. global TundraSingleton
  5199. TundraSingleton = None
  5200. def start( self ):
  5201. self.physics = True
  5202. self.proc.stdin.write( b'startphysics\n' )
  5203. try: self.proc.stdin.flush()
  5204. except:
  5205. global TundraSingleton
  5206. TundraSingleton = None
  5207. def stop( self ):
  5208. self.physics = False
  5209. self.proc.stdin.write( b'stopphysics\n' )
  5210. try: self.proc.stdin.flush()
  5211. except:
  5212. global TundraSingleton
  5213. TundraSingleton = None
  5214. def toggle_physics_debug( self ):
  5215. self._physics_debug = not self._physics_debug
  5216. self.proc.stdin.write( b'physicsdebug\n' )
  5217. try: self.proc.stdin.flush()
  5218. except:
  5219. global TundraSingleton
  5220. TundraSingleton = None
  5221. def exit(self):
  5222. self.proc.stdin.write( b'exit\n' )
  5223. self.proc.stdin.flush()
  5224. global TundraSingleton
  5225. TundraSingleton = None
  5226. ## More UI
  5227. class MENU_preview_material_text(bpy.types.Menu):
  5228. bl_label = 'preview'
  5229. @classmethod
  5230. def poll(self,context):
  5231. if context.active_object and context.active_object.active_material:
  5232. return True
  5233. def draw(self, context):
  5234. layout = self.layout
  5235. mat = context.active_object.active_material
  5236. if mat:
  5237. #CONFIG['TOUCH_TEXTURES'] = False
  5238. preview = generate_material( mat )
  5239. for line in preview.splitlines():
  5240. if line.strip():
  5241. for ww in wordwrap( line ):
  5242. layout.label(text=ww)
  5243. @UI
  5244. class INFO_HT_myheader(bpy.types.Header):
  5245. bl_space_type = 'INFO'
  5246. def draw(self, context):
  5247. layout = self.layout
  5248. wm = context.window_manager
  5249. window = context.window
  5250. scene = context.scene
  5251. rd = scene.render
  5252. ob = context.active_object
  5253. screen = context.screen
  5254. #layout.separator()
  5255. if _USE_JMONKEY_:
  5256. row = layout.row(align=True)
  5257. op = row.operator( 'jmonkey.preview', text='', icon='MONKEY' )
  5258. if _USE_TUNDRA_:
  5259. row = layout.row(align=True)
  5260. op = row.operator( 'tundra.preview', text='', icon='WORLD' )
  5261. if TundraSingleton:
  5262. op = row.operator( 'tundra.preview', text='', icon='META_CUBE' )
  5263. op.EX_SCENE = False
  5264. if not TundraSingleton.physics:
  5265. op = row.operator( 'tundra.start_physics', text='', icon='PLAY' )
  5266. else:
  5267. op = row.operator( 'tundra.stop_physics', text='', icon='PAUSE' )
  5268. op = row.operator( 'tundra.toggle_physics_debug', text='', icon='MOD_PHYSICS' )
  5269. op = row.operator( 'tundra.exit', text='', icon='CANCEL' )
  5270. op = layout.operator( 'ogremeshy.preview', text='', icon='PLUGIN' ); op.mesh = True
  5271. row = layout.row(align=True)
  5272. sub = row.row(align=True)
  5273. sub.menu("INFO_MT_file")
  5274. sub.menu("INFO_MT_add")
  5275. if rd.use_game_engine: sub.menu("INFO_MT_game")
  5276. else: sub.menu("INFO_MT_render")
  5277. row = layout.row(align=False); row.scale_x = 1.25
  5278. row.menu("INFO_MT_instances", icon='NODETREE', text='')
  5279. row.menu("INFO_MT_groups", icon='GROUP', text='')
  5280. layout.template_header()
  5281. if not context.area.show_menus:
  5282. if window.screen.show_fullscreen: layout.operator("screen.back_to_previous", icon='SCREEN_BACK', text="Back to Previous")
  5283. else: layout.template_ID(context.window, "screen", new="screen.new", unlink="screen.delete")
  5284. layout.template_ID(context.screen, "scene", new="scene.new", unlink="scene.delete")
  5285. layout.separator()
  5286. layout.template_running_jobs()
  5287. layout.template_reports_banner()
  5288. layout.separator()
  5289. if rd.has_multiple_engines: layout.prop(rd, "engine", text="")
  5290. layout.label(text=scene.statistics())
  5291. layout.menu( "INFO_MT_help" )
  5292. else:
  5293. layout.template_ID(context.window, "screen", new="screen.new", unlink="screen.delete")
  5294. if ob:
  5295. row = layout.row(align=True)
  5296. row.prop( ob, 'name', text='' )
  5297. row.prop( ob, 'draw_type', text='' )
  5298. row.prop( ob, 'show_x_ray', text='' )
  5299. row = layout.row()
  5300. row.scale_y = 0.75; row.scale_x = 0.9
  5301. row.prop( ob, 'layers', text='' )
  5302. layout.separator()
  5303. row = layout.row(align=True); row.scale_x = 1.1
  5304. row.prop(scene.game_settings, 'material_mode', text='')
  5305. row.prop(scene, 'camera', text='')
  5306. layout.menu( 'MENU_preview_material_text', icon='TEXT', text='' )
  5307. layout.menu( "INFO_MT_ogre_docs" )
  5308. layout.operator("wm.window_fullscreen_toggle", icon='FULLSCREEN_ENTER', text="")
  5309. if OgreToggleInterfaceOp.TOGGLE: layout.operator('ogre.toggle_interface', text='Ogre', icon='CHECKBOX_DEHLT')
  5310. else: layout.operator('ogre.toggle_interface', text='Ogre', icon='CHECKBOX_HLT')
  5311. def export_menu_func_ogre(self, context):
  5312. op = self.layout.operator(INFO_OT_createOgreExport.bl_idname, text="Ogre3D (.scene and .mesh)")
  5313. def export_menu_func_realxtend(self, context):
  5314. op = self.layout.operator(INFO_OT_createRealxtendExport.bl_idname, text="realXtend Tundra (.txml and .mesh)")
  5315. try:
  5316. _header_ = bpy.types.INFO_HT_header
  5317. except:
  5318. print('---blender2ogre addon enable---')
  5319. ## Toggle button for blender2ogre UI panels
  5320. class OgreToggleInterfaceOp(bpy.types.Operator):
  5321. '''Toggle Ogre UI'''
  5322. bl_idname = "ogre.toggle_interface"
  5323. bl_label = "Ogre UI"
  5324. bl_options = {'REGISTER'}
  5325. TOGGLE = True #restore_minimal_interface()
  5326. BLENDER_DEFAULT_HEADER = _header_
  5327. @classmethod
  5328. def poll(cls, context):
  5329. return True
  5330. def invoke(self, context, event):
  5331. #global _header_
  5332. if OgreToggleInterfaceOp.TOGGLE: #_header_:
  5333. print( 'ogre.toggle_interface ENABLE' )
  5334. bpy.utils.register_module(__name__)
  5335. #_header_ = bpy.types.INFO_HT_header
  5336. try: bpy.utils.unregister_class(_header_)
  5337. except: pass
  5338. bpy.utils.unregister_class( INFO_HT_microheader ) # moved to custom header
  5339. OgreToggleInterfaceOp.TOGGLE = False
  5340. else:
  5341. print( 'ogre.toggle_interface DISABLE' )
  5342. #bpy.utils.unregister_module(__name__); # this is not safe, can segfault blender, why?
  5343. hide_user_interface()
  5344. bpy.utils.register_class(_header_)
  5345. restore_minimal_interface()
  5346. OgreToggleInterfaceOp.TOGGLE = True
  5347. return {'FINISHED'}
  5348. class INFO_HT_microheader(bpy.types.Header):
  5349. bl_space_type = 'INFO'
  5350. def draw(self, context):
  5351. layout = self.layout
  5352. try:
  5353. if OgreToggleInterfaceOp.TOGGLE:
  5354. layout.operator('ogre.toggle_interface', text='Ogre', icon='CHECKBOX_DEHLT')
  5355. else:
  5356. layout.operator('ogre.toggle_interface', text='Ogre', icon='CHECKBOX_HLT')
  5357. except: pass # STILL REQUIRED?
  5358. def get_minimal_interface_classes():
  5359. return INFO_OT_createOgreExport, INFO_OT_createRealxtendExport, OgreToggleInterfaceOp, MiniReport, INFO_HT_microheader
  5360. _USE_TUNDRA_ = False
  5361. _USE_JMONKEY_ = False
  5362. def restore_minimal_interface():
  5363. #if not hasattr( bpy.ops.ogre.. #always true
  5364. for cls in get_minimal_interface_classes():
  5365. try: bpy.utils.register_class( cls )
  5366. except: pass
  5367. return False
  5368. try:
  5369. bpy.utils.register_class( INFO_HT_microheader )
  5370. for op in get_minimal_interface_classes(): bpy.utils.register_class( op )
  5371. return False
  5372. except:
  5373. print( 'b2ogre minimal UI already setup' )
  5374. return True
  5375. MyShaders = None
  5376. def register():
  5377. print('Starting blender2ogre', VERSION)
  5378. global MyShaders, _header_, _USE_TUNDRA_, _USE_JMONKEY_
  5379. #bpy.utils.register_module(__name__) ## do not load all the ogre panels by default
  5380. #_header_ = bpy.types.INFO_HT_header
  5381. #bpy.utils.unregister_class(_header_)
  5382. restore_minimal_interface()
  5383. # only test for Tundra2 once - do not do this every panel redraw ##
  5384. if os.path.isdir( CONFIG['TUNDRA_ROOT'] ): _USE_TUNDRA_ = True
  5385. else: _USE_TUNDRA_ = False
  5386. #if os.path.isdir( CONFIG['JMONKEY_ROOT'] ): _USE_JMONKEY_ = True
  5387. #else: _USE_JMONKEY_ = False
  5388. bpy.types.INFO_MT_file_export.append(export_menu_func_ogre)
  5389. bpy.types.INFO_MT_file_export.append(export_menu_func_realxtend)
  5390. bpy.utils.register_class(PopUpDialogOperator)
  5391. if os.path.isdir( CONFIG['USER_MATERIALS'] ):
  5392. scripts,progs = update_parent_material_path( CONFIG['USER_MATERIALS'] )
  5393. for prog in progs:
  5394. print('Ogre shader program', prog.name)
  5395. else:
  5396. print('[WARNING]: Invalid my-shaders path' )
  5397. def unregister():
  5398. print('Unloading blender2ogre', VERSION)
  5399. header = _header_
  5400. bpy.utils.unregister_module(__name__)
  5401. if header:
  5402. bpy.utils.register_class(header)
  5403. bpy.types.INFO_MT_file_export.remove(export_menu_func_ogre)
  5404. bpy.types.INFO_MT_file_export.remove(export_menu_func_realxtend)
  5405. bpy.utils.unregister_class(PopUpDialogOperator)
  5406. ## Blender world panel options for EC_SkyX creation
  5407. ## todo: EC_SkyX has changes a bit lately, see that
  5408. ## all these options are still correct and valid
  5409. ## old todo (?): Move to tundra.py
  5410. bpy.types.World.ogre_skyX = BoolProperty(
  5411. name="enable sky", description="ogre sky",
  5412. default=False
  5413. )
  5414. bpy.types.World.ogre_skyX_time = FloatProperty(
  5415. name="Time Multiplier",
  5416. description="change speed of day/night cycle",
  5417. default=0.3,
  5418. min=0.0, max=5.0
  5419. )
  5420. bpy.types.World.ogre_skyX_wind = FloatProperty(
  5421. name="Wind Direction",
  5422. description="change direction of wind",
  5423. default=33.0,
  5424. min=0.0, max=360.0
  5425. )
  5426. bpy.types.World.ogre_skyX_volumetric_clouds = BoolProperty(
  5427. name="volumetric clouds", description="toggle ogre volumetric clouds",
  5428. default=True
  5429. )
  5430. bpy.types.World.ogre_skyX_cloud_density_x = FloatProperty(
  5431. name="Cloud Density X",
  5432. description="change density of volumetric clouds on X",
  5433. default=0.1,
  5434. min=0.0, max=5.0
  5435. )
  5436. bpy.types.World.ogre_skyX_cloud_density_y = FloatProperty(
  5437. name="Cloud Density Y",
  5438. description="change density of volumetric clouds on Y",
  5439. default=1.0,
  5440. min=0.0, max=5.0
  5441. )
  5442. ## Sky UI panel
  5443. @UI
  5444. class OgreSkyPanel(bpy.types.Panel):
  5445. bl_space_type = 'PROPERTIES'
  5446. bl_region_type = 'WINDOW'
  5447. bl_context = "world"
  5448. bl_label = "Ogre Sky Settings"
  5449. @classmethod
  5450. def poll(cls, context):
  5451. return True
  5452. def draw(self, context):
  5453. layout = self.layout
  5454. box = layout.box()
  5455. box.prop( context.world, 'ogre_skyX' )
  5456. if context.world.ogre_skyX:
  5457. box.prop( context.world, 'ogre_skyX_time' )
  5458. box.prop( context.world, 'ogre_skyX_wind' )
  5459. box.prop( context.world, 'ogre_skyX_volumetric_clouds' )
  5460. if context.world.ogre_skyX_volumetric_clouds:
  5461. box.prop( context.world, 'ogre_skyX_cloud_density_x' )
  5462. box.prop( context.world, 'ogre_skyX_cloud_density_y' )
  5463. class OgreProgram(object):
  5464. '''
  5465. parses .program scripts
  5466. saves bytes to copy later
  5467. self.name = name of program reference
  5468. self.source = name of shader program (.cg, .glsl)
  5469. '''
  5470. def save( self, path ):
  5471. print('saving program to', path)
  5472. f = open( os.path.join(path,self.source), 'wb' )
  5473. f.write(self.source_bytes )
  5474. f.close()
  5475. for name in self.includes:
  5476. f = open( os.path.join(path,name), 'wb' )
  5477. f.write( self.includes[name] )
  5478. f.close()
  5479. PROGRAMS = {}
  5480. SOURCES = {}
  5481. def reload(self): # only one directory is allowed to hold shader programs
  5482. if self.source not in os.listdir( CONFIG['SHADER_PROGRAMS'] ):
  5483. print( 'ERROR: ogre material %s is missing source: %s' %(self.name,self.source) )
  5484. print( CONFIG['SHADER_PROGRAMS'] )
  5485. return False
  5486. url = os.path.join( CONFIG['SHADER_PROGRAMS'], self.source )
  5487. print('shader source:', url)
  5488. self.source_bytes = open( url, 'rb' ).read()#.decode('utf-8')
  5489. print('shader source num bytes:', len(self.source_bytes))
  5490. data = self.source_bytes.decode('utf-8')
  5491. for line in data.splitlines(): # only cg shaders use the include macro?
  5492. if line.startswith('#include') and line.count('"')==2:
  5493. name = line.split()[-1].replace('"','').strip()
  5494. print('shader includes:', name)
  5495. url = os.path.join( CONFIG['SHADER_PROGRAMS'], name )
  5496. self.includes[ name ] = open( url, 'rb' ).read()
  5497. return True
  5498. def __init__(self, name='', data=''):
  5499. self.name=name
  5500. self.data = data.strip()
  5501. self.source = None
  5502. self.includes = {} # cg files may use #include something.cg
  5503. if self.name in OgreProgram.PROGRAMS:
  5504. print('---copy ogreprogram---', self.name)
  5505. other = OgreProgram.PROGRAMS
  5506. self.source = other.source
  5507. self.data = other.data
  5508. self.entry_point = other.entry_point
  5509. self.profiles = other.profiles
  5510. if data: self.parse( self.data )
  5511. if self.name: OgreProgram.PROGRAMS[ self.name ] = self
  5512. def parse( self, txt ):
  5513. self.data = txt
  5514. print('--parsing ogre shader program--' )
  5515. for line in self.data.splitlines():
  5516. print(line)
  5517. line = line.split('//')[0]
  5518. line = line.strip()
  5519. if line.startswith('vertex_program') or line.startswith('fragment_program'):
  5520. a, self.name, self.type = line.split()
  5521. elif line.startswith('source'): self.source = line.split()[-1]
  5522. elif line.startswith('entry_point'): self.entry_point = line.split()[-1]
  5523. elif line.startswith('profiles'): self.profiles = line.split()[1:]
  5524. ## Ogre Material object(s) that is utilized during export stages
  5525. class OgreMaterialScript(object):
  5526. def get_programs(self):
  5527. progs = []
  5528. for name in list(self.vertex_programs.keys()) + list(self.fragment_programs.keys()):
  5529. p = get_shader_program( name ) # OgreProgram.PROGRAMS
  5530. if p: progs.append( p )
  5531. return progs
  5532. def __init__(self, txt, url):
  5533. self.url = url
  5534. self.data = txt.strip()
  5535. self.parent = None
  5536. self.vertex_programs = {}
  5537. self.fragment_programs = {}
  5538. self.texture_units = {}
  5539. self.texture_units_order = []
  5540. self.passes = []
  5541. line = self.data.splitlines()[0]
  5542. assert line.startswith('material')
  5543. if ':' in line:
  5544. line, self.parent = line.split(':')
  5545. self.name = line.split()[-1]
  5546. print( 'new ogre material: %s' %self.name )
  5547. brace = 0
  5548. self.techniques = techs = []
  5549. prog = None # pick up program params
  5550. tex = None # pick up texture_unit options, require "texture" ?
  5551. for line in self.data.splitlines():
  5552. #print( line )
  5553. rawline = line
  5554. line = line.split('//')[0]
  5555. line = line.strip()
  5556. if not line: continue
  5557. if line == '{': brace += 1
  5558. elif line == '}': brace -= 1; prog = None; tex = None
  5559. if line.startswith( 'technique' ):
  5560. tech = {'passes':[]}; techs.append( tech )
  5561. if len(line.split()) > 1: tech['technique-name'] = line.split()[-1]
  5562. elif techs:
  5563. if line.startswith('pass'):
  5564. P = {'texture_units':[], 'vprogram':None, 'fprogram':None, 'body':[]}
  5565. tech['passes'].append( P )
  5566. self.passes.append( P )
  5567. elif tech['passes']:
  5568. P = tech['passes'][-1]
  5569. P['body'].append( rawline )
  5570. if line == '{' or line == '}': continue
  5571. if line.startswith('vertex_program_ref'):
  5572. prog = P['vprogram'] = {'name':line.split()[-1], 'params':{}}
  5573. self.vertex_programs[ prog['name'] ] = prog
  5574. elif line.startswith('fragment_program_ref'):
  5575. prog = P['fprogram'] = {'name':line.split()[-1], 'params':{}}
  5576. self.fragment_programs[ prog['name'] ] = prog
  5577. elif line.startswith('texture_unit'):
  5578. prog = None
  5579. tex = {'name':line.split()[-1], 'params':{}}
  5580. if tex['name'] == 'texture_unit': # ignore unnamed texture units
  5581. print('WARNING: material %s contains unnamed texture_units' %self.name)
  5582. print('---unnamed texture units will be ignored---')
  5583. else:
  5584. P['texture_units'].append( tex )
  5585. self.texture_units[ tex['name'] ] = tex
  5586. self.texture_units_order.append( tex['name'] )
  5587. elif prog:
  5588. p = line.split()[0]
  5589. if p=='param_named':
  5590. items = line.split()
  5591. if len(items) == 4: p, o, t, v = items
  5592. elif len(items) == 3:
  5593. p, o, v = items
  5594. t = 'class'
  5595. elif len(items) > 4:
  5596. o = items[1]; t = items[2]
  5597. v = items[3:]
  5598. opt = { 'name': o, 'type':t, 'raw_value':v }
  5599. prog['params'][ o ] = opt
  5600. if t=='float': opt['value'] = float(v)
  5601. elif t in 'float2 float3 float4'.split(): opt['value'] = [ float(a) for a in v ]
  5602. else: print('unknown type:', t)
  5603. elif tex: # (not used)
  5604. tex['params'][ line.split()[0] ] = line.split()[ 1 : ]
  5605. for P in self.passes:
  5606. lines = P['body']
  5607. while lines and ''.join(lines).count('{')!=''.join(lines).count('}'):
  5608. if lines[-1].strip() == '}': lines.pop()
  5609. else: break
  5610. P['body'] = '\n'.join( lines )
  5611. assert P['body'].count('{') == P['body'].count('}') # if this fails, the parser choked
  5612. #print( self.techniques )
  5613. self.hidden_texture_units = rem = []
  5614. for tex in self.texture_units.values():
  5615. if 'texture' not in tex['params']:
  5616. rem.append( tex )
  5617. for tex in rem:
  5618. print('WARNING: not using texture_unit because it lacks a "texture" parameter', tex['name'])
  5619. self.texture_units.pop( tex['name'] )
  5620. if len(self.techniques)>1:
  5621. print('WARNING: user material %s has more than one technique' %self.url)
  5622. def as_abstract_passes( self ):
  5623. r = []
  5624. for i,P in enumerate(self.passes):
  5625. head = 'abstract pass %s/PASS%s' %(self.name,i)
  5626. r.append( head + '\n' + P['body'] )
  5627. return r
  5628. class MaterialScripts(object):
  5629. ALL_MATERIALS = {}
  5630. ENUM_ITEMS = []
  5631. def __init__(self, url):
  5632. self.url = url
  5633. self.data = ''
  5634. data = open( url, 'rb' ).read()
  5635. try:
  5636. self.data = data.decode('utf-8')
  5637. except:
  5638. self.data = data.decode('latin-1')
  5639. self.materials = {}
  5640. ## chop up .material file, find all material defs ####
  5641. mats = []
  5642. mat = []
  5643. skip = False # for now - programs must be defined in .program files, not in the .material
  5644. for line in self.data.splitlines():
  5645. if not line.strip(): continue
  5646. a = line.split()[0] #NOTE ".split()" strips white space
  5647. if a == 'material':
  5648. mat = []; mats.append( mat )
  5649. mat.append( line )
  5650. elif a in ('vertex_program', 'fragment_program', 'abstract'):
  5651. skip = True
  5652. elif mat and not skip:
  5653. mat.append( line )
  5654. elif skip and line=='}':
  5655. skip = False
  5656. ##########################
  5657. for mat in mats:
  5658. omat = OgreMaterialScript( '\n'.join( mat ), url )
  5659. if omat.name in self.ALL_MATERIALS:
  5660. print( 'WARNING: material %s redefined' %omat.name )
  5661. #print( '--OLD MATERIAL--')
  5662. #print( self.ALL_MATERIALS[ omat.name ].data )
  5663. #print( '--NEW MATERIAL--')
  5664. #print( omat.data )
  5665. self.materials[ omat.name ] = omat
  5666. self.ALL_MATERIALS[ omat.name ] = omat
  5667. if omat.vertex_programs or omat.fragment_programs: # ignore materials without programs
  5668. self.ENUM_ITEMS.append( (omat.name, omat.name, url) )
  5669. @classmethod # only call after parsing all material scripts
  5670. def reset_rna(self, callback=None):
  5671. bpy.types.Material.ogre_parent_material = EnumProperty(
  5672. name="Script Inheritence",
  5673. description='ogre parent material class',
  5674. items=self.ENUM_ITEMS,
  5675. #update=callback
  5676. )
  5677. ## Image/texture proecssing
  5678. def is_image_postprocessed( image ):
  5679. if CONFIG['FORCE_IMAGE_FORMAT'] != 'NONE' or image.use_resize_half or image.use_resize_absolute or image.use_color_quantize or image.use_convert_format:
  5680. return True
  5681. else:
  5682. return False
  5683. class _image_processing_( object ):
  5684. def _reformat( self, name, image ):
  5685. if image.convert_format != 'NONE':
  5686. name = '%s.%s' %(name[:name.rindex('.')], image.convert_format)
  5687. if image.convert_format == 'dds': name = '_DDS_.%s' %name
  5688. elif image.use_resize_half or image.use_resize_absolute or image.use_color_quantize or image.use_convert_format:
  5689. name = '_magick_.%s' %name
  5690. if CONFIG['FORCE_IMAGE_FORMAT'] != 'NONE' and not name.endswith('.dds'):
  5691. name = '%s.%s' %(name[:name.rindex('.')], CONFIG['FORCE_IMAGE_FORMAT'])
  5692. if CONFIG['FORCE_IMAGE_FORMAT'] == 'dds':
  5693. name = '_DDS_.%s' %name
  5694. return name
  5695. def image_magick( self, texture, infile ):
  5696. print('IMAGE MAGICK', infile )
  5697. exe = CONFIG['IMAGE_MAGICK_CONVERT']
  5698. if not os.path.isfile( exe ):
  5699. Report.warnings.append( 'ImageMagick not installed!' )
  5700. print( 'ERROR: can not find Image Magick - convert', exe ); return
  5701. cmd = [ exe, infile ]
  5702. ## enforce max size ##
  5703. x,y = texture.image.size
  5704. ax = texture.image.resize_x
  5705. ay = texture.image.resize_y
  5706. if texture.image.use_convert_format and texture.image.convert_format == 'jpg':
  5707. cmd.append( '-quality' )
  5708. cmd.append( '%s' %texture.image.jpeg_quality )
  5709. if texture.image.use_resize_half:
  5710. cmd.append( '-resize' )
  5711. cmd.append( '%sx%s' %(x/2, y/2) )
  5712. elif texture.image.use_resize_absolute and (x>ax or y>ay):
  5713. cmd.append( '-resize' )
  5714. cmd.append( '%sx%s' %(ax,ay) )
  5715. elif x > CONFIG['MAX_TEXTURE_SIZE'] or y > CONFIG['MAX_TEXTURE_SIZE']:
  5716. cmd.append( '-resize' )
  5717. cmd.append( str(CONFIG['MAX_TEXTURE_SIZE']) )
  5718. if texture.image.use_color_quantize:
  5719. if texture.image.use_color_quantize_dither:
  5720. cmd.append( '+dither' )
  5721. cmd.append( '-colors' )
  5722. cmd.append( str(texture.image.color_quantize) )
  5723. path,name = os.path.split( infile )
  5724. #if (texture.image.use_convert_format and texture.image.convert_format == 'dds') or CONFIG['FORCE_IMAGE_FORMAT'] == 'dds':
  5725. outfile = os.path.join( path, self._reformat(name,texture.image) )
  5726. if outfile.endswith('.dds'):
  5727. temp = os.path.join( path, '_temp_.png' )
  5728. cmd.append( temp )
  5729. print( 'IMAGE MAGICK: %s' %cmd )
  5730. subprocess.call( cmd )
  5731. self.nvcompress( texture, temp, outfile=outfile )
  5732. else:
  5733. cmd.append( outfile )
  5734. print( 'IMAGE MAGICK: %s' %cmd )
  5735. subprocess.call( cmd )
  5736. def nvcompress(self, texture, infile, outfile=None, version=1, fast=False, blocking=True):
  5737. print('[NVCompress DDS Wrapper]', infile )
  5738. assert version in (1,2,3,4,5)
  5739. exe = CONFIG['NVCOMPRESS']
  5740. cmd = [ exe ]
  5741. if texture.image.use_alpha and texture.image.depth==32:
  5742. cmd.append( '-alpha' )
  5743. if not texture.use_mipmap:
  5744. cmd.append( '-nomips' )
  5745. if texture.use_normal_map:
  5746. cmd.append( '-normal' )
  5747. if version in (1,3):
  5748. cmd.append( '-bc%sn' %version )
  5749. else:
  5750. cmd.append( '-bc%s' %version )
  5751. else:
  5752. cmd.append( '-bc%s' %version )
  5753. if fast:
  5754. cmd.append( '-fast' )
  5755. cmd.append( infile )
  5756. if outfile: cmd.append( outfile )
  5757. print( cmd )
  5758. if blocking:
  5759. subprocess.call( cmd )
  5760. else:
  5761. subprocess.Popen( cmd )
  5762. ## NVIDIA texture compress documentation
  5763. _nvcompress_doc = '''
  5764. usage: nvcompress [options] infile [outfile]
  5765. Input options:
  5766. -color The input image is a color map (default).
  5767. -alpha The input image has an alpha channel used for transparency.
  5768. -normal The input image is a normal map.
  5769. -tonormal Convert input to normal map.
  5770. -clamp Clamp wrapping mode (default).
  5771. -repeat Repeat wrapping mode.
  5772. -nomips Disable mipmap generation.
  5773. Compression options:
  5774. -fast Fast compression.
  5775. -nocuda Do not use cuda compressor.
  5776. -rgb RGBA format
  5777. -bc1 BC1 format (DXT1)
  5778. -bc1n BC1 normal map format (DXT1nm)
  5779. -bc1a BC1 format with binary alpha (DXT1a)
  5780. -bc2 BC2 format (DXT3)
  5781. -bc3 BC3 format (DXT5)
  5782. -bc3n BC3 normal map format (DXT5nm)
  5783. -bc4 BC4 format (ATI1)
  5784. -bc5 BC5 format (3Dc/ATI2)
  5785. '''
  5786. class OgreMaterialGenerator( _image_processing_ ):
  5787. def __init__(self, material, path='/tmp', touch_textures=False ):
  5788. self.material = material # top level material
  5789. self.path = path # copy textures to path
  5790. self.passes = []
  5791. self.touch_textures = touch_textures
  5792. if material.node_tree:
  5793. nodes = bpyShaders.get_subnodes( self.material.node_tree, type='MATERIAL_EXT' )
  5794. for node in nodes:
  5795. if node.material:
  5796. self.passes.append( node.material )
  5797. def get_active_programs(self):
  5798. r = []
  5799. for mat in self.passes:
  5800. if mat.use_ogre_parent_material and mat.ogre_parent_material:
  5801. usermat = get_ogre_user_material( mat.ogre_parent_material )
  5802. for prog in usermat.get_programs(): r.append( prog )
  5803. return r
  5804. def get_header(self):
  5805. r = []
  5806. for mat in self.passes:
  5807. if mat.use_ogre_parent_material and mat.ogre_parent_material:
  5808. usermat = get_ogre_user_material( mat.ogre_parent_material )
  5809. r.append( '// user material: %s' %usermat.name )
  5810. for prog in usermat.get_programs():
  5811. r.append( prog.data )
  5812. r.append( '// abstract passes //' )
  5813. r += usermat.as_abstract_passes()
  5814. return '\n'.join( r )
  5815. def get_passes(self):
  5816. r = []
  5817. r.append( self.generate_pass(self.material) )
  5818. for mat in self.passes:
  5819. if mat.use_in_ogre_material_pass: # submaterials
  5820. r.append( self.generate_pass(mat) )
  5821. return r
  5822. def generate_pass( self, mat, pass_name=None ):
  5823. usermat = texnodes = None
  5824. if mat.use_ogre_parent_material and mat.ogre_parent_material:
  5825. usermat = get_ogre_user_material( mat.ogre_parent_material )
  5826. texnodes = bpyShaders.get_texture_subnodes( self.material, mat )
  5827. M = ''
  5828. if not pass_name: pass_name = mat.name
  5829. if usermat:
  5830. M += indent(2, 'pass %s : %s/PASS0' %(pass_name,usermat.name), '{' )
  5831. else:
  5832. M += indent(2, 'pass %s'%pass_name, '{' )
  5833. color = mat.diffuse_color
  5834. alpha = 1.0
  5835. if mat.use_transparency:
  5836. alpha = mat.alpha
  5837. slots = get_image_textures( mat ) # returns texture_slot objects (CLASSIC MATERIAL)
  5838. usealpha = False #mat.ogre_depth_write
  5839. for slot in slots:
  5840. #if slot.use_map_alpha and slot.texture.use_alpha: usealpha = True; break
  5841. if slot.texture.image.use_alpha: usealpha = True; break
  5842. ## force material alpha to 1.0 if textures use_alpha?
  5843. #if usealpha: alpha = 1.0 # let the alpha of the texture control material alpha?
  5844. if mat.use_fixed_pipeline:
  5845. f = mat.ambient
  5846. if mat.use_vertex_color_paint:
  5847. M += indent(3, 'ambient vertexcolour' )
  5848. else: # fall back to basic material
  5849. M += indent(3, 'ambient %s %s %s %s' %(color.r*f, color.g*f, color.b*f, alpha) )
  5850. f = mat.diffuse_intensity
  5851. if mat.use_vertex_color_paint:
  5852. M += indent(3, 'diffuse vertexcolour' )
  5853. else: # fall back to basic material
  5854. M += indent(3, 'diffuse %s %s %s %s' %(color.r*f, color.g*f, color.b*f, alpha) )
  5855. f = mat.specular_intensity
  5856. s = mat.specular_color
  5857. M += indent(3, 'specular %s %s %s %s %s' %(s.r*f, s.g*f, s.b*f, alpha, mat.specular_hardness/4.0) )
  5858. f = mat.emit
  5859. if mat.use_shadeless: # requested by Borris
  5860. M += indent(3, 'emissive %s %s %s 1.0' %(color.r, color.g, color.b) )
  5861. elif mat.use_vertex_color_light:
  5862. M += indent(3, 'emissive vertexcolour' )
  5863. else:
  5864. M += indent(3, 'emissive %s %s %s %s' %(color.r*f, color.g*f, color.b*f, alpha) )
  5865. M += '\n' # pretty printing
  5866. if mat.offset_z:
  5867. M += indent(3, 'depth_bias %s'%mat.offset_z )
  5868. for name in dir(mat): #mat.items() - items returns custom props not pyRNA:
  5869. if name.startswith('ogre_') and name != 'ogre_parent_material':
  5870. var = getattr(mat,name)
  5871. op = name.replace('ogre_', '')
  5872. val = var
  5873. if type(var) == bool:
  5874. if var: val = 'on'
  5875. else: val = 'off'
  5876. M += indent( 3, '%s %s' %(op,val) )
  5877. M += '\n' # pretty printing
  5878. if texnodes and usermat.texture_units:
  5879. for i,name in enumerate(usermat.texture_units_order):
  5880. if i<len(texnodes):
  5881. node = texnodes[i]
  5882. if node.texture:
  5883. geo = bpyShaders.get_connected_input_nodes( self.material, node )[0]
  5884. M += self.generate_texture_unit( node.texture, name=name, uv_layer=geo.uv_layer )
  5885. elif slots:
  5886. for slot in slots:
  5887. M += self.generate_texture_unit( slot.texture, slot=slot )
  5888. M += indent(2, '}' ) # end pass
  5889. return M
  5890. def generate_texture_unit(self, texture, slot=None, name=None, uv_layer=None):
  5891. if not hasattr(texture, 'image'):
  5892. print('WARNING: texture must be of type IMAGE->', texture)
  5893. return ''
  5894. if not texture.image:
  5895. print('WARNING: texture has no image assigned->', texture)
  5896. return ''
  5897. #if slot: print(dir(slot))
  5898. if slot and not slot.use: return ''
  5899. path = self.path #CONFIG['PATH']
  5900. M = ''; _alphahack = None
  5901. if not name: name = '' #texture.name # (its unsafe to use texture block name)
  5902. M += indent(3, 'texture_unit %s' %name, '{' )
  5903. if texture.library: # support library linked textures
  5904. libpath = os.path.split( bpy.path.abspath(texture.library.filepath) )[0]
  5905. iurl = bpy.path.abspath( texture.image.filepath, libpath )
  5906. else:
  5907. iurl = bpy.path.abspath( texture.image.filepath )
  5908. postname = texname = os.path.split(iurl)[-1]
  5909. destpath = path
  5910. if texture.image.packed_file:
  5911. orig = texture.image.filepath
  5912. iurl = os.path.join(path, texname)
  5913. if '.' not in iurl:
  5914. print('WARNING: packed image is of unknown type - assuming PNG format')
  5915. iurl += '.png'
  5916. texname = postname = os.path.split(iurl)[-1]
  5917. if not os.path.isfile( iurl ):
  5918. if self.touch_textures:
  5919. print('MESSAGE: unpacking image: ', iurl)
  5920. texture.image.filepath = iurl
  5921. texture.image.save()
  5922. texture.image.filepath = orig
  5923. else:
  5924. print('MESSAGE: packed image already in temp, not updating', iurl)
  5925. if is_image_postprocessed( texture.image ):
  5926. postname = self._reformat( texname, texture.image )
  5927. print('MESSAGE: image postproc',postname)
  5928. M += indent(4, 'texture %s' %postname )
  5929. exmode = texture.extension
  5930. if exmode in TextureUnit.tex_address_mode:
  5931. M += indent(4, 'tex_address_mode %s' %TextureUnit.tex_address_mode[exmode] )
  5932. # TODO - hijack nodes for better control?
  5933. if slot: # classic blender material slot options
  5934. if exmode == 'CLIP': M += indent(4, 'tex_border_colour %s %s %s' %(slot.color.r, slot.color.g, slot.color.b) )
  5935. M += indent(4, 'scale %s %s' %(1.0/slot.scale.x, 1.0/slot.scale.y) )
  5936. if slot.texture_coords == 'REFLECTION':
  5937. if slot.mapping == 'SPHERE':
  5938. M += indent(4, 'env_map spherical' )
  5939. elif slot.mapping == 'FLAT':
  5940. M += indent(4, 'env_map planar' )
  5941. else: print('WARNING: <%s> has a non-UV mapping type (%s) and not picked a proper projection type of: Sphere or Flat' %(texture.name, slot.mapping))
  5942. ox,oy,oz = slot.offset
  5943. if ox or oy:
  5944. M += indent(4, 'scroll %s %s' %(ox,oy) )
  5945. if oz:
  5946. M += indent(4, 'rotate %s' %oz )
  5947. #if slot.use_map_emission: # problem, user will want to use emission sometimes
  5948. if slot.use_from_dupli: # hijacked again - june7th
  5949. M += indent(4, 'rotate_anim %s' %slot.emission_color_factor )
  5950. if slot.use_map_scatter: # hijacked from volume shaders
  5951. M += indent(4, 'scroll_anim %s %s ' %(slot.density_factor, slot.emission_factor) )
  5952. if slot.uv_layer:
  5953. idx = find_uv_layer_index( slot.uv_layer, self.material )
  5954. M += indent(4, 'tex_coord_set %s' %idx)
  5955. rgba = False
  5956. if texture.image.depth == 32: rgba = True
  5957. btype = slot.blend_type # TODO - fix this hack if/when slots support pyRNA
  5958. ex = False; texop = None
  5959. if btype in TextureUnit.colour_op:
  5960. if btype=='MIX' and slot.use_map_alpha and not slot.use_stencil:
  5961. if slot.diffuse_color_factor >= 1.0: texop = 'alpha_blend'
  5962. else:
  5963. texop = TextureUnit.colour_op[ btype ]
  5964. ex = True
  5965. elif btype=='MIX' and slot.use_map_alpha and slot.use_stencil:
  5966. texop = 'blend_current_alpha'; ex=True
  5967. elif btype=='MIX' and not slot.use_map_alpha and slot.use_stencil:
  5968. texop = 'blend_texture_alpha'; ex=True
  5969. else:
  5970. texop = TextureUnit.colour_op[ btype ]
  5971. elif btype in TextureUnit.colour_op_ex:
  5972. texop = TextureUnit.colour_op_ex[ btype ]
  5973. ex = True
  5974. if texop and ex:
  5975. if texop == 'blend_manual':
  5976. factor = 1.0 - slot.diffuse_color_factor
  5977. M += indent(4, 'colour_op_ex %s src_texture src_current %s' %(texop, factor) )
  5978. else:
  5979. M += indent(4, 'colour_op_ex %s src_texture src_current' %texop )
  5980. elif texop:
  5981. M += indent(4, 'colour_op %s' %texop )
  5982. else:
  5983. if uv_layer:
  5984. idx = find_uv_layer_index( uv_layer )
  5985. M += indent(4, 'tex_coord_set %s' %idx)
  5986. M += indent(3, '}' )
  5987. if self.touch_textures:
  5988. # Copy texture only if newer
  5989. if not os.path.isfile(iurl):
  5990. Report.warnings.append('Missing texture: %s' %iurl )
  5991. else:
  5992. desturl = os.path.join( destpath, texname )
  5993. updated = False
  5994. if not os.path.isfile( desturl ) or os.stat( desturl ).st_mtime < os.stat( iurl ).st_mtime:
  5995. f = open( desturl, 'wb' )
  5996. f.write( open(iurl,'rb').read() )
  5997. f.close()
  5998. updated = True
  5999. posturl = os.path.join(destpath,postname)
  6000. if is_image_postprocessed( texture.image ):
  6001. if not os.path.isfile( posturl ) or updated:
  6002. self.image_magick( texture, desturl ) # calls nvconvert if required
  6003. return M
  6004. class TextureUnit(object):
  6005. colour_op = {
  6006. 'MIX' : 'modulate', # Ogre Default - was "replace" but that kills lighting
  6007. 'ADD' : 'add',
  6008. 'MULTIPLY' : 'modulate',
  6009. #'alpha_blend' : '',
  6010. }
  6011. colour_op_ex = {
  6012. 'MIX' : 'blend_manual',
  6013. 'SCREEN': 'modulate_x2',
  6014. 'LIGHTEN': 'modulate_x4',
  6015. 'SUBTRACT': 'subtract',
  6016. 'OVERLAY': 'add_signed',
  6017. 'DIFFERENCE': 'dotproduct', # best match?
  6018. 'VALUE': 'blend_diffuse_colour',
  6019. }
  6020. tex_address_mode = {
  6021. 'REPEAT': 'wrap',
  6022. 'EXTEND': 'clamp',
  6023. 'CLIP' : 'border',
  6024. 'CHECKER' : 'mirror'
  6025. }
  6026. @UI
  6027. class PANEL_Object(bpy.types.Panel):
  6028. bl_space_type = 'PROPERTIES'
  6029. bl_region_type = 'WINDOW'
  6030. bl_context = "object"
  6031. bl_label = "Object+"
  6032. @classmethod
  6033. def poll(cls, context):
  6034. if _USE_TUNDRA_ and context.active_object:
  6035. return True
  6036. def draw(self, context):
  6037. ob = context.active_object
  6038. layout = self.layout
  6039. box = layout.box()
  6040. box.prop( ob, 'cast_shadows' )
  6041. box.prop( ob, 'use_draw_distance' )
  6042. if ob.use_draw_distance:
  6043. box.prop( ob, 'draw_distance' )
  6044. #if ob.find_armature():
  6045. if ob.type == 'EMPTY':
  6046. box.prop( ob, 'use_avatar' )
  6047. box.prop( ob, 'avatar_reference' )
  6048. @UI
  6049. class PANEL_Speaker(bpy.types.Panel):
  6050. bl_space_type = 'PROPERTIES'
  6051. bl_region_type = 'WINDOW'
  6052. bl_context = "data"
  6053. bl_label = "Sound+"
  6054. @classmethod
  6055. def poll(cls, context):
  6056. if context.active_object and context.active_object.type=='SPEAKER': return True
  6057. def draw(self, context):
  6058. layout = self.layout
  6059. box = layout.box()
  6060. box.prop( context.active_object.data, 'play_on_load' )
  6061. box.prop( context.active_object.data, 'loop' )
  6062. box.prop( context.active_object.data, 'use_spatial' )
  6063. @UI
  6064. class PANEL_MultiResLOD(bpy.types.Panel):
  6065. bl_space_type = 'PROPERTIES'
  6066. bl_region_type = 'WINDOW'
  6067. bl_context = "modifier"
  6068. bl_label = "Multi-Resolution LOD"
  6069. @classmethod
  6070. def poll(cls, context):
  6071. if context.active_object and context.active_object.type=='MESH':
  6072. ob = context.active_object
  6073. if ob.modifiers and ob.modifiers[0].type=='MULTIRES':
  6074. return True
  6075. def draw(self, context):
  6076. ob = context.active_object
  6077. layout = self.layout
  6078. box = layout.box()
  6079. box.prop( ob, 'use_multires_lod' )
  6080. if ob.use_multires_lod:
  6081. box.prop( ob, 'multires_lod_range' )
  6082. ## Public API (continued)
  6083. def material_name( mat ):
  6084. if type(mat) is str:
  6085. return mat
  6086. elif not mat.library:
  6087. return mat.name
  6088. else:
  6089. return clean_object_name(mat.name + mat.library.filepath.replace('/','_'))
  6090. def export_mesh(ob, path='/tmp', force_name=None, ignore_shape_animation=False, normals=True):
  6091. ''' returns materials used by the mesh '''
  6092. return dot_mesh( ob, path, force_name, ignore_shape_animation, normals )
  6093. def generate_material(mat, path='/tmp', copy_programs=False, touch_textures=False):
  6094. ''' returns generated material string '''
  6095. safename = material_name(mat) # supports blender library linking
  6096. M = '// %s genrated by blender2ogre %s\n\n' % (mat.name, VERSION)
  6097. M += 'material %s \n{\n' % safename # start material
  6098. if mat.use_shadows:
  6099. M += indent(1, 'receive_shadows on \n')
  6100. else:
  6101. M += indent(1, 'receive_shadows off \n')
  6102. M += indent(1, 'technique', '{' ) # technique GLSL, CG
  6103. w = OgreMaterialGenerator(mat, path=path, touch_textures=touch_textures)
  6104. if copy_programs:
  6105. progs = w.get_active_programs()
  6106. for prog in progs:
  6107. prog.save(path)
  6108. header = w.get_header()
  6109. passes = w.get_passes()
  6110. M += '\n'.join(passes)
  6111. M += indent(1, '}' ) # end technique
  6112. M += '}\n' # end material
  6113. if len(header) > 0:
  6114. return header + '\n' + M
  6115. else:
  6116. return M
  6117. def get_ogre_user_material( name ):
  6118. if name in MaterialScripts.ALL_MATERIALS:
  6119. return MaterialScripts.ALL_MATERIALS[ name ]
  6120. def get_shader_program( name ):
  6121. if name in OgreProgram.PROGRAMS:
  6122. return OgreProgram.PROGRAMS[ name ]
  6123. else:
  6124. print('WARNING: no shader program named: %s' %name)
  6125. def get_shader_programs():
  6126. return OgreProgram.PROGRAMS.values()
  6127. def parse_material_and_program_scripts( path, scripts, progs, missing ): # recursive
  6128. for name in os.listdir(path):
  6129. url = os.path.join(path,name)
  6130. if os.path.isdir( url ):
  6131. parse_material_and_program_scripts( url, scripts, progs, missing )
  6132. elif os.path.isfile( url ):
  6133. if name.endswith( '.material' ):
  6134. print( '<found material>', url )
  6135. scripts.append( MaterialScripts( url ) )
  6136. if name.endswith('.program'):
  6137. print( '<found program>', url )
  6138. data = open( url, 'rb' ).read().decode('utf-8')
  6139. chk = []; chunks = [ chk ]
  6140. for line in data.splitlines():
  6141. line = line.split('//')[0]
  6142. if line.startswith('}'):
  6143. chk.append( line )
  6144. chk = []; chunks.append( chk )
  6145. elif line.strip():
  6146. chk.append( line )
  6147. for chk in chunks:
  6148. if not chk: continue
  6149. p = OgreProgram( data='\n'.join(chk) )
  6150. if p.source:
  6151. ok = p.reload()
  6152. if not ok: missing.append( p )
  6153. else: progs.append( p )
  6154. def update_parent_material_path( path ):
  6155. ''' updates RNA '''
  6156. print( '>>SEARCHING FOR OGRE MATERIALS: %s' %path )
  6157. scripts = []
  6158. progs = []
  6159. missing = []
  6160. parse_material_and_program_scripts( path, scripts, progs, missing )
  6161. if missing:
  6162. print('WARNING: missing shader programs:')
  6163. for p in missing: print(p.name)
  6164. if missing and not progs:
  6165. print('WARNING: no shader programs were found - set "SHADER_PROGRAMS" to your path')
  6166. MaterialScripts.reset_rna( callback=bpyShaders.on_change_parent_material )
  6167. return scripts, progs
  6168. def get_subcollision_meshes():
  6169. ''' returns all collision meshes found in the scene '''
  6170. r = []
  6171. for ob in bpy.context.scene.objects:
  6172. if ob.type=='MESH' and ob.subcollision: r.append( ob )
  6173. return r
  6174. def get_objects_with_subcollision():
  6175. ''' returns objects that have active sub-collisions '''
  6176. r = []
  6177. for ob in bpy.context.scene.objects:
  6178. if ob.type=='MESH' and ob.collision_mode not in ('NONE', 'PRIMITIVE'):
  6179. r.append( ob )
  6180. return r
  6181. def get_subcollisions(ob):
  6182. prefix = '%s.' %ob.collision_mode
  6183. r = []
  6184. for child in ob.children:
  6185. if child.subcollision and child.name.startswith( prefix ):
  6186. r.append( child )
  6187. return r
  6188. class bpyShaders(bpy.types.Operator):
  6189. '''operator: enables material nodes (workaround for not having IDPointers in pyRNA)'''
  6190. bl_idname = "ogre.force_setup_material_passes"
  6191. bl_label = "force bpyShaders"
  6192. bl_options = {'REGISTER'}
  6193. @classmethod
  6194. def poll(cls, context):
  6195. if context.active_object and context.active_object.active_material: return True
  6196. def invoke(self, context, event):
  6197. mat = context.active_object.active_material
  6198. mat.use_material_passes = True
  6199. bpyShaders.create_material_passes( mat )
  6200. return {'FINISHED'}
  6201. ## setup from MaterialScripts.reset_rna( callback=bpyShaders.on_change_parent_material )
  6202. @staticmethod
  6203. def on_change_parent_material(mat,context):
  6204. print(mat,context)
  6205. print('callback', mat.ogre_parent_material)
  6206. @staticmethod
  6207. def get_subnodes(mat, type='TEXTURE'):
  6208. d = {}
  6209. for node in mat.nodes:
  6210. if node.type==type: d[node.name] = node
  6211. keys = list(d.keys())
  6212. keys.sort()
  6213. r = []
  6214. for key in keys: r.append( d[key] )
  6215. return r
  6216. @staticmethod
  6217. def get_texture_subnodes( parent, submaterial=None ):
  6218. if not submaterial: submaterial = parent.active_node_material
  6219. d = {}
  6220. for link in parent.node_tree.links:
  6221. if link.from_node and link.from_node.type=='TEXTURE':
  6222. if link.to_node and link.to_node.type == 'MATERIAL_EXT':
  6223. if link.to_node.material:
  6224. if link.to_node.material.name == submaterial.name:
  6225. node = link.from_node
  6226. d[node.name] = node
  6227. keys = list(d.keys()) # this breaks if the user renames the node - TODO improve me
  6228. keys.sort()
  6229. r = []
  6230. for key in keys: r.append( d[key] )
  6231. return r
  6232. @staticmethod
  6233. def get_connected_input_nodes( material, node ):
  6234. r = []
  6235. for link in material.node_tree.links:
  6236. if link.to_node and link.to_node.name == node.name:
  6237. r.append( link.from_node )
  6238. return r
  6239. @staticmethod
  6240. def get_or_create_material_passes( mat, n=8 ):
  6241. if not mat.node_tree:
  6242. print('CREATING MATERIAL PASSES', n)
  6243. bpyShaders.create_material_passes( mat, n )
  6244. d = {} # funky, blender259 had this in order, now blender260 has random order
  6245. for node in mat.node_tree.nodes:
  6246. if node.type == 'MATERIAL_EXT' and node.name.startswith('GEN.'):
  6247. d[node.name] = node
  6248. keys = list(d.keys())
  6249. keys.sort()
  6250. r = []
  6251. for key in keys: r.append( d[key] )
  6252. return r
  6253. @staticmethod
  6254. def get_or_create_texture_nodes( mat, n=6 ): # currently not used
  6255. #print('bpyShaders.get_or_create_texture_nodes( %s, %s )' %(mat,n))
  6256. assert mat.node_tree # must call create_material_passes first
  6257. m = []
  6258. for node in mat.node_tree.nodes:
  6259. if node.type == 'MATERIAL_EXT' and node.name.startswith('GEN.'):
  6260. m.append( node )
  6261. if not m:
  6262. m = bpyShaders.get_or_create_material_passes(mat)
  6263. print(m)
  6264. r = []
  6265. for link in mat.node_tree.links:
  6266. print(link, link.to_node, link.from_node)
  6267. if link.to_node and link.to_node.name.startswith('GEN.') and link.from_node.type=='TEXTURE':
  6268. r.append( link.from_node )
  6269. if not r:
  6270. print('--missing texture nodes--')
  6271. r = bpyShaders.create_texture_nodes( mat, n )
  6272. return r
  6273. @staticmethod
  6274. def create_material_passes( mat, n=8, textures=True ):
  6275. #print('bpyShaders.create_material_passes( %s, %s )' %(mat,n))
  6276. mat.use_nodes = True
  6277. tree = mat.node_tree # valid pointer now
  6278. nodes = bpyShaders.get_subnodes( tree, 'MATERIAL' ) # assign base material
  6279. if nodes and not nodes[0].material:
  6280. nodes[0].material = mat
  6281. r = []
  6282. x = 680
  6283. for i in range( n ):
  6284. node = tree.nodes.new( type='MATERIAL_EXT' )
  6285. node.name = 'GEN.%s' %i
  6286. node.location.x = x; node.location.y = 640
  6287. r.append( node )
  6288. x += 220
  6289. #mat.use_nodes = False # TODO set user material to default output
  6290. if textures:
  6291. texnodes = bpyShaders.create_texture_nodes( mat )
  6292. print( texnodes )
  6293. return r
  6294. @staticmethod
  6295. def create_texture_nodes( mat, n=6, geoms=True ):
  6296. #print('bpyShaders.create_texture_nodes( %s )' %mat)
  6297. assert mat.node_tree # must call create_material_passes first
  6298. mats = bpyShaders.get_or_create_material_passes( mat )
  6299. r = {}; x = 400
  6300. for i,m in enumerate(mats):
  6301. r['material'] = m; r['textures'] = []; r['geoms'] = []
  6302. inputs = [] # other inputs mess up material preview #
  6303. for tag in ['Mirror', 'Ambient', 'Emit', 'SpecTra', 'Ray Mirror', 'Translucency']:
  6304. inputs.append( m.inputs[ tag ] )
  6305. for j in range(n):
  6306. tex = mat.node_tree.nodes.new( type='TEXTURE' )
  6307. tex.name = 'TEX.%s.%s' %(j, m.name)
  6308. tex.location.x = x - (j*16)
  6309. tex.location.y = -(j*230)
  6310. input = inputs[j]; output = tex.outputs['Color']
  6311. link = mat.node_tree.links.new( input, output )
  6312. r['textures'].append( tex )
  6313. if geoms:
  6314. geo = mat.node_tree.nodes.new( type='GEOMETRY' )
  6315. link = mat.node_tree.links.new( tex.inputs['Vector'], geo.outputs['UV'] )
  6316. geo.location.x = x - (j*16) - 250
  6317. geo.location.y = -(j*250) - 1500
  6318. r['geoms'].append( geo )
  6319. x += 220
  6320. return r
  6321. @UI
  6322. class PANEL_node_editor_ui( bpy.types.Panel ):
  6323. bl_space_type = 'NODE_EDITOR'
  6324. bl_region_type = 'UI'
  6325. bl_label = "Ogre Material"
  6326. @classmethod
  6327. def poll(self,context):
  6328. if context.space_data.id:
  6329. return True
  6330. def draw(self, context):
  6331. layout = self.layout
  6332. topmat = context.space_data.id # the top level node_tree
  6333. mat = topmat.active_node_material # the currently selected sub-material
  6334. if not mat or topmat.name == mat.name:
  6335. self.bl_label = topmat.name
  6336. if not topmat.use_material_passes:
  6337. layout.operator(
  6338. 'ogre.force_setup_material_passes',
  6339. text="Ogre Material Layers",
  6340. icon='SCENE_DATA'
  6341. )
  6342. ogre_material_panel( layout, topmat, show_programs=False )
  6343. elif mat:
  6344. self.bl_label = mat.name
  6345. ogre_material_panel( layout, mat, topmat, show_programs=False )
  6346. @UI
  6347. class PANEL_node_editor_ui_extra( bpy.types.Panel ):
  6348. bl_space_type = 'NODE_EDITOR'
  6349. bl_region_type = 'UI'
  6350. bl_label = "Ogre Material Advanced"
  6351. bl_options = {'DEFAULT_CLOSED'}
  6352. @classmethod
  6353. def poll(self,context):
  6354. if context.space_data.id: return True
  6355. def draw(self, context):
  6356. layout = self.layout
  6357. topmat = context.space_data.id # the top level node_tree
  6358. mat = topmat.active_node_material # the currently selected sub-material
  6359. if mat:
  6360. self.bl_label = mat.name + ' (advanced)'
  6361. ogre_material_panel_extra( layout, mat )
  6362. else:
  6363. self.bl_label = topmat.name + ' (advanced)'
  6364. ogre_material_panel_extra( layout, topmat )
  6365. def ogre_material_panel_extra( parent, mat ):
  6366. box = parent.box()
  6367. header = box.row()
  6368. if mat.use_fixed_pipeline:
  6369. header.prop( mat, 'use_fixed_pipeline', text='Fixed Pipeline', icon='LAMP_SUN' )
  6370. row = box.row()
  6371. row.prop(mat, "use_vertex_color_paint", text="Vertex Colors")
  6372. row.prop(mat, "use_shadeless")
  6373. if mat.use_shadeless and not mat.use_vertex_color_paint:
  6374. row = box.row()
  6375. row.prop(mat, "diffuse_color", text='')
  6376. elif not mat.use_shadeless:
  6377. if not mat.use_vertex_color_paint:
  6378. row = box.row()
  6379. row.prop(mat, "diffuse_color", text='')
  6380. row.prop(mat, "diffuse_intensity", text='intensity')
  6381. row = box.row()
  6382. row.prop(mat, "specular_color", text='')
  6383. row.prop(mat, "specular_intensity", text='intensity')
  6384. row = box.row()
  6385. row.prop(mat, "specular_hardness")
  6386. row = box.row()
  6387. row.prop(mat, "ambient")
  6388. #row = box.row()
  6389. row.prop(mat, "emit")
  6390. box.prop(mat, 'use_ogre_advanced_options', text='---guru options---' )
  6391. else:
  6392. header.prop( mat, 'use_fixed_pipeline', text='', icon='LAMP_SUN' )
  6393. header.prop(mat, 'use_ogre_advanced_options', text='---guru options---' )
  6394. if mat.use_ogre_advanced_options:
  6395. box.prop(mat, 'offset_z')
  6396. box.prop(mat, "use_shadows")
  6397. box.prop(mat, 'ogre_depth_write' )
  6398. for tag in 'ogre_colour_write ogre_lighting ogre_normalise_normals ogre_light_clip_planes ogre_light_scissor ogre_alpha_to_coverage ogre_depth_check'.split():
  6399. box.prop(mat, tag)
  6400. for tag in 'ogre_polygon_mode ogre_shading ogre_cull_hardware ogre_transparent_sorting ogre_illumination_stage ogre_depth_func ogre_scene_blend_op'.split():
  6401. box.prop(mat, tag)
  6402. def ogre_material_panel( layout, mat, parent=None, show_programs=True ):
  6403. box = layout.box()
  6404. header = box.row()
  6405. header.prop(mat, 'ogre_scene_blend', text='')
  6406. if mat.ogre_scene_blend and 'alpha' in mat.ogre_scene_blend:
  6407. row = box.row()
  6408. if mat.use_transparency:
  6409. row.prop(mat, "use_transparency", text='')
  6410. row.prop(mat, "alpha")
  6411. else:
  6412. row.prop(mat, "use_transparency", text='Transparent')
  6413. if not parent:
  6414. return # only allow on pass1 and higher
  6415. header.prop(mat, 'use_ogre_parent_material', icon='FILE_SCRIPT', text='')
  6416. if mat.use_ogre_parent_material:
  6417. row = box.row()
  6418. row.prop(mat, 'ogre_parent_material', text='')
  6419. s = get_ogre_user_material( mat.ogre_parent_material ) # gets by name
  6420. if s and (s.vertex_programs or s.fragment_programs):
  6421. progs = s.get_programs()
  6422. split = box.row()
  6423. texnodes = None
  6424. if parent:
  6425. texnodes = bpyShaders.get_texture_subnodes( parent, submaterial=mat )
  6426. elif mat.node_tree:
  6427. texnodes = bpyShaders.get_texture_subnodes( mat ) # assume toplevel
  6428. if not progs:
  6429. bx = split.box()
  6430. bx.label( text='(missing shader programs)', icon='ERROR' )
  6431. elif s.texture_units and texnodes:
  6432. bx = split.box()
  6433. for i,name in enumerate(s.texture_units_order):
  6434. if i<len(texnodes):
  6435. row = bx.row()
  6436. #row.label( text=name )
  6437. tex = texnodes[i]
  6438. row.prop( tex, 'texture', text=name )
  6439. if parent:
  6440. inputs = bpyShaders.get_connected_input_nodes( parent, tex )
  6441. if inputs:
  6442. geo = inputs[0]
  6443. assert geo.type == 'GEOMETRY'
  6444. row.prop( geo, 'uv_layer', text='UV' )
  6445. else:
  6446. print('WARNING: no slot for texture unit:', name)
  6447. if show_programs and (s.vertex_programs or s.fragment_programs):
  6448. bx = box.box()
  6449. for name in s.vertex_programs:
  6450. bx.label( text=name )
  6451. for name in s.fragment_programs:
  6452. bx.label( text=name )
  6453. ## Blender addon main entry point.
  6454. ## Allows directly running by "blender --python blender2ogre.py"
  6455. if __name__ == "__main__":
  6456. register()