Files
collada-exporter/io_scene_dae/export_dae.py

2073 lines
81 KiB
Python

# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# ##### END GPL LICENSE BLOCK #####
# Script copyright (C) Juan Linietsky
# Contact Info: juan@godotengine.org
"""
This script is an exporter to the Khronos Collada file format.
http://www.khronos.org/collada/
"""
import os
import time
import math
import shutil
import bpy
import bmesh
from mathutils import Vector, Matrix
from bpy_extras import node_shader_utils
# According to collada spec, order matters
S_ASSET = 0
S_IMGS = 1
S_FX = 2
S_MATS = 3
S_GEOM = 4
S_MORPH = 5
S_SKIN = 6
S_CONT = 7
S_CAMS = 8
S_LAMPS = 9
S_ANIM_CLIPS = 10
S_NODES = 11
S_ANIM = 12
CMP_EPSILON = 0.0001
def snap_tup(tup):
ret = ()
for x in tup:
ret += (x - math.fmod(x, 0.0001), )
return tup
def strmtx(mtx):
s = ""
for x in range(4):
for y in range(4):
s += "{} ".format(mtx[x][y])
s = " {} ".format(s)
return s
def numarr(a, mult=1.0):
s = " "
for x in a:
s += " {}".format(x * mult)
s += " "
return s
def numarr_alpha(a, mult=1.0):
s = " "
for x in a:
s += " {}".format(x * mult)
if len(a) == 3:
s += " 1.0"
s += " "
return s
def strarr(arr):
s = " "
for x in arr:
s += " {}".format(x)
s += " "
return s
class DaeExporter:
def validate_id(self, d):
if (d.find("id-") == 0):
return "z{}".format(d)
return d
def new_id(self, t):
self.last_id += 1
return "id-{}-{}".format(t, self.last_id)
class Vertex:
def close_to(self, v):
if self.vertex - v.vertex.length() > CMP_EPSILON:
return False
if self.normal - v.normal.length() > CMP_EPSILON:
return False
if self.uv - v.uv.length() > CMP_EPSILON:
return False
if self.uv2 - v.uv2.length() > CMP_EPSILON:
return False
return True
def get_tup(self):
tup = (self.vertex.x, self.vertex.y, self.vertex.z, self.normal.x,
self.normal.y, self.normal.z)
for t in self.uv:
tup = tup + (t.x, t.y)
if self.color is not None:
tup = tup + (self.color.x, self.color.y, self.color.z)
if self.tangent is not None:
tup = tup + (self.tangent.x, self.tangent.y, self.tangent.z)
if self.bitangent is not None:
tup = tup + (self.bitangent.x, self.bitangent.y,
self.bitangent.z)
for t in self.bones:
tup = tup + (float(t), )
for t in self.weights:
tup = tup + (float(t), )
return tup
__slots__ = ("vertex", "normal", "tangent", "bitangent", "color", "uv",
"uv2", "bones", "weights")
def __init__(self):
self.vertex = Vector((0.0, 0.0, 0.0))
self.normal = Vector((0.0, 0.0, 0.0))
self.tangent = None
self.bitangent = None
self.color = None
self.uv = []
self.uv2 = Vector((0.0, 0.0))
self.bones = []
self.weights = []
def writel(self, section, indent, text):
if (not (section in self.sections)):
self.sections[section] = []
line = "{}{}".format(indent * "\t", text)
self.sections[section].append(line)
def purge_empty_nodes(self):
sections = {}
for k, v in self.sections.items():
if not (len(v) == 2 and v[0][1:] == v[1][2:]):
sections[k] = v
self.sections = sections
def export_image(self, image):
img_id = self.image_cache.get(image)
if img_id:
return img_id
imgpath = image.filepath
if imgpath.startswith("//"):
imgpath = bpy.path.abspath(imgpath)
print("exporting image path", imgpath)
if (self.config["use_copy_images"]):
basedir = os.path.join(os.path.dirname(self.path), "images")
if (not os.path.isdir(basedir)):
os.makedirs(basedir)
if os.path.isfile(imgpath):
dstfile = os.path.join(basedir, os.path.basename(imgpath))
if not os.path.isfile(dstfile):
shutil.copy(imgpath, dstfile)
imgpath = os.path.join("images", os.path.basename(imgpath))
else:
img_tmp_path = image.filepath
if img_tmp_path.lower().endswith(
tuple(bpy.path.extensions_image)):
image.filepath = os.path.join(
basedir, os.path.basename(img_tmp_path))
else:
image.filepath = os.path.join(
basedir, "{}.png".format(image.name))
dstfile = os.path.join(
basedir, os.path.basename(image.filepath))
if not os.path.isfile(dstfile):
image.save()
imgpath = os.path.join(
"images", os.path.basename(image.filepath))
image.filepath = img_tmp_path
else:
try:
imgpath = os.path.relpath(
imgpath, os.path.dirname(self.path)).replace("\\", "/")
except:
# TODO: Review, not sure why it fails
pass
imgid = self.new_id("image")
print("FOR: {}".format(imgpath))
self.writel(S_IMGS, 1, "<image id=\"{}\" name=\"{}\">".format(
imgid, image.name))
self.writel(S_IMGS, 2, "<init_from>{}</init_from>".format(imgpath))
self.writel(S_IMGS, 1, "</image>")
self.image_cache[image] = imgid
return imgid
def export_material(self, material, double_sided_hint=True):
material_id = self.material_cache.get(material)
if material_id:
return material_id
fxid = self.new_id("fx")
self.writel(S_FX, 1, "<effect id=\"{}\" name=\"{}-fx\">".format(
fxid, material.name))
self.writel(S_FX, 2, "<profile_COMMON>")
# Find and fetch the textures and create sources
sampler_table = {}
diffuse_tex = None
specular_tex = None
emission_tex = None
normal_tex = None
#TODO, use Blender 2.8 principled shader and connected maps
mat_wrap = node_shader_utils.PrincipledBSDFWrapper(material) if material else None
if mat_wrap:
textures_keys = ["base_color_texture", "specular_texture", "normalmap_texture"]
for i, tkey in enumerate(textures_keys):
tex = getattr(mat_wrap, tkey, None)
if tex == None:
continue
if tex.image == None:
continue
# Image
imgid = self.export_image(tex.image)
# Surface
surface_sid = self.new_id("fx_surf")
self.writel(S_FX, 3, "<newparam sid=\"{}\">".format(surface_sid))
self.writel(S_FX, 4, "<surface type=\"2D\">")
self.writel(S_FX, 5, "<init_from>{}</init_from>".format(imgid))
self.writel(S_FX, 5, "<format>A8R8G8B8</format>")
self.writel(S_FX, 4, "</surface>")
self.writel(S_FX, 3, "</newparam>")
# Sampler
sampler_sid = self.new_id("fx_sampler")
self.writel(S_FX, 3, "<newparam sid=\"{}\">".format(sampler_sid))
self.writel(S_FX, 4, "<sampler2D>")
self.writel(S_FX, 5, "<source>{}</source>".format(surface_sid))
self.writel(S_FX, 4, "</sampler2D>")
self.writel(S_FX, 3, "</newparam>")
sampler_table[i] = sampler_sid
if tkey == "base_color_texture" and diffuse_tex is None:
diffuse_tex = sampler_sid
if tkey == "specular_texture" and specular_tex is None:
specular_tex = sampler_sid
"""
# TODO differently, no emission input in the principled shader
if ts.use_map_emit and emission_tex is None:
emission_tex = sampler_sid
"""
if tkey == "normalmap_texture" and normal_tex is None:
normal_tex = sampler_sid
"""
for i in range(len(material.texture_slots)):
ts = material.texture_slots[i]
if not ts:
continue
if not ts.use:
continue
if not ts.texture:
continue
if ts.texture.type != "IMAGE":
continue
if ts.texture.image is None:
continue
# Image
imgid = self.export_image(ts.texture.image)
# Surface
surface_sid = self.new_id("fx_surf")
self.writel(S_FX, 3, "<newparam sid=\"{}\">".format(surface_sid))
self.writel(S_FX, 4, "<surface type=\"2D\">")
self.writel(S_FX, 5, "<init_from>{}</init_from>".format(imgid))
self.writel(S_FX, 5, "<format>A8R8G8B8</format>")
self.writel(S_FX, 4, "</surface>")
self.writel(S_FX, 3, "</newparam>")
# Sampler
sampler_sid = self.new_id("fx_sampler")
self.writel(S_FX, 3, "<newparam sid=\"{}\">".format(sampler_sid))
self.writel(S_FX, 4, "<sampler2D>")
self.writel(S_FX, 5, "<source>{}</source>".format(surface_sid))
self.writel(S_FX, 4, "</sampler2D>")
self.writel(S_FX, 3, "</newparam>")
sampler_table[i] = sampler_sid
if ts.use_map_color_diffuse and diffuse_tex is None:
diffuse_tex = sampler_sid
if ts.use_map_color_spec and specular_tex is None:
specular_tex = sampler_sid
if ts.use_map_emit and emission_tex is None:
emission_tex = sampler_sid
if ts.use_map_normal and normal_tex is None:
normal_tex = sampler_sid
"""
self.writel(S_FX, 3, "<technique sid=\"common\">")
shtype = "blinn"
self.writel(S_FX, 4, "<{}>".format(shtype))
self.writel(S_FX, 5, "<emission>")
if emission_tex is not None:
self.writel(
S_FX, 6, "<texture texture=\"{}\" texcoord=\"CHANNEL1\"/>"
.format(emission_tex))
else:
# TODO: More accurate coloring, if possible
self.writel(S_FX, 6, "<color>{}</color>".format(
numarr_alpha(material.diffuse_color, 1.0)))#material.emit is removed in Blender 2.8
self.writel(S_FX, 5, "</emission>")
self.writel(S_FX, 5, "<ambient>")
self.writel(S_FX, 6, "<color>{}</color>".format(
numarr_alpha((0.0,0.0,0.0), 1.0)))# self.scene.world.ambient_color and material.ambient are removed too
self.writel(S_FX, 5, "</ambient>")
self.writel(S_FX, 5, "<diffuse>")
if diffuse_tex is not None:
self.writel(
S_FX, 6, "<texture texture=\"{}\" texcoord=\"CHANNEL1\"/>"
.format(diffuse_tex))
else:
self.writel(S_FX, 6, "<color>{}</color>".format(numarr_alpha(
material.diffuse_color, 0.8)))# material.diffuse_intensity is removed too
self.writel(S_FX, 5, "</diffuse>")
self.writel(S_FX, 5, "<specular>")
if specular_tex is not None:
self.writel(
S_FX, 6,
"<texture texture=\"{}\" texcoord=\"CHANNEL1\"/>".format(
specular_tex))
else:
self.writel(S_FX, 6, "<color>{}</color>".format(numarr_alpha(
material.specular_color, material.specular_intensity)))
self.writel(S_FX, 5, "</specular>")
self.writel(S_FX, 5, "<shininess>")
self.writel(S_FX, 6, "<float>{}</float>".format(
50))# material.specular_hardness is removed too
self.writel(S_FX, 5, "</shininess>")
self.writel(S_FX, 5, "<reflective>")
self.writel(S_FX, 6, "<color>{}</color>".format(
numarr_alpha((0.5,0.5,0.5))))# material.mirror_color is removed too
self.writel(S_FX, 5, "</reflective>")
"""
#material.use_transparency is removed too
if (material.use_transparency):
self.writel(S_FX, 5, "<transparency>")
self.writel(S_FX, 6, "<float>{}</float>".format(material.alpha))
self.writel(S_FX, 5, "</transparency>")
"""
self.writel(S_FX, 5, "<index_of_refraction>")
self.writel(S_FX, 6, "<float>{}</float>".format(1.2))#material.specular_ior is removed too
self.writel(S_FX, 5, "</index_of_refraction>")
self.writel(S_FX, 4, "</{}>".format(shtype))
self.writel(S_FX, 4, "<extra>")
self.writel(S_FX, 5, "<technique profile=\"FCOLLADA\">")
if (normal_tex):
self.writel(S_FX, 6, "<bump bumptype=\"NORMALMAP\">")
self.writel(
S_FX, 7,
"<texture texture=\"{}\" texcoord=\"CHANNEL1\"/>".format(
normal_tex))
self.writel(S_FX, 6, "</bump>")
self.writel(S_FX, 5, "</technique>")
self.writel(S_FX, 5, "<technique profile=\"GOOGLEEARTH\">")
self.writel(S_FX, 6, "<double_sided>{}</double_sided>".format(
int(double_sided_hint)))
self.writel(S_FX, 5, "</technique>")
"""
if (material.use_shadeless):#material.use_shadeless is removed too
self.writel(S_FX, 5, "<technique profile=\"GODOT\">")
self.writel(S_FX, 6, "<unshaded>1</unshaded>")
self.writel(S_FX, 5, "</technique>")
"""
self.writel(S_FX, 4, "</extra>")
self.writel(S_FX, 3, "</technique>")
self.writel(S_FX, 2, "</profile_COMMON>")
self.writel(S_FX, 1, "</effect>")
# Material (if active)
matid = self.new_id("material")
self.writel(S_MATS, 1, "<material id=\"{}\" name=\"{}\">".format(
matid, material.name))
self.writel(S_MATS, 2, "<instance_effect url=\"#{}\"/>".format(fxid))
self.writel(S_MATS, 1, "</material>")
self.material_cache[material] = matid
return matid
def export_mesh(self, node, armature=None, skeyindex=-1, skel_source=None,
custom_name=None):
mesh = node.data
if (node.data in self.mesh_cache):
return self.mesh_cache[mesh]
if (skeyindex == -1 and mesh.shape_keys is not None and len(
mesh.shape_keys.key_blocks) and self.config["use_shape_key_export"]):
values = []
morph_targets = []
md = None
for k in range(0, len(mesh.shape_keys.key_blocks)):
shape = node.data.shape_keys.key_blocks[k]
values += [shape.value]
shape.value = 0
mid = self.new_id("morph")
for k in range(0, len(mesh.shape_keys.key_blocks)):
shape = node.data.shape_keys.key_blocks[k]
node.show_only_shape_key = True
node.active_shape_key_index = k
shape.value = 1.0
mesh.update()
p = node.data
armature_modifier = None
armature_modifier_state = None
if(self.config["use_exclude_armature_modifier"]):
armature_modifiers = [i for i in node.modifiers if i.type == "ARMATURE"]
armature_modifier = armature_modifiers[0]#node.modifiers.get("Armature")
if(armature_modifier):
# the armature modifier must be disabled too
armature_modifier_state = armature_modifier.show_viewport
armature_modifier.show_viewport = False
print(node)
v = node.to_mesh(preserve_all_data_layers=True, depsgraph=bpy.context.evaluated_depsgraph_get())
print(v)
# Warning, Blender 2.8 does not support anymore the "RENDER" argument to apply modifier
# with render state only...
armature_modifier.show_viewport = armature_modifier_state
self.temp_meshes.add(v)
deps = bpy.context.evaluated_depsgraph_get()
evaluated_node = node.evaluated_get(deps)
evaluated_node.data = v
evaluated_node.data.update()
if (armature and k == 0):
md = self.export_mesh(evaluated_node, armature, k, mid, shape.name)
else:
md = self.export_mesh(evaluated_node, None, k, None, shape.name)
node.data = p
node.data.update()
shape.value = 0.0
morph_targets.append(md)
node.show_only_shape_key = False
node.active_shape_key_index = 0
self.writel(
S_MORPH, 1, "<controller id=\"{}\" name=\"\">".format(mid))
self.writel(
S_MORPH, 2,
"<morph source=\"#{}\" method=\"NORMALIZED\">".format(
morph_targets[0]["id"]))
self.writel(
S_MORPH, 3, "<source id=\"{}-morph-targets\">".format(mid))
self.writel(
S_MORPH, 4,
"<IDREF_array id=\"{}-morph-targets-array\" "
"count=\"{}\">".format(mid, len(morph_targets) - 1))
marr = ""
warr = ""
for i in range(len(morph_targets)):
if (i == 0):
continue
elif (i > 1):
marr += " "
if ("skin_id" in morph_targets[i]):
marr += morph_targets[i]["skin_id"]
else:
marr += morph_targets[i]["id"]
warr += " 0"
self.writel(S_MORPH, 5, marr)
self.writel(S_MORPH, 4, "</IDREF_array>")
self.writel(S_MORPH, 4, "<technique_common>")
self.writel(
S_MORPH, 5, "<accessor source=\"#{}-morph-targets-array\" "
"count=\"{}\" stride=\"1\">".format(
mid, len(morph_targets) - 1))
self.writel(
S_MORPH, 6, "<param name=\"MORPH_TARGET\" type=\"IDREF\"/>")
self.writel(S_MORPH, 5, "</accessor>")
self.writel(S_MORPH, 4, "</technique_common>")
self.writel(S_MORPH, 3, "</source>")
self.writel(
S_MORPH, 3, "<source id=\"{}-morph-weights\">".format(mid))
self.writel(
S_MORPH, 4,
"<float_array id=\"{}-morph-weights-array\" count=\"{}\" >"
.format(mid, len(morph_targets) - 1))
self.writel(S_MORPH, 5, warr)
self.writel(S_MORPH, 4, "</float_array>")
self.writel(S_MORPH, 4, "<technique_common>")
self.writel(
S_MORPH, 5,
"<accessor source=\"#{}-morph-weights-array\" "
"count=\"{}\" stride=\"1\">".format(
mid, len(morph_targets) - 1))
self.writel(
S_MORPH, 6, "<param name=\"MORPH_WEIGHT\" type=\"float\"/>")
self.writel(S_MORPH, 5, "</accessor>")
self.writel(S_MORPH, 4, "</technique_common>")
self.writel(S_MORPH, 3, "</source>")
self.writel(S_MORPH, 3, "<targets>")
self.writel(
S_MORPH, 4, "<input semantic=\"MORPH_TARGET\" "
"source=\"#{}-morph-targets\"/>".format(mid))
self.writel(
S_MORPH, 4, "<input semantic=\"MORPH_WEIGHT\" "
"source=\"#{}-morph-weights\"/>".format(mid))
self.writel(S_MORPH, 3, "</targets>")
self.writel(S_MORPH, 2, "</morph>")
self.writel(S_MORPH, 1, "</controller>")
if armature is not None:
self.armature_for_morph[node] = armature
meshdata = {}
if (armature):
meshdata = morph_targets[0]
meshdata["morph_id"] = mid
else:
meshdata["id"] = morph_targets[0]["id"]
meshdata["morph_id"] = mid
meshdata["material_assign"] = morph_targets[
0]["material_assign"]
self.mesh_cache[node.data] = meshdata
return meshdata
armature_modifier = None
armature_poses = None
armature_modifier_state = None
if(self.config["use_exclude_armature_modifier"]):
armature_modifiers = [i for i in node.modifiers if i.type == "ARMATURE"]
if len(armature_modifiers) > 0:
print(node.name)
armature_modifier = armature_modifiers[0]#node.modifiers.get("Armature")
# Set armature in rest pose
if(armature_modifier):
# the armature modifier must be disabled too
armature_modifier_state = armature_modifier.show_viewport
armature_modifier.show_viewport = False
#doing this per object is inefficient, should be improved, maybe?
armature_poses = [arm.pose_position for arm in bpy.data.armatures]
for arm in bpy.data.armatures:
arm.pose_position = "REST"
apply_modifiers = len(node.modifiers) and self.config[
"use_mesh_modifiers"]
name_to_use = mesh.name
if (custom_name is not None and custom_name != ""):
name_to_use = custom_name
mesh = node.to_mesh(preserve_all_data_layers=False, depsgraph=bpy.context.evaluated_depsgraph_get())
# 2.8 update: warning, Blender does not support anymore the "RENDER" argument to apply modifier
# with render state, only current state
# Restore armature and modifier state
if(armature_modifier):
armature_modifier.show_viewport = armature_modifier_state
for i,arm in enumerate(bpy.data.armatures):
arm.pose_position = armature_poses[i]
self.temp_meshes.add(mesh)
triangulate = self.config["use_triangles"]
if (triangulate):
bm = bmesh.new()
bm.from_mesh(mesh)
bmesh.ops.triangulate(bm, faces=bm.faces)
bm.to_mesh(mesh)
bm.free()
#mesh.update(calc_tessface=True)# 2.79
#mesh.update(calc_edges=False, calc_edges_loose=False, calc_loop_triangles=True)# 2.80
mesh.update(calc_edges=False, calc_edges_loose=False)# 3.0.1
vertices = []
vertex_map = {}
surface_indices = {}
materials = {}
materials = {}
si = None
if armature is not None:
si = self.skeleton_info[armature]
# TODO: Implement automatic tangent detection
has_tangents = self.config["use_tangent_arrays"]
has_colors = len(mesh.vertex_colors)
mat_assign = []
uv_layer_count = len(mesh.uv_layers)
if has_tangents and len(mesh.uv_layers):
try:
mesh.calc_tangents()
except:
self.operator.report(
{"WARNING"},
"CalcTangets failed for mesh \"{}\", no tangets will be "
"exported.".format(mesh.name))
mesh.calc_normals_split()
has_tangents = False
else:
mesh.calc_normals_split()
has_tangents = False
for fi in range(len(mesh.polygons)):
f = mesh.polygons[fi]
if not (f.material_index in surface_indices):
surface_indices[f.material_index] = []
try:
# TODO: Review, understand why it throws
mat = mesh.materials[f.material_index]
except:
mat = None
if (mat is not None):
materials[f.material_index] = self.export_material(
mat, True)#True = deprecated mesh.show_double_sided value, which is removed from Blender 2.8
else:
materials[f.material_index] = None
indices = surface_indices[f.material_index]
vi = []
for lt in range(f.loop_total):
loop_index = f.loop_start + lt
ml = mesh.loops[loop_index]
mv = mesh.vertices[ml.vertex_index]
v = self.Vertex()
v.vertex = Vector(mv.co)
for xt in mesh.uv_layers:
v.uv.append(Vector(xt.data[loop_index].uv))
if (has_colors):
v.color = Vector(
mesh.vertex_colors[0].data[loop_index].color)
v.normal = Vector(ml.normal)
if (has_tangents):
v.tangent = Vector(ml.tangent)
v.bitangent = Vector(ml.bitangent)
if armature is not None:
wsum = 0.0
for vg in mv.groups:
if vg.group >= len(node.vertex_groups):
continue
name = node.vertex_groups[vg.group].name
if (name in si["bone_index"]):
# TODO: Try using 0.0001 since Blender uses
# zero weight
if (vg.weight > 0.001):
v.bones.append(si["bone_index"][name])
v.weights.append(vg.weight)
wsum += vg.weight
if (wsum == 0.0):
if not self.wrongvtx_report:
self.operator.report(
{"WARNING"},
"Mesh for object \"{}\" has unassigned "
"weights. This may look wrong in exported "
"model.".format(node.name))
self.wrongvtx_report = True
# TODO: Explore how to deal with zero-weight bones,
# which remain local
v.bones.append(0)
v.weights.append(1)
tup = v.get_tup()
idx = 0
# Do not optmize if using shapekeys
if (skeyindex == -1 and tup in vertex_map):
idx = vertex_map[tup]
else:
idx = len(vertices)
vertices.append(v)
vertex_map[tup] = idx
vi.append(idx)
if (len(vi) > 2): # Only triangles and above
indices.append(vi)
meshid = self.new_id("mesh")
self.writel(
S_GEOM, 1, "<geometry id=\"{}\" name=\"{}\">".format(
meshid, name_to_use))
self.writel(S_GEOM, 2, "<mesh>")
# Vertex Array
self.writel(S_GEOM, 3, "<source id=\"{}-positions\">".format(meshid))
float_values = ""
for v in vertices:
float_values += " {} {} {}".format(
v.vertex.x, v.vertex.y, v.vertex.z)
self.writel(
S_GEOM, 4, "<float_array id=\"{}-positions-array\" "
"count=\"{}\">{}</float_array>".format(
meshid, len(vertices) * 3, float_values))
self.writel(S_GEOM, 4, "<technique_common>")
self.writel(
S_GEOM, 4, "<accessor source=\"#{}-positions-array\" "
"count=\"{}\" stride=\"3\">".format(meshid, len(vertices)))
self.writel(S_GEOM, 5, "<param name=\"X\" type=\"float\"/>")
self.writel(S_GEOM, 5, "<param name=\"Y\" type=\"float\"/>")
self.writel(S_GEOM, 5, "<param name=\"Z\" type=\"float\"/>")
self.writel(S_GEOM, 4, "</accessor>")
self.writel(S_GEOM, 4, "</technique_common>")
self.writel(S_GEOM, 3, "</source>")
# Normals Array
self.writel(S_GEOM, 3, "<source id=\"{}-normals\">".format(meshid))
float_values = ""
for v in vertices:
float_values += " {} {} {}".format(
v.normal.x, v.normal.y, v.normal.z)
self.writel(
S_GEOM, 4, "<float_array id=\"{}-normals-array\" "
"count=\"{}\">{}</float_array>".format(
meshid, len(vertices) * 3, float_values))
self.writel(S_GEOM, 4, "<technique_common>")
self.writel(
S_GEOM, 4, "<accessor source=\"#{}-normals-array\" count=\"{}\" "
"stride=\"3\">".format(meshid, len(vertices)))
self.writel(S_GEOM, 5, "<param name=\"X\" type=\"float\"/>")
self.writel(S_GEOM, 5, "<param name=\"Y\" type=\"float\"/>")
self.writel(S_GEOM, 5, "<param name=\"Z\" type=\"float\"/>")
self.writel(S_GEOM, 4, "</accessor>")
self.writel(S_GEOM, 4, "</technique_common>")
self.writel(S_GEOM, 3, "</source>")
if (has_tangents):
self.writel(
S_GEOM, 3, "<source id=\"{}-tangents\">".format(meshid))
float_values = ""
for v in vertices:
float_values += " {} {} {}".format(
v.tangent.x, v.tangent.y, v.tangent.z)
self.writel(
S_GEOM, 4, "<float_array id=\"{}-tangents-array\" "
"count=\"{}\">{}</float_array>".format(
meshid, len(vertices) * 3, float_values))
self.writel(S_GEOM, 4, "<technique_common>")
self.writel(
S_GEOM, 4, "<accessor source=\"#{}-tangents-array\" "
"count=\"{}\" stride=\"3\">".format(meshid, len(vertices)))
self.writel(S_GEOM, 5, "<param name=\"X\" type=\"float\"/>")
self.writel(S_GEOM, 5, "<param name=\"Y\" type=\"float\"/>")
self.writel(S_GEOM, 5, "<param name=\"Z\" type=\"float\"/>")
self.writel(S_GEOM, 4, "</accessor>")
self.writel(S_GEOM, 4, "</technique_common>")
self.writel(S_GEOM, 3, "</source>")
self.writel(S_GEOM, 3, "<source id=\"{}-bitangents\">".format(
meshid))
float_values = ""
for v in vertices:
float_values += " {} {} {}".format(
v.bitangent.x, v.bitangent.y, v.bitangent.z)
self.writel(
S_GEOM, 4, "<float_array id=\"{}-bitangents-array\" "
"count=\"{}\">{}</float_array>".format(
meshid, len(vertices) * 3, float_values))
self.writel(S_GEOM, 4, "<technique_common>")
self.writel(
S_GEOM, 4, "<accessor source=\"#{}-bitangents-array\" "
"count=\"{}\" stride=\"3\">".format(meshid, len(vertices)))
self.writel(S_GEOM, 5, "<param name=\"X\" type=\"float\"/>")
self.writel(S_GEOM, 5, "<param name=\"Y\" type=\"float\"/>")
self.writel(S_GEOM, 5, "<param name=\"Z\" type=\"float\"/>")
self.writel(S_GEOM, 4, "</accessor>")
self.writel(S_GEOM, 4, "</technique_common>")
self.writel(S_GEOM, 3, "</source>")
# UV Arrays
for uvi in range(uv_layer_count):
self.writel(S_GEOM, 3, "<source id=\"{}-texcoord-{}\">".format(
meshid, uvi))
float_values = ""
for v in vertices:
try:
float_values += " {} {}".format(v.uv[uvi].x, v.uv[uvi].y)
except:
# TODO: Review, understand better the multi-uv-layer API
float_values += " 0 0 "
self.writel(
S_GEOM, 4, "<float_array id=\"{}-texcoord-{}-array\" "
"count=\"{}\">{}</float_array>".format(
meshid, uvi, len(vertices) * 2, float_values))
self.writel(S_GEOM, 4, "<technique_common>")
self.writel(
S_GEOM, 4, "<accessor source=\"#{}-texcoord-{}-array\" "
"count=\"{}\" stride=\"2\">".format(
meshid, uvi, len(vertices)))
self.writel(S_GEOM, 5, "<param name=\"S\" type=\"float\"/>")
self.writel(S_GEOM, 5, "<param name=\"T\" type=\"float\"/>")
self.writel(S_GEOM, 4, "</accessor>")
self.writel(S_GEOM, 4, "</technique_common>")
self.writel(S_GEOM, 3, "</source>")
# Color Arrays
if (has_colors):
self.writel(S_GEOM, 3, "<source id=\"{}-colors\">".format(meshid))
float_values = ""
for v in vertices:
float_values += " {} {} {}".format(
v.color.x, v.color.y, v.color.z)
self.writel(
S_GEOM, 4, "<float_array id=\"{}-colors-array\" "
"count=\"{}\">{}</float_array>".format(
meshid, len(vertices) * 3, float_values))
self.writel(S_GEOM, 4, "<technique_common>")
self.writel(
S_GEOM, 4, "<accessor source=\"#{}-colors-array\" "
"count=\"{}\" stride=\"3\">".format(meshid, len(vertices)))
self.writel(S_GEOM, 5, "<param name=\"X\" type=\"float\"/>")
self.writel(S_GEOM, 5, "<param name=\"Y\" type=\"float\"/>")
self.writel(S_GEOM, 5, "<param name=\"Z\" type=\"float\"/>")
self.writel(S_GEOM, 4, "</accessor>")
self.writel(S_GEOM, 4, "</technique_common>")
self.writel(S_GEOM, 3, "</source>")
# Triangle Lists
self.writel(S_GEOM, 3, "<vertices id=\"{}-vertices\">".format(meshid))
self.writel(
S_GEOM, 4,
"<input semantic=\"POSITION\" source=\"#{}-positions\"/>".format(
meshid))
self.writel(S_GEOM, 3, "</vertices>")
prim_type = ""
if (triangulate):
prim_type = "triangles"
else:
prim_type = "polygons"
for m in surface_indices:
indices = surface_indices[m]
mat = materials[m]
if (mat is not None):
matref = self.new_id("trimat")
self.writel(
S_GEOM, 3, "<{} count=\"{}\" material=\"{}\">".format(
prim_type,
int(len(indices)), matref)) # TODO: Implement material
mat_assign.append((mat, matref))
else:
self.writel(S_GEOM, 3, "<{} count=\"{}\">".format(
prim_type, int(len(indices)))) # TODO: Implement material
self.writel(
S_GEOM, 4, "<input semantic=\"VERTEX\" "
"source=\"#{}-vertices\" offset=\"0\"/>".format(meshid))
self.writel(
S_GEOM, 4, "<input semantic=\"NORMAL\" "
"source=\"#{}-normals\" offset=\"0\"/>".format(meshid))
for uvi in range(uv_layer_count):
self.writel(
S_GEOM, 4,
"<input semantic=\"TEXCOORD\" source=\"#{}-texcoord-{}\" "
"offset=\"0\" set=\"{}\"/>".format(meshid, uvi, uvi))
if (has_colors):
self.writel(
S_GEOM, 4, "<input semantic=\"COLOR\" "
"source=\"#{}-colors\" offset=\"0\"/>".format(meshid))
if (has_tangents):
self.writel(
S_GEOM, 4, "<input semantic=\"TEXTANGENT\" "
"source=\"#{}-tangents\" offset=\"0\"/>".format(meshid))
self.writel(
S_GEOM, 4, "<input semantic=\"TEXBINORMAL\" "
"source=\"#{}-bitangents\" offset=\"0\"/>".format(meshid))
if (triangulate):
int_values = "<p>"
for p in indices:
for i in p:
int_values += " {}".format(i)
int_values += " </p>"
self.writel(S_GEOM, 4, int_values)
else:
for p in indices:
int_values = "<p>"
for i in p:
int_values += " {}".format(i)
int_values += " </p>"
self.writel(S_GEOM, 4, int_values)
self.writel(S_GEOM, 3, "</{}>".format(prim_type))
self.writel(S_GEOM, 2, "</mesh>")
self.writel(S_GEOM, 1, "</geometry>")
meshdata = {}
meshdata["id"] = meshid
meshdata["material_assign"] = mat_assign
if (skeyindex == -1):
self.mesh_cache[node.data] = meshdata
# Export armature data (if armature exists)
if (armature is not None and (
skel_source is not None or skeyindex == -1)):
contid = self.new_id("controller")
self.writel(S_SKIN, 1, "<controller id=\"{}\">".format(contid))
if (skel_source is not None):
self.writel(S_SKIN, 2, "<skin source=\"#{}\">".format(
skel_source))
else:
self.writel(S_SKIN, 2, "<skin source=\"#{}\">".format(meshid))
self.writel(
S_SKIN, 3, "<bind_shape_matrix>{}</bind_shape_matrix>".format(
strmtx(node.matrix_world)))
# Joint Names
self.writel(S_SKIN, 3, "<source id=\"{}-joints\">".format(contid))
name_values = ""
for v in si["bone_names"]:
name_values += " {}".format(v)
self.writel(
S_SKIN, 4, "<Name_array id=\"{}-joints-array\" "
"count=\"{}\">{}</Name_array>".format(
contid, len(si["bone_names"]), name_values))
self.writel(S_SKIN, 4, "<technique_common>")
self.writel(
S_SKIN, 4, "<accessor source=\"#{}-joints-array\" "
"count=\"{}\" stride=\"1\">".format(
contid, len(si["bone_names"])))
self.writel(S_SKIN, 5, "<param name=\"JOINT\" type=\"Name\"/>")
self.writel(S_SKIN, 4, "</accessor>")
self.writel(S_SKIN, 4, "</technique_common>")
self.writel(S_SKIN, 3, "</source>")
# Pose Matrices!
self.writel(S_SKIN, 3, "<source id=\"{}-bind_poses\">".format(
contid))
pose_values = ""
for v in si["bone_bind_poses"]:
pose_values += " {}".format(strmtx(v))
self.writel(
S_SKIN, 4, "<float_array id=\"{}-bind_poses-array\" "
"count=\"{}\">{}</float_array>".format(
contid, len(si["bone_bind_poses"]) * 16, pose_values))
self.writel(S_SKIN, 4, "<technique_common>")
self.writel(
S_SKIN, 4, "<accessor source=\"#{}-bind_poses-array\" "
"count=\"{}\" stride=\"16\">".format(
contid, len(si["bone_bind_poses"])))
self.writel(
S_SKIN, 5, "<param name=\"TRANSFORM\" type=\"float4x4\"/>")
self.writel(S_SKIN, 4, "</accessor>")
self.writel(S_SKIN, 4, "</technique_common>")
self.writel(S_SKIN, 3, "</source>")
# Skin Weights!
self.writel(S_SKIN, 3, "<source id=\"{}-skin_weights\">".format(
contid))
skin_weights = ""
skin_weights_total = 0
for v in vertices:
skin_weights_total += len(v.weights)
for w in v.weights:
skin_weights += " {}".format(w)
self.writel(
S_SKIN, 4, "<float_array id=\"{}-skin_weights-array\" "
"count=\"{}\">{}</float_array>".format(
contid, skin_weights_total, skin_weights))
self.writel(S_SKIN, 4, "<technique_common>")
self.writel(
S_SKIN, 4, "<accessor source=\"#{}-skin_weights-array\" "
"count=\"{}\" stride=\"1\">".format(
contid, skin_weights_total))
self.writel(S_SKIN, 5, "<param name=\"WEIGHT\" type=\"float\"/>")
self.writel(S_SKIN, 4, "</accessor>")
self.writel(S_SKIN, 4, "</technique_common>")
self.writel(S_SKIN, 3, "</source>")
self.writel(S_SKIN, 3, "<joints>")
self.writel(
S_SKIN, 4,
"<input semantic=\"JOINT\" source=\"#{}-joints\"/>".format(
contid))
self.writel(
S_SKIN, 4, "<input semantic=\"INV_BIND_MATRIX\" "
"source=\"#{}-bind_poses\"/>".format(contid))
self.writel(S_SKIN, 3, "</joints>")
self.writel(
S_SKIN, 3, "<vertex_weights count=\"{}\">".format(
len(vertices)))
self.writel(
S_SKIN, 4, "<input semantic=\"JOINT\" "
"source=\"#{}-joints\" offset=\"0\"/>".format(contid))
self.writel(
S_SKIN, 4, "<input semantic=\"WEIGHT\" "
"source=\"#{}-skin_weights\" offset=\"1\"/>".format(contid))
vcounts = ""
vs = ""
vcount = 0
for v in vertices:
vcounts += " {}".format(len(v.weights))
for b in v.bones:
vs += " {} {}".format(b, vcount)
vcount += 1
self.writel(S_SKIN, 4, "<vcount>{}</vcount>".format(vcounts))
self.writel(S_SKIN, 4, "<v>{}</v>".format(vs))
self.writel(S_SKIN, 3, "</vertex_weights>")
self.writel(S_SKIN, 2, "</skin>")
self.writel(S_SKIN, 1, "</controller>")
meshdata["skin_id"] = contid
return meshdata
def export_mesh_node(self, node, il):
if (node.data is None):
return
armature = None
armcount = 0
for n in node.modifiers:
if (n.type == "ARMATURE"):
if n.object:# make sure the armature modifier is not null
armcount += 1
if (node.parent is not None):
if (node.parent.type == "ARMATURE"):
armature = node.parent
if (armcount > 1):
self.operator.report(
{"WARNING"}, "Object \"{}\" refers "
"to more than one armature! "
"This is unsupported.".format(node.name))
if (armcount == 0):
self.operator.report(
{"WARNING"}, "Object \"{}\" is child "
"of an armature, but has no armature modifier.".format(
node.name))
if (armcount > 0 and not armature):
self.operator.report(
{"WARNING"},
"Object \"{}\" has armature modifier, but is not a child of "
"an armature. This is unsupported.".format(node.name))
if (node.data.shape_keys is not None):
sk = node.data.shape_keys
if (sk.animation_data):
for d in sk.animation_data.drivers:
if (d.driver):
for v in d.driver.variables:
for t in v.targets:
if (t.id is not None and
t.id.name in self.scene.objects):
self.armature_for_morph[
node] = self.scene.objects[t.id.name]
meshdata = self.export_mesh(node, armature)
close_controller = False
if ("skin_id" in meshdata):
close_controller = True
self.writel(
S_NODES, il, "<instance_controller url=\"#{}\">".format(
meshdata["skin_id"]))
for sn in self.skeleton_info[armature]["skeleton_nodes"]:
self.writel(
S_NODES, il + 1, "<skeleton>#{}</skeleton>".format(sn))
elif ("morph_id" in meshdata):
self.writel(
S_NODES, il, "<instance_controller url=\"#{}\">".format(
meshdata["morph_id"]))
close_controller = True
elif (armature is None):
self.writel(S_NODES, il, "<instance_geometry url=\"#{}\">".format(
meshdata["id"]))
if (len(meshdata["material_assign"]) > 0):
self.writel(S_NODES, il + 1, "<bind_material>")
self.writel(S_NODES, il + 2, "<technique_common>")
for m in meshdata["material_assign"]:
self.writel(
S_NODES, il + 3,
"<instance_material symbol=\"{}\" target=\"#{}\"/>".format(
m[1], m[0]))
self.writel(S_NODES, il + 2, "</technique_common>")
self.writel(S_NODES, il + 1, "</bind_material>")
if (close_controller):
self.writel(S_NODES, il, "</instance_controller>")
else:
self.writel(S_NODES, il, "</instance_geometry>")
def export_armature_bone(self, bone, il, si):
is_ctrl_bone = (
self.config["use_exclude_ctrl_bones"] and
(bone.name.startswith("ctrl") or bone.use_deform == False))
if (bone.parent is None and is_ctrl_bone is True):
self.operator.report(
{"WARNING"}, "Root bone cannot be a control bone:"+bone.name)
is_ctrl_bone = False
if (is_ctrl_bone is False):
boneid = self.new_id("bone")
boneidx = si["bone_count"]
si["bone_count"] += 1
bonesid = "{}-{}".format(si["id"], boneidx)
if (bone.name in self.used_bones):
if (self.config["use_anim_action_all"]):
self.operator.report(
{"WARNING"}, "Bone name \"{}\" used in more than one "
"skeleton. Actions might export wrong.".format(
bone.name))
else:
self.used_bones.append(bone.name)
si["bone_index"][bone.name] = boneidx
si["bone_ids"][bone] = boneid
si["bone_names"].append(bonesid)
self.writel(
S_NODES, il, "<node id=\"{}\" sid=\"{}\" name=\"{}\" "
"type=\"JOINT\">".format(boneid, bonesid, bone.name))
if (is_ctrl_bone is False):
il += 1
xform = bone.matrix_local
if (is_ctrl_bone is False):
si["bone_bind_poses"].append(
(si["armature_xform"] @ xform).inverted_safe())
if (bone.parent is not None):
xform = bone.parent.matrix_local.inverted_safe() @ xform
else:
si["skeleton_nodes"].append(boneid)
if (is_ctrl_bone is False):
self.writel(
S_NODES, il, "<matrix sid=\"transform\">{}</matrix>".format(
strmtx(xform)))
for c in bone.children:
self.export_armature_bone(c, il, si)
if (is_ctrl_bone is False):
il -= 1
self.writel(S_NODES, il, "</node>")
def export_armature_node(self, node, il):
if (node.data is None):
return
self.skeletons.append(node)
armature = node.data
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
}
for b in armature.bones:
if (b.parent is not None):
continue
self.export_armature_bone(b, il, self.skeleton_info[node])
if (node.pose):
for b in node.pose.bones:
for x in b.constraints:
if (x.type == "ACTION"):
self.action_constraints.append(x.action)
def export_camera_node(self, node, il):
if (node.data is None):
return
camera = node.data
camid = self.new_id("camera")
self.writel(S_CAMS, 1, "<camera id=\"{}\" name=\"{}\">".format(
camid, camera.name))
self.writel(S_CAMS, 2, "<optics>")
self.writel(S_CAMS, 3, "<technique_common>")
if (camera.type == "PERSP"):
self.writel(S_CAMS, 4, "<perspective>")
self.writel(S_CAMS, 5, "<yfov>{}</yfov>".format(
math.degrees(camera.angle))) # TODO: Review
self.writel(S_CAMS, 5, "<aspect_ratio>{}</aspect_ratio>".format(
self.scene.render.resolution_x /
self.scene.render.resolution_y))
self.writel(S_CAMS, 5, "<znear>{}</znear>".format(
camera.clip_start))
self.writel(S_CAMS, 5, "<zfar>{}</zfar>".format(camera.clip_end))
self.writel(S_CAMS, 4, "</perspective>")
else:
self.writel(S_CAMS, 4, "<orthographic>")
self.writel(S_CAMS, 5, "<xmag>{}</xmag>".format(
camera.ortho_scale * 0.5)) # TODO: Review
self.writel(S_CAMS, 5, "<aspect_ratio>{}</aspect_ratio>".format(
self.scene.render.resolution_x /
self.scene.render.resolution_y))
self.writel(S_CAMS, 5, "<znear>{}</znear>".format(
camera.clip_start))
self.writel(S_CAMS, 5, "<zfar>{}</zfar>".format(camera.clip_end))
self.writel(S_CAMS, 4, "</orthographic>")
self.writel(S_CAMS, 3, "</technique_common>")
self.writel(S_CAMS, 2, "</optics>")
self.writel(S_CAMS, 1, "</camera>")
self.writel(
S_NODES, il, "<instance_camera url=\"#{}\"/>".format(camid))
def export_lamp_node(self, node, il):
if (node.data is None):
return
light = node.data
lightid = self.new_id("light")
self.writel(S_LAMPS, 1, "<light id=\"{}\" name=\"{}\">".format(
lightid, light.name))
self.writel(S_LAMPS, 3, "<technique_common>")
if (light.type == "POINT"):
self.writel(S_LAMPS, 4, "<point>")
self.writel(S_LAMPS, 5, "<color>{}</color>".format(
strarr(light.color)))
# Convert to linear attenuation
att_by_distance = 2.0 / light.distance
self.writel(
S_LAMPS, 5,
"<linear_attenuation>{}</linear_attenuation>".format(
att_by_distance))
if (light.use_sphere):
self.writel(S_LAMPS, 5, "<zfar>{}</zfar>".format(
light.distance))
self.writel(S_LAMPS, 4, "</point>")
elif (light.type == "SPOT"):
self.writel(S_LAMPS, 4, "<spot>")
self.writel(S_LAMPS, 5, "<color>{}</color>".format(
strarr(light.color)))
# Convert to linear attenuation
att_by_distance = 2.0 / light.distance
self.writel(
S_LAMPS, 5,
"<linear_attenuation>{}</linear_attenuation>".format(
att_by_distance))
self.writel(
S_LAMPS, 5, "<falloff_angle>{}</falloff_angle>".format(
math.degrees(light.spot_size / 2)))
self.writel(S_LAMPS, 4, "</spot>")
else: # Write a sun lamp for everything else (not supported)
self.writel(S_LAMPS, 4, "<directional>")
self.writel(S_LAMPS, 5, "<color>{}</color>".format(
strarr(light.color)))
self.writel(S_LAMPS, 4, "</directional>")
self.writel(S_LAMPS, 3, "</technique_common>")
self.writel(S_LAMPS, 1, "</light>")
self.writel(S_NODES, il, "<instance_light url=\"#{}\"/>".format(
lightid))
def export_empty_node(self, node, il):
self.writel(S_NODES, 4, "<extra>")
self.writel(S_NODES, 5, "<technique profile=\"GODOT\">")
self.writel(
S_NODES, 6,
"<empty_draw_type>{}</empty_draw_type>".format(
node.empty_display_type))
self.writel(S_NODES, 5, "</technique>")
self.writel(S_NODES, 4, "</extra>")
def export_curve(self, curve):
splineid = self.new_id("spline")
self.writel(
S_GEOM, 1, "<geometry id=\"{}\" name=\"{}\">".format(
splineid, curve.name))
self.writel(S_GEOM, 2, "<spline closed=\"{}\">".format(
"true" if curve.splines and curve.splines[0].use_cyclic_u else "false"))
points = []
interps = []
handles_in = []
handles_out = []
tilts = []
for cs in curve.splines:
if (cs.type == "BEZIER"):
for s in cs.bezier_points:
points.append(s.co[0])
points.append(s.co[1])
points.append(s.co[2])
handles_in.append(s.handle_left[0])
handles_in.append(s.handle_left[1])
handles_in.append(s.handle_left[2])
handles_out.append(s.handle_right[0])
handles_out.append(s.handle_right[1])
handles_out.append(s.handle_right[2])
tilts.append(s.tilt)
interps.append("BEZIER")
else:
for s in cs.points:
points.append(s.co[0])
points.append(s.co[1])
points.append(s.co[2])
handles_in.append(s.co[0])
handles_in.append(s.co[1])
handles_in.append(s.co[2])
handles_out.append(s.co[0])
handles_out.append(s.co[1])
handles_out.append(s.co[2])
tilts.append(s.tilt)
interps.append("LINEAR")
self.writel(S_GEOM, 3, "<source id=\"{}-positions\">".format(splineid))
position_values = ""
for x in points:
position_values += " {}".format(x)
self.writel(
S_GEOM, 4, "<float_array id=\"{}-positions-array\" "
"count=\"{}\">{}</float_array>".format(
splineid, len(points), position_values))
self.writel(S_GEOM, 4, "<technique_common>")
self.writel(
S_GEOM, 5, "<accessor source=\"#{}-positions-array\" "
"count=\"{}\" stride=\"3\">".format(splineid, len(points) / 3))
self.writel(S_GEOM, 6, "<param name=\"X\" type=\"float\"/>")
self.writel(S_GEOM, 6, "<param name=\"Y\" type=\"float\"/>")
self.writel(S_GEOM, 6, "<param name=\"Z\" type=\"float\"/>")
self.writel(S_GEOM, 5, "</accessor>")
self.writel(S_GEOM, 4, "</technique_common>")
self.writel(S_GEOM, 3, "</source>")
self.writel(
S_GEOM, 3, "<source id=\"{}-intangents\">".format(splineid))
intangent_values = ""
for x in handles_in:
intangent_values += " {}".format(x)
self.writel(
S_GEOM, 4, "<float_array id=\"{}-intangents-array\" "
"count=\"{}\">{}</float_array>".format(
splineid, len(points), intangent_values))
self.writel(S_GEOM, 4, "<technique_common>")
self.writel(
S_GEOM, 5, "<accessor source=\"#{}-intangents-array\" "
"count=\"{}\" stride=\"3\">".format(splineid, len(points) / 3))
self.writel(S_GEOM, 6, "<param name=\"X\" type=\"float\"/>")
self.writel(S_GEOM, 6, "<param name=\"Y\" type=\"float\"/>")
self.writel(S_GEOM, 6, "<param name=\"Z\" type=\"float\"/>")
self.writel(S_GEOM, 5, "</accessor>")
self.writel(S_GEOM, 4, "</technique_common>")
self.writel(S_GEOM, 3, "</source>")
self.writel(S_GEOM, 3, "<source id=\"{}-outtangents\">".format(
splineid))
outtangent_values = ""
for x in handles_out:
outtangent_values += " {}".format(x)
self.writel(
S_GEOM, 4, "<float_array id=\"{}-outtangents-array\" "
"count=\"{}\">{}</float_array>".format(
splineid, len(points), outtangent_values))
self.writel(S_GEOM, 4, "<technique_common>")
self.writel(
S_GEOM, 5, "<accessor source=\"#{}-outtangents-array\" "
"count=\"{}\" stride=\"3\">".format(splineid, len(points) / 3))
self.writel(S_GEOM, 6, "<param name=\"X\" type=\"float\"/>")
self.writel(S_GEOM, 6, "<param name=\"Y\" type=\"float\"/>")
self.writel(S_GEOM, 6, "<param name=\"Z\" type=\"float\"/>")
self.writel(S_GEOM, 5, "</accessor>")
self.writel(S_GEOM, 4, "</technique_common>")
self.writel(S_GEOM, 3, "</source>")
self.writel(
S_GEOM, 3, "<source id=\"{}-interpolations\">".format(splineid))
interpolation_values = ""
for x in interps:
interpolation_values += " {}".format(x)
self.writel(
S_GEOM, 4, "<Name_array id=\"{}-interpolations-array\" "
"count=\"{}\">{}</Name_array>"
.format(splineid, len(interps), interpolation_values))
self.writel(S_GEOM, 4, "<technique_common>")
self.writel(
S_GEOM, 5, "<accessor source=\"#{}-interpolations-array\" "
"count=\"{}\" stride=\"1\">".format(splineid, len(interps)))
self.writel(S_GEOM, 6, "<param name=\"INTERPOLATION\" type=\"name\"/>")
self.writel(S_GEOM, 5, "</accessor>")
self.writel(S_GEOM, 4, "</technique_common>")
self.writel(S_GEOM, 3, "</source>")
self.writel(S_GEOM, 3, "<source id=\"{}-tilts\">".format(splineid))
tilt_values = ""
for x in tilts:
tilt_values += " {}".format(x)
self.writel(
S_GEOM, 4,
"<float_array id=\"{}-tilts-array\" count=\"{}\">{}</float_array>"
.format(splineid, len(tilts), tilt_values))
self.writel(S_GEOM, 4, "<technique_common>")
self.writel(
S_GEOM, 5, "<accessor source=\"#{}-tilts-array\" "
"count=\"{}\" stride=\"1\">".format(splineid, len(tilts)))
self.writel(S_GEOM, 6, "<param name=\"TILT\" type=\"float\"/>")
self.writel(S_GEOM, 5, "</accessor>")
self.writel(S_GEOM, 4, "</technique_common>")
self.writel(S_GEOM, 3, "</source>")
self.writel(S_GEOM, 3, "<control_vertices>")
self.writel(
S_GEOM, 4,
"<input semantic=\"POSITION\" source=\"#{}-positions\"/>"
.format(splineid))
self.writel(
S_GEOM, 4,
"<input semantic=\"IN_TANGENT\" source=\"#{}-intangents\"/>"
.format(splineid))
self.writel(
S_GEOM, 4, "<input semantic=\"OUT_TANGENT\" "
"source=\"#{}-outtangents\"/>".format(splineid))
self.writel(
S_GEOM, 4, "<input semantic=\"INTERPOLATION\" "
"source=\"#{}-interpolations\"/>".format(splineid))
self.writel(
S_GEOM, 4, "<input semantic=\"TILT\" source=\"#{}-tilts\"/>"
.format(splineid))
self.writel(S_GEOM, 3, "</control_vertices>")
self.writel(S_GEOM, 2, "</spline>")
self.writel(S_GEOM, 1, "</geometry>")
return splineid
def export_curve_node(self, node, il):
if (node.data is None):
return
curveid = self.export_curve(node.data)
self.writel(S_NODES, il, "<instance_geometry url=\"#{}\">".format(
curveid))
self.writel(S_NODES, il, "</instance_geometry>")
def export_node(self, node, il):
if (node not in self.valid_nodes):
return
prev_node = bpy.context.view_layer.objects.active
bpy.context.view_layer.objects.active = node
self.writel(
S_NODES, il, "<node id=\"{}\" name=\"{}\" type=\"NODE\">".format(
self.validate_id(node.name), node.name))
il += 1
self.writel(
S_NODES, il, "<matrix sid=\"transform\">{}</matrix>".format(
strmtx(node.matrix_local)))
if (node.type == "MESH"):
self.export_mesh_node(node, il)
elif (node.type == "CURVE"):
self.export_curve_node(node, il)
elif (node.type == "ARMATURE"):
self.export_armature_node(node, il)
elif (node.type == "CAMERA"):
self.export_camera_node(node, il)
elif (node.type == "LAMP"):
self.export_lamp_node(node, il)
elif (node.type == "EMPTY"):
self.export_empty_node(node, il)
for x in sorted(node.children, key=lambda x: x.name):
self.export_node(x, il)
il -= 1
self.writel(S_NODES, il, "</node>")
bpy.context.view_layer.objects.active = prev_node
def is_node_valid(self, node):
if (node.type not in self.config["object_types"]):
return False
if (self.config["use_active_layers"]):
valid = True
"""
for i in range(20):
if (node.layers[i] and self.scene.layers[i]):
valid = True
break
"""
# use collections instead of layers
for col in node.users_collection:
if col.hide_viewport == True:
valid = False
break
if (not valid):
return False
if (self.config["use_export_selected"] and not node.select_get()):
return False
return True
def export_scene(self):
self.writel(S_NODES, 0, "<library_visual_scenes>")
self.writel(
S_NODES, 1, "<visual_scene id=\"{}\" name=\"scene\">".format(
self.scene_name))
for obj in self.scene.objects:
if (obj in self.valid_nodes):
continue
if (self.is_node_valid(obj)):
n = obj
while (n is not None):
if (n not in self.valid_nodes):
self.valid_nodes.append(n)
n = n.parent
for obj in sorted(self.scene.objects, key=lambda x: x.name):
if (obj in self.valid_nodes and obj.parent is None):
self.export_node(obj, 2)
self.writel(S_NODES, 1, "</visual_scene>")
self.writel(S_NODES, 0, "</library_visual_scenes>")
def export_asset(self):
self.writel(S_ASSET, 0, "<asset>")
self.writel(S_ASSET, 1, "<contributor>")
author = "Anonymous"#bpy.context.preferences.system.author seems to be removed from Blender 2.8
self.writel(S_ASSET, 2, "<author>{}</author>".format(author))
self.writel(
S_ASSET, 2, "<authoring_tool>Collada Exporter for Blender 2.6+, "
"by Juan Linietsky (juan@codenix.com)</authoring_tool>")
self.writel(S_ASSET, 1, "</contributor>")
self.writel(S_ASSET, 1, "<created>{}</created>".format(
time.strftime("%Y-%m-%dT%H:%M:%SZ")))
self.writel(S_ASSET, 1, "<modified>{}</modified>".format(
time.strftime("%Y-%m-%dT%H:%M:%SZ")))
self.writel(S_ASSET, 1, "<unit meter=\"1.0\" name=\"meter\"/>")
self.writel(S_ASSET, 1, "<up_axis>Z_UP</up_axis>")
self.writel(S_ASSET, 0, "</asset>")
def export_animation_transform_channel(self, target, keys, matrices=True):
frame_total = len(keys)
anim_id = self.new_id("anim")
self.writel(S_ANIM, 1, "<animation id=\"{}\">".format(anim_id))
source_frames = ""
source_transforms = ""
source_interps = ""
for k in keys:
source_frames += " {}".format(k[0])
if (matrices):
source_transforms += " {}".format(strmtx(k[1]))
else:
source_transforms += " {}".format(k[1])
source_interps += " LINEAR"
# Time Source
self.writel(S_ANIM, 2, "<source id=\"{}-input\">".format(anim_id))
self.writel(
S_ANIM, 3, "<float_array id=\"{}-input-array\" "
"count=\"{}\">{}</float_array>".format(
anim_id, frame_total, source_frames))
self.writel(S_ANIM, 3, "<technique_common>")
self.writel(
S_ANIM, 4, "<accessor source=\"#{}-input-array\" "
"count=\"{}\" stride=\"1\">".format(anim_id, frame_total))
self.writel(S_ANIM, 5, "<param name=\"TIME\" type=\"float\"/>")
self.writel(S_ANIM, 4, "</accessor>")
self.writel(S_ANIM, 3, "</technique_common>")
self.writel(S_ANIM, 2, "</source>")
if (matrices):
# Transform Source
self.writel(
S_ANIM, 2, "<source id=\"{}-transform-output\">".format(
anim_id))
self.writel(
S_ANIM, 3, "<float_array id=\"{}-transform-output-array\" "
"count=\"{}\">{}</float_array>".format(
anim_id, frame_total * 16, source_transforms))
self.writel(S_ANIM, 3, "<technique_common>")
self.writel(
S_ANIM, 4,
"<accessor source=\"#{}-transform-output-array\" count=\"{}\" "
"stride=\"16\">".format(anim_id, frame_total))
self.writel(
S_ANIM, 5, "<param name=\"TRANSFORM\" type=\"float4x4\"/>")
self.writel(S_ANIM, 4, "</accessor>")
self.writel(S_ANIM, 3, "</technique_common>")
self.writel(S_ANIM, 2, "</source>")
else:
# Value Source
self.writel(
S_ANIM, 2,
"<source id=\"{}-transform-output\">".format(anim_id))
self.writel(
S_ANIM, 3, "<float_array id=\"{}-transform-output-array\" "
"count=\"{}\">{}</float_array>".format(
anim_id, frame_total, source_transforms))
self.writel(S_ANIM, 3, "<technique_common>")
self.writel(
S_ANIM, 4, "<accessor source=\"#{}-transform-output-array\" "
"count=\"{}\" stride=\"1\">".format(anim_id, frame_total))
self.writel(S_ANIM, 5, "<param name=\"X\" type=\"float\"/>")
self.writel(S_ANIM, 4, "</accessor>")
self.writel(S_ANIM, 3, "</technique_common>")
self.writel(S_ANIM, 2, "</source>")
# Interpolation Source
self.writel(
S_ANIM, 2, "<source id=\"{}-interpolation-output\">".format(
anim_id))
self.writel(
S_ANIM, 3, "<Name_array id=\"{}-interpolation-output-array\" "
"count=\"{}\">{}</Name_array>".format(
anim_id, frame_total, source_interps))
self.writel(S_ANIM, 3, "<technique_common>")
self.writel(
S_ANIM, 4, "<accessor source=\"#{}-interpolation-output-array\" "
"count=\"{}\" stride=\"1\">".format(anim_id, frame_total))
self.writel(S_ANIM, 5, "<param name=\"INTERPOLATION\" type=\"Name\"/>")
self.writel(S_ANIM, 4, "</accessor>")
self.writel(S_ANIM, 3, "</technique_common>")
self.writel(S_ANIM, 2, "</source>")
self.writel(S_ANIM, 2, "<sampler id=\"{}-sampler\">".format(anim_id))
self.writel(
S_ANIM, 3,
"<input semantic=\"INPUT\" source=\"#{}-input\"/>".format(anim_id))
self.writel(
S_ANIM, 3, "<input semantic=\"OUTPUT\" "
"source=\"#{}-transform-output\"/>".format(anim_id))
self.writel(
S_ANIM, 3, "<input semantic=\"INTERPOLATION\" "
"source=\"#{}-interpolation-output\"/>".format(anim_id))
self.writel(S_ANIM, 2, "</sampler>")
if (matrices):
self.writel(
S_ANIM, 2, "<channel source=\"#{}-sampler\" "
"target=\"{}/transform\"/>".format(anim_id, target))
else:
self.writel(
S_ANIM, 2, "<channel source=\"#{}-sampler\" "
"target=\"{}\"/>".format(anim_id, target))
self.writel(S_ANIM, 1, "</animation>")
return [anim_id]
def export_animation(self, start, end, allowed=None):
# TODO: Blender -> Collada frames needs a little work
# Collada starts from 0, blender usually from 1.
# The last frame must be included also
frame_orig = self.scene.frame_current
frame_len = 1.0 / self.scene.render.fps
frame_sub = 0
if (start > 0):
frame_sub = start * frame_len
tcn = []
xform_cache = {}
blend_cache = {}
# Change frames first, export objects last, boosts performance
for t in range(start, end + 1):
self.scene.frame_set(t)
key = t * frame_len - frame_sub
for node in self.scene.objects:
if (node not in self.valid_nodes):
continue
if (allowed is not None and not (node in allowed)):
if (node.type == "MESH" and node.data is not None and
(node in self.armature_for_morph) and (
self.armature_for_morph[node] in allowed)):
pass
else:
continue
if (node.type == "MESH" and node.data is not None and
node.data.shape_keys is not None and (
node.data in self.mesh_cache) and len(
node.data.shape_keys.key_blocks) and self.config["use_shape_key_export"]):
target = self.mesh_cache[node.data]["morph_id"]
for i in range(len(node.data.shape_keys.key_blocks)):
if (i == 0):
continue
name = "{}-morph-weights({})".format(target, i - 1)
if (not (name in blend_cache)):
blend_cache[name] = []
blend_cache[name].append(
(key, node.data.shape_keys.key_blocks[i].value))
if (node.type == "MESH" and node.parent and
node.parent.type == "ARMATURE"):
# In Collada, nodes that have skin modifier must not export
# animation, animate the skin instead
continue
if (len(node.constraints) > 0 or
node.animation_data is not None):
# If the node has constraints, or animation data, then
# export a sampled animation track
name = self.validate_id(node.name)
if (not (name in xform_cache)):
xform_cache[name] = []
mtx = node.matrix_world.copy()
if (node.parent):
mtx = node.parent.matrix_world.inverted_safe() @ mtx
xform_cache[name].append((key, mtx))
if (node.type == "ARMATURE"):
# All bones exported for now
for bone in node.data.bones:
if((bone.name.startswith("ctrl") or
bone.use_deform == False) and
self.config["use_exclude_ctrl_bones"]):
continue
bone_name = self.skeleton_info[node]["bone_ids"][bone]
if (not (bone_name in xform_cache)):
xform_cache[bone_name] = []
posebone = node.pose.bones[bone.name]
parent_posebone = None
mtx = posebone.matrix.copy()
if (bone.parent):
if (self.config["use_exclude_ctrl_bones"]):
current_parent_posebone = bone.parent
while ((current_parent_posebone.name
.startswith("ctrl") or
current_parent_posebone.use_deform
== False) and
current_parent_posebone.parent):
current_parent_posebone = (
current_parent_posebone.parent)
parent_posebone = node.pose.bones[
current_parent_posebone.name]
else:
parent_posebone = node.pose.bones[
bone.parent.name]
parent_invisible = False
for i in range(3):
if (parent_posebone.scale[i] == 0.0):
parent_invisible = True
if (not parent_invisible):
mtx = (
parent_posebone.matrix
.inverted_safe() @ mtx)
xform_cache[bone_name].append((key, mtx))
self.scene.frame_set(frame_orig)
# Export animation XML
for nid in xform_cache:
tcn += self.export_animation_transform_channel(
nid, xform_cache[nid], True)
for nid in blend_cache:
tcn += self.export_animation_transform_channel(
nid, blend_cache[nid], False)
return tcn
def export_animations(self):
tmp_mat = []
for s in self.skeletons:
tmp_bone_mat = []
for bone in s.pose.bones:
tmp_bone_mat.append(Matrix(bone.matrix_basis))
bone.matrix_basis = Matrix()
tmp_mat.append([Matrix(s.matrix_local), tmp_bone_mat])
self.writel(S_ANIM, 0, "<library_animations>")
if (self.config["use_anim_action_all"] and len(self.skeletons)):
cached_actions = {}
for s in self.skeletons:
if s.animation_data and s.animation_data.action:
cached_actions[s] = s.animation_data.action.name
self.writel(S_ANIM_CLIPS, 0, "<library_animation_clips>")
for x in bpy.data.actions[:]:
if x.users == 0 or x in self.action_constraints:
continue
if (self.config["use_anim_skip_noexp"] and
x.name.endswith("-noexp")):
continue
bones = []
# Find bones used
for p in x.fcurves:
dp = p.data_path
base = "pose.bones[\""
if dp.startswith(base):
dp = dp[len(base):]
if (dp.find("\"") != -1):
dp = dp[:dp.find("\"")]
if (dp not in bones):
bones.append(dp)
allowed_skeletons = []
for i, y in enumerate(self.skeletons):
if (y.animation_data):
for z in y.pose.bones:
if (z.bone.name in bones):
if (y not in allowed_skeletons):
allowed_skeletons.append(y)
y.animation_data.action = x
y.matrix_local = tmp_mat[i][0]
for j, bone in enumerate(s.pose.bones):
bone.matrix_basis = Matrix()
tcn = self.export_animation(int(x.frame_range[0]), int(
x.frame_range[1] + 0.5), allowed_skeletons)
framelen = (1.0 / self.scene.render.fps)
start = x.frame_range[0] * framelen
end = x.frame_range[1] * framelen
self.writel(
S_ANIM_CLIPS, 1, "<animation_clip name=\"{}\" "
"start=\"{}\" end=\"{}\">".format(x.name, start, end))
for z in tcn:
self.writel(S_ANIM_CLIPS, 2,
"<instance_animation url=\"#{}\"/>".format(z))
self.writel(S_ANIM_CLIPS, 1, "</animation_clip>")
if (len(tcn) == 0):
self.operator.report(
{"WARNING"}, "Animation clip \"{}\" contains no "
"tracks.".format(x.name))
self.writel(S_ANIM_CLIPS, 0, "</library_animation_clips>")
for i, s in enumerate(self.skeletons):
if (s.animation_data is None):
continue
if s in cached_actions:
s.animation_data.action = bpy.data.actions[
cached_actions[s]]
else:
s.animation_data.action = None
for j, bone in enumerate(s.pose.bones):
bone.matrix_basis = tmp_mat[i][1][j]
else:
self.export_animation(self.scene.frame_start, self.scene.frame_end)
self.writel(S_ANIM, 0, "</library_animations>")
def export(self):
self.writel(S_GEOM, 0, "<library_geometries>")
self.writel(S_CONT, 0, "<library_controllers>")
self.writel(S_CAMS, 0, "<library_cameras>")
self.writel(S_LAMPS, 0, "<library_lights>")
self.writel(S_IMGS, 0, "<library_images>")
self.writel(S_MATS, 0, "<library_materials>")
self.writel(S_FX, 0, "<library_effects>")
self.export_asset()
self.export_scene()
self.writel(S_GEOM, 0, "</library_geometries>")
# Morphs always go before skin controllers
if S_MORPH in self.sections:
for l in self.sections[S_MORPH]:
self.writel(S_CONT, 0, l)
del self.sections[S_MORPH]
if S_SKIN in self.sections:
for l in self.sections[S_SKIN]:
self.writel(S_CONT, 0, l)
del self.sections[S_SKIN]
self.writel(S_CONT, 0, "</library_controllers>")
self.writel(S_CAMS, 0, "</library_cameras>")
self.writel(S_LAMPS, 0, "</library_lights>")
self.writel(S_IMGS, 0, "</library_images>")
self.writel(S_MATS, 0, "</library_materials>")
self.writel(S_FX, 0, "</library_effects>")
self.purge_empty_nodes()
if (self.config["use_anim"]):
self.export_animations()
try:
f = open(self.path, "wb")
except:
return False
f.write(bytes("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n", "UTF-8"))
f.write(bytes(
"<COLLADA xmlns=\"http://www.collada.org/2005/11/COLLADASchema\" "
"version=\"1.4.1\">\n", "UTF-8"))
s = []
for x in self.sections.keys():
s.append(x)
s.sort()
for x in s:
for l in self.sections[x]:
f.write(bytes(l + "\n", "UTF-8"))
f.write(bytes("<scene>\n", "UTF-8"))
f.write(bytes(
"\t<instance_visual_scene url=\"#{}\" />\n".format(
self.scene_name), "UTF-8"))
f.write(bytes("</scene>\n", "UTF-8"))
f.write(bytes("</COLLADA>\n", "UTF-8"))
return True
__slots__ = ("operator", "scene", "last_id", "scene_name", "sections",
"path", "mesh_cache", "curve_cache", "material_cache",
"image_cache", "skeleton_info", "config", "valid_nodes",
"armature_for_morph", "used_bones", "wrongvtx_report",
"skeletons", "action_constraints", "temp_meshes")
def __init__(self, path, kwargs, operator):
self.operator = operator
self.scene = bpy.context.scene
self.last_id = 0
self.scene_name = self.new_id("scene")
self.sections = {}
self.path = path
self.mesh_cache = {}
self.temp_meshes = set()
self.curve_cache = {}
self.material_cache = {}
self.image_cache = {}
self.skeleton_info = {}
self.config = kwargs
self.valid_nodes = []
self.armature_for_morph = {}
self.used_bones = []
self.wrongvtx_report = False
self.skeletons = []
self.action_constraints = []
def __enter__(self):
return self
def __exit__(self, *exc):
pass
"""
for mesh in self.temp_meshes:
bpy.data.meshes.remove(mesh)
"""
def save(operator, context, filepath="", use_selection=False, **kwargs):
with DaeExporter(filepath, kwargs, operator) as exp:
exp.export()
return {"FINISHED"}