export_dae.py 39 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244
  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. from mathutils import Vector, Matrix
  41. #according to collada spec, order matters
  42. S_ASSET=0
  43. S_IMGS=1
  44. S_FX=2
  45. S_MATS=3
  46. S_GEOM=4
  47. S_CONT=5
  48. S_CAMS=6
  49. S_LAMPS=7
  50. S_ANIM_CLIPS=8
  51. S_NODES=9
  52. S_ANIM=10
  53. CMP_EPSILON=0.0001
  54. def snap_tup(tup):
  55. ret=()
  56. for x in tup:
  57. ret+=( x-math.fmod(x,0.0001), )
  58. return tup
  59. def strmtx(mtx):
  60. s=" "
  61. for x in range(4):
  62. for y in range(4):
  63. s+=str(mtx[x][y])
  64. s+=" "
  65. s+=" "
  66. return s
  67. def numarr(a,mult=1.0):
  68. s=" "
  69. for x in a:
  70. s+=" "+str(x*mult)
  71. s+=" "
  72. return s
  73. def strarr(arr):
  74. s=" "
  75. for x in arr:
  76. s+=" "+str(x)
  77. s+=" "
  78. return s
  79. class DaeExporter:
  80. def validate_id(self,d):
  81. if (d.find("id-")==0):
  82. return "z"+d
  83. return d
  84. def new_id(self,t):
  85. self.last_id+=1
  86. return "id-"+t+"-"+str(self.last_id)
  87. class Vertex:
  88. def close_to(v):
  89. if ( (self.vertex-v.vertex).length() > CMP_EPSILON ):
  90. return False
  91. if ( (self.normal-v.normal).length() > CMP_EPSILON ):
  92. return False
  93. if ( (self.uv-v.uv).length() > CMP_EPSILON ):
  94. return False
  95. if ( (self.uv2-v.uv2).length() > CMP_EPSILON ):
  96. return False
  97. return True
  98. def get_tup(self):
  99. tup = (self.vertex.x,self.vertex.y,self.vertex.z,self.normal.x,self.normal.y,self.normal.z)
  100. for t in self.uv:
  101. tup = tup + (t.x,t.y)
  102. return tup
  103. def __init__(self):
  104. self.vertex = Vector( (0.0,0.0,0.0) )
  105. self.normal = Vector( (0.0,0.0,0.0) )
  106. self.color = Vector( (0.0,0.0,0.0) )
  107. self.uv = []
  108. self.uv2 = Vector( (0.0,0.0) )
  109. self.bones=[]
  110. self.weights=[]
  111. def writel(self,section,indent,text):
  112. if (not (section in self.sections)):
  113. self.sections[section]=[]
  114. line=""
  115. for x in range(indent):
  116. line+="\t"
  117. line+=text
  118. self.sections[section].append(line)
  119. def export_image(self,image):
  120. if (image in self.image_cache):
  121. return self.image_cache[image]
  122. imgpath = image.filepath
  123. if (imgpath.find("//")==0 or imgpath.find("\\\\")==0):
  124. #if relative, convert to absolute
  125. imgpath = bpy.path.abspath(imgpath)
  126. #path is absolute, now do something!
  127. if (self.config["use_copy_images"]):
  128. #copy image
  129. basedir = os.path.dirname(self.path)+"/images"
  130. if (not os.path.isdir(basedir)):
  131. os.makedirs(basedir)
  132. dstfile=basedir+"/"+os.path.basename(imgpath)
  133. if (not os.path.isfile(dstfile)):
  134. shutil.copy(imgpath,dstfile)
  135. imgpath="images/"+os.path.basename(imgpath)
  136. else:
  137. #export relative, always, no one wants absolute paths.
  138. imgpath = os.path.relpath(imgpath,os.path.dirname(self.path)).replace("\\","/") # export unix compatible always
  139. imgid = self.new_id("image")
  140. self.writel(S_IMGS,1,'<image id="'+imgid+'" name="'+image.name+'">')
  141. self.writel(S_IMGS,2,'<init_from>'+imgpath+'</init_from>"/>')
  142. self.writel(S_IMGS,1,'</image>')
  143. self.image_cache[image]=imgid
  144. return imgid
  145. def export_material(self,material,double_sided_hint=True):
  146. if (material in self.material_cache):
  147. return self.material_cache[material]
  148. fxid = self.new_id("fx")
  149. self.writel(S_FX,1,'<effect id="'+fxid+'" name="'+material.name+'-fx">')
  150. self.writel(S_FX,2,'<profile_COMMON>')
  151. #Find and fetch the textures and create sources
  152. sampler_table={}
  153. diffuse_tex=None
  154. specular_tex=None
  155. emission_tex=None
  156. normal_tex=None
  157. for i in range(len(material.texture_slots)):
  158. ts=material.texture_slots[i]
  159. if (not ts):
  160. continue
  161. if (not ts.use):
  162. continue
  163. if (not ts.texture):
  164. continue
  165. if (ts.texture.type!="IMAGE"):
  166. continue
  167. if (ts.texture.image==None):
  168. continue
  169. #image
  170. imgid = self.export_image(ts.texture.image)
  171. #surface
  172. surface_sid = self.new_id("fx_surf")
  173. self.writel(S_FX,3,'<newparam sid="'+surface_sid+'">')
  174. self.writel(S_FX,4,'<surface type="2D">')
  175. self.writel(S_FX,5,'<init_from>'+imgid+'</init_from>') #this is sooo weird
  176. self.writel(S_FX,5,'<format>A8R8G8B8</format>')
  177. self.writel(S_FX,4,'</surface>')
  178. self.writel(S_FX,3,'</newparam>')
  179. #sampler, collada sure likes it difficult
  180. sampler_sid = self.new_id("fx_sampler")
  181. self.writel(S_FX,3,'<newparam sid="'+sampler_sid+'">')
  182. self.writel(S_FX,4,'<sampler2D>')
  183. self.writel(S_FX,5,'<source>'+surface_sid+'</source>')
  184. self.writel(S_FX,4,'</sampler2D>')
  185. self.writel(S_FX,3,'</newparam>')
  186. sampler_table[i]=sampler_sid
  187. if (ts.use_map_color_diffuse and diffuse_tex==None):
  188. diffuse_tex=sampler_sid
  189. if (ts.use_map_color_spec and specular_tex==None):
  190. specular_tex=sampler_sid
  191. if (ts.use_map_emit and emission_tex==None):
  192. emission_tex=sampler_sid
  193. if (ts.use_map_normal and normal_tex==None):
  194. normal_tex=sampler_sid
  195. self.writel(S_FX,3,'<technique sid="common">')
  196. shtype="blinn"
  197. self.writel(S_FX,4,'<'+shtype+'>')
  198. #ambient? from where?
  199. self.writel(S_FX,5,'<emission>')
  200. if (emission_tex!=None):
  201. self.writel(S_FX,6,'<texture texture="'+emission_tex+'" texcoord="CHANNEL1"/>')
  202. else:
  203. self.writel(S_FX,6,'<color>'+numarr(material.diffuse_color,material.emit)+' </color>') # not totally right but good enough
  204. self.writel(S_FX,5,'</emission>')
  205. self.writel(S_FX,5,'<ambient>')
  206. self.writel(S_FX,6,'<color>'+numarr(self.scene.world.ambient_color,material.ambient)+' </color>')
  207. self.writel(S_FX,5,'</ambient>')
  208. self.writel(S_FX,5,'<diffuse>')
  209. if (diffuse_tex!=None):
  210. self.writel(S_FX,6,'<texture texture="'+diffuse_tex+'" texcoord="CHANNEL1"/>')
  211. else:
  212. self.writel(S_FX,6,'<color>'+numarr(material.diffuse_color,material.diffuse_intensity)+'</color>')
  213. self.writel(S_FX,5,'</diffuse>')
  214. self.writel(S_FX,5,'<specular>')
  215. if (specular_tex!=None):
  216. self.writel(S_FX,6,'<texture texture="'+specular_tex+'" texcoord="CHANNEL1"/>')
  217. else:
  218. self.writel(S_FX,6,'<color>'+numarr(material.specular_color,material.specular_intensity)+'</color>')
  219. self.writel(S_FX,5,'</specular>')
  220. self.writel(S_FX,5,'<shininess>')
  221. self.writel(S_FX,6,'<float>'+str(material.specular_hardness)+'</float>')
  222. self.writel(S_FX,5,'</shininess>')
  223. self.writel(S_FX,5,'<reflective>')
  224. self.writel(S_FX,6,'<color>'+strarr(material.mirror_color)+'</color>')
  225. self.writel(S_FX,5,'</reflective>')
  226. if (material.use_transparency):
  227. self.writel(S_FX,5,'<transparency>')
  228. self.writel(S_FX,6,'<float>'+str(material.alpha)+'</float>')
  229. self.writel(S_FX,5,'</transparency>')
  230. self.writel(S_FX,5,'<index_of_refraction>'+str(material.specular_ior)+'</index_of_refraction>')
  231. self.writel(S_FX,4,'</'+shtype+'>')
  232. self.writel(S_FX,4,'<extra>')
  233. self.writel(S_FX,5,'<technique profile="FCOLLADA">')
  234. if (normal_tex):
  235. self.writel(S_FX,6,'<bump bumptype="NORMALMAP">')
  236. self.writel(S_FX,7,'<texture texture="'+normal_tex+'" texcoord="CHANNEL1"/>')
  237. self.writel(S_FX,6,'</bump>')
  238. self.writel(S_FX,5,'</technique>')
  239. self.writel(S_FX,5,'<technique profile="GOOGLEEARTH">')
  240. self.writel(S_FX,6,'<double_sided>'+["0","1"][double_sided_hint]+"</double_sided>")
  241. self.writel(S_FX,5,'</technique>')
  242. self.writel(S_FX,4,'</extra>')
  243. self.writel(S_FX,3,'</technique>')
  244. self.writel(S_FX,2,'</profile_COMMON>')
  245. self.writel(S_FX,1,'</effect>')
  246. # Also export blender material in all it's glory (if set as active)
  247. #Material
  248. matid = self.new_id("material")
  249. self.writel(S_MATS,1,'<material id="'+matid+'" name="'+material.name+'">')
  250. self.writel(S_MATS,2,'<instance_effect url="#'+fxid+'"/>')
  251. self.writel(S_MATS,1,'</material>')
  252. self.material_cache[material]=matid
  253. return matid
  254. def export_mesh(self,node,armature=None):
  255. if (len(node.modifiers) and self.config["use_mesh_modifiers"]):
  256. mesh=node.to_mesh(self.scene,True,"RENDER") #is this allright?
  257. else:
  258. mesh=node.data
  259. if (mesh in self.mesh_cache):
  260. return self.mesh_cache[mesh]
  261. mesh.update(calc_tessface=True)
  262. vertices=[]
  263. vertex_map={}
  264. surface_indices={}
  265. materials={}
  266. materials={}
  267. si=None
  268. if (armature!=None):
  269. si=self.skeleton_info[armature]
  270. has_uv=False
  271. has_uv2=False
  272. has_weights=armature!=None
  273. has_colors=False
  274. mat_assign=[]
  275. uv_layer_count=len(mesh.uv_textures)
  276. for fi in range(len(mesh.tessfaces)):
  277. f=mesh.tessfaces[fi]
  278. if (not (f.material_index in surface_indices)):
  279. surface_indices[f.material_index]=[]
  280. print("Type: "+str(type(f.material_index)))
  281. print("IDX: "+str(f.material_index)+"/"+str(len(mesh.materials)))
  282. try:
  283. #Bizarre blender behavior i don't understand, so catching exception
  284. mat = mesh.materials[f.material_index]
  285. except:
  286. mat= None
  287. if (mat!=None):
  288. materials[f.material_index]=self.export_material( mat,mesh.show_double_sided )
  289. else:
  290. materials[f.material_index]=None #weird, has no material?
  291. indices = surface_indices[f.material_index]
  292. vi=[]
  293. #make triangles always
  294. if (len(f.vertices)==3):
  295. vi.append(0)
  296. vi.append(1)
  297. vi.append(2)
  298. elif (len(f.vertices)==4):
  299. vi.append(0)
  300. vi.append(1)
  301. vi.append(2)
  302. vi.append(0)
  303. vi.append(2)
  304. vi.append(3)
  305. for x in vi:
  306. mv = mesh.vertices[f.vertices[x]]
  307. v = self.Vertex()
  308. v.vertex = Vector( mv.co )
  309. for xt in mesh.tessface_uv_textures:
  310. d = xt.data[fi]
  311. uvsrc = [d.uv1,d.uv2,d.uv3,d.uv4]
  312. v.uv.append( Vector( uvsrc[x] ) )
  313. if (f.use_smooth):
  314. v.normal=Vector( mv.normal )
  315. else:
  316. v.normal=Vector( f.normal )
  317. # if (armature):
  318. # v.vertex = node.matrix_world * v.vertex
  319. #v.color=Vertex(mv. ???
  320. if (armature!=None):
  321. wsum=0.0
  322. for vg in mv.groups:
  323. if vg.group >= len(node.vertex_groups):
  324. continue;
  325. name = node.vertex_groups[vg.group].name
  326. if (name in si["bone_index"]):
  327. if (vg.weight>0.001): #blender has a lot of zero weight stuff
  328. v.bones.append(si["bone_index"][name])
  329. v.weights.append(vg.weight)
  330. wsum+=vg.weight
  331. tup = v.get_tup()
  332. idx = 0
  333. if (tup in vertex_map):
  334. idx = vertex_map[tup]
  335. else:
  336. idx = len(vertices)
  337. vertices.append(v)
  338. vertex_map[tup]=idx
  339. indices.append(idx)
  340. meshid = self.new_id("mesh")
  341. self.writel(S_GEOM,1,'<geometry id="'+meshid+'" name="'+mesh.name+'">')
  342. self.writel(S_GEOM,2,'<mesh>')
  343. # Vertex Array
  344. self.writel(S_GEOM,3,'<source id="'+meshid+'-positions">')
  345. float_values=""
  346. for v in vertices:
  347. float_values+=" "+str(v.vertex.x)+" "+str(v.vertex.y)+" "+str(v.vertex.z)
  348. self.writel(S_GEOM,4,'<float_array id="'+meshid+'-positions-array" count="'+str(len(vertices)*3)+'">'+float_values+'</float_array>')
  349. self.writel(S_GEOM,4,'<technique_common>')
  350. self.writel(S_GEOM,4,'<accessor source="#'+meshid+'-positions-array" count="'+str(len(vertices))+'" stride="3">')
  351. self.writel(S_GEOM,5,'<param name="X" type="float"/>')
  352. self.writel(S_GEOM,5,'<param name="Y" type="float"/>')
  353. self.writel(S_GEOM,5,'<param name="Z" type="float"/>')
  354. self.writel(S_GEOM,4,'</accessor>')
  355. self.writel(S_GEOM,4,'</technique_common>')
  356. self.writel(S_GEOM,3,'</source>')
  357. # Normal Array
  358. self.writel(S_GEOM,3,'<source id="'+meshid+'-normals">')
  359. float_values=""
  360. for v in vertices:
  361. float_values+=" "+str(v.normal.x)+" "+str(v.normal.y)+" "+str(v.normal.z)
  362. self.writel(S_GEOM,4,'<float_array id="'+meshid+'-normals-array" count="'+str(len(vertices)*3)+'">'+float_values+'</float_array>')
  363. self.writel(S_GEOM,4,'<technique_common>')
  364. self.writel(S_GEOM,4,'<accessor source="#'+meshid+'-normals-array" count="'+str(len(vertices))+'" stride="3">')
  365. self.writel(S_GEOM,5,'<param name="X" type="float"/>')
  366. self.writel(S_GEOM,5,'<param name="Y" type="float"/>')
  367. self.writel(S_GEOM,5,'<param name="Z" type="float"/>')
  368. self.writel(S_GEOM,4,'</accessor>')
  369. self.writel(S_GEOM,4,'</technique_common>')
  370. self.writel(S_GEOM,3,'</source>')
  371. # UV Arrays
  372. for uvi in range(uv_layer_count):
  373. self.writel(S_GEOM,3,'<source id="'+meshid+'-texcoord-'+str(uvi)+'">')
  374. float_values=""
  375. for v in vertices:
  376. float_values+=" "+str(v.uv[uvi].x)+" "+str(v.uv[uvi].y)
  377. self.writel(S_GEOM,4,'<float_array id="'+meshid+'-texcoord-'+str(uvi)+'-array" count="'+str(len(vertices)*2)+'">'+float_values+'</float_array>')
  378. self.writel(S_GEOM,4,'<technique_common>')
  379. self.writel(S_GEOM,4,'<accessor source="#'+meshid+'-texcoord-'+str(uvi)+'-array" count="'+str(len(vertices))+'" stride="2">')
  380. self.writel(S_GEOM,5,'<param name="S" type="float"/>')
  381. self.writel(S_GEOM,5,'<param name="T" type="float"/>')
  382. self.writel(S_GEOM,4,'</accessor>')
  383. self.writel(S_GEOM,4,'</technique_common>')
  384. self.writel(S_GEOM,3,'</source>')
  385. # Triangle Lists
  386. self.writel(S_GEOM,3,'<vertices id="'+meshid+'-vertices">')
  387. self.writel(S_GEOM,4,'<input semantic="POSITION" source="#'+meshid+'-positions"/>')
  388. self.writel(S_GEOM,3,'</vertices>')
  389. for m in surface_indices:
  390. indices = surface_indices[m]
  391. mat = materials[m]
  392. if (mat!=None):
  393. matref = self.new_id("trimat")
  394. self.writel(S_GEOM,3,'<triangles count="'+str(int(len(indices)/3))+'" material="'+matref+'">') # todo material
  395. mat_assign.append( (mat,matref) )
  396. else:
  397. self.writel(S_GEOM,3,'<triangles count="'+str(int(len(indices)/3))+'">') # todo material
  398. self.writel(S_GEOM,4,'<input semantic="VERTEX" source="#'+meshid+'-vertices" offset="0"/>')
  399. self.writel(S_GEOM,4,'<input semantic="NORMAL" source="#'+meshid+'-normals" offset="1"/>')
  400. extra_indices=0
  401. for uvi in range(uv_layer_count):
  402. self.writel(S_GEOM,4,'<input semantic="TEXCOORD" source="#'+meshid+'-texcoord-'+str(uvi)+'" offset="'+str(2+uvi)+'" set="'+str(uvi)+'"/>')
  403. extra_indices+=1
  404. int_values="<p>"
  405. for i in range(len(indices)):
  406. int_values+=" "+str(indices[i]) # vertex index
  407. int_values+=" "+str(indices[i]) # normal index
  408. for e in range(extra_indices):
  409. int_values+=" "+str(indices[i]) # normal index
  410. int_values+="</p>"
  411. self.writel(S_GEOM,4,int_values)
  412. self.writel(S_GEOM,3,'</triangles>')
  413. self.writel(S_GEOM,2,'</mesh>')
  414. self.writel(S_GEOM,1,'</geometry>')
  415. meshdata={}
  416. meshdata["id"]=meshid
  417. meshdata["material_assign"]=mat_assign
  418. self.mesh_cache[mesh]=meshdata
  419. # Export armature data (if armature exists)
  420. if (armature!=None):
  421. contid = self.new_id("controller")
  422. self.writel(S_CONT,1,'<controller id="'+contid+'">')
  423. self.writel(S_CONT,2,'<skin source="'+meshid+'">')
  424. self.writel(S_CONT,3,'<bind_shape_matrix>'+strmtx(node.matrix_world)+'</bind_shape_matrix>')
  425. #Joint Names
  426. self.writel(S_CONT,3,'<source id="'+contid+'-joints">')
  427. name_values=""
  428. for v in si["bone_names"]:
  429. name_values+=" "+v
  430. self.writel(S_CONT,4,'<Name_array id="'+contid+'-joints-array" count="'+str(len(si["bone_names"]))+'">'+name_values+'</Name_array>')
  431. self.writel(S_CONT,4,'<technique_common>')
  432. self.writel(S_CONT,4,'<accessor source="#'+contid+'-joints-array" count="'+str(len(si["bone_names"]))+'" stride="1">')
  433. self.writel(S_CONT,5,'<param name="JOINT" type="Name"/>')
  434. self.writel(S_CONT,4,'</accessor>')
  435. self.writel(S_CONT,4,'</technique_common>')
  436. self.writel(S_CONT,3,'</source>')
  437. #Pose Matrices!
  438. self.writel(S_CONT,3,'<source id="'+contid+'-bind_poses">')
  439. pose_values=""
  440. for v in si["bone_bind_poses"]:
  441. pose_values+=" "+strmtx(v)
  442. self.writel(S_CONT,4,'<float_array id="'+contid+'-bind_poses-array" count="'+str(len(si["bone_bind_poses"])*16)+'">'+pose_values+'</float_array>')
  443. self.writel(S_CONT,4,'<technique_common>')
  444. self.writel(S_CONT,4,'<accessor source="#'+contid+'-bind_poses-array" count="'+str(len(si["bone_bind_poses"]))+'" stride="16">')
  445. self.writel(S_CONT,5,'<param name="TRANSFORM" type="float4x4"/>')
  446. self.writel(S_CONT,4,'</accessor>')
  447. self.writel(S_CONT,4,'</technique_common>')
  448. self.writel(S_CONT,3,'</source>')
  449. #Skin Weights!
  450. self.writel(S_CONT,3,'<source id="'+contid+'-skin_weights">')
  451. skin_weights=""
  452. skin_weights_total=0
  453. for v in vertices:
  454. skin_weights_total+=len(v.weights)
  455. for w in v.weights:
  456. skin_weights+=" "+str(w)
  457. self.writel(S_CONT,4,'<float_array id="'+contid+'-skin_weights-array" count="'+str(skin_weights_total)+'">'+skin_weights+'</float_array>')
  458. self.writel(S_CONT,4,'<technique_common>')
  459. self.writel(S_CONT,4,'<accessor source="#'+contid+'-skin_weights-array" count="'+str(skin_weights_total)+'" stride="1">')
  460. self.writel(S_CONT,5,'<param name="WEIGHT" type="float"/>')
  461. self.writel(S_CONT,4,'</accessor>')
  462. self.writel(S_CONT,4,'</technique_common>')
  463. self.writel(S_CONT,3,'</source>')
  464. self.writel(S_CONT,3,'<joints>')
  465. self.writel(S_CONT,4,'<input semantic="JOINT" source="#'+contid+'-joints"/>')
  466. self.writel(S_CONT,4,'<input semantic="INV_BIND_MATRIX" source="#'+contid+'-bind_poses"/>')
  467. self.writel(S_CONT,3,'</joints>')
  468. self.writel(S_CONT,3,'<vertex_weights count="'+str(len(vertices))+'">')
  469. self.writel(S_CONT,4,'<input semantic="JOINT" source="#'+contid+'-joints" offset="0"/>')
  470. self.writel(S_CONT,4,'<input semantic="WEIGHT" source="#'+contid+'-skin_weights" offset="1"/>')
  471. vcounts=""
  472. vs=""
  473. vcount=0
  474. for v in vertices:
  475. vcounts+=" "+str(len(v.weights))
  476. for b in v.bones:
  477. vs+=" "+str(b)
  478. vs+=" "+str(vcount)
  479. vcount+=1
  480. self.writel(S_CONT,4,'<vcount>'+vcounts+'</vcount>')
  481. self.writel(S_CONT,4,'<v>'+vs+'</v>')
  482. self.writel(S_CONT,3,'</vertex_weights>')
  483. self.writel(S_CONT,2,'</skin>')
  484. self.writel(S_CONT,1,'</controller>')
  485. meshdata["skin_id"]=contid
  486. return meshdata
  487. def export_mesh_node(self,node,il):
  488. if (node.data==None):
  489. return
  490. armature=None
  491. if (node.parent!=None):
  492. if (node.parent.type=="ARMATURE"):
  493. armature=node.parent
  494. meshdata = self.export_mesh(node,armature)
  495. if (armature==None):
  496. self.writel(S_NODES,il,'<instance_geometry url="#'+meshdata["id"]+'">')
  497. else:
  498. self.writel(S_NODES,il,'<instance_controller url="#'+meshdata["skin_id"]+'">')
  499. for sn in self.skeleton_info[armature]["skeleton_nodes"]:
  500. self.writel(S_NODES,il+1,'<skeleton>#'+sn+'</skeleton>')
  501. if (len(meshdata["material_assign"])>0):
  502. self.writel(S_NODES,il+1,'<bind_material>')
  503. self.writel(S_NODES,il+2,'<technique_common>')
  504. for m in meshdata["material_assign"]:
  505. self.writel(S_NODES,il+3,'<instance_material symbol="'+m[1]+'" target="#'+m[0]+'"/>')
  506. self.writel(S_NODES,il+2,'</technique_common>')
  507. self.writel(S_NODES,il+1,'</bind_material>')
  508. if (armature==None):
  509. self.writel(S_NODES,il,'</instance_geometry>')
  510. else:
  511. self.writel(S_NODES,il,'</instance_controller>')
  512. def export_armature_bone(self,bone,il,si):
  513. boneid = self.new_id("bone")
  514. boneidx = si["bone_count"]
  515. si["bone_count"]+=1
  516. bonesid = si["name"]+"-"+str(boneidx)
  517. si["bone_index"][bone.name]=boneidx
  518. si["bone_ids"][bone]=boneid
  519. si["bone_names"].append(bonesid)
  520. self.writel(S_NODES,il,'<node id="'+boneid+'" sid="'+bonesid+'" name="'+bone.name+'" type="JOINT">')
  521. il+=1
  522. xform = bone.matrix_local
  523. si["bone_bind_poses"].append((si["armature_xform"] * xform).inverted())
  524. if (bone.parent!=None):
  525. xform = bone.parent.matrix_local.inverted() * xform
  526. else:
  527. si["skeleton_nodes"].append(boneid)
  528. self.writel(S_NODES,il,'<matrix sid="transform">'+strmtx(xform)+'</matrix>')
  529. for c in bone.children:
  530. self.export_armature_bone(c,il,si)
  531. il-=1
  532. self.writel(S_NODES,il,'</node>')
  533. def export_armature_node(self,node,il):
  534. if (node.data==None):
  535. return
  536. self.skeletons.append(node)
  537. armature = node.data
  538. self.skeleton_info[node]={ "bone_count":0, "name":node.name, "bone_index":{},"bone_ids":{},"bone_names":[],"bone_bind_poses":[],"skeleton_nodes":[],"armature_xform":node.matrix_world }
  539. for b in armature.bones:
  540. if (b.parent!=None):
  541. continue
  542. self.export_armature_bone(b,il,self.skeleton_info[node])
  543. if (node.pose):
  544. for b in node.pose.bones:
  545. for x in b.constraints:
  546. if (x.type=='ACTION'):
  547. self.action_constraints.append(x.action)
  548. def export_camera_node(self,node,il):
  549. if (node.data==None):
  550. return
  551. camera=node.data
  552. camid=self.new_id("camera")
  553. self.writel(S_CAMS,1,'<camera id="'+camid+'" name="'+camera.name+'">')
  554. self.writel(S_CAMS,2,'<optics>')
  555. self.writel(S_CAMS,3,'<technique_common>')
  556. if (camera.type=="PERSP"):
  557. self.writel(S_CAMS,4,'<perspective>')
  558. self.writel(S_CAMS,5,'<yfov> '+str(math.degrees(camera.angle))+' </yfov>') # I think?
  559. self.writel(S_CAMS,5,'<aspect_ratio> '+str(self.scene.render.resolution_x / self.scene.render.resolution_y)+' </aspect_ratio>')
  560. self.writel(S_CAMS,5,'<znear> '+str(camera.clip_start)+' </znear>')
  561. self.writel(S_CAMS,5,'<zfar> '+str(camera.clip_end)+' </zfar>')
  562. self.writel(S_CAMS,4,'</perspective>')
  563. else:
  564. self.writel(S_CAMS,4,'<orthografic>')
  565. self.writel(S_CAMS,5,'<xmag> '+str(camera.ortho_scale)+' </xmag>') # I think?
  566. self.writel(S_CAMS,5,'<aspect_ratio> '+str(self.scene.render.resolution_x / self.scene.render.resolution_y)+' </aspect_ratio>')
  567. self.writel(S_CAMS,5,'<znear> '+str(camera.clip_start)+' </znear>')
  568. self.writel(S_CAMS,5,'<zfar> '+str(camera.clip_end)+' </zfar>')
  569. self.writel(S_CAMS,4,'</orthografic>')
  570. self.writel(S_CAMS,3,'</technique_common>')
  571. self.writel(S_CAMS,2,'</optics>')
  572. self.writel(S_CAMS,1,'</camera>')
  573. self.writel(S_NODES,il,'<instance_camera url="#'+camid+'"/>')
  574. def export_lamp_node(self,node,il):
  575. if (node.data==None):
  576. return
  577. light=node.data
  578. lightid=self.new_id("light")
  579. self.writel(S_LAMPS,1,'<light id="'+lightid+'" name="'+light.name+'">')
  580. self.writel(S_LAMPS,2,'<optics>')
  581. self.writel(S_LAMPS,3,'<technique_common>')
  582. if (light.type=="POINT"):
  583. self.writel(S_LAMPS,4,'<point>')
  584. self.writel(S_LAMPS,5,'<color>'+strarr(light.color)+'</color>')
  585. att_by_distance = 2.0 / light.distance # convert to linear attenuation
  586. self.writel(S_LAMPS,5,'<linear_attenuation>'+str(att_by_distance)+'</linear_attenuation>')
  587. if (light.use_sphere):
  588. self.writel(S_LAMPS,5,'<zfar>'+str(light.distance)+'</zfar>')
  589. self.writel(S_LAMPS,4,'</point>')
  590. elif (light.type=="SPOT"):
  591. self.writel(S_LAMPS,4,'<spot>')
  592. self.writel(S_LAMPS,5,'<color>'+strarr(light.color)+'</color>')
  593. att_by_distance = 2.0 / light.distance # convert to linear attenuation
  594. self.writel(S_LAMPS,5,'<linear_attenuation>'+str(att_by_distance)+'</linear_attenuation>')
  595. self.writel(S_LAMPS,5,'<falloff_angle>'+str(math.degrees(light.spot_size))+'</falloff_angle>')
  596. self.writel(S_LAMPS,4,'</spot>')
  597. else: #write a sun lamp for everything else (not supported)
  598. self.writel(S_LAMPS,4,'<directional>')
  599. self.writel(S_LAMPS,5,'<color>'+strarr(light.color)+'</color>')
  600. self.writel(S_LAMPS,4,'</directional>')
  601. self.writel(S_LAMPS,3,'</technique_common>')
  602. self.writel(S_LAMPS,2,'</optics>')
  603. self.writel(S_LAMPS,1,'</light>')
  604. self.writel(S_NODES,il,'<instance_light url="#'+lightid+'"/>')
  605. def export_curve(self,curve):
  606. splineid = self.new_id("spline")
  607. self.writel(S_GEOM,1,'<geometry id="'+splineid+'" name="'+curve.name+'">')
  608. self.writel(S_GEOM,2,'<spline closed="0">')
  609. points=[]
  610. interps=[]
  611. handles_in=[]
  612. handles_out=[]
  613. tilts=[]
  614. for cs in curve.splines:
  615. if (cs.type=="BEZIER"):
  616. for s in cs.bezier_points:
  617. points.append(s.co[0])
  618. points.append(s.co[1])
  619. points.append(s.co[2])
  620. handles_in.append(s.handle_left[0])
  621. handles_in.append(s.handle_left[1])
  622. handles_in.append(s.handle_left[2])
  623. handles_out.append(s.handle_right[0])
  624. handles_out.append(s.handle_right[1])
  625. handles_out.append(s.handle_right[2])
  626. tilts.append(s.tilt)
  627. interps.append("BEZIER")
  628. else:
  629. for s in cs.points:
  630. points.append(s.co[0])
  631. points.append(s.co[1])
  632. points.append(s.co[2])
  633. handles_in.append(s.co[0])
  634. handles_in.append(s.co[1])
  635. handles_in.append(s.co[2])
  636. handles_out.append(s.co[0])
  637. handles_out.append(s.co[1])
  638. handles_out.append(s.co[2])
  639. tilts.append(s.tilt)
  640. interps.append("LINEAR")
  641. self.writel(S_GEOM,3,'<source id="'+splineid+'-positions">')
  642. position_values=""
  643. for x in points:
  644. position_values+=" "+str(x)
  645. self.writel(S_GEOM,4,'<float_array id="'+splineid+'-positions-array" count="'+str(len(points))+'">'+position_values+'</float_array>')
  646. self.writel(S_GEOM,4,'<technique_common>')
  647. self.writel(S_GEOM,4,'<accessor source="#'+splineid+'-positions-array" count="'+str(len(points)/3)+'" stride="3">')
  648. self.writel(S_GEOM,5,'<param name="X" type="float"/>')
  649. self.writel(S_GEOM,5,'<param name="Y" type="float"/>')
  650. self.writel(S_GEOM,5,'<param name="Z" type="float"/>')
  651. self.writel(S_GEOM,4,'</accessor>')
  652. self.writel(S_GEOM,3,'</source>')
  653. self.writel(S_GEOM,3,'<source id="'+splineid+'-intangents">')
  654. intangent_values=""
  655. for x in handles_in:
  656. intangent_values+=" "+str(x)
  657. self.writel(S_GEOM,4,'<float_array id="'+splineid+'-intangents-array" count="'+str(len(points))+'">'+intangent_values+'</float_array>')
  658. self.writel(S_GEOM,4,'<technique_common>')
  659. self.writel(S_GEOM,4,'<accessor source="#'+splineid+'-intangents-array" count="'+str(len(points)/3)+'" stride="3">')
  660. self.writel(S_GEOM,5,'<param name="X" type="float"/>')
  661. self.writel(S_GEOM,5,'<param name="Y" type="float"/>')
  662. self.writel(S_GEOM,5,'<param name="Z" type="float"/>')
  663. self.writel(S_GEOM,4,'</accessor>')
  664. self.writel(S_GEOM,3,'</source>')
  665. self.writel(S_GEOM,3,'<source id="'+splineid+'-outtangents">')
  666. outtangent_values=""
  667. for x in handles_out:
  668. outtangent_values+=" "+str(x)
  669. self.writel(S_GEOM,4,'<float_array id="'+splineid+'-outtangents-array" count="'+str(len(points))+'">'+outtangent_values+'</float_array>')
  670. self.writel(S_GEOM,4,'<technique_common>')
  671. self.writel(S_GEOM,4,'<accessor source="#'+splineid+'-outtangents-array" count="'+str(len(points)/3)+'" stride="3">')
  672. self.writel(S_GEOM,5,'<param name="X" type="float"/>')
  673. self.writel(S_GEOM,5,'<param name="Y" type="float"/>')
  674. self.writel(S_GEOM,5,'<param name="Z" type="float"/>')
  675. self.writel(S_GEOM,4,'</accessor>')
  676. self.writel(S_GEOM,3,'</source>')
  677. self.writel(S_GEOM,3,'<source id="'+splineid+'-interpolations">')
  678. interpolation_values=""
  679. for x in interps:
  680. interpolation_values+=" "+x
  681. self.writel(S_GEOM,4,'<Name_array id="'+splineid+'-interpolations-array" count="'+str(len(interps))+'">'+interpolation_values+'</Name_array>')
  682. self.writel(S_GEOM,4,'<technique_common>')
  683. self.writel(S_GEOM,4,'<accessor source="#'+splineid+'-interpolations-array" count="'+str(len(interps))+'" stride="1">')
  684. self.writel(S_GEOM,5,'<param name="INTERPOLATION" type="name"/>')
  685. self.writel(S_GEOM,4,'</accessor>')
  686. self.writel(S_GEOM,3,'</source>')
  687. self.writel(S_GEOM,3,'<source id="'+splineid+'-tilts">')
  688. tilt_values=""
  689. for x in tilts:
  690. tilt_values+=" "+str(x)
  691. self.writel(S_GEOM,4,'<float_array id="'+splineid+'-tilts-array" count="'+str(len(tilts))+'">'+tilt_values+'</float_array>')
  692. self.writel(S_GEOM,4,'<technique_common>')
  693. self.writel(S_GEOM,4,'<accessor source="#'+splineid+'-tilts-array" count="'+str(len(tilts))+'" stride="1">')
  694. self.writel(S_GEOM,5,'<param name="TILT" type="float"/>')
  695. self.writel(S_GEOM,4,'</accessor>')
  696. self.writel(S_GEOM,3,'</source>')
  697. self.writel(S_GEOM,3,'<control_vertices>')
  698. self.writel(S_GEOM,4,'<input semantic="POSITION" source="#'+splineid+'-positions"/>')
  699. self.writel(S_GEOM,4,'<input semantic="IN_TANGENT" source="#'+splineid+'-intangents"/>')
  700. self.writel(S_GEOM,4,'<input semantic="OUT_TANGENT" source="#'+splineid+'-outtangents"/>')
  701. self.writel(S_GEOM,4,'<input semantic="INTERPOLATION" source="#'+splineid+'-interpolations"/>')
  702. self.writel(S_GEOM,4,'<input semantic="TILT" source="#'+splineid+'-tilts"/>')
  703. self.writel(S_GEOM,3,'</control_vertices>')
  704. self.writel(S_GEOM,2,'</spline>')
  705. self.writel(S_GEOM,1,'</geometry>')
  706. return splineid
  707. def export_curve_node(self,node,il):
  708. if (node.data==None):
  709. return
  710. curveid = self.export_curve(node.data)
  711. self.writel(S_NODES,il,'<instance_geometry url="#'+curveid+'">')
  712. self.writel(S_NODES,il,'</instance_geometry>')
  713. def export_node(self,node,il):
  714. if (not self.is_node_valid(node)):
  715. return
  716. self.writel(S_NODES,il,'<node id="'+self.validate_id(node.name)+'" name="'+node.name+'" type="NODE">')
  717. il+=1
  718. self.writel(S_NODES,il,'<matrix sid="transform">'+strmtx(node.matrix_local)+'</matrix>')
  719. print("NODE TYPE: "+node.type+" NAME: "+node.name)
  720. if (node.type=="MESH"):
  721. self.export_mesh_node(node,il)
  722. elif (node.type=="CURVE"):
  723. self.export_curve_node(node,il)
  724. elif (node.type=="ARMATURE"):
  725. self.export_armature_node(node,il)
  726. elif (node.type=="CAMERA"):
  727. self.export_camera_node(node,il)
  728. elif (node.type=="LAMP"):
  729. self.export_lamp_node(node,il)
  730. self.valid_nodes.append(node)
  731. for x in node.children:
  732. self.export_node(x,il)
  733. il-=1
  734. self.writel(S_NODES,il,'</node>')
  735. def is_node_valid(self,node):
  736. if (not node.type in self.config["object_types"]):
  737. return False
  738. if (self.config["use_active_layers"]):
  739. valid=False
  740. for i in range(20):
  741. if (node.layers[i] and self.scene.layers[i]):
  742. valid=True
  743. break
  744. if (not valid):
  745. return False
  746. if (self.config["use_export_selected"] and not node.select):
  747. return False
  748. return True
  749. def export_scene(self):
  750. self.writel(S_NODES,0,'<library_visual_scenes>')
  751. self.writel(S_NODES,1,'<visual_scene id="'+self.scene_name+'" name="scene">')
  752. for obj in self.scene.objects:
  753. if (obj.parent==None):
  754. self.export_node(obj,2)
  755. self.writel(S_NODES,1,'</visual_scene>')
  756. self.writel(S_NODES,0,'</library_visual_scenes>')
  757. def export_asset(self):
  758. self.writel(S_ASSET,0,'<asset>')
  759. # Why is this time stuff mandatory?, no one could care less...
  760. self.writel(S_ASSET,1,'<contributor>')
  761. self.writel(S_ASSET,2,'<author> Anonymous </author>') #Who made Collada, the FBI ?
  762. 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 ?
  763. self.writel(S_ASSET,1,'</contributor>')
  764. self.writel(S_ASSET,1,'<created>'+time.strftime("%Y-%m-%dT%H:%M:%SZ ")+'</created>')
  765. self.writel(S_ASSET,1,'<modified>'+time.strftime("%Y-%m-%dT%H:%M:%SZ")+'</modified>')
  766. self.writel(S_ASSET,1,'<unit meter="1.0" name="meter"/>')
  767. self.writel(S_ASSET,1,'<up_axis>Z_UP</up_axis>')
  768. self.writel(S_ASSET,0,'</asset>')
  769. def export_animation_transform_channel(self,target,transform_keys):
  770. frame_total=len(transform_keys)
  771. anim_id=self.new_id("anim")
  772. self.writel(S_ANIM,1,'<animation id="'+anim_id+'">')
  773. source_frames = ""
  774. source_transforms = ""
  775. source_interps = ""
  776. for k in transform_keys:
  777. source_frames += " "+str(k[0])
  778. source_transforms += " "+strmtx(k[1])
  779. source_interps +=" LINEAR"
  780. # Time Source
  781. self.writel(S_ANIM,2,'<source id="'+anim_id+'-input">')
  782. self.writel(S_ANIM,3,'<float_array id="'+anim_id+'-input-array" count="'+str(frame_total)+'">'+source_frames+'</float_array>')
  783. self.writel(S_ANIM,3,'<technique_common>')
  784. self.writel(S_ANIM,4,'<accessor source="'+anim_id+'-input-array" count="'+str(frame_total)+'" stride="1">')
  785. self.writel(S_ANIM,5,'<param name="TIME" type="float"/>')
  786. self.writel(S_ANIM,4,'</accessor>')
  787. self.writel(S_ANIM,3,'</technique_common>')
  788. self.writel(S_ANIM,2,'</source>')
  789. # Transform Source
  790. self.writel(S_ANIM,2,'<source id="'+anim_id+'-transform-output">')
  791. self.writel(S_ANIM,3,'<float_array id="'+anim_id+'-transform-output-array" count="'+str(frame_total*16)+'">'+source_transforms+'</float_array>')
  792. self.writel(S_ANIM,3,'<technique_common>')
  793. self.writel(S_ANIM,4,'<accessor source="'+anim_id+'-transform-output-array" count="'+str(frame_total)+'" stride="16">')
  794. self.writel(S_ANIM,5,'<param name="TRANSFORM" type="float4x4"/>')
  795. self.writel(S_ANIM,4,'</accessor>')
  796. self.writel(S_ANIM,3,'</technique_common>')
  797. self.writel(S_ANIM,2,'</source>')
  798. # Interpolation Source
  799. self.writel(S_ANIM,2,'<source id="'+anim_id+'-interpolation-output">')
  800. self.writel(S_ANIM,3,'<Name_array id="'+anim_id+'-interpolation-output-array" count="'+str(frame_total)+'">'+source_interps+'</Name_array>')
  801. self.writel(S_ANIM,3,'<technique_common>')
  802. self.writel(S_ANIM,4,'<accessor source="'+anim_id+'-interpolation-output-array" count="'+str(frame_total)+'" stride="1">')
  803. self.writel(S_ANIM,5,'<param name="INTERPOLATION" type="Name"/>')
  804. self.writel(S_ANIM,4,'</accessor>')
  805. self.writel(S_ANIM,3,'</technique_common>')
  806. self.writel(S_ANIM,2,'</source>')
  807. self.writel(S_ANIM,2,'<sampler id="'+anim_id+'-sampler">')
  808. self.writel(S_ANIM,3,'<input semantic="INPUT" source="#'+anim_id+'-input"/>')
  809. self.writel(S_ANIM,3,'<input semantic="OUTPUT" source="#'+anim_id+'-transform-output"/>')
  810. self.writel(S_ANIM,3,'<input semantic="INTERPOLATION" source="#'+anim_id+'-interpolation-output"/>')
  811. self.writel(S_ANIM,2,'</sampler>')
  812. self.writel(S_ANIM,2,'<channel source="#'+anim_id+'-sampler" target="'+target+'/transform"/>')
  813. self.writel(S_ANIM,1,'</animation>')
  814. return [anim_id]
  815. def export_animation(self,start,end,allowed=None):
  816. #Blender -> Collada frames needs a little work
  817. #Collada starts from 0, blender usually from 1
  818. #The last frame must be included also
  819. frame_len = 1.0 / self.scene.render.fps
  820. frame_total = end - start + 1
  821. frame_sub = 0
  822. if (start>0):
  823. frame_sub=start*frame_len
  824. tcn = []
  825. xform_cache={}
  826. # Change frames first, export objects last
  827. # This improves performance enormously
  828. print("anim from: "+str(start)+" to "+str(end)+" allowed: "+str(allowed))
  829. for t in range(start,end+1):
  830. self.scene.frame_set(t)
  831. key = t * frame_len - frame_sub
  832. # print("Export Anim Frame "+str(t)+"/"+str(self.scene.frame_end+1))
  833. for node in self.scene.objects:
  834. if (not node in self.valid_nodes):
  835. continue
  836. if (allowed!=None and not (node in allowed)):
  837. continue
  838. if (node.type=="MESH" and node.parent and node.parent.type=="ARMATURE"):
  839. continue #In Collada, nodes that have skin modifier must not export animation, animate the skin instead.
  840. if (len(node.constraints)>0 or node.animation_data!=None):
  841. #If the node has constraints, or animation data, then export a sampled animation track
  842. name=self.validate_id(node.name)
  843. if (not (name in xform_cache)):
  844. xform_cache[name]=[]
  845. mtx = node.matrix_world.copy()
  846. if (node.parent):
  847. mtx = node.parent.matrix_world.inverted() * mtx
  848. xform_cache[name].append( (key,mtx) )
  849. if (node.type=="ARMATURE"):
  850. #All bones exported for now
  851. for bone in node.data.bones:
  852. bone_name=self.skeleton_info[node]["bone_ids"][bone]
  853. if (not (bone_name in xform_cache)):
  854. print("has bone: "+bone_name)
  855. xform_cache[bone_name]=[]
  856. posebone = node.pose.bones[bone.name]
  857. parent_posebone=None
  858. mtx = posebone.matrix.copy()
  859. if (bone.parent):
  860. parent_posebone=node.pose.bones[bone.parent.name]
  861. mtx = parent_posebone.matrix.inverted() * mtx
  862. xform_cache[bone_name].append( (key,mtx) )
  863. #export animation xml
  864. for nid in xform_cache:
  865. tcn+=self.export_animation_transform_channel(nid,xform_cache[nid])
  866. return tcn
  867. def export_animations(self):
  868. self.writel(S_ANIM,0,'<library_animations>')
  869. if (self.config["use_anim_action_all"] and len(self.skeletons)):
  870. self.writel(S_ANIM_CLIPS,0,'<library_animation_clips>')
  871. for x in bpy.data.actions[:]:
  872. if x in self.action_constraints:
  873. continue
  874. bones=[]
  875. #find bones used
  876. for p in x.fcurves:
  877. dp = str(p.data_path)
  878. base = "pose.bones[\""
  879. if (dp.find(base)==0):
  880. dp=dp[len(base):]
  881. if (dp.find('"')!=-1):
  882. dp=dp[:dp.find('"')]
  883. if (not dp in bones):
  884. bones.append(dp)
  885. allowed_skeletons=[]
  886. for y in self.skeletons:
  887. if (y.animation_data):
  888. for z in y.pose.bones:
  889. if (z.bone.name in bones):
  890. if (not y in allowed_skeletons):
  891. allowed_skeletons.append(y)
  892. y.animation_data.action=x;
  893. print(str(x))
  894. tcn = self.export_animation(int(x.frame_range[0]),int(x.frame_range[1]),allowed_skeletons)
  895. framelen=(1.0/self.scene.render.fps)
  896. start = x.frame_range[0]*framelen
  897. end = x.frame_range[1]*framelen
  898. print("Export anim: "+x.name)
  899. self.writel(S_ANIM_CLIPS,1,'<animation_clip name="'+x.name+'" start="'+str(start)+'" end="'+str(end)+'">')
  900. for z in tcn:
  901. self.writel(S_ANIM_CLIPS,2,'<instance_animation url="#'+z+'">')
  902. self.writel(S_ANIM_CLIPS,1,'</animation_clip>')
  903. self.writel(S_ANIM_CLIPS,0,'</library_animation_clips>')
  904. else:
  905. self.export_animation(self.scene.frame_start,self.scene.frame_end)
  906. self.writel(S_ANIM,0,'</library_animations>')
  907. def export(self):
  908. self.writel(S_GEOM,0,'<library_geometries>')
  909. self.writel(S_CONT,0,'<library_controllers>')
  910. self.writel(S_CAMS,0,'<library_cameras>')
  911. self.writel(S_LAMPS,0,'<library_lights>')
  912. self.writel(S_IMGS,0,'<library_images>')
  913. self.writel(S_MATS,0,'<library_materials>')
  914. self.writel(S_FX,0,'<library_effects>')
  915. self.skeletons=[]
  916. self.action_constraints=[]
  917. self.export_asset()
  918. self.export_scene()
  919. self.writel(S_GEOM,0,'</library_geometries>')
  920. self.writel(S_CONT,0,'</library_controllers>')
  921. self.writel(S_CAMS,0,'</library_cameras>')
  922. self.writel(S_LAMPS,0,'</library_lights>')
  923. self.writel(S_IMGS,0,'</library_images>')
  924. self.writel(S_MATS,0,'</library_materials>')
  925. self.writel(S_FX,0,'</library_effects>')
  926. if (self.config["use_anim"]):
  927. self.export_animations()
  928. try:
  929. f = open(self.path,"wb")
  930. except:
  931. return False
  932. f.write(bytes('<?xml version="1.0" encoding="utf-8"?>\n',"UTF-8"))
  933. f.write(bytes('<COLLADA xmlns="http://www.collada.org/2005/11/COLLADASchema" version="1.4.1">\n',"UTF-8"))
  934. s=[]
  935. for x in self.sections.keys():
  936. s.append(x)
  937. s.sort()
  938. for x in s:
  939. for l in self.sections[x]:
  940. f.write(bytes(l+"\n","UTF-8"))
  941. f.write(bytes('<scene>\n',"UTF-8"))
  942. f.write(bytes('\t<instance_visual_scene url="#'+self.scene_name+'" />\n',"UTF-8"))
  943. f.write(bytes('</scene>\n',"UTF-8"))
  944. f.write(bytes('</COLLADA>\n',"UTF-8"))
  945. return True
  946. def __init__(self,path,kwargs):
  947. self.scene=bpy.context.scene
  948. self.last_id=0
  949. self.scene_name=self.new_id("scene")
  950. self.sections={}
  951. self.path=path
  952. self.mesh_cache={}
  953. self.curve_cache={}
  954. self.material_cache={}
  955. self.image_cache={}
  956. self.skeleton_info={}
  957. self.config=kwargs
  958. self.valid_nodes=[]
  959. def save(operator, context,
  960. filepath="",
  961. use_selection=False,
  962. **kwargs
  963. ):
  964. exp = DaeExporter(filepath,kwargs)
  965. exp.export()
  966. return {'FINISHED'} # so the script wont run after we have batch exported.