mirror of
https://github.com/minetest/irrlicht.git
synced 2024-12-27 19:20:31 +01:00
1612 lines
60 KiB
Python
1612 lines
60 KiB
Python
|
#!BPY
|
||
|
|
||
|
"""
|
||
|
Name: 'B3D Exporter (.b3d)...'
|
||
|
Blender: 259
|
||
|
Group: 'Export'
|
||
|
Tooltip: 'Export to Blitz3D file format (.b3d)'
|
||
|
"""
|
||
|
__author__ = ["iego 'GaNDaLDF' Parisi, MTLZ (is06), Joerg Henrichs, Marianne Gagnon"]
|
||
|
__url__ = ["www.gandaldf.com"]
|
||
|
__version__ = "3.0"
|
||
|
__bpydoc__ = """\
|
||
|
"""
|
||
|
|
||
|
# BLITZ3D EXPORTER 3.0
|
||
|
# Copyright (C) 2009 by Diego "GaNDaLDF" Parisi - www.gandaldf.com
|
||
|
# Lightmap issue fixed by Capricorn 76 Pty. Ltd. - www.capricorn76.com
|
||
|
# Blender 2.63 compatiblity based on work by MTLZ, www.is06.com
|
||
|
# With changes by Marianne Gagnon, Joerg Henrichs and Vincent Lejeune, supertuxkart.sf.net (Copyright (C) 2011-2012)
|
||
|
#
|
||
|
# LICENSE:
|
||
|
# 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, write to the Free Software
|
||
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||
|
|
||
|
bl_info = {
|
||
|
"name": "B3D (BLITZ3D) Model Exporter",
|
||
|
"description": "Exports a blender scene or object to the B3D (BLITZ3D) format",
|
||
|
"author": "Diego 'GaNDaLDF' Parisi, MTLZ (is06), Joerg Henrichs, Marianne Gagnon, Vincent Lejeune",
|
||
|
"version": (3,1),
|
||
|
"blender": (2, 5, 9),
|
||
|
"api": 31236,
|
||
|
"location": "File > Export",
|
||
|
"warning": '', # used for warning icon and text in addons panel
|
||
|
"wiki_url": "http://supertuxkart.sourceforge.net/Get_involved",
|
||
|
"tracker_url": "https://sourceforge.net/apps/trac/supertuxkart/",
|
||
|
"category": "Import-Export"}
|
||
|
|
||
|
|
||
|
import bpy
|
||
|
import sys,os,os.path,struct,math,string
|
||
|
import mathutils
|
||
|
import math
|
||
|
|
||
|
if not hasattr(sys,"argv"): sys.argv = ["???"]
|
||
|
|
||
|
|
||
|
#Global Stacks
|
||
|
b3d_parameters = {}
|
||
|
texture_flags = []
|
||
|
texs_stack = {}
|
||
|
brus_stack = []
|
||
|
vertex_groups = []
|
||
|
bone_stack = {}
|
||
|
keys_stack = []
|
||
|
|
||
|
texture_count = 0
|
||
|
|
||
|
# bone_stack indices constants
|
||
|
BONE_PARENT_MATRIX = 0
|
||
|
BONE_PARENT = 1
|
||
|
BONE_ITSELF = 2
|
||
|
|
||
|
# texture stack indices constants
|
||
|
TEXTURE_ID = 0
|
||
|
TEXTURE_FLAGS = 1
|
||
|
|
||
|
per_face_vertices = {}
|
||
|
|
||
|
the_scene = None
|
||
|
|
||
|
#Transformation Matrix
|
||
|
TRANS_MATRIX = mathutils.Matrix([[1,0,0,0],[0,0,1,0],[0,1,0,0],[0,0,0,1]])
|
||
|
BONE_TRANS_MATRIX = mathutils.Matrix([[-1,0,0,0],[0,0,-1,0],[0,-1,0,0],[0,0,0,1]])
|
||
|
|
||
|
DEBUG = False
|
||
|
PROGRESS = True
|
||
|
PROGRESS_VERBOSE = False
|
||
|
|
||
|
tesselated_objects = {}
|
||
|
|
||
|
#Support Functions
|
||
|
def write_int(value):
|
||
|
return struct.pack("<i",value)
|
||
|
|
||
|
def write_float(value):
|
||
|
return struct.pack("<f",value)
|
||
|
|
||
|
def write_float_couple(value1, value2):
|
||
|
return struct.pack("<ff", value1, value2)
|
||
|
|
||
|
def write_float_triplet(value1, value2, value3):
|
||
|
return struct.pack("<fff", value1, value2, value3)
|
||
|
|
||
|
def write_float_quad(value1, value2, value3, value4):
|
||
|
return struct.pack("<ffff", value1, value2, value3, value4)
|
||
|
|
||
|
def write_string(value):
|
||
|
encoded = str.encode(value)
|
||
|
if len(encoded) > 48:
|
||
|
value = encoded[0:48]
|
||
|
binary_format = "<%ds"%(len(encoded)+1)
|
||
|
return struct.pack(binary_format, encoded)
|
||
|
|
||
|
def write_chunk(name,value):
|
||
|
dummy = bytearray()
|
||
|
return dummy + name + write_int(len(value)) + value
|
||
|
|
||
|
trimmed_paths = {}
|
||
|
|
||
|
def getArmatureAnimationEnd(armature):
|
||
|
end_frame = 1
|
||
|
if armature.animation_data.action:
|
||
|
ipo = armature.animation_data.action.fcurves
|
||
|
for curve in ipo:
|
||
|
if "pose" in curve.data_path:
|
||
|
end_frame = max(end_frame, curve.keyframe_points[-1].co[0])
|
||
|
|
||
|
for nla_track in armature.animation_data.nla_tracks:
|
||
|
if len(nla_track.strips) > 0:
|
||
|
end_frame = max(end_frame, nla_track.strips[-1].frame_end)
|
||
|
|
||
|
return end_frame
|
||
|
|
||
|
# ==== Write B3D File ====
|
||
|
# (main exporter function)
|
||
|
def write_b3d_file(filename, objects=[]):
|
||
|
global texture_flags, texs_stack, trimmed_paths, tesselated_objects
|
||
|
global brus_stack, vertex_groups, bone_stack, keys_stack
|
||
|
|
||
|
#Global Stacks
|
||
|
texture_flags = []
|
||
|
texs_stack = {}
|
||
|
brus_stack = []
|
||
|
vertex_groups = []
|
||
|
bone_stack = []
|
||
|
keys_stack = []
|
||
|
trimmed_paths = {}
|
||
|
file_buf = bytearray()
|
||
|
temp_buf = bytearray()
|
||
|
tesselated_objects = {}
|
||
|
|
||
|
import time
|
||
|
start = time.time()
|
||
|
|
||
|
temp_buf += write_int(1) #Version
|
||
|
temp_buf += write_texs(objects) #TEXS
|
||
|
temp_buf += write_brus(objects) #BRUS
|
||
|
temp_buf += write_node(objects) #NODE
|
||
|
|
||
|
if len(temp_buf) > 0:
|
||
|
file_buf += write_chunk(b"BB3D",temp_buf)
|
||
|
temp_buf = ""
|
||
|
|
||
|
file = open(filename,'wb')
|
||
|
file.write(file_buf)
|
||
|
file.close()
|
||
|
|
||
|
# free memory
|
||
|
trimmed_paths = {}
|
||
|
|
||
|
end = time.time()
|
||
|
|
||
|
print("Exported in", (end - start))
|
||
|
|
||
|
|
||
|
def tesselate_if_needed(objdata):
|
||
|
if objdata not in tesselated_objects:
|
||
|
objdata.calc_tessface()
|
||
|
tesselated_objects[objdata] = True
|
||
|
return objdata
|
||
|
|
||
|
def getUVTextures(obj_data):
|
||
|
# BMesh in blender 2.63 broke this
|
||
|
if bpy.app.version[1] >= 63:
|
||
|
return tesselate_if_needed(obj_data).tessface_uv_textures
|
||
|
else:
|
||
|
return obj_data.uv_textures
|
||
|
|
||
|
def getFaces(obj_data):
|
||
|
# BMesh in blender 2.63 broke this
|
||
|
if bpy.app.version[1] >= 63:
|
||
|
return tesselate_if_needed(obj_data).tessfaces
|
||
|
else:
|
||
|
return obj_data.faces
|
||
|
|
||
|
def getVertexColors(obj_data):
|
||
|
# BMesh in blender 2.63 broke this
|
||
|
if bpy.app.version[1] >= 63:
|
||
|
return tesselate_if_needed(obj_data).tessface_vertex_colors
|
||
|
else:
|
||
|
return obj_data.vertex_colors
|
||
|
|
||
|
# ==== Write TEXS Chunk ====
|
||
|
def write_texs(objects=[]):
|
||
|
global b3d_parameters
|
||
|
global trimmed_paths
|
||
|
global texture_count
|
||
|
texs_buf = bytearray()
|
||
|
temp_buf = bytearray()
|
||
|
layer_max = 0
|
||
|
obj_count = 0
|
||
|
set_wrote = 0
|
||
|
|
||
|
if objects:
|
||
|
exp_obj = objects
|
||
|
else:
|
||
|
if b3d_parameters.get("export-selected"):
|
||
|
exp_obj = [ob for ob in bpy.data.objects if ob.select]
|
||
|
else:
|
||
|
exp_obj = bpy.data.objects
|
||
|
|
||
|
if PROGRESS: print(len(exp_obj),"TEXS")
|
||
|
|
||
|
if PROGRESS_VERBOSE: progress = 0
|
||
|
|
||
|
for obj in exp_obj:
|
||
|
|
||
|
if PROGRESS_VERBOSE:
|
||
|
progress = progress + 1
|
||
|
if (progress % 10 == 0): print("TEXS",progress,"/",len(exp_obj))
|
||
|
|
||
|
if obj.type == "MESH":
|
||
|
set_count = 0
|
||
|
set_wrote = 0
|
||
|
#data = obj.getData(mesh = True)
|
||
|
data = obj.data
|
||
|
|
||
|
# FIXME?
|
||
|
#orig_uvlayer = data.activeUVLayer
|
||
|
|
||
|
layer_set = [[],[],[],[],[],[],[],[]]
|
||
|
|
||
|
# 8 UV layers are supported
|
||
|
texture_flags.append([None,None,None,None,None,None,None,None])
|
||
|
|
||
|
#if len(data.getUVLayerNames()) <= 8:
|
||
|
uv_textures = getUVTextures(data)
|
||
|
if len(uv_textures) <= 8:
|
||
|
if len(uv_textures) > layer_max:
|
||
|
layer_max = len(uv_textures)
|
||
|
else:
|
||
|
layer_max = 8
|
||
|
|
||
|
for face in getFaces(data):
|
||
|
for iuvlayer,uvlayer in enumerate(uv_textures):
|
||
|
if iuvlayer < 8:
|
||
|
|
||
|
# FIXME?
|
||
|
#data.activeUVLayer = uvlayer
|
||
|
|
||
|
#layer_set[iuvlayer].append(face.uv)
|
||
|
new_data = None
|
||
|
try:
|
||
|
new_data = uvlayer.data[face.index].uv
|
||
|
except:
|
||
|
pass
|
||
|
|
||
|
layer_set[iuvlayer].append( new_data )
|
||
|
|
||
|
for i in range(len(uv_textures)):
|
||
|
if set_wrote:
|
||
|
set_count += 1
|
||
|
set_wrote = 0
|
||
|
|
||
|
for iuvlayer in range(i,len(uv_textures)):
|
||
|
if layer_set[i] == layer_set[iuvlayer]:
|
||
|
if texture_flags[obj_count][iuvlayer] is None:
|
||
|
if set_count == 0:
|
||
|
tex_flag = 1
|
||
|
elif set_count == 1:
|
||
|
tex_flag = 65536
|
||
|
elif set_count > 1:
|
||
|
tex_flag = 1
|
||
|
if b3d_parameters.get("mipmap"):
|
||
|
enable_mipmaps=8
|
||
|
else:
|
||
|
enable_mipmaps=0
|
||
|
texture_flags[obj_count][iuvlayer] = tex_flag | enable_mipmaps
|
||
|
set_wrote = 1
|
||
|
|
||
|
for face in getFaces(data):
|
||
|
for iuvlayer,uvlayer in enumerate(uv_textures):
|
||
|
if iuvlayer < 8:
|
||
|
|
||
|
if not (iuvlayer < len(uv_textures)):
|
||
|
continue
|
||
|
|
||
|
# FIXME?
|
||
|
#data.activeUVLayer = uvlayer
|
||
|
|
||
|
#if DEBUG: print("<uv face=", face.index, ">")
|
||
|
|
||
|
img = getUVTextures(data)[iuvlayer].data[face.index].image
|
||
|
|
||
|
if img:
|
||
|
|
||
|
if img.filepath in trimmed_paths:
|
||
|
img_name = trimmed_paths[img.filepath]
|
||
|
else:
|
||
|
img_name = bpy.path.basename(img.filepath)
|
||
|
trimmed_paths[img.filepath] = img_name
|
||
|
|
||
|
if not img_name in texs_stack:
|
||
|
texs_stack[img_name] = [len(texs_stack), texture_flags[obj_count][iuvlayer]]
|
||
|
temp_buf += write_string(img_name) #Texture File Name
|
||
|
temp_buf += write_int(texture_flags[obj_count][iuvlayer]) #Flags
|
||
|
temp_buf += write_int(2) #Blend
|
||
|
temp_buf += write_float(0) #X_Pos
|
||
|
temp_buf += write_float(0) #Y_Pos
|
||
|
temp_buf += write_float(1) #X_Scale
|
||
|
temp_buf += write_float(1) #Y_Scale
|
||
|
temp_buf += write_float(0) #Rotation
|
||
|
#else:
|
||
|
# if DEBUG: print(" <image id=(previous)","name=","'"+img_name+"'","/>")
|
||
|
|
||
|
#if DEBUG: print("</uv>")
|
||
|
|
||
|
obj_count += 1
|
||
|
|
||
|
#FIXME?
|
||
|
#if orig_uvlayer:
|
||
|
# data.activeUVLayer = orig_uvlayer
|
||
|
|
||
|
texture_count = layer_max
|
||
|
|
||
|
if len(temp_buf) > 0:
|
||
|
texs_buf += write_chunk(b"TEXS",temp_buf)
|
||
|
temp_buf = ""
|
||
|
|
||
|
return texs_buf
|
||
|
|
||
|
# ==== Write BRUS Chunk ====
|
||
|
def write_brus(objects=[]):
|
||
|
global b3d_parameters
|
||
|
global trimmed_paths
|
||
|
global texture_count
|
||
|
brus_buf = bytearray()
|
||
|
temp_buf = bytearray()
|
||
|
mat_count = 0
|
||
|
obj_count = 0
|
||
|
|
||
|
if DEBUG: print("<!-- BRUS chunk -->")
|
||
|
|
||
|
if objects:
|
||
|
exp_obj = objects
|
||
|
else:
|
||
|
if b3d_parameters.get("export-selected"):
|
||
|
exp_obj = [ob for ob in bpy.data.objects if ob.select]
|
||
|
else:
|
||
|
exp_obj = bpy.data.objects
|
||
|
|
||
|
if PROGRESS: print(len(exp_obj),"BRUS")
|
||
|
if PROGRESS_VERBOSE: progress = 0
|
||
|
|
||
|
for obj in exp_obj:
|
||
|
|
||
|
if PROGRESS_VERBOSE:
|
||
|
progress += 1
|
||
|
if (progress % 10 == 0): print("BRUS",progress,"/",len(exp_obj))
|
||
|
|
||
|
if obj.type == "MESH":
|
||
|
data = obj.data
|
||
|
|
||
|
uv_textures = getUVTextures(data)
|
||
|
|
||
|
if len(uv_textures) <= 0:
|
||
|
continue
|
||
|
|
||
|
if DEBUG: print("<obj name=",obj.name,">")
|
||
|
|
||
|
img_found = 0
|
||
|
|
||
|
for face in getFaces(data):
|
||
|
|
||
|
face_stack = []
|
||
|
|
||
|
for iuvlayer,uvlayer in enumerate(uv_textures):
|
||
|
if iuvlayer < 8:
|
||
|
|
||
|
img_id = -1
|
||
|
|
||
|
if face.index >= len(uv_textures[iuvlayer].data):
|
||
|
continue
|
||
|
|
||
|
img = uv_textures[iuvlayer].data[face.index].image
|
||
|
|
||
|
if not img:
|
||
|
continue
|
||
|
|
||
|
img_found = 1
|
||
|
|
||
|
if img.filepath in trimmed_paths:
|
||
|
img_name = trimmed_paths[img.filepath]
|
||
|
else:
|
||
|
img_name = os.path.basename(img.filepath)
|
||
|
trimmed_paths[img.filepath] = img_name
|
||
|
|
||
|
if DEBUG: print(" <!-- Building FACE 'stack' -->")
|
||
|
|
||
|
if img_name in texs_stack:
|
||
|
img_id = texs_stack[img_name][TEXTURE_ID]
|
||
|
|
||
|
face_stack.insert(iuvlayer,img_id)
|
||
|
if DEBUG: print(" <uv face=",face.index,"layer=", iuvlayer, " imgid=", img_id, "/>")
|
||
|
|
||
|
for i in range(len(face_stack),texture_count):
|
||
|
face_stack.append(-1)
|
||
|
|
||
|
|
||
|
if DEBUG: print(" <!-- Writing chunk -->")
|
||
|
|
||
|
if not img_found:
|
||
|
if data.materials:
|
||
|
if data.materials[face.material_index]:
|
||
|
mat_data = data.materials[face.material_index]
|
||
|
mat_colr = mat_data.diffuse_color[0]
|
||
|
mat_colg = mat_data.diffuse_color[1]
|
||
|
mat_colb = mat_data.diffuse_color[2]
|
||
|
mat_alpha = mat_data.alpha
|
||
|
mat_name = mat_data.name
|
||
|
|
||
|
if not mat_name in brus_stack:
|
||
|
brus_stack.append(mat_name)
|
||
|
temp_buf += write_string(mat_name) #Brush Name
|
||
|
temp_buf += write_float(mat_colr) #Red
|
||
|
temp_buf += write_float(mat_colg) #Green
|
||
|
temp_buf += write_float(mat_colb) #Blue
|
||
|
temp_buf += write_float(mat_alpha) #Alpha
|
||
|
temp_buf += write_float(0) #Shininess
|
||
|
temp_buf += write_int(1) #Blend
|
||
|
if b3d_parameters.get("vertex-colors") and len(getVertexColors(data)):
|
||
|
temp_buf += write_int(2) #Fx
|
||
|
else:
|
||
|
temp_buf += write_int(0) #Fx
|
||
|
|
||
|
for i in face_stack:
|
||
|
temp_buf += write_int(i) #Texture ID
|
||
|
else:
|
||
|
if b3d_parameters.get("vertex-colors") and len(getVertexColors(data)) > 0:
|
||
|
if not face_stack in brus_stack:
|
||
|
brus_stack.append(face_stack)
|
||
|
mat_count += 1
|
||
|
temp_buf += write_string("Brush.%.3i"%mat_count) #Brush Name
|
||
|
temp_buf += write_float(1) #Red
|
||
|
temp_buf += write_float(1) #Green
|
||
|
temp_buf += write_float(1) #Blue
|
||
|
temp_buf += write_float(1) #Alpha
|
||
|
temp_buf += write_float(0) #Shininess
|
||
|
temp_buf += write_int(1) #Blend
|
||
|
temp_buf += write_int(2) #Fx
|
||
|
|
||
|
for i in face_stack:
|
||
|
temp_buf += write_int(i) #Texture ID
|
||
|
else: # img_found
|
||
|
|
||
|
if not face_stack in brus_stack:
|
||
|
brus_stack.append(face_stack)
|
||
|
mat_count += 1
|
||
|
temp_buf += write_string("Brush.%.3i"%mat_count) #Brush Name
|
||
|
temp_buf += write_float(1) #Red
|
||
|
temp_buf += write_float(1) #Green
|
||
|
temp_buf += write_float(1) #Blue
|
||
|
temp_buf += write_float(1) #Alpha
|
||
|
temp_buf += write_float(0) #Shininess
|
||
|
temp_buf += write_int(1) #Blend
|
||
|
|
||
|
if DEBUG: print(" <brush id=",len(brus_stack),">")
|
||
|
|
||
|
if b3d_parameters.get("vertex-colors") and len(getVertexColors(data)) > 0:
|
||
|
temp_buf += write_int(2) #Fx
|
||
|
else:
|
||
|
temp_buf += write_int(0) #Fx
|
||
|
|
||
|
for i in face_stack:
|
||
|
temp_buf += write_int(i) #Texture ID
|
||
|
if DEBUG: print(" <texture id=",i,">")
|
||
|
|
||
|
if DEBUG: print(" </brush>")
|
||
|
|
||
|
if DEBUG: print("")
|
||
|
|
||
|
if DEBUG: print("</obj>")
|
||
|
obj_count += 1
|
||
|
|
||
|
#FIXME?
|
||
|
#if orig_uvlayer:
|
||
|
# data.activeUVLayer = orig_uvlayer
|
||
|
|
||
|
if len(temp_buf) > 0:
|
||
|
brus_buf += write_chunk(b"BRUS",write_int(texture_count) + temp_buf) #N Texs
|
||
|
temp_buf = ""
|
||
|
|
||
|
return brus_buf
|
||
|
|
||
|
# ==== Write NODE Chunk ====
|
||
|
def write_node(objects=[]):
|
||
|
global bone_stack
|
||
|
global keys_stack
|
||
|
global b3d_parameters
|
||
|
global the_scene
|
||
|
|
||
|
root_buf = []
|
||
|
node_buf = []
|
||
|
main_buf = bytearray()
|
||
|
temp_buf = []
|
||
|
obj_count = 0
|
||
|
amb_light = 0
|
||
|
|
||
|
num_mesh = 0
|
||
|
num_ligs = 0
|
||
|
num_cams = 0
|
||
|
num_lorc = 0
|
||
|
#exp_scn = Blender.Scene.GetCurrent()
|
||
|
#exp_scn = the_scene
|
||
|
#exp_con = exp_scn.getRenderingContext()
|
||
|
|
||
|
#first_frame = Blender.Draw.Create(exp_con.startFrame())
|
||
|
#last_frame = Blender.Draw.Create(exp_con.endFrame())
|
||
|
#num_frames = last_frame.val - first_frame.val
|
||
|
first_frame = the_scene.frame_start
|
||
|
|
||
|
if DEBUG: print("<node first_frame=", first_frame, ">")
|
||
|
|
||
|
if objects:
|
||
|
exp_obj = objects
|
||
|
else:
|
||
|
if b3d_parameters.get("export-selected"):
|
||
|
exp_obj = [ob for ob in bpy.data.objects if ob.select]
|
||
|
else:
|
||
|
exp_obj = bpy.data.objects
|
||
|
|
||
|
for obj in exp_obj:
|
||
|
if obj.type == "MESH":
|
||
|
num_mesh += 1
|
||
|
if obj.type == "CAMERA":
|
||
|
num_cams += 1
|
||
|
if obj.type == "LAMP":
|
||
|
num_ligs += 1
|
||
|
|
||
|
if b3d_parameters.get("cameras"):
|
||
|
num_lorc += num_cams
|
||
|
|
||
|
if b3d_parameters.get("lights"):
|
||
|
num_lorc += 1
|
||
|
num_lorc += num_ligs
|
||
|
|
||
|
if num_mesh + num_lorc > 1:
|
||
|
exp_root = 1
|
||
|
else:
|
||
|
exp_root = 0
|
||
|
|
||
|
if exp_root:
|
||
|
root_buf.append(write_string("ROOT")) #Node Name
|
||
|
|
||
|
root_buf.append(write_float_triplet(0, 0, 0)) #Position X,Y,Z
|
||
|
root_buf.append(write_float_triplet(1, 1, 1)) #Scale X, Y, Z
|
||
|
root_buf.append(write_float_quad(1, 0, 0, 0)) #Rotation W, X, Y, Z
|
||
|
|
||
|
if PROGRESS: progress = 0
|
||
|
|
||
|
for obj in exp_obj:
|
||
|
|
||
|
if PROGRESS:
|
||
|
progress += 1
|
||
|
print("NODE:",progress,"/",len(exp_obj),obj.name)
|
||
|
|
||
|
if obj.type == "MESH":
|
||
|
|
||
|
if DEBUG: print(" <mesh name=",obj.name,">")
|
||
|
|
||
|
bone_stack = {}
|
||
|
keys_stack = []
|
||
|
|
||
|
anim_data = None
|
||
|
|
||
|
# check if this object has an armature modifier
|
||
|
for curr_mod in obj.modifiers:
|
||
|
if curr_mod.type == 'ARMATURE':
|
||
|
arm = curr_mod.object
|
||
|
if arm is not None:
|
||
|
anim_data = arm.animation_data
|
||
|
|
||
|
# check if this object has an armature parent (second way to do armature animations in blender)
|
||
|
if anim_data is None:
|
||
|
if obj.parent:
|
||
|
if obj.parent.type == "ARMATURE":
|
||
|
arm = obj.parent
|
||
|
if arm.animation_data:
|
||
|
anim_data = arm.animation_data
|
||
|
|
||
|
if anim_data:
|
||
|
matrix = mathutils.Matrix()
|
||
|
|
||
|
temp_buf.append(write_string(obj.name)) #Node Name
|
||
|
|
||
|
position = matrix.to_translation()
|
||
|
temp_buf.append(write_float_triplet(position[0], position[1], position[2])) #Position X, Y, Z
|
||
|
|
||
|
scale = matrix.to_scale()
|
||
|
temp_buf.append(write_float_triplet(scale[0], scale[2], scale[1])) #Scale X, Y, Z
|
||
|
|
||
|
if DEBUG: print(" <arm name=", obj.name, " loc=", -position[0], position[1], position[2], " scale=", scale[0], scale[1], scale[2], "/>")
|
||
|
|
||
|
quat = matrix.to_quaternion()
|
||
|
quat.normalize()
|
||
|
|
||
|
temp_buf.append(write_float_quad(quat.w, quat.x, quat.z, quat.y))
|
||
|
else:
|
||
|
if b3d_parameters.get("local-space"):
|
||
|
matrix = TRANS_MATRIX.copy()
|
||
|
scale_matrix = mathutils.Matrix()
|
||
|
else:
|
||
|
matrix = obj.matrix_world*TRANS_MATRIX
|
||
|
scale_matrix = obj.matrix_world.copy()
|
||
|
|
||
|
|
||
|
if bpy.app.version[1] >= 62:
|
||
|
# blender 2.62 broke the API : Column-major access was changed to row-major access
|
||
|
tmp = mathutils.Vector([matrix[0][1], matrix[1][1], matrix[2][1], matrix[3][1]])
|
||
|
matrix[0][1] = matrix[0][2]
|
||
|
matrix[1][1] = matrix[1][2]
|
||
|
matrix[2][1] = matrix[2][2]
|
||
|
matrix[3][1] = matrix[3][2]
|
||
|
|
||
|
matrix[0][2] = tmp[0]
|
||
|
matrix[1][2] = tmp[1]
|
||
|
matrix[2][2] = tmp[2]
|
||
|
matrix[3][2] = tmp[3]
|
||
|
else:
|
||
|
tmp = mathutils.Vector(matrix[1])
|
||
|
matrix[1] = matrix[2]
|
||
|
matrix[2] = tmp
|
||
|
|
||
|
temp_buf.append(write_string(obj.name)) #Node Name
|
||
|
|
||
|
#print("Matrix : ", matrix)
|
||
|
position = matrix.to_translation()
|
||
|
|
||
|
temp_buf.append(write_float_triplet(position[0], position[2], position[1]))
|
||
|
|
||
|
scale = scale_matrix.to_scale()
|
||
|
temp_buf.append(write_float_triplet(scale[0], scale[2], scale[1]))
|
||
|
|
||
|
quat = matrix.to_quaternion()
|
||
|
quat.normalize()
|
||
|
|
||
|
temp_buf.append(write_float_quad(quat.w, quat.x, quat.z, quat.y))
|
||
|
|
||
|
if DEBUG:
|
||
|
print(" <position>",position[0],position[2],position[1],"</position>")
|
||
|
print(" <scale>",scale[0],scale[1],scale[2],"</scale>")
|
||
|
print(" <rotation>", quat.w, quat.x, quat.y, quat.z, "</rotation>")
|
||
|
|
||
|
if anim_data:
|
||
|
the_scene.frame_set(1,subframe=0.0)
|
||
|
|
||
|
arm_matrix = arm.matrix_world
|
||
|
|
||
|
if b3d_parameters.get("local-space"):
|
||
|
arm_matrix = mathutils.Matrix()
|
||
|
|
||
|
def read_armature(arm_matrix,bone,parent = None):
|
||
|
if (parent and not bone.parent.name == parent.name):
|
||
|
return
|
||
|
|
||
|
matrix = mathutils.Matrix(bone.matrix)
|
||
|
|
||
|
if parent:
|
||
|
|
||
|
#print("==== "+bone.name+" ====")
|
||
|
a = (bone.matrix_local)
|
||
|
|
||
|
#print("A : [%.2f %.2f %.2f %.2f]" % (a[0][0], a[0][1], a[0][2], a[0][3]))
|
||
|
#print(" [%.2f %.2f %.2f %.2f]" % (a[1][0], a[1][1], a[1][2], a[1][3]))
|
||
|
#print(" [%.2f %.2f %.2f %.2f]" % (a[2][0], a[2][1], a[2][2], a[2][3]))
|
||
|
#print(" [%.2f %.2f %.2f %.2f]" % (a[3][0], a[3][1], a[3][2], a[3][3]))
|
||
|
|
||
|
b = (parent.matrix_local.inverted().to_4x4())
|
||
|
|
||
|
#print("B : [%.2f %.2f %.2f %.2f]" % (b[0][0], b[0][1], b[0][2], b[0][3]))
|
||
|
#print(" [%.2f %.2f %.2f %.2f]" % (b[1][0], b[1][1], b[1][2], b[1][3]))
|
||
|
#print(" [%.2f %.2f %.2f %.2f]" % (b[2][0], b[2][1], b[2][2], b[2][3]))
|
||
|
#print(" [%.2f %.2f %.2f %.2f]" % (b[3][0], b[3][1], b[3][2], b[3][3]))
|
||
|
|
||
|
par_matrix = b * a
|
||
|
|
||
|
transform = mathutils.Matrix([[1,0,0,0],[0,0,-1,0],[0,-1,0,0],[0,0,0,1]])
|
||
|
par_matrix = transform*par_matrix*transform
|
||
|
|
||
|
# FIXME: that's ugly, find a clean way to change the matrix.....
|
||
|
if bpy.app.version[1] >= 62:
|
||
|
# blender 2.62 broke the API : Column-major access was changed to row-major access
|
||
|
# TODO: test me
|
||
|
par_matrix[1][3] = -par_matrix[1][3]
|
||
|
par_matrix[2][3] = -par_matrix[2][3]
|
||
|
else:
|
||
|
par_matrix[3][1] = -par_matrix[3][1]
|
||
|
par_matrix[3][2] = -par_matrix[3][2]
|
||
|
|
||
|
#c = par_matrix
|
||
|
#print("With parent")
|
||
|
#print("C : [%.3f %.3f %.3f %.3f]" % (c[0][0], c[0][1], c[0][2], c[0][3]))
|
||
|
#print(" [%.3f %.3f %.3f %.3f]" % (c[1][0], c[1][1], c[1][2], c[1][3]))
|
||
|
#print(" [%.3f %.3f %.3f %.3f]" % (c[2][0], c[2][1], c[2][2], c[2][3]))
|
||
|
#print(" [%.3f %.3f %.3f %.3f]" % (c[3][0], c[3][1], c[3][2], c[3][3]))
|
||
|
|
||
|
else:
|
||
|
|
||
|
#print("==== "+bone.name+" ====")
|
||
|
#print("Without parent")
|
||
|
|
||
|
m = arm_matrix*bone.matrix_local
|
||
|
|
||
|
#c = arm.matrix_world
|
||
|
#print("A : [%.3f %.3f %.3f %.3f]" % (c[0][0], c[0][1], c[0][2], c[0][3]))
|
||
|
#print(" [%.3f %.3f %.3f %.3f]" % (c[1][0], c[1][1], c[1][2], c[1][3]))
|
||
|
#print(" [%.3f %.3f %.3f %.3f]" % (c[2][0], c[2][1], c[2][2], c[2][3]))
|
||
|
#print(" [%.3f %.3f %.3f %.3f]" % (c[3][0], c[3][1], c[3][2], c[3][3]))
|
||
|
|
||
|
#c = bone.matrix_local
|
||
|
#print("B : [%.3f %.3f %.3f %.3f]" % (c[0][0], c[0][1], c[0][2], c[0][3]))
|
||
|
#print(" [%.3f %.3f %.3f %.3f]" % (c[1][0], c[1][1], c[1][2], c[1][3]))
|
||
|
#print(" [%.3f %.3f %.3f %.3f]" % (c[2][0], c[2][1], c[2][2], c[2][3]))
|
||
|
#print(" [%.3f %.3f %.3f %.3f]" % (c[3][0], c[3][1], c[3][2], c[3][3]))
|
||
|
|
||
|
par_matrix = m*mathutils.Matrix([[-1,0,0,0],[0,0,1,0],[0,1,0,0],[0,0,0,1]])
|
||
|
|
||
|
#c = par_matrix
|
||
|
#print("C : [%.3f %.3f %.3f %.3f]" % (c[0][0], c[0][1], c[0][2], c[0][3]))
|
||
|
#print(" [%.3f %.3f %.3f %.3f]" % (c[1][0], c[1][1], c[1][2], c[1][3]))
|
||
|
#print(" [%.3f %.3f %.3f %.3f]" % (c[2][0], c[2][1], c[2][2], c[2][3]))
|
||
|
#print(" [%.3f %.3f %.3f %.3f]" % (c[3][0], c[3][1], c[3][2], c[3][3]))
|
||
|
|
||
|
|
||
|
bone_stack[bone.name] = [par_matrix,parent,bone]
|
||
|
|
||
|
if bone.children:
|
||
|
for child in bone.children: read_armature(arm_matrix,child,bone)
|
||
|
|
||
|
for bone in arm.data.bones.values():
|
||
|
if not bone.parent:
|
||
|
read_armature(arm_matrix,bone)
|
||
|
|
||
|
frame_count = first_frame
|
||
|
|
||
|
last_frame = int(getArmatureAnimationEnd(arm))
|
||
|
num_frames = last_frame - first_frame
|
||
|
|
||
|
while frame_count <= last_frame:
|
||
|
|
||
|
the_scene.frame_set(int(frame_count), subframe=0.0)
|
||
|
|
||
|
if DEBUG: print(" <frame id=", int(frame_count), ">")
|
||
|
arm_pose = arm.pose
|
||
|
arm_matrix = arm.matrix_world
|
||
|
|
||
|
transform = mathutils.Matrix([[-1,0,0,0],[0,0,1,0],[0,1,0,0],[0,0,0,1]])
|
||
|
arm_matrix = transform*arm_matrix
|
||
|
#arm_quat = arm_matrix.to_quaternion()
|
||
|
#arm_quat.normalize()
|
||
|
|
||
|
for bone_name in arm.data.bones.keys():
|
||
|
#bone_matrix = mathutils.Matrix(arm_pose.bones[bone_name].poseMatrix)
|
||
|
bone_matrix = mathutils.Matrix(arm_pose.bones[bone_name].matrix)
|
||
|
|
||
|
#print("(outer loop) bone_matrix for",bone_name,"=", bone_matrix)
|
||
|
|
||
|
#print(bone_name,":",bone_matrix)
|
||
|
|
||
|
#bone_matrix = bpy.data.scenes[0].objects[0].pose.bones['Bone'].matrix
|
||
|
|
||
|
for ibone in bone_stack:
|
||
|
|
||
|
bone = bone_stack[ibone]
|
||
|
|
||
|
if bone[BONE_ITSELF].name == bone_name:
|
||
|
|
||
|
if DEBUG: print(" <bone id=",ibone,"name=",bone_name,">")
|
||
|
|
||
|
# == 2.4 exporter ==
|
||
|
#if bone_stack[ibone][1]:
|
||
|
# par_matrix = Blender.Mathutils.Matrix(arm_pose.bones[bone_stack[ibone][1].name].poseMatrix)
|
||
|
# bone_matrix *= par_matrix.invert()
|
||
|
#else:
|
||
|
# if b3d_parameters.get("local-space"):
|
||
|
# bone_matrix *= TRANS_MATRIX
|
||
|
# else:
|
||
|
# bone_matrix *= arm_matrix
|
||
|
#bone_loc = bone_matrix.translationPart()
|
||
|
#bone_rot = bone_matrix.rotationPart().toQuat()
|
||
|
#bone_rot.normalize()
|
||
|
#bone_sca = bone_matrix.scalePart()
|
||
|
#keys_stack.append([frame_count - first_frame.val+1,bone_name,bone_loc,bone_sca,bone_rot])
|
||
|
|
||
|
# if has parent
|
||
|
if bone[BONE_PARENT]:
|
||
|
par_matrix = mathutils.Matrix(arm_pose.bones[bone[BONE_PARENT].name].matrix)
|
||
|
bone_matrix = par_matrix.inverted()*bone_matrix
|
||
|
else:
|
||
|
if b3d_parameters.get("local-space"):
|
||
|
bone_matrix = bone_matrix*mathutils.Matrix([[-1,0,0,0],[0,0,1,0],[0,1,0,0],[0,0,0,1]])
|
||
|
else:
|
||
|
|
||
|
#if frame_count == 1:
|
||
|
# print("====",bone_name,"====")
|
||
|
# print("arm_matrix = ", arm_matrix)
|
||
|
# print("bone_matrix = ", bone_matrix)
|
||
|
|
||
|
bone_matrix = arm_matrix*bone_matrix
|
||
|
|
||
|
#if frame_count == 1:
|
||
|
# print("arm_matrix*bone_matrix", bone_matrix)
|
||
|
|
||
|
|
||
|
#print("bone_matrix =", bone_matrix)
|
||
|
|
||
|
bone_sca = bone_matrix.to_scale()
|
||
|
bone_loc = bone_matrix.to_translation()
|
||
|
|
||
|
# FIXME: silly tweaks to resemble the Blender 2.4 exporter output
|
||
|
if b3d_parameters.get("local-space"):
|
||
|
|
||
|
bone_rot = bone_matrix.to_quaternion()
|
||
|
bone_rot.normalize()
|
||
|
|
||
|
|
||
|
if not bone[BONE_PARENT]:
|
||
|
tmp = bone_rot.z
|
||
|
bone_rot.z = bone_rot.y
|
||
|
bone_rot.y = tmp
|
||
|
|
||
|
bone_rot.x = -bone_rot.x
|
||
|
else:
|
||
|
tmp = bone_loc.z
|
||
|
bone_loc.z = bone_loc.y
|
||
|
bone_loc.y = tmp
|
||
|
|
||
|
else:
|
||
|
bone_rot = bone_matrix.to_quaternion()
|
||
|
bone_rot.normalize()
|
||
|
|
||
|
# Sometimes to_quaternion exhibits precision issue with parent bone
|
||
|
# Use quaternion product instead of getting quaternion from the product.
|
||
|
#if not bone[BONE_PARENT]:
|
||
|
# bone_rot = arm_pose.bones[bone_name].matrix.to_quaternion() * arm_quat
|
||
|
# bone_rot.x = -bone_rot.x
|
||
|
# tmp = bone_rot.z
|
||
|
# bone_rot.z = bone_rot.y
|
||
|
# bone_rot.y = tmp
|
||
|
|
||
|
keys_stack.append([frame_count - first_frame+1, bone_name, bone_loc, bone_sca, bone_rot])
|
||
|
if DEBUG: print(" <loc>", bone_loc, "</loc>")
|
||
|
if DEBUG: print(" <rot>", bone_rot, "</rot>")
|
||
|
if DEBUG: print(" <scale>", bone_sca, "</scale>")
|
||
|
if DEBUG: print(" </bone>")
|
||
|
|
||
|
frame_count += 1
|
||
|
|
||
|
if DEBUG: print(" </frame>")
|
||
|
|
||
|
#Blender.Set("curframe",0)
|
||
|
#Blender.Window.Redraw()
|
||
|
|
||
|
temp_buf.append(write_node_mesh(obj,obj_count,anim_data,exp_root)) #NODE MESH
|
||
|
|
||
|
if anim_data:
|
||
|
temp_buf.append(write_node_anim(num_frames)) #NODE ANIM
|
||
|
|
||
|
for ibone in bone_stack:
|
||
|
if not bone_stack[ibone][BONE_PARENT]:
|
||
|
temp_buf.append(write_node_node(ibone)) #NODE NODE
|
||
|
|
||
|
obj_count += 1
|
||
|
|
||
|
if len(temp_buf) > 0:
|
||
|
node_buf.append(write_chunk(b"NODE",b"".join(temp_buf)))
|
||
|
temp_buf = []
|
||
|
|
||
|
if DEBUG: print(" </mesh>")
|
||
|
|
||
|
if b3d_parameters.get("cameras"):
|
||
|
if obj.type == "CAMERA":
|
||
|
data = obj.data
|
||
|
matrix = obj.getMatrix("worldspace")
|
||
|
matrix *= TRANS_MATRIX
|
||
|
|
||
|
if data.type == "ORTHO":
|
||
|
cam_type = 2
|
||
|
cam_zoom = round(data.scale,4)
|
||
|
else:
|
||
|
cam_type = 1
|
||
|
cam_zoom = round(data.lens,4)
|
||
|
|
||
|
cam_near = round(data.clipStart,4)
|
||
|
cam_far = round(data.clipEnd,4)
|
||
|
|
||
|
node_name = ("CAMS"+"\n%s"%obj.name+"\n%s"%cam_type+\
|
||
|
"\n%s"%cam_zoom+"\n%s"%cam_near+"\n%s"%cam_far)
|
||
|
temp_buf.append(write_string(node_name)) #Node Name
|
||
|
|
||
|
position = matrix.translation_part()
|
||
|
temp_buf.append(write_float_triplet(-position[0], position[1], position[2]))
|
||
|
|
||
|
scale = matrix.scale_part()
|
||
|
temp_buf.append(write_float_triplet(scale[0], scale[1], scale[2]))
|
||
|
|
||
|
matrix *= mathutils.Matrix.Rotation(180,4,'Y')
|
||
|
quat = matrix.to_quat()
|
||
|
quat.normalize()
|
||
|
|
||
|
temp_buf.append(write_float_quad(quat.w, quat.x, quat.y, -quat.z))
|
||
|
|
||
|
if len(temp_buf) > 0:
|
||
|
node_buf.append(write_chunk(b"NODE",b"".join(temp_buf)))
|
||
|
temp_buf = []
|
||
|
|
||
|
if b3d_parameters.get("lights"):
|
||
|
if amb_light == 0:
|
||
|
data = Blender.World.GetCurrent()
|
||
|
|
||
|
amb_light = 1
|
||
|
amb_color = (int(data.amb[2]*255) |(int(data.amb[1]*255) << 8) | (int(data.amb[0]*255) << 16))
|
||
|
|
||
|
node_name = (b"AMBI"+"\n%s"%amb_color)
|
||
|
temp_buf.append(write_string(node_name)) #Node Name
|
||
|
|
||
|
temp_buf.append(write_float_triplet(0, 0, 0)) #Position X, Y, Z
|
||
|
temp_buf.append(write_float_triplet(1, 1, 1)) #Scale X, Y, Z
|
||
|
temp_buf.append(write_float_quad(1, 0, 0, 0)) #Rotation W, X, Y, Z
|
||
|
|
||
|
if len(temp_buf) > 0:
|
||
|
node_buf.append(write_chunk(b"NODE",b"".join(temp_buf)))
|
||
|
temp_buf = []
|
||
|
|
||
|
if obj.type == "LAMP":
|
||
|
data = obj.getData()
|
||
|
matrix = obj.getMatrix("worldspace")
|
||
|
matrix *= TRANS_MATRIX
|
||
|
|
||
|
if data.type == 0:
|
||
|
lig_type = 2
|
||
|
elif data.type == 2:
|
||
|
lig_type = 3
|
||
|
else:
|
||
|
lig_type = 1
|
||
|
|
||
|
lig_angle = round(data.spotSize,4)
|
||
|
lig_color = (int(data.b*255) |(int(data.g*255) << 8) | (int(data.r*255) << 16))
|
||
|
lig_range = round(data.dist,4)
|
||
|
|
||
|
node_name = ("LIGS"+"\n%s"%obj.name+"\n%s"%lig_type+\
|
||
|
"\n%s"%lig_angle+"\n%s"%lig_color+"\n%s"%lig_range)
|
||
|
temp_buf.append(write_string(node_name)) #Node Name
|
||
|
|
||
|
position = matrix.translation_part()
|
||
|
temp_buf.append(write_float_triplet(-position[0], position[1], position[2]))
|
||
|
if DEBUG: print(" <position>",-position[0],position[1],position[2],"</position>")
|
||
|
|
||
|
scale = matrix.scale_part()
|
||
|
temp_buf.append(write_float_triplet(scale[0], scale[1], scale[2]))
|
||
|
|
||
|
if DEBUG: print(" <scale>",scale[0],scale[1],scale[2],"</scale>")
|
||
|
|
||
|
matrix *= mathutils.Matrix.Rotation(180,4,'Y')
|
||
|
quat = matrix.toQuat()
|
||
|
quat.normalize()
|
||
|
|
||
|
temp_buf.append(write_float_quad(quat.w, quat.x, quat.y, -quat.z))
|
||
|
if DEBUG: print(" <rotation>", quat.w, quat.x, quat.y, quat.z, "</rotation>")
|
||
|
|
||
|
if len(temp_buf) > 0:
|
||
|
node_buf.append(write_chunk(b"NODE","b".join(temp_buf)))
|
||
|
temp_buf = []
|
||
|
|
||
|
if len(node_buf) > 0:
|
||
|
if exp_root:
|
||
|
main_buf += write_chunk(b"NODE",b"".join(root_buf) + b"".join(node_buf))
|
||
|
else:
|
||
|
main_buf += b"".join(node_buf)
|
||
|
|
||
|
node_buf = []
|
||
|
root_buf = []
|
||
|
|
||
|
if DEBUG: print("</node>")
|
||
|
|
||
|
return main_buf
|
||
|
|
||
|
# ==== Write NODE MESH Chunk ====
|
||
|
def write_node_mesh(obj,obj_count,arm_action,exp_root):
|
||
|
global vertex_groups
|
||
|
vertex_groups = []
|
||
|
mesh_buf = bytearray()
|
||
|
temp_buf = bytearray()
|
||
|
|
||
|
if arm_action:
|
||
|
data = obj.data
|
||
|
elif b3d_parameters.get("apply-modifiers"):
|
||
|
data = obj.to_mesh(the_scene, True, 'PREVIEW') # Apply modifiers
|
||
|
else:
|
||
|
data = obj.data
|
||
|
|
||
|
temp_buf += write_int(-1) #Brush ID
|
||
|
temp_buf += write_node_mesh_vrts(obj, data, obj_count, arm_action, exp_root) #NODE MESH VRTS
|
||
|
temp_buf += write_node_mesh_tris(obj, data, obj_count, arm_action, exp_root) #NODE MESH TRIS
|
||
|
|
||
|
if len(temp_buf) > 0:
|
||
|
mesh_buf += write_chunk(b"MESH",temp_buf)
|
||
|
temp_buf = ""
|
||
|
|
||
|
return mesh_buf
|
||
|
|
||
|
def build_vertex_groups(data):
|
||
|
for f in getFaces(data):
|
||
|
for v in f.vertices:
|
||
|
vertex_groups.append({})
|
||
|
|
||
|
|
||
|
# ==== Write NODE MESH VRTS Chunk ====
|
||
|
def write_node_mesh_vrts(obj, data, obj_count, arm_action, exp_root):
|
||
|
vrts_buf = bytearray()
|
||
|
temp_buf = []
|
||
|
obj_flags = 0
|
||
|
|
||
|
#global time_in_a
|
||
|
#global time_in_b
|
||
|
#global time_in_b1
|
||
|
#global time_in_b2
|
||
|
#global time_in_b3
|
||
|
#global time_in_b4
|
||
|
|
||
|
#data = obj.getData(mesh = True)
|
||
|
global the_scene
|
||
|
|
||
|
# FIXME: port to 2.5 API?
|
||
|
#orig_uvlayer = data.activeUVLayer
|
||
|
|
||
|
if b3d_parameters.get("vertex-normals"):
|
||
|
obj_flags += 1
|
||
|
|
||
|
#if b3d_parameters.get("vertex-colors") and data.getColorLayerNames():
|
||
|
if b3d_parameters.get("vertex-colors") and len(getVertexColors(data)) > 0:
|
||
|
obj_flags += 2
|
||
|
|
||
|
temp_buf.append(write_int(obj_flags)) #Flags
|
||
|
#temp_buf += write_int(len(data.getUVLayerNames())) #UV Set
|
||
|
temp_buf.append(write_int(len(getUVTextures(data)))) #UV Set
|
||
|
temp_buf.append(write_int(2)) #UV Set Size
|
||
|
|
||
|
# ---- Prepare the mesh "stack"
|
||
|
build_vertex_groups(data)
|
||
|
|
||
|
# ---- Fill the mesh "stack"
|
||
|
if DEBUG: print("")
|
||
|
if DEBUG: print(" <!-- Building vertex_groups -->\n")
|
||
|
|
||
|
ivert = -1
|
||
|
|
||
|
|
||
|
#if PROGRESS_VERBOSE:
|
||
|
# progress = 0
|
||
|
# print(" vertex_groups, face:",0,"/",len(getFaces(data)))
|
||
|
|
||
|
the_scene.frame_set(1,subframe=0.0)
|
||
|
|
||
|
if b3d_parameters.get("local-space"):
|
||
|
mesh_matrix = mathutils.Matrix()
|
||
|
else:
|
||
|
mesh_matrix = obj.matrix_world.copy()
|
||
|
|
||
|
#import time
|
||
|
|
||
|
uvdata = getUVTextures(data)
|
||
|
uv_layers_count = len(uvdata)
|
||
|
uv_textures = {}
|
||
|
weldable_vertices = {}
|
||
|
vertex_id_mapping = {}
|
||
|
for face in getFaces(data):
|
||
|
for vertex_index,vert in enumerate(face.vertices):
|
||
|
#for id,vert in enumerate(data.vertices):
|
||
|
|
||
|
if vert not in uv_textures:
|
||
|
uv_textures[vert] = {}
|
||
|
if face not in uv_textures[vert]:
|
||
|
uv_textures[vert][face] = {}
|
||
|
|
||
|
for iuvlayer in range(uv_layers_count):
|
||
|
if vertex_index == 0:
|
||
|
uv_textures[vert][face][iuvlayer] = uvdata[iuvlayer].data[face.index].uv1
|
||
|
elif vertex_index == 1:
|
||
|
uv_textures[vert][face][iuvlayer] = uvdata[iuvlayer].data[face.index].uv2
|
||
|
elif vertex_index == 2:
|
||
|
uv_textures[vert][face][iuvlayer] = uvdata[iuvlayer].data[face.index].uv3
|
||
|
elif vertex_index == 3:
|
||
|
uv_textures[vert][face][iuvlayer] = uvdata[iuvlayer].data[face.index].uv4
|
||
|
|
||
|
for idVertex, vertexFaces in uv_textures.items():
|
||
|
is_weldable = True
|
||
|
|
||
|
for iuvlayer in range(uv_layers_count):
|
||
|
u = None
|
||
|
v = None
|
||
|
for idFace, face in vertexFaces.items():
|
||
|
if iuvlayer not in face:
|
||
|
continue
|
||
|
faceuvlayer = face[iuvlayer]
|
||
|
if u is None:
|
||
|
u = round(faceuvlayer[0]*1000)
|
||
|
v = round(faceuvlayer[1]*1000)
|
||
|
else:
|
||
|
if round(faceuvlayer[0] * 1000) != u or round(faceuvlayer[1]*1000) != v:
|
||
|
is_weldable = False
|
||
|
break
|
||
|
if not is_weldable:
|
||
|
break
|
||
|
|
||
|
weldable_vertices[idVertex] = is_weldable
|
||
|
|
||
|
|
||
|
#print('uv_textures', uv_textures)
|
||
|
#print('weldable_vertices', weldable_vertices)
|
||
|
|
||
|
for face in getFaces(data):
|
||
|
|
||
|
if DEBUG: print(" <!-- Face",face.index,"-->")
|
||
|
|
||
|
#if PROGRESS_VERBOSE:
|
||
|
# progress += 1
|
||
|
# if (progress % 50 == 0): print(" vertex_groups, face:",progress,"/",len(data.faces))
|
||
|
|
||
|
per_face_vertices[face.index] = []
|
||
|
|
||
|
for vertex_id,vert in enumerate(face.vertices):
|
||
|
|
||
|
if vert in weldable_vertices and weldable_vertices[vert] and vert in vertex_id_mapping:
|
||
|
per_face_vertices[face.index].append(vertex_id_mapping[vert])
|
||
|
continue
|
||
|
|
||
|
ivert += 1
|
||
|
vertex_id_mapping[vert] = ivert
|
||
|
per_face_vertices[face.index].append(ivert)
|
||
|
|
||
|
#a = time.time()
|
||
|
|
||
|
if arm_action:
|
||
|
v = mesh_matrix * data.vertices[vert].co
|
||
|
vert_matrix = mathutils.Matrix.Translation(v)
|
||
|
else:
|
||
|
vert_matrix = mathutils.Matrix.Translation(data.vertices[vert].co)
|
||
|
|
||
|
vert_matrix *= TRANS_MATRIX
|
||
|
vcoord = vert_matrix.to_translation()
|
||
|
|
||
|
temp_buf.append(write_float_triplet(vcoord.x, vcoord.z, vcoord.y))
|
||
|
|
||
|
#b = time.time()
|
||
|
#time_in_a += b - a
|
||
|
|
||
|
if b3d_parameters.get("vertex-normals"):
|
||
|
norm_matrix = mathutils.Matrix.Translation(data.vertices[vert].normal)
|
||
|
|
||
|
if arm_action:
|
||
|
norm_matrix *= mesh_matrix
|
||
|
|
||
|
norm_matrix *= TRANS_MATRIX
|
||
|
normal_vector = norm_matrix.to_translation()
|
||
|
|
||
|
temp_buf.append(write_float_triplet(normal_vector.x, #NX
|
||
|
normal_vector.z, #NY
|
||
|
normal_vector.y)) #NZ
|
||
|
|
||
|
#c = time.time()
|
||
|
#time_in_b += c - b
|
||
|
|
||
|
if b3d_parameters.get("vertex-colors") and len(getVertexColors(data)) > 0:
|
||
|
vertex_colors = getVertexColors(data)
|
||
|
if vertex_id == 0:
|
||
|
vcolor = vertex_colors[0].data[face.index].color1
|
||
|
elif vertex_id == 1:
|
||
|
vcolor = vertex_colors[0].data[face.index].color2
|
||
|
elif vertex_id == 2:
|
||
|
vcolor = vertex_colors[0].data[face.index].color3
|
||
|
elif vertex_id == 3:
|
||
|
vcolor = vertex_colors[0].data[face.index].color4
|
||
|
|
||
|
valpha = 1.0
|
||
|
if (len(vertex_colors) > 1):
|
||
|
if vertex_id == 0:
|
||
|
valpha = vertex_colors[1].data[face.index].color1.r
|
||
|
elif vertex_id == 1:
|
||
|
valpha = vertex_colors[1].data[face.index].color2.r
|
||
|
elif vertex_id == 2:
|
||
|
valpha = vertex_colors[1].data[face.index].color3.r
|
||
|
elif vertex_id == 3:
|
||
|
valpha = vertex_colors[1].data[face.index].color4.r
|
||
|
|
||
|
temp_buf.append(write_float_quad(vcolor.r, #R
|
||
|
vcolor.g, #G
|
||
|
vcolor.b, #B
|
||
|
valpha)) #A (FIXME?)
|
||
|
|
||
|
#d = time.time()
|
||
|
#time_in_b1 += d - c
|
||
|
|
||
|
for vg in obj.vertex_groups:
|
||
|
w = 0.0
|
||
|
try:
|
||
|
w = vg.weight(vert)
|
||
|
except:
|
||
|
pass
|
||
|
vertex_groups[ivert][vg.name] = w
|
||
|
|
||
|
#e = time.time()
|
||
|
#time_in_b2 += e - d
|
||
|
|
||
|
for iuvlayer in range(uv_layers_count):
|
||
|
uv = [10, 10]
|
||
|
if vert in uv_textures:
|
||
|
vertex_uv = uv_textures[vert]
|
||
|
|
||
|
if face in vertex_uv:
|
||
|
vertex_face_uv = vertex_uv[face]
|
||
|
|
||
|
if iuvlayer in vertex_face_uv:
|
||
|
uv = vertex_face_uv[iuvlayer]
|
||
|
|
||
|
temp_buf.append(write_float_couple(uv[0], 1-uv[1]) ) # U, V
|
||
|
|
||
|
|
||
|
|
||
|
if DEBUG: print("")
|
||
|
|
||
|
#c = time.time()
|
||
|
#time_in_b += c - b
|
||
|
#print("time_in_a = ",time_in_a)
|
||
|
#print("time_in_b = ",time_in_b)
|
||
|
#print("time_in_b1 = ", time_in_b1)
|
||
|
#print("time_in_b2 = ", time_in_b2)
|
||
|
#print("time_in_b3 = ", time_in_b3)
|
||
|
#print("time_in_b4 = ", time_in_b4)
|
||
|
|
||
|
if len(temp_buf) > 0:
|
||
|
vrts_buf += write_chunk(b"VRTS",b"".join(temp_buf))
|
||
|
temp_buf = []
|
||
|
|
||
|
return vrts_buf
|
||
|
|
||
|
# ==== Write NODE MESH TRIS Chunk ====
|
||
|
def write_node_mesh_tris(obj, data, obj_count,arm_action,exp_root):
|
||
|
|
||
|
global texture_count
|
||
|
|
||
|
#FIXME?
|
||
|
#orig_uvlayer = data.activeUVLayer
|
||
|
|
||
|
# An dictoriary that maps all brush-ids to a list of faces
|
||
|
# using this brush. This helps to sort the triangles by
|
||
|
# brush, creating less mesh buffer in irrlicht.
|
||
|
dBrushId2Face = {}
|
||
|
|
||
|
if DEBUG: print("")
|
||
|
|
||
|
for face in getFaces(data):
|
||
|
img_found = 0
|
||
|
face_stack = []
|
||
|
|
||
|
uv_textures = getUVTextures(data)
|
||
|
uv_layer_count = len(uv_textures)
|
||
|
for iuvlayer,uvlayer in enumerate(uv_textures):
|
||
|
if iuvlayer < 8:
|
||
|
|
||
|
if iuvlayer >= uv_layer_count:
|
||
|
continue
|
||
|
|
||
|
img_id = -1
|
||
|
|
||
|
img = uv_textures[iuvlayer].data[face.index].image
|
||
|
if img:
|
||
|
|
||
|
if img.filepath in trimmed_paths:
|
||
|
img_name = trimmed_paths[img.filepath]
|
||
|
else:
|
||
|
img_name = os.path.basename(img.filepath)
|
||
|
trimmed_paths[img.filepath] = img_name
|
||
|
|
||
|
img_found = 1
|
||
|
if img_name in texs_stack:
|
||
|
img_id = texs_stack[img_name][TEXTURE_ID]
|
||
|
|
||
|
face_stack.insert(iuvlayer,img_id)
|
||
|
|
||
|
for i in range(len(face_stack),texture_count):
|
||
|
face_stack.append(-1)
|
||
|
|
||
|
if img_found == 0:
|
||
|
brus_id = -1
|
||
|
if data.materials and data.materials[face.material_index]:
|
||
|
mat_name = data.materials[face.material_index].name
|
||
|
for i in range(len(brus_stack)):
|
||
|
if brus_stack[i] == mat_name:
|
||
|
brus_id = i
|
||
|
break
|
||
|
else:
|
||
|
for i in range(len(brus_stack)):
|
||
|
if brus_stack[i] == face_stack:
|
||
|
brus_id = i
|
||
|
break
|
||
|
else:
|
||
|
brus_id = -1
|
||
|
for i in range(len(brus_stack)):
|
||
|
if brus_stack[i] == face_stack:
|
||
|
brus_id = i
|
||
|
break
|
||
|
if brus_id == -1:
|
||
|
print("Cannot find in brus stack : ", face_stack)
|
||
|
|
||
|
if brus_id in dBrushId2Face:
|
||
|
dBrushId2Face[brus_id].append(face)
|
||
|
else:
|
||
|
dBrushId2Face[brus_id] = [face]
|
||
|
|
||
|
if DEBUG: print(" <!-- Face",face.index,"in brush",brus_id,"-->")
|
||
|
|
||
|
tris_buf = bytearray()
|
||
|
|
||
|
if DEBUG: print("")
|
||
|
if DEBUG: print(" <!-- TRIS chunk -->")
|
||
|
|
||
|
if PROGRESS_VERBOSE: progress = 0
|
||
|
|
||
|
for brus_id in dBrushId2Face.keys():
|
||
|
|
||
|
if PROGRESS_VERBOSE:
|
||
|
progress += 1
|
||
|
print("BRUS:",progress,"/",len(dBrushId2Face.keys()))
|
||
|
|
||
|
temp_buf = [write_int(brus_id)] #Brush ID
|
||
|
|
||
|
if DEBUG: print(" <brush id=", brus_id, ">")
|
||
|
|
||
|
if PROGRESS_VERBOSE: progress2 = 0
|
||
|
|
||
|
for face in dBrushId2Face[brus_id]:
|
||
|
|
||
|
if PROGRESS_VERBOSE:
|
||
|
progress2 += 1
|
||
|
if (progress2 % 50 == 0): print(" TRIS:",progress2,"/",len(dBrushId2Face[brus_id]))
|
||
|
|
||
|
vertices = per_face_vertices[face.index]
|
||
|
|
||
|
temp_buf.append(write_int(vertices[2])) #A
|
||
|
temp_buf.append(write_int(vertices[1])) #B
|
||
|
temp_buf.append(write_int(vertices[0])) #C
|
||
|
|
||
|
if DEBUG: print(" <face id=", vertices[2], vertices[1], vertices[0],"/> <!-- face",face.index,"-->")
|
||
|
|
||
|
if len(face.vertices) == 4:
|
||
|
temp_buf.append(write_int(vertices[3])) #A
|
||
|
temp_buf.append(write_int(vertices[2])) #B
|
||
|
temp_buf.append(write_int(vertices[0])) #C
|
||
|
if DEBUG: print(" <face id=", vertices[3], vertices[2], vertices[0],"/> <!-- face",face.index,"-->")
|
||
|
|
||
|
if DEBUG: print(" </brush>")
|
||
|
tris_buf += write_chunk(b"TRIS", b"".join(temp_buf))
|
||
|
|
||
|
return tris_buf
|
||
|
|
||
|
# ==== Write NODE ANIM Chunk ====
|
||
|
def write_node_anim(num_frames):
|
||
|
anim_buf = bytearray()
|
||
|
temp_buf = bytearray()
|
||
|
|
||
|
temp_buf += write_int(0) #Flags
|
||
|
temp_buf += write_int(num_frames) #Frames
|
||
|
temp_buf += write_float(60) #FPS
|
||
|
|
||
|
if len(temp_buf) > 0:
|
||
|
anim_buf += write_chunk(b"ANIM",temp_buf)
|
||
|
temp_buf = ""
|
||
|
|
||
|
return anim_buf
|
||
|
|
||
|
# ==== Write NODE NODE Chunk ====
|
||
|
def write_node_node(ibone):
|
||
|
node_buf = bytearray()
|
||
|
temp_buf = []
|
||
|
|
||
|
bone = bone_stack[ibone]
|
||
|
|
||
|
matrix = bone[BONE_PARENT_MATRIX]
|
||
|
temp_buf.append(write_string(bone[BONE_ITSELF].name)) #Node Name
|
||
|
|
||
|
|
||
|
|
||
|
# FIXME: we should use the same matrix format everywhere to not require this
|
||
|
|
||
|
position = matrix.to_translation()
|
||
|
if bone[BONE_PARENT]:
|
||
|
temp_buf.append(write_float_triplet(-position[0], position[2], position[1]))
|
||
|
else:
|
||
|
temp_buf.append(write_float_triplet(position[0], position[2], position[1]))
|
||
|
|
||
|
|
||
|
scale = matrix.to_scale()
|
||
|
temp_buf.append(write_float_triplet(scale[0], scale[2], scale[1]))
|
||
|
|
||
|
quat = matrix.to_quaternion()
|
||
|
quat.normalize()
|
||
|
|
||
|
temp_buf.append(write_float_quad(quat.w, quat.x, quat.z, quat.y))
|
||
|
|
||
|
temp_buf.append(write_node_bone(ibone))
|
||
|
temp_buf.append(write_node_keys(ibone))
|
||
|
|
||
|
for iibone in bone_stack:
|
||
|
if bone_stack[iibone][BONE_PARENT] == bone_stack[ibone][BONE_ITSELF]:
|
||
|
temp_buf.append(write_node_node(iibone))
|
||
|
|
||
|
if len(temp_buf) > 0:
|
||
|
node_buf += write_chunk(b"NODE", b"".join(temp_buf))
|
||
|
temp_buf = []
|
||
|
|
||
|
return node_buf
|
||
|
|
||
|
# ==== Write NODE BONE Chunk ====
|
||
|
def write_node_bone(ibone):
|
||
|
bone_buf = bytearray()
|
||
|
temp_buf = []
|
||
|
|
||
|
my_name = bone_stack[ibone][BONE_ITSELF].name
|
||
|
|
||
|
for ivert in range(len(vertex_groups)):
|
||
|
if my_name in vertex_groups[ivert]:
|
||
|
vert_influ = vertex_groups[ivert][my_name]
|
||
|
#if DEBUG: print(" <bone name=",bone_stack[ibone][BONE_ITSELF].name,"face_vertex_id=", ivert + iuv,
|
||
|
# " weigth=", vert_influ[1] , "/>")
|
||
|
temp_buf.append(write_int(ivert)) # Face Vertex ID
|
||
|
temp_buf.append(write_float(vert_influ)) #Weight
|
||
|
|
||
|
bone_buf += write_chunk(b"BONE", b"".join(temp_buf))
|
||
|
temp_buf = []
|
||
|
|
||
|
return bone_buf
|
||
|
|
||
|
# ==== Write NODE KEYS Chunk ====
|
||
|
def write_node_keys(ibone):
|
||
|
keys_buf = bytearray()
|
||
|
temp_buf = []
|
||
|
|
||
|
temp_buf.append(write_int(7)) #Flags
|
||
|
|
||
|
my_name = bone_stack[ibone][BONE_ITSELF].name
|
||
|
|
||
|
for ikeys in range(len(keys_stack)):
|
||
|
if keys_stack[ikeys][1] == my_name:
|
||
|
temp_buf.append(write_int(keys_stack[ikeys][0])) #Frame
|
||
|
|
||
|
position = keys_stack[ikeys][2]
|
||
|
# FIXME: we should use the same matrix format everywhere and not require this
|
||
|
if b3d_parameters.get("local-space"):
|
||
|
if bone_stack[ibone][BONE_PARENT]:
|
||
|
temp_buf.append(write_float_triplet(-position[0], position[2], position[1]))
|
||
|
else:
|
||
|
temp_buf.append(write_float_triplet(position[0], position[2], position[1]))
|
||
|
else:
|
||
|
temp_buf.append(write_float_triplet(-position[0], position[1], position[2]))
|
||
|
|
||
|
scale = keys_stack[ikeys][3]
|
||
|
temp_buf.append(write_float_triplet(scale[0], scale[1], scale[2]))
|
||
|
|
||
|
quat = keys_stack[ikeys][4]
|
||
|
quat.normalize()
|
||
|
|
||
|
temp_buf.append(write_float_quad(quat.w, -quat.x, quat.y, quat.z))
|
||
|
#break
|
||
|
|
||
|
keys_buf += write_chunk(b"KEYS",b"".join(temp_buf))
|
||
|
temp_buf = []
|
||
|
|
||
|
return keys_buf
|
||
|
|
||
|
|
||
|
# ==== CONFIRM OPERATOR ====
|
||
|
class B3D_Confirm_Operator(bpy.types.Operator):
|
||
|
bl_idname = ("screen.b3d_confirm")
|
||
|
bl_label = ("File Exists, Overwrite?")
|
||
|
|
||
|
def invoke(self, context, event):
|
||
|
wm = context.window_manager
|
||
|
return wm.invoke_props_dialog(self)
|
||
|
|
||
|
def execute(self, context):
|
||
|
write_b3d_file(B3D_Confirm_Operator.filepath)
|
||
|
return {'FINISHED'}
|
||
|
|
||
|
|
||
|
#class ObjectListItem(bpy.types.PropertyGroup):
|
||
|
# id = bpy.props.IntProperty(name="ID")
|
||
|
#
|
||
|
#bpy.utils.register_class(ObjectListItem)
|
||
|
|
||
|
# ==== EXPORT OPERATOR ====
|
||
|
|
||
|
class B3D_Export_Operator(bpy.types.Operator):
|
||
|
bl_idname = ("screen.b3d_export")
|
||
|
bl_label = ("B3D Export")
|
||
|
filepath = bpy.props.StringProperty(subtype="FILE_PATH")
|
||
|
|
||
|
selected = bpy.props.BoolProperty(name="Export Selected Only", default=False)
|
||
|
vnormals = bpy.props.BoolProperty(name="Export Vertex Normals", default=True)
|
||
|
vcolors = bpy.props.BoolProperty(name="Export Vertex Colors", default=True)
|
||
|
cameras = bpy.props.BoolProperty(name="Export Cameras", default=False)
|
||
|
lights = bpy.props.BoolProperty(name="Export Lights", default=False)
|
||
|
mipmap = bpy.props.BoolProperty(name="Mipmap", default=False)
|
||
|
localsp = bpy.props.BoolProperty(name="Use Local Space Coords", default=False)
|
||
|
applymodifiers = bpy.props.BoolProperty(name="Apply modifiers", default=True)
|
||
|
|
||
|
overwrite_without_asking = bpy.props.BoolProperty(name="Overwrite without asking", default=False)
|
||
|
|
||
|
#skip_dialog = False
|
||
|
|
||
|
#objects = bpy.props.CollectionProperty(type=ObjectListItem, options={'HIDDEN'})
|
||
|
|
||
|
def invoke(self, context, event):
|
||
|
blend_filepath = context.blend_data.filepath
|
||
|
if not blend_filepath:
|
||
|
blend_filepath = "Untitled.b3d"
|
||
|
else:
|
||
|
blend_filepath = os.path.splitext(blend_filepath)[0] + ".b3d"
|
||
|
self.filepath = blend_filepath
|
||
|
|
||
|
context.window_manager.fileselect_add(self)
|
||
|
return {'RUNNING_MODAL'}
|
||
|
|
||
|
def execute(self, context):
|
||
|
|
||
|
global b3d_parameters
|
||
|
global the_scene
|
||
|
b3d_parameters["export-selected"] = self.selected
|
||
|
b3d_parameters["vertex-normals" ] = self.vnormals
|
||
|
b3d_parameters["vertex-colors" ] = self.vcolors
|
||
|
b3d_parameters["cameras" ] = self.cameras
|
||
|
b3d_parameters["lights" ] = self.lights
|
||
|
b3d_parameters["mipmap" ] = self.mipmap
|
||
|
b3d_parameters["local-space" ] = self.localsp
|
||
|
b3d_parameters["apply-modifiers"] = self.applymodifiers
|
||
|
|
||
|
the_scene = context.scene
|
||
|
|
||
|
if self.filepath == "":
|
||
|
return {'FINISHED'}
|
||
|
|
||
|
if not self.filepath.endswith(".b3d"):
|
||
|
self.filepath += ".b3d"
|
||
|
|
||
|
print("EXPORT", self.filepath," vcolor = ", self.vcolors)
|
||
|
|
||
|
obj_list = []
|
||
|
try:
|
||
|
# FIXME: silly and ugly hack, the list of objects to export is passed through
|
||
|
# a custom scene property
|
||
|
obj_list = context.scene.obj_list
|
||
|
except:
|
||
|
pass
|
||
|
|
||
|
if len(obj_list) > 0:
|
||
|
|
||
|
#objlist = []
|
||
|
#for a in self.objects:
|
||
|
# objlist.append(bpy.data.objects[a.id])
|
||
|
#
|
||
|
#write_b3d_file(self.filepath, obj_list)
|
||
|
|
||
|
write_b3d_file(self.filepath, obj_list)
|
||
|
else:
|
||
|
if os.path.exists(self.filepath) and not self.overwrite_without_asking:
|
||
|
#self.report({'ERROR'}, "File Exists")
|
||
|
B3D_Confirm_Operator.filepath = self.filepath
|
||
|
bpy.ops.screen.b3d_confirm('INVOKE_DEFAULT')
|
||
|
return {'FINISHED'}
|
||
|
else:
|
||
|
write_b3d_file(self.filepath)
|
||
|
return {'FINISHED'}
|
||
|
|
||
|
|
||
|
# Add to a menu
|
||
|
def menu_func_export(self, context):
|
||
|
global the_scene
|
||
|
the_scene = context.scene
|
||
|
self.layout.operator(B3D_Export_Operator.bl_idname, text="B3D (.b3d)")
|
||
|
|
||
|
def register():
|
||
|
bpy.types.INFO_MT_file_export.append(menu_func_export)
|
||
|
bpy.utils.register_module(__name__)
|
||
|
|
||
|
def unregister():
|
||
|
bpy.types.INFO_MT_file_export.remove(menu_func_export)
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
register()
|
||
|
|