export_dae.py 50 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565
  1. # ##### BEGIN GPL LICENSE BLOCK #####
  2. #
  3. # This program is free software; you can redistribute it and/or
  4. # modify it under the terms of the GNU General Public License
  5. # as published by the Free Software Foundation; either version 2
  6. # of the License, or (at your option) any later version.
  7. #
  8. # This program 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
  11. # GNU General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU General Public License
  14. # along with this program; if not, write to the Free Software Foundation,
  15. # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  16. #
  17. # ##### END GPL LICENSE BLOCK #####
  18. # <pep8 compliant>
  19. # Script copyright (C) Juan Linietsky
  20. # Contact Info: [email protected]
  21. """
  22. This script is an exporter to the Khronos Collada file format.
  23. http://www.khronos.org/collada/
  24. """
  25. # TODO:
  26. # Materials & Textures
  27. # Optionally export Vertex Colors
  28. # Morph Targets
  29. # Control bone removal
  30. # Copy textures
  31. # Export Keyframe Optimization
  32. # --
  33. # Morph Targets
  34. # Blender native material? (?)
  35. import os
  36. import time
  37. import math # math.pi
  38. import shutil
  39. import bpy
  40. import bmesh
  41. from mathutils import Vector, Matrix
  42. #according to collada spec, order matters
  43. S_ASSET=0
  44. S_IMGS=1
  45. S_FX=2
  46. S_MATS=3
  47. S_GEOM=4
  48. S_MORPH=5
  49. S_SKIN=6
  50. S_CONT=7
  51. S_CAMS=8
  52. S_LAMPS=9
  53. S_ANIM_CLIPS=10
  54. S_NODES=11
  55. S_ANIM=12
  56. CMP_EPSILON=0.0001
  57. def snap_tup(tup):
  58. ret=()
  59. for x in tup:
  60. ret+=( x-math.fmod(x,0.0001), )
  61. return tup
  62. def strmtx(mtx):
  63. s=" "
  64. for x in range(4):
  65. for y in range(4):
  66. s+=str(mtx[x][y])
  67. s+=" "
  68. s+=" "
  69. return s
  70. def numarr(a,mult=1.0):
  71. s=" "
  72. for x in a:
  73. s+=" "+str(x*mult)
  74. s+=" "
  75. return s
  76. def strarr(arr):
  77. s=" "
  78. for x in arr:
  79. s+=" "+str(x)
  80. s+=" "
  81. return s
  82. class DaeExporter:
  83. def validate_id(self,d):
  84. if (d.find("id-")==0):
  85. return "z"+d
  86. return d
  87. def new_id(self,t):
  88. self.last_id+=1
  89. return "id-"+t+"-"+str(self.last_id)
  90. class Vertex:
  91. def close_to(v):
  92. if ( (self.vertex-v.vertex).length() > CMP_EPSILON ):
  93. return False
  94. if ( (self.normal-v.normal).length() > CMP_EPSILON ):
  95. return False
  96. if ( (self.uv-v.uv).length() > CMP_EPSILON ):
  97. return False
  98. if ( (self.uv2-v.uv2).length() > CMP_EPSILON ):
  99. return False
  100. return True
  101. def get_tup(self):
  102. tup = (self.vertex.x,self.vertex.y,self.vertex.z,self.normal.x,self.normal.y,self.normal.z)
  103. for t in self.uv:
  104. tup = tup + (t.x,t.y)
  105. if (self.color!=None):
  106. tup = tup + (self.color.x,self.color.y,self.color.z)
  107. if (self.tangent!=None):
  108. tup = tup + (self.tangent.x,self.tangent.y,self.tangent.z)
  109. if (self.bitangent!=None):
  110. tup = tup + (self.bitangent.x,self.bitangent.y,self.bitangent.z)
  111. #for t in self.bones:
  112. # tup = tup + (t)
  113. #for t in self.weights:
  114. # tup = tup + (t)
  115. return tup
  116. def __init__(self):
  117. self.vertex = Vector( (0.0,0.0,0.0) )
  118. self.normal = Vector( (0.0,0.0,0.0) )
  119. self.tangent = None
  120. self.bitangent = None
  121. self.color = None
  122. self.uv = []
  123. self.uv2 = Vector( (0.0,0.0) )
  124. self.bones=[]
  125. self.weights=[]
  126. def writel(self,section,indent,text):
  127. if (not (section in self.sections)):
  128. self.sections[section]=[]
  129. line=""
  130. for x in range(indent):
  131. line+="\t"
  132. line+=text
  133. self.sections[section].append(line)
  134. def export_image(self,image):
  135. if (image in self.image_cache):
  136. return self.image_cache[image]
  137. imgpath = image.filepath
  138. if (imgpath.find("//")==0 or imgpath.find("\\\\")==0):
  139. #if relative, convert to absolute
  140. imgpath = bpy.path.abspath(imgpath)
  141. #path is absolute, now do something!
  142. if (self.config["use_copy_images"]):
  143. #copy image
  144. basedir = os.path.dirname(self.path)+"/images"
  145. if (not os.path.isdir(basedir)):
  146. os.makedirs(basedir)
  147. dstfile=basedir+"/"+os.path.basename(imgpath)
  148. if (not os.path.isfile(dstfile)):
  149. shutil.copy(imgpath,dstfile)
  150. imgpath="images/"+os.path.basename(imgpath)
  151. else:
  152. #export relative, always, no one wants absolute paths.
  153. imgpath = os.path.relpath(imgpath,os.path.dirname(self.path)).replace("\\","/") # export unix compatible always
  154. imgid = self.new_id("image")
  155. self.writel(S_IMGS,1,'<image id="'+imgid+'" name="'+image.name+'">')
  156. self.writel(S_IMGS,2,'<init_from>'+imgpath+'</init_from>"/>')
  157. self.writel(S_IMGS,1,'</image>')
  158. self.image_cache[image]=imgid
  159. return imgid
  160. def export_material(self,material,double_sided_hint=True):
  161. if (material in self.material_cache):
  162. return self.material_cache[material]
  163. fxid = self.new_id("fx")
  164. self.writel(S_FX,1,'<effect id="'+fxid+'" name="'+material.name+'-fx">')
  165. self.writel(S_FX,2,'<profile_COMMON>')
  166. #Find and fetch the textures and create sources
  167. sampler_table={}
  168. diffuse_tex=None
  169. specular_tex=None
  170. emission_tex=None
  171. normal_tex=None
  172. for i in range(len(material.texture_slots)):
  173. ts=material.texture_slots[i]
  174. if (not ts):
  175. continue
  176. if (not ts.use):
  177. continue
  178. if (not ts.texture):
  179. continue
  180. if (ts.texture.type!="IMAGE"):
  181. continue
  182. if (ts.texture.image==None):
  183. continue
  184. #image
  185. imgid = self.export_image(ts.texture.image)
  186. #surface
  187. surface_sid = self.new_id("fx_surf")
  188. self.writel(S_FX,3,'<newparam sid="'+surface_sid+'">')
  189. self.writel(S_FX,4,'<surface type="2D">')
  190. self.writel(S_FX,5,'<init_from>'+imgid+'</init_from>') #this is sooo weird
  191. self.writel(S_FX,5,'<format>A8R8G8B8</format>')
  192. self.writel(S_FX,4,'</surface>')
  193. self.writel(S_FX,3,'</newparam>')
  194. #sampler, collada sure likes it difficult
  195. sampler_sid = self.new_id("fx_sampler")
  196. self.writel(S_FX,3,'<newparam sid="'+sampler_sid+'">')
  197. self.writel(S_FX,4,'<sampler2D>')
  198. self.writel(S_FX,5,'<source>'+surface_sid+'</source>')
  199. self.writel(S_FX,4,'</sampler2D>')
  200. self.writel(S_FX,3,'</newparam>')
  201. sampler_table[i]=sampler_sid
  202. if (ts.use_map_color_diffuse and diffuse_tex==None):
  203. diffuse_tex=sampler_sid
  204. if (ts.use_map_color_spec and specular_tex==None):
  205. specular_tex=sampler_sid
  206. if (ts.use_map_emit and emission_tex==None):
  207. emission_tex=sampler_sid
  208. if (ts.use_map_normal and normal_tex==None):
  209. normal_tex=sampler_sid
  210. self.writel(S_FX,3,'<technique sid="common">')
  211. shtype="blinn"
  212. self.writel(S_FX,4,'<'+shtype+'>')
  213. #ambient? from where?
  214. self.writel(S_FX,5,'<emission>')
  215. if (emission_tex!=None):
  216. self.writel(S_FX,6,'<texture texture="'+emission_tex+'" texcoord="CHANNEL1"/>')
  217. else:
  218. self.writel(S_FX,6,'<color>'+numarr(material.diffuse_color,material.emit)+' </color>') # not totally right but good enough
  219. self.writel(S_FX,5,'</emission>')
  220. self.writel(S_FX,5,'<ambient>')
  221. self.writel(S_FX,6,'<color>'+numarr(self.scene.world.ambient_color,material.ambient)+' </color>')
  222. self.writel(S_FX,5,'</ambient>')
  223. self.writel(S_FX,5,'<diffuse>')
  224. if (diffuse_tex!=None):
  225. self.writel(S_FX,6,'<texture texture="'+diffuse_tex+'" texcoord="CHANNEL1"/>')
  226. else:
  227. self.writel(S_FX,6,'<color>'+numarr(material.diffuse_color,material.diffuse_intensity)+'</color>')
  228. self.writel(S_FX,5,'</diffuse>')
  229. self.writel(S_FX,5,'<specular>')
  230. if (specular_tex!=None):
  231. self.writel(S_FX,6,'<texture texture="'+specular_tex+'" texcoord="CHANNEL1"/>')
  232. else:
  233. self.writel(S_FX,6,'<color>'+numarr(material.specular_color,material.specular_intensity)+'</color>')
  234. self.writel(S_FX,5,'</specular>')
  235. self.writel(S_FX,5,'<shininess>')
  236. self.writel(S_FX,6,'<float>'+str(material.specular_hardness)+'</float>')
  237. self.writel(S_FX,5,'</shininess>')
  238. self.writel(S_FX,5,'<reflective>')
  239. self.writel(S_FX,6,'<color>'+strarr(material.mirror_color)+'</color>')
  240. self.writel(S_FX,5,'</reflective>')
  241. if (material.use_transparency):
  242. self.writel(S_FX,5,'<transparency>')
  243. self.writel(S_FX,6,'<float>'+str(material.alpha)+'</float>')
  244. self.writel(S_FX,5,'</transparency>')
  245. self.writel(S_FX,4,'</'+shtype+'>')
  246. self.writel(S_FX,4,'<index_of_refraction>'+str(material.specular_ior)+'</index_of_refraction>')
  247. self.writel(S_FX,4,'<extra>')
  248. self.writel(S_FX,5,'<technique profile="FCOLLADA">')
  249. if (normal_tex):
  250. self.writel(S_FX,6,'<bump bumptype="NORMALMAP">')
  251. self.writel(S_FX,7,'<texture texture="'+normal_tex+'" texcoord="CHANNEL1"/>')
  252. self.writel(S_FX,6,'</bump>')
  253. self.writel(S_FX,5,'</technique>')
  254. self.writel(S_FX,5,'<technique profile="GOOGLEEARTH">')
  255. self.writel(S_FX,6,'<double_sided>'+["0","1"][double_sided_hint]+"</double_sided>")
  256. self.writel(S_FX,5,'</technique>')
  257. self.writel(S_FX,4,'</extra>')
  258. self.writel(S_FX,3,'</technique>')
  259. self.writel(S_FX,2,'</profile_COMMON>')
  260. self.writel(S_FX,1,'</effect>')
  261. # Also export blender material in all it's glory (if set as active)
  262. #Material
  263. matid = self.new_id("material")
  264. self.writel(S_MATS,1,'<material id="'+matid+'" name="'+material.name+'">')
  265. self.writel(S_MATS,2,'<instance_effect url="#'+fxid+'"/>')
  266. self.writel(S_MATS,1,'</material>')
  267. self.material_cache[material]=matid
  268. return matid
  269. def export_mesh(self,node,armature=None,skeyindex=-1,skel_source=None):
  270. mesh = node.data
  271. if (node.data in self.mesh_cache):
  272. return self.mesh_cache[mesh]
  273. if (skeyindex==-1 and mesh.shape_keys!=None and len(mesh.shape_keys.key_blocks)):
  274. values=[]
  275. morph_targets=[]
  276. md=None
  277. for k in range(0,len(mesh.shape_keys.key_blocks)):
  278. shape = node.data.shape_keys.key_blocks[k]
  279. values+=[shape.value] #save value
  280. shape.value=0
  281. mid = self.new_id("morph")
  282. for k in range(0,len(mesh.shape_keys.key_blocks)):
  283. shape = node.data.shape_keys.key_blocks[k]
  284. node.show_only_shape_key=True
  285. node.active_shape_key_index = k
  286. shape.value = 1.0
  287. mesh.update()
  288. """
  289. oldval = shape.value
  290. shape.value = 1.0
  291. """
  292. p = node.data
  293. v = node.to_mesh(bpy.context.scene, True, "RENDER")
  294. node.data = v
  295. # self.export_node(node,il,shape.name)
  296. node.data.update()
  297. if (armature and k==0):
  298. md=self.export_mesh(node,armature,k,mid)
  299. else:
  300. md=self.export_mesh(node,None,k)
  301. node.data = p
  302. node.data.update()
  303. shape.value = 0.0
  304. morph_targets.append(md)
  305. """
  306. shape.value = oldval
  307. """
  308. node.show_only_shape_key=False
  309. node.active_shape_key_index = 0
  310. self.writel(S_MORPH,1,'<controller id="'+mid+'" name="">')
  311. #if ("skin_id" in morph_targets[0]):
  312. # self.writel(S_MORPH,2,'<morph source="#'+morph_targets[0]["skin_id"]+'" method="NORMALIZED">')
  313. #else:
  314. self.writel(S_MORPH,2,'<morph source="#'+morph_targets[0]["id"]+'" method="NORMALIZED">')
  315. self.writel(S_MORPH,3,'<source id="'+mid+'-morph-targets">')
  316. self.writel(S_MORPH,4,'<IDREF_array id="'+mid+'-morph-targets-array" count="'+str(len(morph_targets)-1)+'">')
  317. marr=""
  318. warr=""
  319. for i in range(len(morph_targets)):
  320. if (i==0):
  321. continue
  322. elif (i>1):
  323. marr+=" "
  324. if ("skin_id" in morph_targets[i]):
  325. marr+=morph_targets[i]["skin_id"]
  326. else:
  327. marr+=morph_targets[i]["id"]
  328. warr+=" 0"
  329. self.writel(S_MORPH,5,marr)
  330. self.writel(S_MORPH,4,'</IDREF_array>')
  331. self.writel(S_MORPH,4,'<technique_common>')
  332. self.writel(S_MORPH,5,'<accessor source="#'+mid+'-morph-targets-array" count="'+str(len(morph_targets)-1)+'" stride="1">')
  333. self.writel(S_MORPH,6,'<param name="MORPH_TARGET" type="IDREF"/>')
  334. self.writel(S_MORPH,5,'</accessor>')
  335. self.writel(S_MORPH,4,'</technique_common>')
  336. self.writel(S_MORPH,3,'</source>')
  337. self.writel(S_MORPH,3,'<source id="'+mid+'-morph-weights">')
  338. self.writel(S_MORPH,4,'<float_array id="'+mid+'-morph-weights-array" count="'+str(len(morph_targets)-1)+'" >')
  339. self.writel(S_MORPH,5,warr)
  340. self.writel(S_MORPH,4,'</float_array>')
  341. self.writel(S_MORPH,4,'<technique_common>')
  342. self.writel(S_MORPH,5,'<accessor source="#'+mid+'-morph-weights-array" count="'+str(len(morph_targets)-1)+'" stride="1">')
  343. self.writel(S_MORPH,6,'<param name="MORPH_WEIGHT" type="float"/>')
  344. self.writel(S_MORPH,5,'</accessor>')
  345. self.writel(S_MORPH,4,'</technique_common>')
  346. self.writel(S_MORPH,3,'</source>')
  347. self.writel(S_MORPH,3,'<targets>')
  348. self.writel(S_MORPH,4,'<input semantic="MORPH_TARGET" source="#'+mid+'-morph-targets"/>')
  349. self.writel(S_MORPH,4,'<input semantic="MORPH_WEIGHT" source="#'+mid+'-morph-weights"/>')
  350. self.writel(S_MORPH,3,'</targets>')
  351. self.writel(S_MORPH,2,'</morph>')
  352. self.writel(S_MORPH,1,'</controller>')
  353. if (armature!=None):
  354. self.armature_for_morph[node]=armature
  355. meshdata={}
  356. if (armature):
  357. meshdata = morph_targets[0]
  358. meshdata["morph_id"]=mid
  359. else:
  360. meshdata["id"]=morph_targets[0]["id"]
  361. meshdata["morph_id"]=mid
  362. meshdata["material_assign"]=morph_targets[0]["material_assign"]
  363. self.mesh_cache[node.data]=meshdata
  364. return meshdata
  365. apply_modifiers = len(node.modifiers) and self.config["use_mesh_modifiers"]
  366. mesh=node.to_mesh(self.scene,apply_modifiers,"RENDER") #is this allright?
  367. triangulate=self.config["use_triangles"]
  368. if (triangulate):
  369. bm = bmesh.new()
  370. bm.from_mesh(mesh)
  371. bmesh.ops.triangulate(bm, faces=bm.faces)
  372. bm.to_mesh(mesh)
  373. bm.free()
  374. mesh.update(calc_tessface=True)
  375. vertices=[]
  376. vertex_map={}
  377. surface_indices={}
  378. materials={}
  379. materials={}
  380. si=None
  381. if (armature!=None):
  382. si=self.skeleton_info[armature]
  383. has_uv=False
  384. has_uv2=False
  385. has_weights=armature!=None
  386. has_tangents=self.config["use_tangent_arrays"] # could detect..
  387. has_colors=len(mesh.vertex_colors)
  388. mat_assign=[]
  389. uv_layer_count=len(mesh.uv_textures)
  390. if (len(mesh.uv_textures)):
  391. mesh.calc_tangents()
  392. else:
  393. mesh.calc_normals_split()
  394. for fi in range(len(mesh.polygons)):
  395. f=mesh.polygons[fi]
  396. if (not (f.material_index in surface_indices)):
  397. surface_indices[f.material_index]=[]
  398. print("Type: "+str(type(f.material_index)))
  399. print("IDX: "+str(f.material_index)+"/"+str(len(mesh.materials)))
  400. try:
  401. #Bizarre blender behavior i don't understand, so catching exception
  402. mat = mesh.materials[f.material_index]
  403. except:
  404. mat= None
  405. if (mat!=None):
  406. materials[f.material_index]=self.export_material( mat,mesh.show_double_sided )
  407. else:
  408. materials[f.material_index]=None #weird, has no material?
  409. indices = surface_indices[f.material_index]
  410. vi=[]
  411. #vertices always 3
  412. """
  413. if (len(f.vertices)==3):
  414. vi.append(0)
  415. vi.append(1)
  416. vi.append(2)
  417. elif (len(f.vertices)==4):
  418. #todo, should use shortest path
  419. vi.append(0)
  420. vi.append(1)
  421. vi.append(2)
  422. vi.append(0)
  423. vi.append(2)
  424. vi.append(3)
  425. """
  426. for lt in range(f.loop_total):
  427. loop_index = f.loop_start + lt
  428. ml = mesh.loops[loop_index]
  429. mv = mesh.vertices[ml.vertex_index]
  430. v = self.Vertex()
  431. v.vertex = Vector( mv.co )
  432. for xt in mesh.uv_layers:
  433. v.uv.append( Vector( xt.data[loop_index].uv ) )
  434. if (has_colors):
  435. v.color = Vector( mesh.vertex_colors[0].data[loop_index].color )
  436. v.normal = Vector( ml.normal )
  437. if (has_tangents):
  438. v.tangent = Vector( ml.tangent )
  439. v.bitangent = Vector( ml.bitangent )
  440. # if (armature):
  441. # v.vertex = node.matrix_world * v.vertex
  442. #v.color=Vertex(mv. ???
  443. if (armature!=None):
  444. wsum=0.0
  445. for vg in mv.groups:
  446. if vg.group >= len(node.vertex_groups):
  447. continue;
  448. name = node.vertex_groups[vg.group].name
  449. if (name in si["bone_index"]):
  450. #could still put the weight as 0.0001 maybe
  451. if (vg.weight>0.001): #blender has a lot of zero weight stuff
  452. v.bones.append(si["bone_index"][name])
  453. v.weights.append(vg.weight)
  454. wsum+=vg.weight
  455. tup = v.get_tup()
  456. idx = 0
  457. if (skeyindex==-1 and tup in vertex_map): #do not optmize if using shapekeys
  458. idx = vertex_map[tup]
  459. else:
  460. idx = len(vertices)
  461. vertices.append(v)
  462. vertex_map[tup]=idx
  463. vi.append(idx)
  464. if (len(vi)>2):
  465. #only triangles and above
  466. indices.append(vi)
  467. meshid = self.new_id("mesh")
  468. self.writel(S_GEOM,1,'<geometry id="'+meshid+'" name="'+mesh.name+'">')
  469. self.writel(S_GEOM,2,'<mesh>')
  470. # Vertex Array
  471. self.writel(S_GEOM,3,'<source id="'+meshid+'-positions">')
  472. float_values=""
  473. for v in vertices:
  474. float_values+=" "+str(v.vertex.x)+" "+str(v.vertex.y)+" "+str(v.vertex.z)
  475. self.writel(S_GEOM,4,'<float_array id="'+meshid+'-positions-array" count="'+str(len(vertices)*3)+'">'+float_values+'</float_array>')
  476. self.writel(S_GEOM,4,'<technique_common>')
  477. self.writel(S_GEOM,4,'<accessor source="#'+meshid+'-positions-array" count="'+str(len(vertices))+'" stride="3">')
  478. self.writel(S_GEOM,5,'<param name="X" type="float"/>')
  479. self.writel(S_GEOM,5,'<param name="Y" type="float"/>')
  480. self.writel(S_GEOM,5,'<param name="Z" type="float"/>')
  481. self.writel(S_GEOM,4,'</accessor>')
  482. self.writel(S_GEOM,4,'</technique_common>')
  483. self.writel(S_GEOM,3,'</source>')
  484. # Normal Array
  485. self.writel(S_GEOM,3,'<source id="'+meshid+'-normals">')
  486. float_values=""
  487. for v in vertices:
  488. float_values+=" "+str(v.normal.x)+" "+str(v.normal.y)+" "+str(v.normal.z)
  489. self.writel(S_GEOM,4,'<float_array id="'+meshid+'-normals-array" count="'+str(len(vertices)*3)+'">'+float_values+'</float_array>')
  490. self.writel(S_GEOM,4,'<technique_common>')
  491. self.writel(S_GEOM,4,'<accessor source="#'+meshid+'-normals-array" count="'+str(len(vertices))+'" stride="3">')
  492. self.writel(S_GEOM,5,'<param name="X" type="float"/>')
  493. self.writel(S_GEOM,5,'<param name="Y" type="float"/>')
  494. self.writel(S_GEOM,5,'<param name="Z" type="float"/>')
  495. self.writel(S_GEOM,4,'</accessor>')
  496. self.writel(S_GEOM,4,'</technique_common>')
  497. self.writel(S_GEOM,3,'</source>')
  498. if (has_tangents):
  499. self.writel(S_GEOM,3,'<source id="'+meshid+'-tangents">')
  500. float_values=""
  501. for v in vertices:
  502. float_values+=" "+str(v.tangent.x)+" "+str(v.tangent.y)+" "+str(v.tangent.z)
  503. self.writel(S_GEOM,4,'<float_array id="'+meshid+'-tangents-array" count="'+str(len(vertices)*3)+'">'+float_values+'</float_array>')
  504. self.writel(S_GEOM,4,'<technique_common>')
  505. self.writel(S_GEOM,4,'<accessor source="#'+meshid+'-tangents-array" count="'+str(len(vertices))+'" stride="3">')
  506. self.writel(S_GEOM,5,'<param name="X" type="float"/>')
  507. self.writel(S_GEOM,5,'<param name="Y" type="float"/>')
  508. self.writel(S_GEOM,5,'<param name="Z" type="float"/>')
  509. self.writel(S_GEOM,4,'</accessor>')
  510. self.writel(S_GEOM,4,'</technique_common>')
  511. self.writel(S_GEOM,3,'</source>')
  512. self.writel(S_GEOM,3,'<source id="'+meshid+'-bitangents">')
  513. float_values=""
  514. for v in vertices:
  515. float_values+=" "+str(v.bitangent.x)+" "+str(v.bitangent.y)+" "+str(v.bitangent.z)
  516. self.writel(S_GEOM,4,'<float_array id="'+meshid+'-bitangents-array" count="'+str(len(vertices)*3)+'">'+float_values+'</float_array>')
  517. self.writel(S_GEOM,4,'<technique_common>')
  518. self.writel(S_GEOM,4,'<accessor source="#'+meshid+'-bitangents-array" count="'+str(len(vertices))+'" stride="3">')
  519. self.writel(S_GEOM,5,'<param name="X" type="float"/>')
  520. self.writel(S_GEOM,5,'<param name="Y" type="float"/>')
  521. self.writel(S_GEOM,5,'<param name="Z" type="float"/>')
  522. self.writel(S_GEOM,4,'</accessor>')
  523. self.writel(S_GEOM,4,'</technique_common>')
  524. self.writel(S_GEOM,3,'</source>')
  525. # UV Arrays
  526. for uvi in range(uv_layer_count):
  527. self.writel(S_GEOM,3,'<source id="'+meshid+'-texcoord-'+str(uvi)+'">')
  528. float_values=""
  529. for v in vertices:
  530. try:
  531. float_values+=" "+str(v.uv[uvi].x)+" "+str(v.uv[uvi].y)
  532. except:
  533. # I don't understand this weird multi-uv-layer API, but with this it seems to works
  534. float_values+=" 0 0 "
  535. self.writel(S_GEOM,4,'<float_array id="'+meshid+'-texcoord-'+str(uvi)+'-array" count="'+str(len(vertices)*2)+'">'+float_values+'</float_array>')
  536. self.writel(S_GEOM,4,'<technique_common>')
  537. self.writel(S_GEOM,4,'<accessor source="#'+meshid+'-texcoord-'+str(uvi)+'-array" count="'+str(len(vertices))+'" stride="2">')
  538. self.writel(S_GEOM,5,'<param name="S" type="float"/>')
  539. self.writel(S_GEOM,5,'<param name="T" type="float"/>')
  540. self.writel(S_GEOM,4,'</accessor>')
  541. self.writel(S_GEOM,4,'</technique_common>')
  542. self.writel(S_GEOM,3,'</source>')
  543. # Color Arrays
  544. if (has_colors):
  545. self.writel(S_GEOM,3,'<source id="'+meshid+'-colors">')
  546. float_values=""
  547. for v in vertices:
  548. float_values+=" "+str(v.color.x)+" "+str(v.color.y)+" "+str(v.color.z)
  549. self.writel(S_GEOM,4,'<float_array id="'+meshid+'-colors-array" count="'+str(len(vertices)*3)+'">'+float_values+'</float_array>')
  550. self.writel(S_GEOM,4,'<technique_common>')
  551. self.writel(S_GEOM,4,'<accessor source="#'+meshid+'-colors-array" count="'+str(len(vertices))+'" stride="3">')
  552. self.writel(S_GEOM,5,'<param name="X" type="float"/>')
  553. self.writel(S_GEOM,5,'<param name="Y" type="float"/>')
  554. self.writel(S_GEOM,5,'<param name="Z" type="float"/>')
  555. self.writel(S_GEOM,4,'</accessor>')
  556. self.writel(S_GEOM,4,'</technique_common>')
  557. self.writel(S_GEOM,3,'</source>')
  558. # Triangle Lists
  559. self.writel(S_GEOM,3,'<vertices id="'+meshid+'-vertices">')
  560. self.writel(S_GEOM,4,'<input semantic="POSITION" source="#'+meshid+'-positions"/>')
  561. self.writel(S_GEOM,3,'</vertices>')
  562. prim_type=""
  563. if (triangulate):
  564. prim_type="triangles"
  565. else:
  566. prim_type="polygons"
  567. for m in surface_indices:
  568. indices = surface_indices[m]
  569. mat = materials[m]
  570. if (mat!=None):
  571. matref = self.new_id("trimat")
  572. self.writel(S_GEOM,3,'<'+prim_type+' count="'+str(int(len(indices)))+'" material="'+matref+'">') # todo material
  573. mat_assign.append( (mat,matref) )
  574. else:
  575. self.writel(S_GEOM,3,'<'+prim_type+' count="'+str(int(len(indices)))+'">') # todo material
  576. self.writel(S_GEOM,4,'<input semantic="VERTEX" source="#'+meshid+'-vertices" offset="0"/>')
  577. self.writel(S_GEOM,4,'<input semantic="NORMAL" source="#'+meshid+'-normals" offset="0"/>')
  578. for uvi in range(uv_layer_count):
  579. self.writel(S_GEOM,4,'<input semantic="TEXCOORD" source="#'+meshid+'-texcoord-'+str(uvi)+'" offset="0" set="'+str(uvi)+'"/>')
  580. if (has_colors):
  581. self.writel(S_GEOM,4,'<input semantic="COLOR" source="#'+meshid+'-colors" offset="0"/>')
  582. if (has_tangents):
  583. self.writel(S_GEOM,4,'<input semantic="TEXTANGENT" source="#'+meshid+'-tangents" offset="0"/>')
  584. self.writel(S_GEOM,4,'<input semantic="TEXBINORMAL" source="#'+meshid+'-bitangents" offset="0"/>')
  585. if (triangulate):
  586. int_values="<p>"
  587. for p in indices:
  588. for i in p:
  589. int_values+=" "+str(i)
  590. int_values+=" </p>"
  591. self.writel(S_GEOM,4,int_values)
  592. else:
  593. for p in indices:
  594. int_values="<p>"
  595. for i in p:
  596. int_values+=" "+str(i)
  597. int_values+=" </p>"
  598. self.writel(S_GEOM,4,int_values)
  599. self.writel(S_GEOM,3,'</'+prim_type+'>')
  600. self.writel(S_GEOM,2,'</mesh>')
  601. self.writel(S_GEOM,1,'</geometry>')
  602. meshdata={}
  603. meshdata["id"]=meshid
  604. meshdata["material_assign"]=mat_assign
  605. if (skeyindex==-1):
  606. self.mesh_cache[node.data]=meshdata
  607. # Export armature data (if armature exists)
  608. if (armature!=None and (skel_source!=None or skeyindex==-1)):
  609. contid = self.new_id("controller")
  610. self.writel(S_SKIN,1,'<controller id="'+contid+'">')
  611. if (skel_source!=None):
  612. self.writel(S_SKIN,2,'<skin source="#'+skel_source+'">')
  613. else:
  614. self.writel(S_SKIN,2,'<skin source="#'+meshid+'">')
  615. self.writel(S_SKIN,3,'<bind_shape_matrix>'+strmtx(node.matrix_world)+'</bind_shape_matrix>')
  616. #Joint Names
  617. self.writel(S_SKIN,3,'<source id="'+contid+'-joints">')
  618. name_values=""
  619. for v in si["bone_names"]:
  620. name_values+=" "+v
  621. self.writel(S_SKIN,4,'<Name_array id="'+contid+'-joints-array" count="'+str(len(si["bone_names"]))+'">'+name_values+'</Name_array>')
  622. self.writel(S_SKIN,4,'<technique_common>')
  623. self.writel(S_SKIN,4,'<accessor source="#'+contid+'-joints-array" count="'+str(len(si["bone_names"]))+'" stride="1">')
  624. self.writel(S_SKIN,5,'<param name="JOINT" type="Name"/>')
  625. self.writel(S_SKIN,4,'</accessor>')
  626. self.writel(S_SKIN,4,'</technique_common>')
  627. self.writel(S_SKIN,3,'</source>')
  628. #Pose Matrices!
  629. self.writel(S_SKIN,3,'<source id="'+contid+'-bind_poses">')
  630. pose_values=""
  631. for v in si["bone_bind_poses"]:
  632. pose_values+=" "+strmtx(v)
  633. self.writel(S_SKIN,4,'<float_array id="'+contid+'-bind_poses-array" count="'+str(len(si["bone_bind_poses"])*16)+'">'+pose_values+'</float_array>')
  634. self.writel(S_SKIN,4,'<technique_common>')
  635. self.writel(S_SKIN,4,'<accessor source="#'+contid+'-bind_poses-array" count="'+str(len(si["bone_bind_poses"]))+'" stride="16">')
  636. self.writel(S_SKIN,5,'<param name="TRANSFORM" type="float4x4"/>')
  637. self.writel(S_SKIN,4,'</accessor>')
  638. self.writel(S_SKIN,4,'</technique_common>')
  639. self.writel(S_SKIN,3,'</source>')
  640. #Skin Weights!
  641. self.writel(S_SKIN,3,'<source id="'+contid+'-skin_weights">')
  642. skin_weights=""
  643. skin_weights_total=0
  644. for v in vertices:
  645. skin_weights_total+=len(v.weights)
  646. for w in v.weights:
  647. skin_weights+=" "+str(w)
  648. self.writel(S_SKIN,4,'<float_array id="'+contid+'-skin_weights-array" count="'+str(skin_weights_total)+'">'+skin_weights+'</float_array>')
  649. self.writel(S_SKIN,4,'<technique_common>')
  650. self.writel(S_SKIN,4,'<accessor source="#'+contid+'-skin_weights-array" count="'+str(skin_weights_total)+'" stride="1">')
  651. self.writel(S_SKIN,5,'<param name="WEIGHT" type="float"/>')
  652. self.writel(S_SKIN,4,'</accessor>')
  653. self.writel(S_SKIN,4,'</technique_common>')
  654. self.writel(S_SKIN,3,'</source>')
  655. self.writel(S_SKIN,3,'<joints>')
  656. self.writel(S_SKIN,4,'<input semantic="JOINT" source="#'+contid+'-joints"/>')
  657. self.writel(S_SKIN,4,'<input semantic="INV_BIND_MATRIX" source="#'+contid+'-bind_poses"/>')
  658. self.writel(S_SKIN,3,'</joints>')
  659. self.writel(S_SKIN,3,'<vertex_weights count="'+str(len(vertices))+'">')
  660. self.writel(S_SKIN,4,'<input semantic="JOINT" source="#'+contid+'-joints" offset="0"/>')
  661. self.writel(S_SKIN,4,'<input semantic="WEIGHT" source="#'+contid+'-skin_weights" offset="1"/>')
  662. vcounts=""
  663. vs=""
  664. vcount=0
  665. for v in vertices:
  666. vcounts+=" "+str(len(v.weights))
  667. for b in v.bones:
  668. vs+=" "+str(b)
  669. vs+=" "+str(vcount)
  670. vcount+=1
  671. self.writel(S_SKIN,4,'<vcount>'+vcounts+'</vcount>')
  672. self.writel(S_SKIN,4,'<v>'+vs+'</v>')
  673. self.writel(S_SKIN,3,'</vertex_weights>')
  674. self.writel(S_SKIN,2,'</skin>')
  675. self.writel(S_SKIN,1,'</controller>')
  676. meshdata["skin_id"]=contid
  677. return meshdata
  678. def export_mesh_node(self,node,il):
  679. if (node.data==None):
  680. return
  681. armature=None
  682. if (node.parent!=None):
  683. if (node.parent.type=="ARMATURE"):
  684. armature=node.parent
  685. meshdata = self.export_mesh(node,armature)
  686. close_controller=False
  687. if ("skin_id" in meshdata):
  688. close_controller=True
  689. self.writel(S_NODES,il,'<instance_controller url="#'+meshdata["skin_id"]+'">')
  690. for sn in self.skeleton_info[armature]["skeleton_nodes"]:
  691. self.writel(S_NODES,il+1,'<skeleton>#'+sn+'</skeleton>')
  692. elif ("morph_id" in meshdata):
  693. self.writel(S_NODES,il,'<instance_controller url="#'+meshdata["morph_id"]+'">')
  694. close_controller=True
  695. elif (armature==None):
  696. self.writel(S_NODES,il,'<instance_geometry url="#'+meshdata["id"]+'">')
  697. if (len(meshdata["material_assign"])>0):
  698. self.writel(S_NODES,il+1,'<bind_material>')
  699. self.writel(S_NODES,il+2,'<technique_common>')
  700. for m in meshdata["material_assign"]:
  701. self.writel(S_NODES,il+3,'<instance_material symbol="'+m[1]+'" target="#'+m[0]+'"/>')
  702. self.writel(S_NODES,il+2,'</technique_common>')
  703. self.writel(S_NODES,il+1,'</bind_material>')
  704. if (close_controller):
  705. self.writel(S_NODES,il,'</instance_controller>')
  706. else:
  707. self.writel(S_NODES,il,'</instance_geometry>')
  708. def export_armature_bone(self,bone,il,si):
  709. boneid = self.new_id("bone")
  710. boneidx = si["bone_count"]
  711. si["bone_count"]+=1
  712. bonesid = si["id"]+"-"+str(boneidx)
  713. si["bone_index"][bone.name]=boneidx
  714. si["bone_ids"][bone]=boneid
  715. si["bone_names"].append(bonesid)
  716. self.writel(S_NODES,il,'<node id="'+boneid+'" sid="'+bonesid+'" name="'+bone.name+'" type="JOINT">')
  717. il+=1
  718. xform = bone.matrix_local
  719. si["bone_bind_poses"].append((si["armature_xform"] * xform).inverted())
  720. if (bone.parent!=None):
  721. xform = bone.parent.matrix_local.inverted() * xform
  722. else:
  723. si["skeleton_nodes"].append(boneid)
  724. self.writel(S_NODES,il,'<matrix sid="transform">'+strmtx(xform)+'</matrix>')
  725. for c in bone.children:
  726. self.export_armature_bone(c,il,si)
  727. il-=1
  728. self.writel(S_NODES,il,'</node>')
  729. def export_armature_node(self,node,il):
  730. if (node.data==None):
  731. return
  732. self.skeletons.append(node)
  733. armature = node.data
  734. self.skeleton_info[node]={ "bone_count":0, "id":self.new_id("skelbones"),"name":node.name, "bone_index":{},"bone_ids":{},"bone_names":[],"bone_bind_poses":[],"skeleton_nodes":[],"armature_xform":node.matrix_world }
  735. for b in armature.bones:
  736. if (b.parent!=None):
  737. continue
  738. self.export_armature_bone(b,il,self.skeleton_info[node])
  739. if (node.pose):
  740. for b in node.pose.bones:
  741. for x in b.constraints:
  742. if (x.type=='ACTION'):
  743. self.action_constraints.append(x.action)
  744. def export_camera_node(self,node,il):
  745. if (node.data==None):
  746. return
  747. camera=node.data
  748. camid=self.new_id("camera")
  749. self.writel(S_CAMS,1,'<camera id="'+camid+'" name="'+camera.name+'">')
  750. self.writel(S_CAMS,2,'<optics>')
  751. self.writel(S_CAMS,3,'<technique_common>')
  752. if (camera.type=="PERSP"):
  753. self.writel(S_CAMS,4,'<perspective>')
  754. self.writel(S_CAMS,5,'<yfov> '+str(math.degrees(camera.angle))+' </yfov>') # I think?
  755. self.writel(S_CAMS,5,'<aspect_ratio> '+str(self.scene.render.resolution_x / self.scene.render.resolution_y)+' </aspect_ratio>')
  756. self.writel(S_CAMS,5,'<znear> '+str(camera.clip_start)+' </znear>')
  757. self.writel(S_CAMS,5,'<zfar> '+str(camera.clip_end)+' </zfar>')
  758. self.writel(S_CAMS,4,'</perspective>')
  759. else:
  760. self.writel(S_CAMS,4,'<orthografic>')
  761. self.writel(S_CAMS,5,'<xmag> '+str(camera.ortho_scale)+' </xmag>') # I think?
  762. self.writel(S_CAMS,5,'<aspect_ratio> '+str(self.scene.render.resolution_x / self.scene.render.resolution_y)+' </aspect_ratio>')
  763. self.writel(S_CAMS,5,'<znear> '+str(camera.clip_start)+' </znear>')
  764. self.writel(S_CAMS,5,'<zfar> '+str(camera.clip_end)+' </zfar>')
  765. self.writel(S_CAMS,4,'</orthografic>')
  766. self.writel(S_CAMS,3,'</technique_common>')
  767. self.writel(S_CAMS,2,'</optics>')
  768. self.writel(S_CAMS,1,'</camera>')
  769. self.writel(S_NODES,il,'<instance_camera url="#'+camid+'"/>')
  770. def export_lamp_node(self,node,il):
  771. if (node.data==None):
  772. return
  773. light=node.data
  774. lightid=self.new_id("light")
  775. self.writel(S_LAMPS,1,'<light id="'+lightid+'" name="'+light.name+'">')
  776. #self.writel(S_LAMPS,2,'<optics>')
  777. self.writel(S_LAMPS,3,'<technique_common>')
  778. if (light.type=="POINT"):
  779. self.writel(S_LAMPS,4,'<point>')
  780. self.writel(S_LAMPS,5,'<color>'+strarr(light.color)+'</color>')
  781. att_by_distance = 2.0 / light.distance # convert to linear attenuation
  782. self.writel(S_LAMPS,5,'<linear_attenuation>'+str(att_by_distance)+'</linear_attenuation>')
  783. if (light.use_sphere):
  784. self.writel(S_LAMPS,5,'<zfar>'+str(light.distance)+'</zfar>')
  785. self.writel(S_LAMPS,4,'</point>')
  786. elif (light.type=="SPOT"):
  787. self.writel(S_LAMPS,4,'<spot>')
  788. self.writel(S_LAMPS,5,'<color>'+strarr(light.color)+'</color>')
  789. att_by_distance = 2.0 / light.distance # convert to linear attenuation
  790. self.writel(S_LAMPS,5,'<linear_attenuation>'+str(att_by_distance)+'</linear_attenuation>')
  791. self.writel(S_LAMPS,5,'<falloff_angle>'+str(math.degrees(light.spot_size))+'</falloff_angle>')
  792. self.writel(S_LAMPS,4,'</spot>')
  793. else: #write a sun lamp for everything else (not supported)
  794. self.writel(S_LAMPS,4,'<directional>')
  795. self.writel(S_LAMPS,5,'<color>'+strarr(light.color)+'</color>')
  796. self.writel(S_LAMPS,4,'</directional>')
  797. self.writel(S_LAMPS,3,'</technique_common>')
  798. #self.writel(S_LAMPS,2,'</optics>')
  799. self.writel(S_LAMPS,1,'</light>')
  800. self.writel(S_NODES,il,'<instance_light url="#'+lightid+'"/>')
  801. def export_curve(self,curve):
  802. splineid = self.new_id("spline")
  803. self.writel(S_GEOM,1,'<geometry id="'+splineid+'" name="'+curve.name+'">')
  804. self.writel(S_GEOM,2,'<spline closed="0">')
  805. points=[]
  806. interps=[]
  807. handles_in=[]
  808. handles_out=[]
  809. tilts=[]
  810. for cs in curve.splines:
  811. if (cs.type=="BEZIER"):
  812. for s in cs.bezier_points:
  813. points.append(s.co[0])
  814. points.append(s.co[1])
  815. points.append(s.co[2])
  816. handles_in.append(s.handle_left[0])
  817. handles_in.append(s.handle_left[1])
  818. handles_in.append(s.handle_left[2])
  819. handles_out.append(s.handle_right[0])
  820. handles_out.append(s.handle_right[1])
  821. handles_out.append(s.handle_right[2])
  822. tilts.append(s.tilt)
  823. interps.append("BEZIER")
  824. else:
  825. for s in cs.points:
  826. points.append(s.co[0])
  827. points.append(s.co[1])
  828. points.append(s.co[2])
  829. handles_in.append(s.co[0])
  830. handles_in.append(s.co[1])
  831. handles_in.append(s.co[2])
  832. handles_out.append(s.co[0])
  833. handles_out.append(s.co[1])
  834. handles_out.append(s.co[2])
  835. tilts.append(s.tilt)
  836. interps.append("LINEAR")
  837. self.writel(S_GEOM,3,'<source id="'+splineid+'-positions">')
  838. position_values=""
  839. for x in points:
  840. position_values+=" "+str(x)
  841. self.writel(S_GEOM,4,'<float_array id="'+splineid+'-positions-array" count="'+str(len(points))+'">'+position_values+'</float_array>')
  842. self.writel(S_GEOM,4,'<technique_common>')
  843. self.writel(S_GEOM,4,'<accessor source="#'+splineid+'-positions-array" count="'+str(len(points)/3)+'" stride="3">')
  844. self.writel(S_GEOM,5,'<param name="X" type="float"/>')
  845. self.writel(S_GEOM,5,'<param name="Y" type="float"/>')
  846. self.writel(S_GEOM,5,'<param name="Z" type="float"/>')
  847. self.writel(S_GEOM,4,'</accessor>')
  848. self.writel(S_GEOM,3,'</source>')
  849. self.writel(S_GEOM,3,'<source id="'+splineid+'-intangents">')
  850. intangent_values=""
  851. for x in handles_in:
  852. intangent_values+=" "+str(x)
  853. self.writel(S_GEOM,4,'<float_array id="'+splineid+'-intangents-array" count="'+str(len(points))+'">'+intangent_values+'</float_array>')
  854. self.writel(S_GEOM,4,'<technique_common>')
  855. self.writel(S_GEOM,4,'<accessor source="#'+splineid+'-intangents-array" count="'+str(len(points)/3)+'" stride="3">')
  856. self.writel(S_GEOM,5,'<param name="X" type="float"/>')
  857. self.writel(S_GEOM,5,'<param name="Y" type="float"/>')
  858. self.writel(S_GEOM,5,'<param name="Z" type="float"/>')
  859. self.writel(S_GEOM,4,'</accessor>')
  860. self.writel(S_GEOM,3,'</source>')
  861. self.writel(S_GEOM,3,'<source id="'+splineid+'-outtangents">')
  862. outtangent_values=""
  863. for x in handles_out:
  864. outtangent_values+=" "+str(x)
  865. self.writel(S_GEOM,4,'<float_array id="'+splineid+'-outtangents-array" count="'+str(len(points))+'">'+outtangent_values+'</float_array>')
  866. self.writel(S_GEOM,4,'<technique_common>')
  867. self.writel(S_GEOM,4,'<accessor source="#'+splineid+'-outtangents-array" count="'+str(len(points)/3)+'" stride="3">')
  868. self.writel(S_GEOM,5,'<param name="X" type="float"/>')
  869. self.writel(S_GEOM,5,'<param name="Y" type="float"/>')
  870. self.writel(S_GEOM,5,'<param name="Z" type="float"/>')
  871. self.writel(S_GEOM,4,'</accessor>')
  872. self.writel(S_GEOM,3,'</source>')
  873. self.writel(S_GEOM,3,'<source id="'+splineid+'-interpolations">')
  874. interpolation_values=""
  875. for x in interps:
  876. interpolation_values+=" "+x
  877. self.writel(S_GEOM,4,'<Name_array id="'+splineid+'-interpolations-array" count="'+str(len(interps))+'">'+interpolation_values+'</Name_array>')
  878. self.writel(S_GEOM,4,'<technique_common>')
  879. self.writel(S_GEOM,4,'<accessor source="#'+splineid+'-interpolations-array" count="'+str(len(interps))+'" stride="1">')
  880. self.writel(S_GEOM,5,'<param name="INTERPOLATION" type="name"/>')
  881. self.writel(S_GEOM,4,'</accessor>')
  882. self.writel(S_GEOM,3,'</source>')
  883. self.writel(S_GEOM,3,'<source id="'+splineid+'-tilts">')
  884. tilt_values=""
  885. for x in tilts:
  886. tilt_values+=" "+str(x)
  887. self.writel(S_GEOM,4,'<float_array id="'+splineid+'-tilts-array" count="'+str(len(tilts))+'">'+tilt_values+'</float_array>')
  888. self.writel(S_GEOM,4,'<technique_common>')
  889. self.writel(S_GEOM,4,'<accessor source="#'+splineid+'-tilts-array" count="'+str(len(tilts))+'" stride="1">')
  890. self.writel(S_GEOM,5,'<param name="TILT" type="float"/>')
  891. self.writel(S_GEOM,4,'</accessor>')
  892. self.writel(S_GEOM,3,'</source>')
  893. self.writel(S_GEOM,3,'<control_vertices>')
  894. self.writel(S_GEOM,4,'<input semantic="POSITION" source="#'+splineid+'-positions"/>')
  895. self.writel(S_GEOM,4,'<input semantic="IN_TANGENT" source="#'+splineid+'-intangents"/>')
  896. self.writel(S_GEOM,4,'<input semantic="OUT_TANGENT" source="#'+splineid+'-outtangents"/>')
  897. self.writel(S_GEOM,4,'<input semantic="INTERPOLATION" source="#'+splineid+'-interpolations"/>')
  898. self.writel(S_GEOM,4,'<input semantic="TILT" source="#'+splineid+'-tilts"/>')
  899. self.writel(S_GEOM,3,'</control_vertices>')
  900. self.writel(S_GEOM,2,'</spline>')
  901. self.writel(S_GEOM,1,'</geometry>')
  902. return splineid
  903. def export_curve_node(self,node,il):
  904. if (node.data==None):
  905. return
  906. curveid = self.export_curve(node.data)
  907. self.writel(S_NODES,il,'<instance_geometry url="#'+curveid+'">')
  908. self.writel(S_NODES,il,'</instance_geometry>')
  909. def export_node(self,node,il):
  910. if (not self.is_node_valid(node)):
  911. return
  912. bpy.context.scene.objects.active = node
  913. self.writel(S_NODES,il,'<node id="'+self.validate_id(node.name)+'" name="'+node.name+'" type="NODE">')
  914. il+=1
  915. self.writel(S_NODES,il,'<matrix sid="transform">'+strmtx(node.matrix_local)+'</matrix>')
  916. print("NODE TYPE: "+node.type+" NAME: "+node.name)
  917. if (node.type=="MESH"):
  918. self.export_mesh_node(node,il)
  919. elif (node.type=="CURVE"):
  920. self.export_curve_node(node,il)
  921. elif (node.type=="ARMATURE"):
  922. self.export_armature_node(node,il)
  923. elif (node.type=="CAMERA"):
  924. self.export_camera_node(node,il)
  925. elif (node.type=="LAMP"):
  926. self.export_lamp_node(node,il)
  927. self.valid_nodes.append(node)
  928. for x in node.children:
  929. self.export_node(x,il)
  930. il-=1
  931. self.writel(S_NODES,il,'</node>')
  932. def is_node_valid(self,node):
  933. if (not node.type in self.config["object_types"]):
  934. return False
  935. if (self.config["use_active_layers"]):
  936. valid=False
  937. for i in range(20):
  938. if (node.layers[i] and self.scene.layers[i]):
  939. valid=True
  940. break
  941. if (not valid):
  942. return False
  943. if (self.config["use_export_selected"] and not node.select):
  944. return False
  945. return True
  946. def export_scene(self):
  947. self.writel(S_NODES,0,'<library_visual_scenes>')
  948. self.writel(S_NODES,1,'<visual_scene id="'+self.scene_name+'" name="scene">')
  949. for obj in self.scene.objects:
  950. if (obj.parent==None):
  951. self.export_node(obj,2)
  952. self.writel(S_NODES,1,'</visual_scene>')
  953. self.writel(S_NODES,0,'</library_visual_scenes>')
  954. def export_asset(self):
  955. self.writel(S_ASSET,0,'<asset>')
  956. # Why is this time stuff mandatory?, no one could care less...
  957. self.writel(S_ASSET,1,'<contributor>')
  958. self.writel(S_ASSET,2,'<author> Anonymous </author>') #Who made Collada, the FBI ?
  959. self.writel(S_ASSET,2,'<authoring_tool> Collada Exporter for Blender 2.6+, by Juan Linietsky ([email protected]) </authoring_tool>') #Who made Collada, the FBI ?
  960. self.writel(S_ASSET,1,'</contributor>')
  961. self.writel(S_ASSET,1,'<created>'+time.strftime("%Y-%m-%dT%H:%M:%SZ ")+'</created>')
  962. self.writel(S_ASSET,1,'<modified>'+time.strftime("%Y-%m-%dT%H:%M:%SZ")+'</modified>')
  963. self.writel(S_ASSET,1,'<unit meter="1.0" name="meter"/>')
  964. self.writel(S_ASSET,1,'<up_axis>Z_UP</up_axis>')
  965. self.writel(S_ASSET,0,'</asset>')
  966. def export_animation_transform_channel(self,target,keys,matrices=True):
  967. frame_total=len(keys)
  968. anim_id=self.new_id("anim")
  969. self.writel(S_ANIM,1,'<animation id="'+anim_id+'">')
  970. source_frames = ""
  971. source_transforms = ""
  972. source_interps = ""
  973. for k in keys:
  974. source_frames += " "+str(k[0])
  975. if (matrices):
  976. source_transforms += " "+strmtx(k[1])
  977. else:
  978. source_transforms += " "+str(k[1])
  979. source_interps +=" LINEAR"
  980. # Time Source
  981. self.writel(S_ANIM,2,'<source id="'+anim_id+'-input">')
  982. self.writel(S_ANIM,3,'<float_array id="'+anim_id+'-input-array" count="'+str(frame_total)+'">'+source_frames+'</float_array>')
  983. self.writel(S_ANIM,3,'<technique_common>')
  984. self.writel(S_ANIM,4,'<accessor source="#'+anim_id+'-input-array" count="'+str(frame_total)+'" stride="1">')
  985. self.writel(S_ANIM,5,'<param name="TIME" type="float"/>')
  986. self.writel(S_ANIM,4,'</accessor>')
  987. self.writel(S_ANIM,3,'</technique_common>')
  988. self.writel(S_ANIM,2,'</source>')
  989. if (matrices):
  990. # Transform Source
  991. self.writel(S_ANIM,2,'<source id="'+anim_id+'-transform-output">')
  992. self.writel(S_ANIM,3,'<float_array id="'+anim_id+'-transform-output-array" count="'+str(frame_total*16)+'">'+source_transforms+'</float_array>')
  993. self.writel(S_ANIM,3,'<technique_common>')
  994. self.writel(S_ANIM,4,'<accessor source="#'+anim_id+'-transform-output-array" count="'+str(frame_total)+'" stride="16">')
  995. self.writel(S_ANIM,5,'<param name="TRANSFORM" type="float4x4"/>')
  996. self.writel(S_ANIM,4,'</accessor>')
  997. self.writel(S_ANIM,3,'</technique_common>')
  998. self.writel(S_ANIM,2,'</source>')
  999. else:
  1000. # Value Source
  1001. self.writel(S_ANIM,2,'<source id="'+anim_id+'-transform-output">')
  1002. self.writel(S_ANIM,3,'<float_array id="'+anim_id+'-transform-output-array" count="'+str(frame_total)+'">'+source_transforms+'</float_array>')
  1003. self.writel(S_ANIM,3,'<technique_common>')
  1004. self.writel(S_ANIM,4,'<accessor source="#'+anim_id+'-transform-output-array" count="'+str(frame_total)+'" stride="1">')
  1005. self.writel(S_ANIM,5,'<param name="X" type="float"/>')
  1006. self.writel(S_ANIM,4,'</accessor>')
  1007. self.writel(S_ANIM,3,'</technique_common>')
  1008. self.writel(S_ANIM,2,'</source>')
  1009. # Interpolation Source
  1010. self.writel(S_ANIM,2,'<source id="'+anim_id+'-interpolation-output">')
  1011. self.writel(S_ANIM,3,'<Name_array id="'+anim_id+'-interpolation-output-array" count="'+str(frame_total)+'">'+source_interps+'</Name_array>')
  1012. self.writel(S_ANIM,3,'<technique_common>')
  1013. self.writel(S_ANIM,4,'<accessor source="#'+anim_id+'-interpolation-output-array" count="'+str(frame_total)+'" stride="1">')
  1014. self.writel(S_ANIM,5,'<param name="INTERPOLATION" type="Name"/>')
  1015. self.writel(S_ANIM,4,'</accessor>')
  1016. self.writel(S_ANIM,3,'</technique_common>')
  1017. self.writel(S_ANIM,2,'</source>')
  1018. self.writel(S_ANIM,2,'<sampler id="'+anim_id+'-sampler">')
  1019. self.writel(S_ANIM,3,'<input semantic="INPUT" source="#'+anim_id+'-input"/>')
  1020. self.writel(S_ANIM,3,'<input semantic="OUTPUT" source="#'+anim_id+'-transform-output"/>')
  1021. self.writel(S_ANIM,3,'<input semantic="INTERPOLATION" source="#'+anim_id+'-interpolation-output"/>')
  1022. self.writel(S_ANIM,2,'</sampler>')
  1023. if (matrices):
  1024. self.writel(S_ANIM,2,'<channel source="#'+anim_id+'-sampler" target="'+target+'/transform"/>')
  1025. else:
  1026. self.writel(S_ANIM,2,'<channel source="#'+anim_id+'-sampler" target="'+target+'"/>')
  1027. self.writel(S_ANIM,1,'</animation>')
  1028. return [anim_id]
  1029. def export_animation(self,start,end,allowed=None):
  1030. #Blender -> Collada frames needs a little work
  1031. #Collada starts from 0, blender usually from 1
  1032. #The last frame must be included also
  1033. frame_orig = self.scene.frame_current
  1034. frame_len = 1.0 / self.scene.render.fps
  1035. frame_total = end - start + 1
  1036. frame_sub = 0
  1037. if (start>0):
  1038. frame_sub=start*frame_len
  1039. tcn = []
  1040. xform_cache={}
  1041. blend_cache={}
  1042. # Change frames first, export objects last
  1043. # This improves performance enormously
  1044. print("anim from: "+str(start)+" to "+str(end)+" allowed: "+str(allowed))
  1045. for t in range(start,end+1):
  1046. self.scene.frame_set(t)
  1047. key = t * frame_len - frame_sub
  1048. # print("Export Anim Frame "+str(t)+"/"+str(self.scene.frame_end+1))
  1049. for node in self.scene.objects:
  1050. if (not node in self.valid_nodes):
  1051. continue
  1052. if (allowed!=None and not (node in allowed)):
  1053. if (node.type=="MESH" and node.data!=None and (node in self.armature_for_morph) and (self.armature_for_morph[node] in allowed)):
  1054. pass #all good you pass with flying colors for morphs inside of action
  1055. else:
  1056. continue
  1057. if (node.type=="MESH" and node.data!=None and node.data.shape_keys!=None and (node.data in self.mesh_cache) and len(node.data.shape_keys.key_blocks)):
  1058. target = self.mesh_cache[node.data]["morph_id"]
  1059. for i in range(len(node.data.shape_keys.key_blocks)):
  1060. if (i==0):
  1061. continue
  1062. name=target+"-morph-weights("+str(i-1)+")"
  1063. if (not (name in blend_cache)):
  1064. blend_cache[name]=[]
  1065. blend_cache[name].append( (key,node.data.shape_keys.key_blocks[i].value) )
  1066. if (node.type=="MESH" and node.parent and node.parent.type=="ARMATURE"):
  1067. continue #In Collada, nodes that have skin modifier must not export animation, animate the skin instead.
  1068. if (len(node.constraints)>0 or node.animation_data!=None):
  1069. #If the node has constraints, or animation data, then export a sampled animation track
  1070. name=self.validate_id(node.name)
  1071. if (not (name in xform_cache)):
  1072. xform_cache[name]=[]
  1073. mtx = node.matrix_world.copy()
  1074. if (node.parent):
  1075. mtx = node.parent.matrix_world.inverted() * mtx
  1076. xform_cache[name].append( (key,mtx) )
  1077. if (node.type=="ARMATURE"):
  1078. #All bones exported for now
  1079. for bone in node.data.bones:
  1080. bone_name=self.skeleton_info[node]["bone_ids"][bone]
  1081. if (not (bone_name in xform_cache)):
  1082. print("has bone: "+bone_name)
  1083. xform_cache[bone_name]=[]
  1084. posebone = node.pose.bones[bone.name]
  1085. parent_posebone=None
  1086. mtx = posebone.matrix.copy()
  1087. if (bone.parent):
  1088. parent_posebone=node.pose.bones[bone.parent.name]
  1089. parent_invisible=False
  1090. for i in range(3):
  1091. if (parent_posebone.scale[i]==0.0):
  1092. parent_invisible=True
  1093. if (not parent_invisible):
  1094. mtx = parent_posebone.matrix.inverted() * mtx
  1095. xform_cache[bone_name].append( (key,mtx) )
  1096. self.scene.frame_set(frame_orig)
  1097. #export animation xml
  1098. for nid in xform_cache:
  1099. tcn+=self.export_animation_transform_channel(nid,xform_cache[nid],True)
  1100. for nid in blend_cache:
  1101. tcn+=self.export_animation_transform_channel(nid,blend_cache[nid],False)
  1102. return tcn
  1103. def export_animations(self):
  1104. self.writel(S_ANIM,0,'<library_animations>')
  1105. if (self.config["use_anim_action_all"] and len(self.skeletons)):
  1106. cached_actions = {}
  1107. for s in self.skeletons:
  1108. if s.animation_data and s.animation_data.action:
  1109. cached_actions[s] = s.animation_data.action.name
  1110. self.writel(S_ANIM_CLIPS,0,'<library_animation_clips>')
  1111. for x in bpy.data.actions[:]:
  1112. if x.users==0 or x in self.action_constraints:
  1113. continue
  1114. if (self.config["use_anim_skip_noexp"] and x.name.endswith("-noexp")):
  1115. continue
  1116. bones=[]
  1117. #find bones used
  1118. for p in x.fcurves:
  1119. dp = str(p.data_path)
  1120. base = "pose.bones[\""
  1121. if (dp.find(base)==0):
  1122. dp=dp[len(base):]
  1123. if (dp.find('"')!=-1):
  1124. dp=dp[:dp.find('"')]
  1125. if (not dp in bones):
  1126. bones.append(dp)
  1127. allowed_skeletons=[]
  1128. for y in self.skeletons:
  1129. if (y.animation_data):
  1130. for z in y.pose.bones:
  1131. if (z.bone.name in bones):
  1132. if (not y in allowed_skeletons):
  1133. allowed_skeletons.append(y)
  1134. y.animation_data.action=x;
  1135. print(str(x))
  1136. tcn = self.export_animation(int(x.frame_range[0]),int(x.frame_range[1]),allowed_skeletons)
  1137. framelen=(1.0/self.scene.render.fps)
  1138. start = x.frame_range[0]*framelen
  1139. end = x.frame_range[1]*framelen
  1140. print("Export anim: "+x.name)
  1141. self.writel(S_ANIM_CLIPS,1,'<animation_clip name="'+x.name+'" start="'+str(start)+'" end="'+str(end)+'">')
  1142. for z in tcn:
  1143. self.writel(S_ANIM_CLIPS,2,'<instance_animation url="#'+z+'"/>')
  1144. self.writel(S_ANIM_CLIPS,1,'</animation_clip>')
  1145. self.writel(S_ANIM_CLIPS,0,'</library_animation_clips>')
  1146. for s in self.skeletons:
  1147. if (s.animation_data==None):
  1148. continue
  1149. if s in cached_actions:
  1150. s.animation_data.action = bpy.data.actions[cached_actions[s]]
  1151. else:
  1152. s.animation_data.action = None
  1153. else:
  1154. self.export_animation(self.scene.frame_start,self.scene.frame_end)
  1155. self.writel(S_ANIM,0,'</library_animations>')
  1156. def export(self):
  1157. self.writel(S_GEOM,0,'<library_geometries>')
  1158. self.writel(S_CONT,0,'<library_controllers>')
  1159. self.writel(S_CAMS,0,'<library_cameras>')
  1160. self.writel(S_LAMPS,0,'<library_lights>')
  1161. self.writel(S_IMGS,0,'<library_images>')
  1162. self.writel(S_MATS,0,'<library_materials>')
  1163. self.writel(S_FX,0,'<library_effects>')
  1164. self.skeletons=[]
  1165. self.action_constraints=[]
  1166. self.export_asset()
  1167. self.export_scene()
  1168. self.writel(S_GEOM,0,'</library_geometries>')
  1169. #morphs always go before skin controllers
  1170. if S_MORPH in self.sections:
  1171. for l in self.sections[S_MORPH]:
  1172. self.writel(S_CONT,0,l)
  1173. del self.sections[S_MORPH]
  1174. #morphs always go before skin controllers
  1175. if S_SKIN in self.sections:
  1176. for l in self.sections[S_SKIN]:
  1177. self.writel(S_CONT,0,l)
  1178. del self.sections[S_SKIN]
  1179. self.writel(S_CONT,0,'</library_controllers>')
  1180. self.writel(S_CAMS,0,'</library_cameras>')
  1181. self.writel(S_LAMPS,0,'</library_lights>')
  1182. self.writel(S_IMGS,0,'</library_images>')
  1183. self.writel(S_MATS,0,'</library_materials>')
  1184. self.writel(S_FX,0,'</library_effects>')
  1185. if (self.config["use_anim"]):
  1186. self.export_animations()
  1187. try:
  1188. f = open(self.path,"wb")
  1189. except:
  1190. return False
  1191. f.write(bytes('<?xml version="1.0" encoding="utf-8"?>\n',"UTF-8"))
  1192. f.write(bytes('<COLLADA xmlns="http://www.collada.org/2005/11/COLLADASchema" version="1.4.1">\n',"UTF-8"))
  1193. s=[]
  1194. for x in self.sections.keys():
  1195. s.append(x)
  1196. s.sort()
  1197. for x in s:
  1198. for l in self.sections[x]:
  1199. f.write(bytes(l+"\n","UTF-8"))
  1200. f.write(bytes('<scene>\n',"UTF-8"))
  1201. f.write(bytes('\t<instance_visual_scene url="#'+self.scene_name+'" />\n',"UTF-8"))
  1202. f.write(bytes('</scene>\n',"UTF-8"))
  1203. f.write(bytes('</COLLADA>\n',"UTF-8"))
  1204. return True
  1205. def __init__(self,path,kwargs):
  1206. self.scene=bpy.context.scene
  1207. self.last_id=0
  1208. self.scene_name=self.new_id("scene")
  1209. self.sections={}
  1210. self.path=path
  1211. self.mesh_cache={}
  1212. self.curve_cache={}
  1213. self.material_cache={}
  1214. self.image_cache={}
  1215. self.skeleton_info={}
  1216. self.config=kwargs
  1217. self.valid_nodes=[]
  1218. self.armature_for_morph={}
  1219. def save(operator, context,
  1220. filepath="",
  1221. use_selection=False,
  1222. **kwargs
  1223. ):
  1224. exp = DaeExporter(filepath,kwargs)
  1225. exp.export()
  1226. return {'FINISHED'} # so the script wont run after we have batch exported.