io_export_ogreDotScene.py 314 KB

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