Create an animation script for imported models.

The animation script is just a plist of relevant frame and skin
information. Documentation is included in the generated script.
This commit is contained in:
Bill Currie 2012-04-19 22:06:43 +09:00
parent ff05074e70
commit 6bae99d66c
3 changed files with 107 additions and 12 deletions

View file

@ -46,7 +46,7 @@ if "bpy" in locals():
import bpy
from bpy.props import BoolProperty, FloatProperty, StringProperty, EnumProperty
from bpy.props import FloatVectorProperty
from bpy.props import FloatVectorProperty, PointerProperty
from bpy_extras.io_utils import ExportHelper, ImportHelper, path_reference_mode, axis_conversion
SYNCTYPE=(
@ -80,6 +80,14 @@ class QFMDLSettings(bpy.types.PropertyGroup):
items=EFFECTS,
name="Effects",
description="Particle trail effects")
#doesn't work :(
#script = PointerProperty(
# type=bpy.types.Object,
# name="Script",
# description="Script for animating frames and skins")
script = StringProperty(
name="Script",
description="Script for animating frames and skins")
class ImportMDL6(bpy.types.Operator, ImportHelper):
'''Load a Quake MDL (v6) File'''
@ -131,6 +139,7 @@ class MDLPanel(bpy.types.Panel):
layout.prop(obj.qfmdl, "synctype")
layout.prop(obj.qfmdl, "rotate")
layout.prop(obj.qfmdl, "effects")
layout.prop(obj.qfmdl, "script")
def menu_func_import(self, context):
self.layout.operator(ImportMDL6.bl_idname, text="Quake MDL (.mdl)")
@ -143,7 +152,7 @@ def menu_func_export(self, context):
def register():
bpy.utils.register_module(__name__)
bpy.types.Object.qfmdl = bpy.props.PointerProperty(type=QFMDLSettings)
bpy.types.Object.qfmdl = PointerProperty(type=QFMDLSettings)
bpy.types.INFO_MT_file_import.append(menu_func_import)
bpy.types.INFO_MT_file_export.append(menu_func_export)

View file

@ -25,6 +25,7 @@ from mathutils import Vector,Matrix
from .quakepal import palette
from .mdl import MDL
from .qfplist import pldata
def make_verts(mdl, framenum, subframenum=0):
frame = mdl.frames[framenum]
@ -70,6 +71,7 @@ def make_faces(mdl):
def load_skins(mdl):
def load_skin(skin, name):
skin.name = name
img = bpy.data.images.new(name, mdl.skinwidth, mdl.skinheight)
mdl.images.append(img)
p = [0.0] * mdl.skinwidth * mdl.skinheight * 4
@ -170,13 +172,14 @@ def build_actions(mdl):
ad = sk.animation_data_create()
track = ad.nla_tracks.new ();
track.name = mdl.name
start_frame = 1
start_frame = 1.0
for frame in mdl.frames:
act = bpy.data.actions.new(frame.name)
data = []
other_keys = mdl.keys[:]
if frame.type:
for j, subframe in enumerate(frame.frames):
subframe.frameno = start_frame + j
co = []
if j > 1:
co.append ((1.0, 0.0))
@ -194,6 +197,7 @@ def build_actions(mdl):
for k in other_keys:
data.append((k, co))
else:
sub.frameno = start_frame + j
data.append((frame.key, [(1.0, 1.0)]))
if frame.key in other_keys:
del(other_keys[other_keys.index(frame.key)])
@ -232,13 +236,72 @@ def merge_frames(mdl):
i += 1
def write_text(mdl):
string = "$eyeposition %g %g %g\n" % mdl.eyeposition
string += "$flags %d\n" % mdl.flags
if mdl.synctype:
string += "$sync\n"
header="""
/* This script represents the animation data within the model file. It
is generated automatically on import, and is optional when exporting.
If no script is used when exporting, only a single frame and single
skin will be exported.
The fundamental format of the script is documented at
http://quakeforge.net/doxygen/property-list.html
The expected layout is a top-level dictionary with two expected
entries:
frames array of frame entries
skins array of skin entries
A frame entry is a dictionary with the following fields:
name The name of the frame to be written to the mdl file. In a
frame group, this will form the base for sub-frame names
(name + relative frame number: eg, frame1) if the
sub-frame does not have a name field. (string)
frameno The blender frame to use for the captured animation. In a
frame group, this will be used as the base frame for any
sub-frames that do not specify a frame. While fractional
frames are supported, YMMV. (string:float)
frames Array of frame entries. If present, the current frame
entry is a frame group, and the frame entries specify
sub-frames. (array of dictionary)
NOTE: only top-level frames may be frame groups
intervals Array of frame end times for frame groups. No meaning
in blender, but the quake engine uses them for client-side
animations. Times must be ascending, but any step > 0 is
valid. Ignored for single frames. If not present in a
frame group, the sub-frames of the group will be written
as single frames (in order to undo the auto-group feature
of the importer). Excess times will be ignored, missing
times will be generated at 0.1
second intervals.
(array of string:float).
A skin entry is a dictionary with the following fields:
name The name of the blender image to be used as the skin.
Ignored for skin groups (animated skins). (string)
skins Array of skin entries. If present, the current skin
entry is a skin group (animated skin), and the skin
entries specify sub-skin. (array of dictionary)
NOTE: only top-level skins may be skins groups
intervals Array of skin end times for skin groups. No meaning
in blender, but the quake engine uses them for client-side
animations. Times must be ascending, but any step > 0 is
valid. Ignored for single skins. If not present in a
skin group, it will be generated using 0.1 second
intervals. Excess times will be ignored, missing times
will be generated at 0.1 second intervals.
(array of string:float).
*/
"""
d={'frames':[], 'skins':[]}
for f in mdl.frames:
d['frames'].append(f.info())
for s in mdl.skins:
d['skins'].append(s.info())
pl = pldata()
string = header + pl.write(d)
txt = bpy.data.texts.new(mdl.name)
txt.from_string(string)
return txt.name
mdl.text = txt
def parse_flags(flags):
#NOTE these are in QuakeForge priority order; a little different to id.
@ -268,6 +331,7 @@ def set_properties(mdl):
mdl.obj.qfmdl.synctype = 'ST_SYNC'
mdl.obj.qfmdl.rotate = (mdl.flags & MDL.EF_ROTATE) and True or False
mdl.obj.qfmdl.effects = parse_flags(mdl.flags)
mdl.obj.qfmdl.script = mdl.text.name #FIXME really want the text object
def import_mdl(operator, context, filepath):
bpy.context.user_preferences.edit.use_global_undo = False
@ -293,9 +357,7 @@ def import_mdl(operator, context, filepath):
build_shape_keys(mdl)
merge_frames(mdl)
build_actions(mdl)
#operator.report({'INFO'},
# "Extra settings saved in the %s text block." % write_text(mdl))
write_text(mdl)
set_properties(mdl)
mdl.mesh.update()

View file

@ -40,7 +40,18 @@ class MDL:
class Skin:
def __init__(self):
pass
self.name = ''
def info(self):
info={}
if self.type:
if self.times:
info['intervals'] = list(map(lambda t: str(t), self.times))
info['skins'] = []
for s in self.skins:
info['skins'].append(s.info())
if self.name:
info['name'] = self.name
return info
def read(self, mdl, sub=0):
self.width, self.height = mdl.skinwidth, mdl.skinheight
if sub:
@ -110,6 +121,19 @@ class MDL:
self.verts = []
self.frames = []
self.times = []
def info(self):
info={}
if self.type:
if self.times:
info['intervals'] = list(map(lambda t: str(t), self.times))
info['frames'] = []
for f in self.frames:
info['frames'].append(f.info())
if hasattr(self, 'frameno'):
info['frameno'] = str(self.frameno)
if self.name:
info['name'] = self.name
return info
def add_vert(self, vert):
self.verts.append(vert)
for i, v in enumerate(vert.r):