diff options
-rw-r--r-- | release/scripts/blender2cal3d.py | 1520 | ||||
-rw-r--r-- | source/blender/python/api2_2x/CurNurb.c | 16 | ||||
-rw-r--r-- | source/blender/python/api2_2x/doc/Curve.py | 15 |
3 files changed, 31 insertions, 1520 deletions
diff --git a/release/scripts/blender2cal3d.py b/release/scripts/blender2cal3d.py deleted file mode 100644 index 15ed6e435f5..00000000000 --- a/release/scripts/blender2cal3d.py +++ /dev/null @@ -1,1520 +0,0 @@ -#!BPY - -""" -Name: 'Cal3D' -Blender: 241 -Group: 'Export' -Tip: 'Export armature/bone/mesh/action data to the Cal3D format.' -""" - -# blender2cal3D.py -# Copyright (C) 2003-2004 Jean-Baptiste LAMY -- jibalamy@free.fr -# Copyright (C) 2004 Matthias Braun -- matze@braunis.de -# -# 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 - - -__version__ = "0.14" -__author__ = "Jean-Baptiste 'Jiba' Lamy" -__email__ = "jibalamy@free.fr" -__url__ = "Soya3d's homepage http://home.gna.org/oomadness/en/soya/" -__bpydoc__ = """This script is a Blender => Cal3D converter. -(See http://blender.org and http://cal3d.sourceforge.net) - -USAGE - -To install it, place the script in your $HOME/.blender/scripts directory. -Then open the File->Export->Cal3d menu. And select the filename of the .cfg file. -The exporter will create a set of other files with same prefix (ie. bla.cfg, bla.xsf, -bla_Action1.xaf, bla_Action2.xaf, ...). -You should be able to open the .cfg file in cal3d_miniviewer. - - -NOT (YET) SUPPORTED - - - Rotation, translation, or stretching Blender objects is still quite - buggy, so AVOID MOVING / ROTATING / RESIZE OBJECTS (either mesh or armature) ! - Instead, edit the object (with tab), select all points / bones (with "a"), - and move / rotate / resize them. - - no support for exporting springs yet - - no support for exporting material colors (most games should only use images - I think...) - - -KNOWN ISSUES - - - Cal3D versions <=0.9.1 have a bug where animations aren't played when the root bone - is not animated - - Cal3D versions <=0.9.1 have a bug where objects that aren't influenced by any bones - are not drawn (fixed in Cal3D CVS) - - -NOTES - -It requires a very recent version of Blender (tested with 2.41). - -Build a model follows a few rules: - - Use only a single armature - - Use only a single rootbone (Cal3D doesn't support floating bones) - - Use only locrot keys (Cal3D doesn't support bone's size change) - - Don't try to create child/parent constructs in blender object, that gets exported - incorrectly at the moment - - Objects or animations whose names start by "_" are not exported (hidden object) - -It can be run in batch mode, as following : - blender model.blend -P blender2cal3d.py --blender2cal3d FILENAME=model.cfg EXPORT_FOR_SOYA=1 -You can pass as many parameters as you want at the end, "EXPORT_FOR_SOYA=1" is just an -exemple. The parameters are the same than below. - - -Logging is sent through python logger instance. you can control verbosity by changing -log.set_level(DEBUG|WARNING|ERROR|CRITICAL). to print to it use log.debug(), -log.warning(), log.error() and log.critical(). the logger breaks normal operation -by printing _all_ info messages. ( log.info() ). Output is sent to stdout and to -a blender text. - -Psyco support, turned off by default. can speed up export by 25% on my tests. -Turned off by default. - -hotshot_export function to see profile. just replace export with hotshot export -to see. - -Any vertices found without influence are placed into a vertex group called -"_no_inf". [FIXME] i need to stop this for batch more -running under gui mode the last export dir is saved into the blender registry -and called back again. -""" - - -# Parameters : - -# Filename to export to (if "", display a file selector dialog). -FILENAME = "" - -# True (=1) to export for the Soya 3D engine -# (http://oomadness.tuxfamily.org/en/soya). -# (=> rotate meshes and skeletons so as X is right, Y is top and -Z is front) -EXPORT_FOR_SOYA = 0 - -# Enables LODs computation. LODs computation is quite slow, and the algo is -# surely not optimal :-( -LODS = 0 - -# Scale the model (not supported by Soya). -SCALE = 1.0 - -# Set to 1 if you want to prefix all filename with the model name -# (e.g. knight_walk.xaf instead of walk.xaf) -PREFIX_FILE_WITH_MODEL_NAME = 0 - -# Set to 0 to use Cal3D binary format -XML = 1 - -# Configuration text buffer -CONFIG_TEXT = "" - -# Allows to replace a material by another -MATERIAL_MAP = {} - -# Flip the UV texture coordinates -FLIP_TEXTURE_COORDS = 1 - -MESSAGES = "" - -# See also BASE_MATRIX below, if you want to rotate/scale/translate the model at -# the exportation. - -######################################################################################### -# Code starts here. -# The script should be quite re-useable for writing another Blender animation exporter. -# Most of the hell of it is to deal with Blender's head-tail-roll bone's definition. - -import sys, os, os.path, struct, math, string - -try: - import psyco - psyco.full() -except: - print "* Blender2Cal3D * (Psyco not found)" - -import Blender, Blender.Scene -from Blender import Registry -from Blender.Window import DrawProgressBar -from Blender import Draw, BGL - -import logging -reload(logging) - -import types - -import textwrap - -# HACK -- it seems that some Blender versions don't define sys.argv, -# which may crash Python if a warning occurs. -if not hasattr(sys, "argv"): sys.argv = ["???"] - -# our own logger class. it works just the same as a normal logger except -# all info messages get show. -class Logger(logging.Logger): - def __init__(self,name,level=logging.NOTSET): - logging.Logger.__init__(self,name,level) - - self.has_warnings=False - self.has_errors=False - self.has_critical=False - - def info(self,msg,*args,**kwargs): - apply(self._log,(logging.INFO,msg,args),kwargs) - - def warning(self,msg,*args,**kwargs): - logging.Logger.warning(self,msg,*args,**kwargs) - self.has_warnings=True - - def error(self,msg,*args,**kwargs): - logging.Logger.error(self,msg,*args,**kwargs) - self.has_errors=True - - def critical(self,msg,*args,**kwargs): - logging.Logger.critical(self,msg,*args,**kwargs) - self.has_errors=True - - -# should be able to make this print to stdout in realtime and save MESSAGES -# as well. perhaps also have a log to file option -class LogHandler(logging.StreamHandler): - def __init__(self): - logging.StreamHandler.__init__(self,sys.stdout) - - if "blender2cal3d_log" not in Blender.Text.Get(): - self.outtext=Blender.Text.New("blender2cal3d_log") - else: - self.outtext=Blender.Text.Get('blender2cal3d_log') - self.outtext.clear() - - self.lastmsg='' - - def emit(self,record): - # print to stdout and to a new blender text object - - msg=self.format(record) - - if msg==self.lastmsg: - return - - self.lastmsg=msg - - self.outtext.write("%s\n" %msg) - - logging.StreamHandler.emit(self,record) - - """ - try: - msg=self.format(record) - if not hasattr(types,"UnicodeType"): - self.stream.write("%s\n" % msg) - else: - try: - self.stream.write("%s\n" % msg) - except UnicodeError: - self.stream.write("%s\n" % msg.encode("UTF-8")) - - self.flush() - except: - self.handleError(record) - """ -logging.setLoggerClass(Logger) -log=logging.getLogger('blender2cal3d') - -handler=LogHandler() -formatter=logging.Formatter('%(levelname)s %(message)s') -handler.setFormatter(formatter) - -log.addHandler(handler) -# set this to minimum output level. eg. logging.DEBUG, logging.WARNING, logging.ERROR -# logging.CRITICAL. logging.INFO will make little difference as these always get -# output'd -log.setLevel(logging.WARNING) - -log.info("Starting...") - -# transforms a blender to a cal3d quaternion notation (x,y,z,w) -def blender2cal3dquat(q): - return [q.x, q.y, q.z, q.w] - -def quaternion2matrix(q): - xx = q[0] * q[0] - yy = q[1] * q[1] - zz = q[2] * q[2] - xy = q[0] * q[1] - xz = q[0] * q[2] - yz = q[1] * q[2] - wx = q[3] * q[0] - wy = q[3] * q[1] - wz = q[3] * q[2] - return [[1.0 - 2.0 * (yy + zz), 2.0 * (xy + wz), 2.0 * (xz - wy), 0.0], - [ 2.0 * (xy - wz), 1.0 - 2.0 * (xx + zz), 2.0 * (yz + wx), 0.0], - [ 2.0 * (xz + wy), 2.0 * (yz - wx), 1.0 - 2.0 * (xx + yy), 0.0], - [0.0 , 0.0 , 0.0 , 1.0]] - -# def matrix2quaternion(m): -# s = math.sqrt(abs(m[0][0] + m[1][1] + m[2][2] + m[3][3])) -# if s == 0.0: -# x = abs(m[2][1] - m[1][2]) -# y = abs(m[0][2] - m[2][0]) -# z = abs(m[1][0] - m[0][1]) -# if (x >= y) and (x >= z): return 1.0, 0.0, 0.0, 0.0 -# elif (y >= x) and (y >= z): return 0.0, 1.0, 0.0, 0.0 -# else: return 0.0, 0.0, 1.0, 0.0 -# return quaternion_normalize([ -# -(m[2][1] - m[1][2]) / (2.0 * s), -# -(m[0][2] - m[2][0]) / (2.0 * s), -# -(m[1][0] - m[0][1]) / (2.0 * s), -# 0.5 * s, -# ]) - -def matrix2quaternion(m): - t = m[0][0] + m[1][1] + m[2][2] + m[3][3] - if t > 0.00000001: - s = math.sqrt(t) * 2 - return quaternion_normalize([ - -(m[2][1] - m[1][2]) / s, - -(m[0][2] - m[2][0]) / s, - -(m[1][0] - m[0][1]) / s, - 0.25 * s, - ]) - else: - if (m[0][0] > m[1][1]) and (m[0][0] > m[2][2]): - s = math.sqrt(1.0 + m[0][0] - m[1][1] - m[2][2]) * 2 - return quaternion_normalize([ - 0.25 * s, - -(m[1][0] + m[0][1]) / s, - -(m[0][2] + m[2][0]) / s, - -(m[2][1] - m[1][2]) / s, - ]) - elif m[1][1] > m[2][2]: - s = math.sqrt(1.0 + m[1][1] - m[0][0] - m[2][2]) * 2 - return quaternion_normalize([ - -(m[1][0] + m[0][1]) / s, - 0.25 * s, - -(m[2][1] + m[1][2]) / s, - -(m[0][2] - m[2][0]) / s, - ]) - else: - s = math.sqrt(1.0 + m[2][2] - m[0][0] - m[1][1]) * 2 - return quaternion_normalize([ - -(m[0][2] + m[2][0]) / s, - -(m[2][1] + m[1][2]) / s, - 0.25 * s, - -(m[1][0] - m[0][1]) / s, - ]) - -def quaternion_normalize(q): - l = math.sqrt(q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3]) - return q[0] / l, q[1] / l, q[2] / l, q[3] / l - -# multiplies 2 quaternions in x,y,z,w notation -def quaternion_multiply(q1, q2): - return [ - q2[3] * q1[0] + q2[0] * q1[3] + q2[1] * q1[2] - q2[2] * q1[1], - q2[3] * q1[1] + q2[1] * q1[3] + q2[2] * q1[0] - q2[0] * q1[2], - q2[3] * q1[2] + q2[2] * q1[3] + q2[0] * q1[1] - q2[1] * q1[0], - q2[3] * q1[3] - q2[0] * q1[0] - q2[1] * q1[1] - q2[2] * q1[2], - ] - -def matrix_translate(m, v): - m[3][0] += v[0] - m[3][1] += v[1] - m[3][2] += v[2] - return m - -def matrix_multiply(b, a): - return [ [ - a[0][0] * b[0][0] + a[0][1] * b[1][0] + a[0][2] * b[2][0], - a[0][0] * b[0][1] + a[0][1] * b[1][1] + a[0][2] * b[2][1], - a[0][0] * b[0][2] + a[0][1] * b[1][2] + a[0][2] * b[2][2], - 0.0, - ], [ - a[1][0] * b[0][0] + a[1][1] * b[1][0] + a[1][2] * b[2][0], - a[1][0] * b[0][1] + a[1][1] * b[1][1] + a[1][2] * b[2][1], - a[1][0] * b[0][2] + a[1][1] * b[1][2] + a[1][2] * b[2][2], - 0.0, - ], [ - a[2][0] * b[0][0] + a[2][1] * b[1][0] + a[2][2] * b[2][0], - a[2][0] * b[0][1] + a[2][1] * b[1][1] + a[2][2] * b[2][1], - a[2][0] * b[0][2] + a[2][1] * b[1][2] + a[2][2] * b[2][2], - 0.0, - ], [ - a[3][0] * b[0][0] + a[3][1] * b[1][0] + a[3][2] * b[2][0] + b[3][0], - a[3][0] * b[0][1] + a[3][1] * b[1][1] + a[3][2] * b[2][1] + b[3][1], - a[3][0] * b[0][2] + a[3][1] * b[1][2] + a[3][2] * b[2][2] + b[3][2], - 1.0, - ] ] - -def matrix_invert(m): - det = (m[0][0] * (m[1][1] * m[2][2] - m[2][1] * m[1][2]) - - m[1][0] * (m[0][1] * m[2][2] - m[2][1] * m[0][2]) - + m[2][0] * (m[0][1] * m[1][2] - m[1][1] * m[0][2])) - if det == 0.0: return None - det = 1.0 / det - r = [ [ - det * (m[1][1] * m[2][2] - m[2][1] * m[1][2]), - - det * (m[0][1] * m[2][2] - m[2][1] * m[0][2]), - det * (m[0][1] * m[1][2] - m[1][1] * m[0][2]), - 0.0, - ], [ - - det * (m[1][0] * m[2][2] - m[2][0] * m[1][2]), - det * (m[0][0] * m[2][2] - m[2][0] * m[0][2]), - - det * (m[0][0] * m[1][2] - m[1][0] * m[0][2]), - 0.0 - ], [ - det * (m[1][0] * m[2][1] - m[2][0] * m[1][1]), - - det * (m[0][0] * m[2][1] - m[2][0] * m[0][1]), - det * (m[0][0] * m[1][1] - m[1][0] * m[0][1]), - 0.0, - ] ] - r.append([ - -(m[3][0] * r[0][0] + m[3][1] * r[1][0] + m[3][2] * r[2][0]), - -(m[3][0] * r[0][1] + m[3][1] * r[1][1] + m[3][2] * r[2][1]), - -(m[3][0] * r[0][2] + m[3][1] * r[1][2] + m[3][2] * r[2][2]), - 1.0, - ]) - return r - -def matrix_rotate_x(angle): - cos = math.cos(math.radians(angle)) - sin = math.sin(math.radians(angle)) - return [ - [1.0, 0.0, 0.0, 0.0], - [0.0, cos, sin, 0.0], - [0.0, -sin, cos, 0.0], - [0.0, 0.0, 0.0, 1.0], - ] - -def matrix_rotate_y(angle): - cos = math.cos(math.radians(angle)) - sin = math.sin(math.radians(angle)) - return [ - [cos, 0.0, -sin, 0.0], - [0.0, 1.0, 0.0, 0.0], - [sin, 0.0, cos, 0.0], - [0.0, 0.0, 0.0, 1.0], - ] - -def matrix_rotate_z(angle): - cos = math.cos(math.radians(angle)) - sin = math.sin(math.radians(angle)) - return [ - [ cos, sin, 0.0, 0.0], - [-sin, cos, 0.0, 0.0], - [ 0.0, 0.0, 1.0, 0.0], - [ 0.0, 0.0, 0.0, 1.0], - ] - -def matrix_rotate(axis, angle): - vx = axis[0] - vy = axis[1] - vz = axis[2] - vx2 = vx * vx - vy2 = vy * vy - vz2 = vz * vz - cos = math.cos(math.radians(angle)) - sin = math.sin(math.radians(angle)) - co1 = 1.0 - cos - return [ - [vx2 * co1 + cos, vx * vy * co1 + vz * sin, vz * vx * co1 - vy * sin, 0.0], - [vx * vy * co1 - vz * sin, vy2 * co1 + cos, vy * vz * co1 + vx * sin, 0.0], - [vz * vx * co1 + vy * sin, vy * vz * co1 - vx * sin, vz2 * co1 + cos, 0.0], - [0.0, 0.0, 0.0, 1.0], - ] - -def matrix_scale(fx, fy, fz): - return [ - [ fx, 0.0, 0.0, 0.0], - [0.0, fy, 0.0, 0.0], - [0.0, 0.0, fz, 0.0], - [0.0, 0.0, 0.0, 1.0], - ] - -def point_by_matrix(p, m): - return [p[0] * m[0][0] + p[1] * m[1][0] + p[2] * m[2][0] + m[3][0], - p[0] * m[0][1] + p[1] * m[1][1] + p[2] * m[2][1] + m[3][1], - p[0] * m[0][2] + p[1] * m[1][2] + p[2] * m[2][2] + m[3][2]] - -def point_distance(p1, p2): - return math.sqrt((p2[0] - p1[0]) ** 2 + (p2[1] - p1[1]) ** 2 + (p2[2] - p1[2]) ** 2) - -def vector_add(v1, v2): - return [v1[0]+v2[0], v1[1]+v2[1], v1[2]+v2[2]] - -def vector_sub(v1, v2): - return [v1[0]-v2[0], v1[1]-v2[1], v1[2]-v2[2]] - -def vector_by_matrix(p, m): - return [p[0] * m[0][0] + p[1] * m[1][0] + p[2] * m[2][0], - p[0] * m[0][1] + p[1] * m[1][1] + p[2] * m[2][1], - p[0] * m[0][2] + p[1] * m[1][2] + p[2] * m[2][2]] - -def vector_length(v): - return math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]) - -def vector_normalize(v): - l = math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]) - if l: return v[0] / l, v[1] / l, v[2] / l - else: return v - -def vector_dotproduct(v1, v2): - return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2] - -def vector_crossproduct(v1, v2): - return [ - v1[1] * v2[2] - v1[2] * v2[1], - v1[2] * v2[0] - v1[0] * v2[2], - v1[0] * v2[1] - v1[1] * v2[0], - ] - -def vector_angle(v1, v2): - s = vector_length(v1) * vector_length(v2) - f = vector_dotproduct(v1, v2) / s - if f >= 1.0: return 0.0 - if f <= -1.0: return math.pi / 2.0 - return math.atan(-f / math.sqrt(1.0 - f * f)) + math.pi / 2.0 - -def blender_bone2matrix(head, tail, roll): - # Convert bone rest state (defined by bone.head, bone.tail and bone.roll) - # to a matrix (the more standard notation). - # Taken from blenkernel/intern/armature.c in Blender source. - # See also DNA_armature_types.h:47. - - target = [0.0, 1.0, 0.0] - delta = [tail[0] - head[0], tail[1] - head[1], tail[2] - head[2]] - nor = vector_normalize(delta) - axis = vector_crossproduct(target, nor) - - if vector_dotproduct(axis, axis) > 0.0000000000001: - axis = vector_normalize(axis) - theta = math.acos(vector_dotproduct(target, nor)) - bMatrix = matrix_rotate(axis, math.degrees(theta)) - - else: - if vector_dotproduct(target, nor) > 0.0: updown = 1.0 - else: updown = -1.0 - - # Quoted from Blender source : "I think this should work ..." - bMatrix = [ - [updown, 0.0, 0.0, 0.0], - [0.0, updown, 0.0, 0.0], - [0.0, 0.0, 1.0, 0.0], - [0.0, 0.0, 0.0, 1.0], - ] - - rMatrix = matrix_rotate(nor, roll) - return matrix_multiply(rMatrix, bMatrix) - - -# Hack for having the model rotated right. -# Put in BASE_MATRIX your own rotation if you need some. - -BASE_MATRIX = None - - -# Cal3D data structures - -CAL3D_VERSION = 910 - -NEXT_MATERIAL_ID = 0 -class Material: - def __init__(self, map_filename = None): - self.ambient_r = 255 - self.ambient_g = 255 - self.ambient_b = 255 - self.ambient_a = 255 - self.diffuse_r = 255 - self.diffuse_g = 255 - self.diffuse_b = 255 - self.diffuse_a = 255 - self.specular_r = 255 - self.specular_g = 255 - self.specular_b = 255 - self.specular_a = 255 - self.shininess = 1.0 - - log.debug("Material with name %s",map_filename) - - if map_filename: self.maps_filenames = [map_filename] - else: self.maps_filenames = [] - - MATERIALS[map_filename] = self - - global NEXT_MATERIAL_ID - self.id = NEXT_MATERIAL_ID - NEXT_MATERIAL_ID += 1 - - # old cal3d format - def to_cal3d(self): - s = "CRF\0" + struct.pack("iBBBBBBBBBBBBfi", CAL3D_VERSION, self.ambient_r, self.ambient_g, self.ambient_b, self.ambient_a, self.diffuse_r, self.diffuse_g, self.diffuse_b, self.diffuse_a, self.specular_r, self.specular_g, self.specular_b, self.specular_a, self.shininess, len(self.maps_filenames)) - for map_filename in self.maps_filenames: - s += struct.pack("i", len(map_filename) + 1) - s += map_filename + "\0" - return s - - # new xml format - def to_cal3d_xml(self): - s = "<?xml version=\"1.0\"?>\n" - s += "<HEADER MAGIC=\"XRF\" VERSION=\"%i\"/>\n" % CAL3D_VERSION - s += "<MATERIAL NUMMAPS=\"" + str(len(self.maps_filenames)) + "\">\n" - s += " <AMBIENT>" + str(self.ambient_r) + " " + str(self.ambient_g) + " " + str(self.ambient_b) + " " + str(self.ambient_a) + "</AMBIENT>\n"; - s += " <DIFFUSE>" + str(self.diffuse_r) + " " + str(self.diffuse_g) + " " + str(self.diffuse_b) + " " + str(self.diffuse_a) + "</DIFFUSE>\n"; - s += " <SPECULAR>" + str(self.specular_r) + " " + str(self.specular_g) + " " + str(self.specular_b) + " " + str(self.specular_a) + "</SPECULAR>\n"; - s += " <SHININESS>" + str(self.shininess) + "</SHININESS>\n"; - - for map_filename in self.maps_filenames: - s += " <MAP>" + map_filename + "</MAP>\n"; - - s += "</MATERIAL>\n"; - - return s - -MATERIALS = {} - -class Mesh: - def __init__(self, name): - name=string.replace(name,'.','_') - self.name = name - self.submeshes = [] - - self.next_submesh_id = 0 - - def to_cal3d(self): - s = "CMF\0" + struct.pack("ii", CAL3D_VERSION, len(self.submeshes)) - s += "".join(map(SubMesh.to_cal3d, self.submeshes)) - return s - - def to_cal3d_xml(self): - s = "<?xml version=\"1.0\"?>\n" - s += "<HEADER MAGIC=\"XMF\" VERSION=\"%i\"/>\n" % CAL3D_VERSION - s += "<MESH NUMSUBMESH=\"%i\">\n" % len(self.submeshes) - s += "".join(map(SubMesh.to_cal3d_xml, self.submeshes)) - s += "</MESH>\n" - return s - -class SubMesh: - def __init__(self, mesh, material): - self.material = material - self.vertices = [] - self.faces = [] - self.nb_lodsteps = 0 - self.springs = [] - - self.next_vertex_id = 0 - - self.mesh = mesh - self.id = mesh.next_submesh_id - mesh.next_submesh_id += 1 - mesh.submeshes.append(self) - - def compute_lods(self): - """Computes LODs info for Cal3D (there's no Blender related stuff here).""" - - log.info("Start LODs computation...") - - vertex2faces = {} - for face in self.faces: - for vertex in (face.vertex1, face.vertex2, face.vertex3): - l = vertex2faces.get(vertex) - if not l: vertex2faces[vertex] = [face] - else: l.append(face) - - couple_treated = {} - couple_collapse_factor = [] - for face in self.faces: - for a, b in ((face.vertex1, face.vertex2), (face.vertex1, face.vertex3), (face.vertex2, face.vertex3)): - a = a.cloned_from or a - b = b.cloned_from or b - if a.id > b.id: a, b = b, a - if not couple_treated.has_key((a, b)): - # The collapse factor is simply the distance between the 2 points :-( - # This should be improved !! - if vector_dotproduct(a.normal, b.normal) < 0.9: continue - couple_collapse_factor.append((point_distance(a.loc, b.loc), a, b)) - couple_treated[a, b] = 1 - - couple_collapse_factor.sort() - - collapsed = {} - new_vertices = [] - new_faces = [] - for factor, v1, v2 in couple_collapse_factor: - # Determines if v1 collapses to v2 or v2 to v1. - # We choose to keep the vertex which is on the smaller number of faces, since - # this one has more chance of being in an extrimity of the body. - # Though heuristic, this rule yields very good results in practice. - if len(vertex2faces[v1]) < len(vertex2faces[v2]): v2, v1 = v1, v2 - elif len(vertex2faces[v1]) == len(vertex2faces[v2]): - if collapsed.get(v1, 0): v2, v1 = v1, v2 # v1 already collapsed, try v2 - - if (not collapsed.get(v1, 0)) and (not collapsed.get(v2, 0)): - collapsed[v1] = 1 - collapsed[v2] = 1 - - # Check if v2 is already colapsed - while v2.collapse_to: v2 = v2.collapse_to - - common_faces = filter(vertex2faces[v1].__contains__, vertex2faces[v2]) - - v1.collapse_to = v2 - v1.face_collapse_count = len(common_faces) - - for clone in v1.clones: - # Find the clone of v2 that correspond to this clone of v1 - possibles = [] - for face in vertex2faces[clone]: - possibles.append(face.vertex1) - possibles.append(face.vertex2) - possibles.append(face.vertex3) - clone.collapse_to = v2 - for vertex in v2.clones: - if vertex in possibles: - clone.collapse_to = vertex - break - - clone.face_collapse_count = 0 - new_vertices.append(clone) - - # HACK -- all faces get collapsed with v1 (and no faces are collapsed with v1's - # clones). This is why we add v1 in new_vertices after v1's clones. - # This hack has no other incidence that consuming a little few memory for the - # extra faces if some v1's clone are collapsed but v1 is not. - new_vertices.append(v1) - - self.nb_lodsteps += 1 + len(v1.clones) - - new_faces.extend(common_faces) - for face in common_faces: - face.can_collapse = 1 - - # Updates vertex2faces - vertex2faces[face.vertex1].remove(face) - vertex2faces[face.vertex2].remove(face) - vertex2faces[face.vertex3].remove(face) - vertex2faces[v2].extend(vertex2faces[v1]) - - new_vertices.extend(filter(lambda vertex: not vertex.collapse_to, self.vertices)) - new_vertices.reverse() # Cal3D want LODed vertices at the end - for i in range(len(new_vertices)): new_vertices[i].id = i - self.vertices = new_vertices - - new_faces.extend(filter(lambda face: not face.can_collapse, self.faces)) - new_faces.reverse() # Cal3D want LODed faces at the end - self.faces = new_faces - - log.info("LODs computed : %s vertices can be removed (from a total of %s)." % (self.nb_lodsteps, len(self.vertices))) - - def rename_vertices(self, new_vertices): - """Rename (change ID) of all vertices, such as self.vertices == new_vertices.""" - for i in range(len(new_vertices)): new_vertices[i].id = i - self.vertices = new_vertices - - def to_cal3d(self): - s = struct.pack("iiiiii", self.material.id, len(self.vertices), len(self.faces), self.nb_lodsteps, len(self.springs), len(self.material.maps_filenames)) - s += "".join(map(Vertex.to_cal3d, self.vertices)) - s += "".join(map(Spring.to_cal3d, self.springs)) - s += "".join(map(Face .to_cal3d, self.faces)) - return s - - def to_cal3d_xml(self): - s = " <SUBMESH NUMVERTICES=\"%i\" NUMFACES=\"%i\" MATERIAL=\"%i\" " % \ - (len(self.vertices), len(self.faces), self.material.id) - s += "NUMLODSTEPS=\"%i\" NUMSPRINGS=\"%i\" NUMTEXCOORDS=\"%i\">\n" % \ - (self.nb_lodsteps, len(self.springs), - len(self.material.maps_filenames)) - s += "".join(map(Vertex.to_cal3d_xml, self.vertices)) - s += "".join(map(Spring.to_cal3d_xml, self.springs)) - s += "".join(map(Face.to_cal3d_xml, self.faces)) - s += " </SUBMESH>\n" - return s - -class Vertex: - def __init__(self, submesh, loc, normal): - self.loc = loc - self.normal = normal - self.collapse_to = None - self.face_collapse_count = 0 - self.maps = [] - self.influences = [] - self.weight = None - - self.cloned_from = None - self.clones = [] - - self.submesh = submesh - self.id = submesh.next_vertex_id - submesh.next_vertex_id += 1 - submesh.vertices.append(self) - - def to_cal3d(self): - if self.collapse_to: collapse_id = self.collapse_to.id - else: collapse_id = -1 - s = struct.pack("ffffffii", self.loc[0], self.loc[1], self.loc[2], self.normal[0], self.normal[1], self.normal[2], collapse_id, self.face_collapse_count) - s += "".join(map(Map.to_cal3d, self.maps)) - s += struct.pack("i", len(self.influences)) - s += "".join(map(Influence.to_cal3d, self.influences)) - if not self.weight is None: s += struct.pack("f", len(self.weight)) - return s - - def to_cal3d_xml(self): - if self.collapse_to: - collapse_id = self.collapse_to.id - else: - collapse_id = -1 - s = " <VERTEX ID=\"%i\" NUMINFLUENCES=\"%i\">\n" % \ - (self.id, len(self.influences)) - s += " <POS>%f %f %f</POS>\n" % (self.loc[0], self.loc[1], self.loc[2]) - s += " <NORM>%f %f %f</NORM>\n" % \ - (self.normal[0], self.normal[1], self.normal[2]) - if collapse_id != -1: - s += " <COLLAPSEID>%i</COLLAPSEID>\n" % collapse_id - s += " <COLLAPSECOUNT>%i</COLLAPSECOUNT>\n" % \ - self.face_collapse_count - s += "".join(map(Map.to_cal3d_xml, self.maps)) - s += "".join(map(Influence.to_cal3d_xml, self.influences)) - if not self.weight is None: - s += " <PHYSIQUE>%f</PHYSIQUE>\n" % len(self.weight) - s += " </VERTEX>\n" - return s - -class Map: - def __init__(self, u, v): - self.u = u - self.v = v - - def to_cal3d(self): - return struct.pack("ff", self.u, self.v) - - def to_cal3d_xml(self): - return " <TEXCOORD>%f %f</TEXCOORD>\n" % (self.u, self.v) - -class Influence: - def __init__(self, bone, weight): - self.bone = bone - self.weight = weight - - def to_cal3d(self): - return struct.pack("if", self.bone.id, self.weight) - - def to_cal3d_xml(self): - return " <INFLUENCE ID=\"%i\">%f</INFLUENCE>\n" % \ - (self.bone.id, self.weight) - -class Spring: - def __init__(self, vertex1, vertex2): - self.vertex1 = vertex1 - self.vertex2 = vertex2 - self.spring_coefficient = 0.0 - self.main_looplength = 0.0 - - def to_cal3d(self): - return struct.pack("iiff", self.vertex1.id, self.vertex2.id, self.spring_coefficient, self.main_looplength) - - def to_cal3d_xml(self): - return " <SPRING VERTEXID=\"%i %i\" COEF=\"%f\" LENGTH=\"%f\"/>\n" % \ - (self.vertex1.id, self.vertex2.id, self.spring_coefficient, - self.main_looplength) - -class Face: - def __init__(self, submesh, vertex1, vertex2, vertex3): - self.vertex1 = vertex1 - self.vertex2 = vertex2 - self.vertex3 = vertex3 - - self.can_collapse = 0 - - self.submesh = submesh - submesh.faces.append(self) - - def to_cal3d(self): - return struct.pack("iii", self.vertex1.id, self.vertex2.id, self.vertex3.id) - - def to_cal3d_xml(self): - return " <FACE VERTEXID=\"%i %i %i\"/>\n" % \ - (self.vertex1.id, self.vertex2.id, self.vertex3.id) - -class Skeleton: - def __init__(self): - self.bones = [] - - self.next_bone_id = 0 - - def to_cal3d(self): - s = "CSF\0" + struct.pack("ii", CAL3D_VERSION, len(self.bones)) - s += "".join(map(Bone.to_cal3d, self.bones)) - return s - - def to_cal3d_xml(self): - s = "<?xml version=\"1.0\"?>\n" - s += "<HEADER MAGIC=\"XSF\" VERSION=\"%i\"/>\n" % CAL3D_VERSION - s += "<SKELETON NUMBONES=\"%i\">\n" % len(self.bones) - s += "".join(map(Bone.to_cal3d_xml, self.bones)) - s += "</SKELETON>\n" - return s - -BONES = {} - -class Bone: - def __init__(self, skeleton, parent, name, loc, rot): - self.parent = parent - name=string.replace(name,'.','_') - self.name = name - self.loc = loc - self.rot = rot - self.children = [] - - self.matrix = self.local_matrix = matrix_translate(quaternion2matrix(rot), loc) - - if parent: - self.matrix = matrix_multiply(parent.matrix, self.matrix) - parent.children.append(self) - - # lloc and lrot are the bone => model space transformation (translation and rotation). - # They are probably specific to Cal3D. - m = matrix_invert(self.matrix) - self.lloc = m[3][0], m[3][1], m[3][2] - self.lrot = matrix2quaternion(m) - - self.skeleton = skeleton - self.id = skeleton.next_bone_id - skeleton.next_bone_id += 1 - skeleton.bones.append(self) - - BONES[name] = self - - def to_cal3d(self): - s = struct.pack("i", len(self.name) + 1) + self.name + "\0" - - # We need to negate quaternion W value, but why ? - s += struct.pack("ffffffffffffff", self.loc[0], self.loc[1], self.loc[2], self.rot[0], self.rot[1], self.rot[2], -self.rot[3], self.lloc[0], self.lloc[1], self.lloc[2], self.lrot[0], self.lrot[1], self.lrot[2], -self.lrot[3]) - if self.parent: s += struct.pack("i", self.parent.id) - else: s += struct.pack("i", -1) - s += struct.pack("i", len(self.children)) - s += "".join(map(lambda bone: struct.pack("i", bone.id), self.children)) - return s - - def to_cal3d_xml(self): - s = " <BONE ID=\"%i\" NAME=\"%s\" NUMCHILD=\"%i\">\n" % \ - (self.id, self.name, len(self.children)) - # We need to negate quaternion W value, but why ? - s += " <TRANSLATION>%f %f %f</TRANSLATION>\n" % \ - (self.loc[0], self.loc[1], self.loc[2]) - s += " <ROTATION>%f %f %f %f</ROTATION>\n" % \ - (self.rot[0], self.rot[1], self.rot[2], -self.rot[3]) - s += " <LOCALTRANSLATION>%f %f %f</LOCALTRANSLATION>\n" % \ - (self.lloc[0], self.lloc[1], self.lloc[2]) - s += " <LOCALROTATION>%f %f %f %f</LOCALROTATION>\n" % \ - (self.lrot[0], self.lrot[1], self.lrot[2], -self.lrot[3]) - if self.parent: - s += " <PARENTID>%i</PARENTID>\n" % self.parent.id - else: - s += " <PARENTID>%i</PARENTID>\n" % -1 - s += "".join(map(lambda bone: " <CHILDID>%i</CHILDID>\n" % bone.id, - self.children)) - s += " </BONE>\n" - return s - -class Animation: - def __init__(self, name, duration = 0.0): - name=string.replace(name,'.','_') - self.name = name - self.duration = duration - self.tracks = {} # Map bone names to tracks - - def to_cal3d(self): - s = "CAF\0" + struct.pack("ifi", CAL3D_VERSION, self.duration, len(self.tracks)) - s += "".join(map(Track.to_cal3d, self.tracks.values())) - return s - - def to_cal3d_xml(self): - s = "<?xml version=\"1.0\"?>\n" - s += "<HEADER MAGIC=\"XAF\" VERSION=\"%i\"/>\n" % CAL3D_VERSION - s += "<ANIMATION DURATION=\"%f\" NUMTRACKS=\"%i\">\n" % \ - (self.duration, len(self.tracks)) - s += "".join(map(Track.to_cal3d_xml, self.tracks.values())) - s += "</ANIMATION>\n" - return s - -class Track: - def __init__(self, animation, bone): - self.bone = bone - self.keyframes = [] - - self.animation = animation - animation.tracks[bone.name] = self - - def to_cal3d(self): - s = struct.pack("ii", self.bone.id, len(self.keyframes)) - s += "".join(map(KeyFrame.to_cal3d, self.keyframes)) - return s - - def to_cal3d_xml(self): - s = " <TRACK BONEID=\"%i\" NUMKEYFRAMES=\"%i\">\n" % \ - (self.bone.id, len(self.keyframes)) - s += "".join(map(KeyFrame.to_cal3d_xml, self.keyframes)) - s += " </TRACK>\n" - return s - -class KeyFrame: - def __init__(self, track, time, loc, rot): - self.time = time - self.loc = loc - self.rot = rot - - self.track = track - track.keyframes.append(self) - - def to_cal3d(self): - # We need to negate quaternion W value, but why ? - return struct.pack("ffffffff", self.time, self.loc[0], self.loc[1], self.loc[2], self.rot[0], self.rot[1], self.rot[2], -self.rot[3]) - - def to_cal3d_xml(self): - s = " <KEYFRAME TIME=\"%f\">\n" % self.time - s += " <TRANSLATION>%f %f %f</TRANSLATION>\n" % \ - (self.loc[0], self.loc[1], self.loc[2]) - # We need to negate quaternion W value, but why ? - s += " <ROTATION>%f %f %f %f</ROTATION>\n" % \ - (self.rot[0], self.rot[1], self.rot[2], -self.rot[3]) - s += " </KEYFRAME>\n" - return s - -def export(filename): - global MESSAGES - - if EXPORT_FOR_SOYA: - global BASE_MATRIX - BASE_MATRIX = matrix_rotate_x(-90.0) - - # Get the scene - scene = Blender.Scene.GetCurrent() - - # ---- Export skeleton (=armature) ---------------------------------------- - - if Blender.mode == 'interactive': DrawProgressBar(0.0,'Exporting skeleton...') - - skeleton = Skeleton() - - foundarmature = False - for obj in scene.objects: #Blender.Object.Get(): - if obj.type == 'Armature': - data = obj.data - - if foundarmature == True: - log.error("Found multiple armatures! '" + obj.getName() + "' ignored.\n") - continue - - foundarmature = True - matrix = obj.getMatrix() - if BASE_MATRIX: - matrix = matrix_multiply(BASE_MATRIX, matrix) - - def treat_bone(b, parent = None): - head = b.head["BONESPACE"] - tail = b.tail["BONESPACE"] - - # Turns the Blender's head-tail-roll notation into a quaternion - quat = matrix2quaternion(blender_bone2matrix(head, tail, b.roll["BONESPACE"])) - - if parent: - # Compute the translation from the parent bone's head to the child - # bone's head, in the parent bone coordinate system. - # The translation is parent_tail - parent_head + child_head, - # but parent_tail and parent_head must be converted from the parent's parent - # system coordinate into the parent system coordinate. - - parent_invert_transform = matrix_invert(quaternion2matrix(parent.rot)) - parent_head = vector_by_matrix(parent.head, parent_invert_transform) - parent_tail = vector_by_matrix(parent.tail, parent_invert_transform) - - - - #ploc = vector_add(head, b.getLoc()) - parentheadtotail = vector_sub(parent_tail, parent_head) - # hmm this should be handled by the IPos, but isn't for non-animated - # bones which are transformed in the pose mode... - #loc = vector_add(ploc, parentheadtotail) - #rot = quaternion_multiply(blender2cal3dquat(b.getQuat()), quat) - loc = parentheadtotail - rot = quat - - log.debug("Parented Bone: %s",b.name) - - bone = Bone(skeleton, parent, b.name, loc, rot) - else: - # Apply the armature's matrix to the root bones - head = point_by_matrix(head, matrix) - tail = point_by_matrix(tail, matrix) - quat = matrix2quaternion(matrix_multiply(matrix, quaternion2matrix(quat))) # Probably not optimal - - # loc = vector_add(head, b.getLoc()) - # rot = quaternion_multiply(blender2cal3dquat(b.getQuat()), quat) - loc = head - rot = quat - - log.debug("Non Parented Bone: %s",b.name) - - # Here, the translation is simply the head vector - bone = Bone(skeleton, None, b.name, loc, rot) - - bone.head = head - bone.tail = tail - - if b.hasChildren(): - for child in b.children: - treat_bone(child, bone) - - foundroot = False - for b in data.bones.values(): - # child bones are handled in treat_bone - if b.parent != None: - continue - if foundroot == True: - log.warning("Warning: Found multiple root-bones, this may not be supported in cal3d.") - #print "Ignoring bone '" + b.name + "' and it's childs." - #continue - - treat_bone(b) - foundroot = True - - # ---- Export Mesh data --------------------------------------------------- - - if Blender.mode == 'interactive': DrawProgressBar(0.3,'Exporting meshes...') - - meshes = [] - - for obj in scene.objects: #Blender.Object.Get(): - if obj.type == 'Mesh': - data = obj.data - if data.faces: - mesh_name = obj.name - if mesh_name[0]=='_': continue - - log.debug("Mesh: %s",mesh_name) - - mesh = Mesh(mesh_name) - meshes.append(mesh) - - matrix = obj.getMatrix() - if BASE_MATRIX: - matrix = matrix_multiply(BASE_MATRIX, matrix) - - faces = data.faces - while faces: - image = faces[0].image - image_filename = image and image.filename - image_name = os.path.splitext(os.path.basename(image_filename))[0] - #print "MATERIAL", image_filename, image_name - if MATERIAL_MAP.has_key(image_name): - image_filename2 = os.path.join(os.path.dirname(image_filename), MATERIAL_MAP[image_name] + os.path.splitext(image_filename)[1]) - #print "=>", image_filename - else: image_filename2 = image_filename - material = MATERIALS.get(image_filename2) or Material(image_filename2) - outputuv = len(material.maps_filenames) > 0 - - # TODO add material color support here - - submesh = SubMesh(mesh, material) - vertices = {} - for face in faces[:]: - if (face.image and face.image.filename) == image_filename: - faces.remove(face) - - if not face.smooth: - try: - p1 = face.v[0].co - p2 = face.v[1].co - p3 = face.v[2].co - except IndexError: - log.error("You have faces with less that three verticies!") - continue - - normal = vector_normalize(vector_by_matrix(vector_crossproduct( - [p3[0] - p2[0], p3[1] - p2[1], p3[2] - p2[2]], - [p1[0] - p2[0], p1[1] - p2[1], p1[2] - p2[2]], - ), matrix)) - - face_vertices = [] - for i in range(len(face.v)): - vertex = vertices.get(face.v[i].index) - if not vertex: - coord = point_by_matrix (face.v[i].co, matrix) - if face.smooth: - normal = vector_normalize(vector_by_matrix(face.v[i].no, matrix)) - vertex = vertices[face.v[i].index] = Vertex(submesh, coord, normal) - - influences = data.getVertexInfluences(face.v[i].index) - # should this really be a warning? (well currently enabled, - # because blender has some bugs where it doesn't return - # influences in python api though they are set, and because - # cal3d<=0.9.1 had bugs where objects without influences - # aren't drawn. - if not influences: - log.error("A vertex of object '%s' has no influences.\n(This occurs on objects placed in an invisible layer, you can fix it by using a single layer)\n. The vertex has been added to a vertex group called _no_inf" % obj.getName()) - if '_no_inf' not in data.getVertGroupNames(): - data.addVertGroup('_no_inf') - - data.assignVertsToGroup('_no_inf',[face.v[i].index],0.5,'add') - - # sum of influences is not always 1.0 in Blender ?!?! - sum = 0.0 - for bone_name, weight in influences: - sum += weight - - for bone_name, weight in influences: - bone_name=string.replace(bone_name,'.','_') - if bone_name=='': - log.critical('Found bone with no name which influences %s' % obj.getName()) - continue - if bone_name not in BONES: - log.error("Couldn't find bone '%s' which influences object '%s'.\n" % (bone_name, obj.getName())) - continue - vertex.influences.append(Influence(BONES[bone_name], weight / sum)) - - elif not face.smooth: - # We cannot share vertex for non-smooth faces, since Cal3D does not - # support vertex sharing for 2 vertices with different normals. - # => we must clone the vertex. - - old_vertex = vertex - vertex = Vertex(submesh, vertex.loc, normal) - vertex.cloned_from = old_vertex - vertex.influences = old_vertex.influences - old_vertex.clones.append(vertex) - - if data.hasFaceUV(): - uv = [face.uv[i][0], face.uv[i][1]] - if FLIP_TEXTURE_COORDS: uv[1] = -uv[1] - - if not vertex.maps: - if outputuv: vertex.maps.append(Map(*uv)) - elif (vertex.maps[0].u != uv[0]) or (vertex.maps[0].v != uv[1]): - # This vertex can be shared for Blender, but not for Cal3D !!! - # Cal3D does not support vertex sharing for 2 vertices with - # different UV texture coodinates. - # => we must clone the vertex. - - for clone in vertex.clones: - if (clone.maps[0].u == uv[0]) and (clone.maps[0].v == uv[1]): - vertex = clone - break - else: # Not yet cloned... - old_vertex = vertex - vertex = Vertex(submesh, vertex.loc, vertex.normal) - vertex.cloned_from = old_vertex - vertex.influences = old_vertex.influences - if outputuv: vertex.maps.append(Map(*uv)) - old_vertex.clones.append(vertex) - - face_vertices.append(vertex) - - # Split faces with more than 3 vertices - for i in range(1, len(face.v) - 1): - Face(submesh, face_vertices[0], face_vertices[i], face_vertices[i + 1]) - - # Computes LODs info - if LODS: - submesh.compute_lods() - - # ---- Export animations -------------------------------------------------- - - if Blender.mode == 'interactive': DrawProgressBar(0.7,'Exporting animations...') - - ANIMATIONS = {} - - for a in Blender.Armature.NLA.GetActions().iteritems(): - animation_name = a[0] - - log.debug( "Animation: %s",animation_name) - - animation = Animation(animation_name) - animation.duration = 0.0 - - for b in a[1].getAllChannelIpos().iteritems(): - bone_name = string.replace(b[0],'.','_') - if bone_name not in BONES: - log.error("No Bone '" + bone_name + "' defined (from Animation '" + animation_name + "' ?!?\n") - continue - - bone = BONES[bone_name] - - track = Track(animation, bone) - track.finished = 0 - animation.tracks[bone_name] = track - - ipo = b[1] - - times = [] - - # SideNote: MatzeB: Ipo.getCurve(curvename) is broken in blender 2.33 and - # below if the Ipo comes from an Action, so only use Ipo.getCurves()! - # also blender upto 2.33a had a bug where IpoCurve.evaluate was not - # exposed to the python interface :-/ - - #run 1: we need to find all time values where we need to produce keyframes - for curve in ipo.getCurves(): - curve_name = curve.name - - if curve_name not in ["QuatW", "QuatX", "QuatY", "QuatZ", "LocX", "LocY", "LocZ"]: - log.error("Curve type %s not supported in Action '%s' Bone '%s'.\n"\ - % (curve_name, animation_name, bone_name)) - - for p in curve.bezierPoints: - time = p.pt[0] - if time not in times: - times.append(time) - - times.sort() - - # run2: now create keyframes - for time in times: - cal3dtime = (time-1) / 25.0 # assume 25FPS by default - if cal3dtime > animation.duration: - animation.duration = cal3dtime - trans = [0, 0, 0] - quat = [0, 0, 0, 0] - - for curve in ipo.getCurves(): - cname = curve.name - val = curve[time] - if cname == "LocX": trans[0] = val - elif cname == "LocY": trans[1] = val - elif cname == "LocZ": trans[2] = val - elif cname == "QuatW": quat[3] = val - elif cname == "QuatX": quat[0] = val - elif cname == "QuatY": quat[1] = val - elif cname == "QuatZ": quat[2] = val - log.debug('Curve: %s' % cname) - - if quat==[0,0,0,0]: - log.critical('You are using just Loc keys. You must use LocRot keys instead.') - continue - - transt = vector_by_matrix(trans, bone.local_matrix) - - loc = vector_add(bone.loc, transt) - rot = quaternion_multiply(quat, bone.rot) - - try: - rot = quaternion_normalize(rot) - except: - import traceback - log.error('quaternion_normalize failed') - - KeyFrame(track, cal3dtime, loc, rot) - - if animation.duration <= 0: - log.warning("Ignoring Animation '" + animation_name + \ - "': duration is 0.") - continue - ANIMATIONS[animation_name] = animation - - # Save all data - if filename.endswith(".cfg"): - filename = os.path.splitext(filename)[0] - BASENAME = os.path.basename(filename) - DIRNAME = os.path.dirname(filename) - - try: os.makedirs(DIRNAME) - except: pass - - if PREFIX_FILE_WITH_MODEL_NAME: PREFIX = BASENAME + "_" - else: PREFIX = "" - if XML: FORMAT_PREFIX = "x"; encode = lambda x: x.to_cal3d_xml() - else: FORMAT_PREFIX = "c"; encode = lambda x: x.to_cal3d() - #print DIRNAME + " - " + BASENAME - - cfg = open(os.path.join(DIRNAME, BASENAME + ".cfg"), "wb") - print >> cfg, "# Cal3D model exported from Blender with blender2cal3d.py" - print >> cfg - - if SCALE != 1.0: - print >> cfg, "scale=%s" % SCALE - print >> cfg - - filename = BASENAME + "." + FORMAT_PREFIX + "sf" - open(os.path.join(DIRNAME, filename), "wb").write(encode(skeleton)) - print >> cfg, "skeleton=%s" % filename - print >> cfg - - for animation in ANIMATIONS.values(): - if not animation.name.startswith("_"): - if animation.duration: # Cal3D does not support animation with only one state - filename = PREFIX + animation.name + "." + FORMAT_PREFIX + "af" - open(os.path.join(DIRNAME, filename), "wb").write(encode(animation)) - print >> cfg, "animation=%s" % filename - - print >> cfg - - for mesh in meshes: - if not mesh.name.startswith("_"): - filename = PREFIX + mesh.name + "." + FORMAT_PREFIX + "mf" - open(os.path.join(DIRNAME, filename), "wb").write(encode(mesh)) - print >> cfg, "mesh=%s" % filename - print >> cfg - - materials = MATERIALS.values() - materials.sort(lambda a, b: cmp(a.id, b.id)) - for material in materials: - if material.maps_filenames: - filename = PREFIX + os.path.splitext(os.path.basename(material.maps_filenames[0]))[0] + "." + FORMAT_PREFIX + "rf" - else: - filename = PREFIX + "plain." + FORMAT_PREFIX + "rf" - open(os.path.join(DIRNAME, filename), "wb").write(encode(material)) - print >> cfg, "material=%s" % filename - print >> cfg - - try: - glob_params = Blender.Text.get("soya_params").asLines() - for glob_param in glob_params: - print >> cfg, glob_param - except: pass - - if CONFIG_TEXT: - for line in Blender.Text.get(CONFIG_TEXT).asLines(): - if not line.startswith("material_"): - print >> cfg, line - - - # Remove soya cached data -- they need to be re-computed, since the model have changed - for filename in os.listdir(DIRNAME): - if filename.startswith("neighbors"): - os.remove(os.path.join(DIRNAME, filename)) - - log.info("Saved to '%s.cfg'" % BASENAME) - log.info("Done.") - - if Blender.mode == 'interactive': DrawProgressBar(1.0,'Done!') - -class BlenderGui: - def __init__(self): - text="""A log has been written to a blender text window. Change this window type to -a text window and you will be able to select the file.""" - - text=textwrap.wrap(text,40) - - text+=[''] - - if log.has_critical: - text+=['There were critical errors!!!!'] - - elif log.has_errors: - text+=['There were errors!'] - - elif log.has_warnings: - text+=['There were warnings'] - - # add any more text before here - text.reverse() - - self.msg=text - - Blender.Draw.Register(self.gui, self.event, self.button_event) - - def gui(self,): - quitbutton = Blender.Draw.Button("Exit", 1, 0, 0, 100, 20, "Close Window") - - y=35 - - for line in self.msg: - BGL.glRasterPos2i(10,y) - Blender.Draw.Text(line) - y+=15 - - def event(self,evt, val): - if evt == Blender.Draw.ESCKEY: - Blender.Draw.Exit() - return - - def button_event(self,evt): - if evt == 1: - Blender.Draw.Exit() - return - -def hotshot_export(filename): - import hotshot,hotshot.stats - prof=hotshot.Profile('blender2cal3d.prof') - print prof.runcall(export,filename) - prof.close() - stats=hotshot.stats.load('blender2cal3d.prof') - stats.strip_dirs() - stats.sort_stats('time','calls') - stats.print_stats() - -# Main script -def fs_callback(filename): - save_to_registry(filename) - #hotshot_export(filename) - export(filename) - BlenderGui() - -def save_to_registry(filename): - dir,name=os.path.split(filename) - d={'default_path':dir, - } - - log.info('storing %s to registry' % str(d)) - - Registry.SetKey('blender2cal3d',d) - -def get_from_registry(): - d=Registry.GetKey('blender2cal3d') - if d: - log.info('got %s from registry' % str(d)) - return d['default_path'] - else: - return '' - -# Check for batch mode -if "--blender2cal3d" in sys.argv: - args = sys.argv[sys.argv.index("--blender2cal3d") + 1:] - for arg in args: - attr, val = arg.split("=") - try: val = int(val) - except: - try: val = float(val) - except: pass - globals()[attr] = val - - if CONFIG_TEXT: - for line in Blender.Text.get(CONFIG_TEXT).asLines(): - if line.startswith("material_"): - old, new = line[9:].split("=") - MATERIAL_MAP[old] = new - log.info("Maps material %s to %s" % (old, new)) - - export(FILENAME) - Blender.Quit() - -else: - if FILENAME: fs_callback(FILENAME) - else: - defaultname = Blender.Get("filename") - - if defaultname.endswith(".blend"): - defaultname = defaultname[0:len(defaultname)-len(".blend")] + ".cfg" - - dir,name=os.path.split(defaultname) - - lastpath=get_from_registry() - defaultname=os.path.join(lastpath,name) - - Blender.Window.FileSelector(fs_callback, "Cal3D Export", defaultname) - - diff --git a/source/blender/python/api2_2x/CurNurb.c b/source/blender/python/api2_2x/CurNurb.c index 79e9a3249aa..a4a1f4505da 100644 --- a/source/blender/python/api2_2x/CurNurb.c +++ b/source/blender/python/api2_2x/CurNurb.c @@ -62,6 +62,7 @@ PyObject *CurNurb_append( BPy_CurNurb * self, PyObject * args ); static PyObject *CurNurb_isNurb( BPy_CurNurb * self ); static PyObject *CurNurb_isCyclic( BPy_CurNurb * self ); static PyObject *CurNurb_dump( BPy_CurNurb * self ); +static PyObject *CurNurb_switchDirection( BPy_CurNurb * self ); char M_CurNurb_doc[] = "CurNurb"; @@ -130,6 +131,8 @@ static PyMethodDef BPy_CurNurb_methods[] = { "( ) - boolean function tests if this spline is cyclic (closed) or not (open)"}, {"dump", ( PyCFunction ) CurNurb_dump, METH_NOARGS, "( ) - dumps Nurb data)"}, + {"switchDirection", ( PyCFunction ) CurNurb_switchDirection, METH_NOARGS, + "( ) - swaps curve beginning and end)"}, {NULL, NULL, 0, NULL} }; @@ -1060,3 +1063,16 @@ PyObject *CurNurb_dump( BPy_CurNurb * self ) Py_RETURN_NONE; } + +PyObject *CurNurb_switchDirection( BPy_CurNurb * self ) { + Nurb *nurb = self->nurb; + if( ! self->nurb ){ /* bail on error */ + printf("\n no Nurb in this CurNurb"); + Py_RETURN_NONE; + } + + switchdirectionNurb( nurb ); + + Py_RETURN_NONE; +} + diff --git a/source/blender/python/api2_2x/doc/Curve.py b/source/blender/python/api2_2x/doc/Curve.py index f65ce0ad00f..d1756f53336 100644 --- a/source/blender/python/api2_2x/doc/Curve.py +++ b/source/blender/python/api2_2x/doc/Curve.py @@ -467,6 +467,21 @@ class Curve: @raise AttributeError: throws exception if curve_num is out of range. """ + def switchDirection( ): + """ + Reverse the direction of a curve. + @return: None + + + I{B{Example:}} + # This example switches the direction of all curves on the active object. + from Blender import * + scn = Scene.GetCurrent() + ob = scn.objects.active # must be a curve + data = ob.data + for cu in data: cu.switchDirection() + """ + def getNumCurves(): """ Get the number of curves in this Curve Data object. |