From 6bae99d66c5e49f1744a0453d7352fcb35780133 Mon Sep 17 00:00:00 2001 From: Bill Currie Date: Thu, 19 Apr 2012 22:06:43 +0900 Subject: [PATCH] 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. --- tools/io_mesh_qfmdl/__init__.py | 13 ++++- tools/io_mesh_qfmdl/import_mdl.py | 80 +++++++++++++++++++++++++++---- tools/io_mesh_qfmdl/mdl.py | 26 +++++++++- 3 files changed, 107 insertions(+), 12 deletions(-) diff --git a/tools/io_mesh_qfmdl/__init__.py b/tools/io_mesh_qfmdl/__init__.py index 5ba5cf58c..f2e318a75 100644 --- a/tools/io_mesh_qfmdl/__init__.py +++ b/tools/io_mesh_qfmdl/__init__.py @@ -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) diff --git a/tools/io_mesh_qfmdl/import_mdl.py b/tools/io_mesh_qfmdl/import_mdl.py index 4a7df5c11..166d7a64c 100644 --- a/tools/io_mesh_qfmdl/import_mdl.py +++ b/tools/io_mesh_qfmdl/import_mdl.py @@ -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() diff --git a/tools/io_mesh_qfmdl/mdl.py b/tools/io_mesh_qfmdl/mdl.py index 521b6bf03..e0750b25d 100644 --- a/tools/io_mesh_qfmdl/mdl.py +++ b/tools/io_mesh_qfmdl/mdl.py @@ -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):