initial actor panel integration

This commit is contained in:
Jack Andersen 2015-05-24 12:19:28 -10:00
parent 461893d7a1
commit 1d4786344a
15 changed files with 1259 additions and 87 deletions

View File

@ -1,12 +1,9 @@
#if _WIN32
#else
#include <errno.h> #include <errno.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <system_error> #include <system_error>
#include <string> #include <string>
#endif
#include "CBlenderConnection.hpp" #include "CBlenderConnection.hpp"
@ -20,7 +17,7 @@
#define TEMP_SHELLSCRIPT "/home/jacko/hecl/blender/blendershell.py" #define TEMP_SHELLSCRIPT "/home/jacko/hecl/blender/blendershell.py"
size_t CBlenderConnection::readLine(char* buf, size_t bufSz) size_t CBlenderConnection::_readLine(char* buf, size_t bufSz)
{ {
size_t readBytes = 0; size_t readBytes = 0;
while (true) while (true)
@ -51,7 +48,7 @@ err:
return 0; return 0;
} }
size_t CBlenderConnection::writeLine(const char* buf) size_t CBlenderConnection::_writeLine(const char* buf)
{ {
ssize_t ret, nlerr; ssize_t ret, nlerr;
ret = write(m_writepipe[1], buf, strlen(buf)); ret = write(m_writepipe[1], buf, strlen(buf));
@ -65,7 +62,7 @@ err:
throw std::error_code(errno, std::system_category()); throw std::error_code(errno, std::system_category());
} }
size_t CBlenderConnection::readBuf(char* buf, size_t len) size_t CBlenderConnection::_readBuf(char* buf, size_t len)
{ {
ssize_t ret = read(m_readpipe[0], buf, len); ssize_t ret = read(m_readpipe[0], buf, len);
if (ret < 0) if (ret < 0)
@ -73,7 +70,7 @@ size_t CBlenderConnection::readBuf(char* buf, size_t len)
return ret; return ret;
} }
size_t CBlenderConnection::writeBuf(const char* buf, size_t len) size_t CBlenderConnection::_writeBuf(const char* buf, size_t len)
{ {
ssize_t ret = write(m_writepipe[1], buf, len); ssize_t ret = write(m_writepipe[1], buf, len);
if (ret < 0) if (ret < 0)
@ -81,7 +78,7 @@ size_t CBlenderConnection::writeBuf(const char* buf, size_t len)
return ret; return ret;
} }
void CBlenderConnection::closePipe() void CBlenderConnection::_closePipe()
{ {
close(m_readpipe[0]); close(m_readpipe[0]);
close(m_writepipe[1]); close(m_writepipe[1]);
@ -115,10 +112,11 @@ CBlenderConnection::CBlenderConnection(bool silenceBlender)
char writefds[32]; char writefds[32];
snprintf(writefds, 32, "%d", m_readpipe[1]); snprintf(writefds, 32, "%d", m_readpipe[1]);
/* User-specified blender first */ /* Try user-specified blender first */
if (blenderBin) if (blenderBin)
{ {
execlp(blenderBin, blenderBin, "--background", "-P", TEMP_SHELLSCRIPT, execlp(blenderBin, blenderBin,
"--background", "-P", TEMP_SHELLSCRIPT,
"--", readfds, writefds, NULL); "--", readfds, writefds, NULL);
if (errno != ENOENT) if (errno != ENOENT)
{ {
@ -128,8 +126,9 @@ CBlenderConnection::CBlenderConnection(bool silenceBlender)
} }
} }
/* Default blender next */ /* Otherwise default blender */
execlp(DEFAULT_BLENDER_BIN, DEFAULT_BLENDER_BIN, "--background", "-P", TEMP_SHELLSCRIPT, execlp(DEFAULT_BLENDER_BIN, DEFAULT_BLENDER_BIN,
"--background", "-P", TEMP_SHELLSCRIPT,
"--", readfds, writefds, NULL); "--", readfds, writefds, NULL);
if (errno != ENOENT) if (errno != ENOENT)
{ {
@ -149,17 +148,18 @@ CBlenderConnection::CBlenderConnection(bool silenceBlender)
/* Handle first response */ /* Handle first response */
char lineBuf[256]; char lineBuf[256];
readLine(lineBuf, sizeof(lineBuf)); _readLine(lineBuf, sizeof(lineBuf));
if (!strcmp(lineBuf, "NOLAUNCH")) if (!strcmp(lineBuf, "NOLAUNCH"))
{ {
closePipe(); _closePipe();
throw std::runtime_error("Unable to launch blender"); throw std::runtime_error("Unable to launch blender");
} }
else if (!strcmp(lineBuf, "NOBLENDER")) else if (!strcmp(lineBuf, "NOBLENDER"))
{ {
closePipe(); _closePipe();
if (blenderBin) if (blenderBin)
throw std::runtime_error("Unable to find blender at '" + std::string(blenderBin) + "' or '" + throw std::runtime_error("Unable to find blender at '" +
std::string(blenderBin) + "' or '" +
std::string(DEFAULT_BLENDER_BIN) + "'"); std::string(DEFAULT_BLENDER_BIN) + "'");
else else
throw std::runtime_error("Unable to find blender at '" + throw std::runtime_error("Unable to find blender at '" +
@ -167,32 +167,76 @@ CBlenderConnection::CBlenderConnection(bool silenceBlender)
} }
else if (!strcmp(lineBuf, "NOADDON")) else if (!strcmp(lineBuf, "NOADDON"))
{ {
closePipe(); _closePipe();
throw std::runtime_error("HECL addon not installed within blender"); throw std::runtime_error("HECL addon not installed within blender");
} }
else if (strcmp(lineBuf, "READY")) else if (strcmp(lineBuf, "READY"))
{ {
closePipe(); _closePipe();
throw std::runtime_error("read '" + std::string(lineBuf) + "' from blender; expected 'READY'"); throw std::runtime_error("read '" + std::string(lineBuf) +
"' from blender; expected 'READY'");
} }
writeLine("ACK"); _writeLine("ACK");
writeLine("HELLOBLENDER!!");
readLine(lineBuf, sizeof(lineBuf));
printf("%s\n", lineBuf);
quitBlender();
} }
CBlenderConnection::~CBlenderConnection() CBlenderConnection::~CBlenderConnection()
{ {
closePipe(); _closePipe();
}
bool CBlenderConnection::openBlend(const std::string& path)
{
_writeLine(("OPEN" + path).c_str());
char lineBuf[256];
_readLine(lineBuf, sizeof(lineBuf));
if (!strcmp(lineBuf, "FINISHED"))
{
m_loadedBlend = path;
return true;
}
return false;
}
bool CBlenderConnection::cookBlend(std::function<void*(uint32_t)> bufGetter,
const std::string& expectedType,
const std::string& platform,
bool bigEndian)
{
char lineBuf[256];
char reqLine[512];
snprintf(reqLine, 512, "COOK %s %c", platform.c_str(), bigEndian?'>':'<');
_writeLine(reqLine);
_readLine(lineBuf, sizeof(lineBuf));
if (strcmp(expectedType.c_str(), lineBuf))
{
throw std::runtime_error("expected '" + m_loadedBlend +
"' to contain " + expectedType +
" not " + lineBuf);
return false;
}
_writeLine("ACK");
for (_readLine(lineBuf, sizeof(lineBuf));
!strcmp("BUF", lineBuf);
_readLine(lineBuf, sizeof(lineBuf)))
{
uint32_t sz;
_readBuf(&sz, 4);
void* buf = bufGetter(sz);
_readBuf(buf, sz);
}
if (!strcmp("SUCCESS", lineBuf))
return true;
else if (!strcmp("EXCEPTION", lineBuf))
throw std::runtime_error("blender script exception");
return false;
} }
void CBlenderConnection::quitBlender() void CBlenderConnection::quitBlender()
{ {
writeLine("QUIT"); _writeLine("QUIT");
char lineBuf[256]; char lineBuf[256];
readLine(lineBuf, sizeof(lineBuf)); _readLine(lineBuf, sizeof(lineBuf));
printf("%s\n", lineBuf);
} }

View File

@ -8,6 +8,9 @@
#include <unistd.h> #include <unistd.h>
#endif #endif
#include <string>
#include <functional>
class CBlenderConnection class CBlenderConnection
{ {
#if _WIN32 #if _WIN32
@ -19,15 +22,26 @@ class CBlenderConnection
int m_readpipe[2]; int m_readpipe[2];
int m_writepipe[2]; int m_writepipe[2];
#endif #endif
size_t readLine(char* buf, size_t bufSz); std::string m_loadedBlend;
size_t writeLine(const char* buf); size_t _readLine(char* buf, size_t bufSz);
size_t readBuf(char* buf, size_t len); size_t _writeLine(const char* buf);
size_t writeBuf(const char* buf, size_t len); size_t _readBuf(char* buf, size_t len);
void closePipe(); size_t _writeBuf(const char* buf, size_t len);
void _closePipe();
public: public:
CBlenderConnection(bool silenceBlender=false); CBlenderConnection(bool silenceBlender=false);
~CBlenderConnection(); ~CBlenderConnection();
bool openBlend(const std::string& path);
enum CookPlatform
{
CP_MODERN = 0,
CP_GX = 1,
};
bool cookBlend(std::function<void*(uint32_t)> bufGetter,
const std::string& expectedType,
const std::string& platform,
bool bigEndian=false);
void quitBlender(); void quitBlender();
}; };

View File

@ -13,14 +13,15 @@ bl_info = {
"category": "System"} "category": "System"}
# Package import # Package import
from . import hmdl from . import hmdl, sact
import bpy, os, sys import bpy, os, sys
from bpy.app.handlers import persistent from bpy.app.handlers import persistent
# Appendable list allowing external addons to register additional resource types # Appendable list allowing external addons to register additional resource types
hecl_export_types = [ hecl_typeS = [
('NONE', "None", "Active scene not using HECL", None, None), ('NONE', "None", "Active scene not using HECL", None, None),
('MESH', "Mesh", "Active scene represents an HMDL Mesh", hmdl.panel_draw, hmdl.cook)] ('MESH', "Mesh", "Active scene represents an HMDL Mesh", hmdl.draw, hmdl.cook),
('ACTOR', "Actor", "Active scene represents a HECL Actor", sact.draw, sact.cook)]
# Main Scene Panel # Main Scene Panel
class hecl_scene_panel(bpy.types.Panel): class hecl_scene_panel(bpy.types.Panel):
@ -37,46 +38,67 @@ class hecl_scene_panel(bpy.types.Panel):
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
type_row = layout.row(align=True) type_row = layout.row(align=True)
type_row.prop_menu_enum(context.scene, 'hecl_export_type', text='Export Type') type_row.prop_menu_enum(context.scene, 'hecl_type', text='Export Type')
for exp_type in hecl_export_types: for exp_type in hecl_typeS:
if exp_type[0] == context.scene.hecl_export_type and callable(exp_type[3]): if exp_type[0] == context.scene.hecl_type and callable(exp_type[3]):
exp_type[3](self, context) exp_type[3](self.layout, context)
break break
# Blender-selected polymorphism # Blender-selected polymorphism cook
def do_package(writefd, platform_type, endian_char): def do_cook(writebuf, platform_type, endian_char):
for tp in hecl_export_types: for tp in hecl_typeS:
if tp[0] == bpy.context.scene.hecl_export_type: if tp[0] == bpy.context.scene.hecl_type:
if callable(tp[4]): if callable(tp[4]):
tp[4](writefd, platform_type, endian_char) return tp[4](writefd, platform_type, endian_char)
return False
# Blender export-type registration # Blender export-type registration
def register_export_type_enum(): def register_export_type_enum():
bpy.types.Scene.hecl_export_type = bpy.props.EnumProperty(items= bpy.types.Scene.hecl_type = bpy.props.EnumProperty(items=
[tp[:3] for tp in hecl_export_types], [tp[:3] for tp in hecl_typeS],
name="HECL Export Type", name="HECL Export Type",
description="Selects how active scene is exported by HECL") description="Selects how active scene is exported by HECL")
# Function for external addons to register export types with HECL # Function for external addons to register export types with HECL
def add_export_type(type_tuple): def add_export_type(type_tuple):
type_tup = tuple(type_tuple) type_tup = tuple(type_tuple)
for tp in hecl_export_types: for tp in hecl_typeS:
if tp[0] == type_tup[0]: if tp[0] == type_tup[0]:
raise RuntimeError("Type already registered with HECL") raise RuntimeError("Type already registered with HECL")
hecl_export_types.append(type_tup) hecl_types.append(type_tup)
register_export_type_enum() register_export_type_enum()
# Shell command receiver (from HECL driver)
def command(cmdline, writepipeline, writepipebuf):
if cmdline[0] == b'COOK':
resource_type = bpy.context.scene.hecl_type.encode()
writepipeline(resource_type)
ackbytes = readpipeline()
if ackbytes != b'ACK':
return
try:
result = do_cook(writepipebuf, cmdline[1].decode(), cmdline[2].decode())
if result == None or result == True:
writepipeline(b'SUCCESS')
else:
writepipeline(b'FAILURE')
except:
writepipeline(b'EXCEPTION')
# Registration # Registration
def register(): def register():
hmdl.register()
bpy.utils.register_class(hecl_scene_panel)
register_export_type_enum() register_export_type_enum()
hmdl.register()
sact.register()
bpy.utils.register_class(hecl_scene_panel)
def unregister(): def unregister():
hmdl.unregister() hmdl.unregister()
sact.unregister()
bpy.utils.unregister_class(hecl_scene_panel) bpy.utils.unregister_class(hecl_scene_panel)
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -528,4 +528,3 @@ class hmdl_mesh_operator(bpy.types.Operator):
context.window_manager.clipboard = str_out context.window_manager.clipboard = str_out
self.report({'INFO'}, "Wrote mesh C to clipboard") self.report({'INFO'}, "Wrote mesh C to clipboard")
return {'FINISHED'} return {'FINISHED'}

View File

@ -10,7 +10,6 @@ or have a new one established.
import struct import struct
import bpy import bpy
from . import hmdl_anim
class hmdl_skin: class hmdl_skin:
@ -72,14 +71,14 @@ class hmdl_skin:
# Generate Rigging Info structure (call after all index-buffers generated) # Generate Rigging Info structure (call after all index-buffers generated)
def generate_rigging_info(self, endian_char): def generate_rigging_info(self, bone_dict, endian_char):
skin_entries = [] skin_entries = []
for bone_array in self.bone_arrays: for bone_array in self.bone_arrays:
skin_bytes = bytearray() skin_bytes = bytearray()
skin_bytes += struct.pack(endian_char + 'I', len(bone_array)) skin_bytes += struct.pack(endian_char + 'I', len(bone_array))
for bone in bone_array: for bone in bone_array:
skin_bytes += struct.pack(endian_char + 'i', hmdl_anim.hashbone(bone)) skin_bytes += struct.pack(endian_char + 'I', bone_dict[bone])
skin_entries.append(skin_bytes) skin_entries.append(skin_bytes)
# Generate skinning data # Generate skinning data

View File

@ -23,10 +23,7 @@ Positions, Normals, UV coordinates, and Weight Vectors
import struct, bpy, bmesh import struct, bpy, bmesh
from mathutils import Vector from mathutils import Vector
from . import hmdl_shader from . import HMDLShader, HMDLSkin, HMDLMesh, HMDLTxtr
from . import hmdl_skin
from . import hmdl_mesh
from . import hmdl_anim
def get_3d_context(object_): def get_3d_context(object_):
window = bpy.context.window_manager.windows[0] window = bpy.context.window_manager.windows[0]
@ -94,7 +91,9 @@ def generate_skeleton_info(armature, endian_char='<'):
# Takes a Blender 'Mesh' object (not the datablock) # Takes a Blender 'Mesh' object (not the datablock)
# and performs a one-shot conversion process to HMDL; packaging # and performs a one-shot conversion process to HMDL; packaging
# into the HECL data-pipeline and returning a hash once complete # into the HECL data-pipeline and returning a hash once complete
def cook(writefd, platform_type, endian_char): def cook(writebuffunc, platform, endianchar):
print('COOKING HMDL')
return True
mesh_obj = bpy.data.objects[bpy.context.scene.hecl_mesh_obj] mesh_obj = bpy.data.objects[bpy.context.scene.hecl_mesh_obj]
if mesh_obj.type != 'MESH': if mesh_obj.type != 'MESH':
raise RuntimeError("%s is not a mesh" % mesh_obj.name) raise RuntimeError("%s is not a mesh" % mesh_obj.name)
@ -279,9 +278,7 @@ def cook(writefd, platform_type, endian_char):
return db_id, new_hash, final_data return db_id, new_hash, final_data
def draw(layout, context):
def panel_draw(self, context):
layout = self.layout
layout.prop_search(context.scene, 'hecl_mesh_obj', context.scene, 'objects') layout.prop_search(context.scene, 'hecl_mesh_obj', context.scene, 'objects')
if not len(context.scene.hecl_mesh_obj): if not len(context.scene.hecl_mesh_obj):
layout.label("Mesh not specified", icon='ERROR') layout.label("Mesh not specified", icon='ERROR')
@ -301,8 +298,11 @@ def register():
bpy.types.Scene.hecl_mesh_obj = bpy.props.StringProperty( bpy.types.Scene.hecl_mesh_obj = bpy.props.StringProperty(
name='HECL Mesh Object', name='HECL Mesh Object',
description='Blender Mesh Object to export during HECL\'s cook process') description='Blender Mesh Object to export during HECL\'s cook process')
bpy.utils.register_class(hmdl_shader.hecl_shader_operator) bpy.types.Scene.hecl_actor_obj = bpy.props.StringProperty(
name='HECL Actor Object',
description='Blender Empty Object to export during HECL\'s cook process')
bpy.utils.register_class(HMDLShader.hecl_shader_operator)
pass pass
def unregister(): def unregister():
bpy.utils.unregister_class(hmdl_shader.hecl_shader_operator) bpy.utils.unregister_class(HMDLShader.hecl_shader_operator)
pass pass

View File

@ -1,7 +1,4 @@
''' '''
HMDL Export Blender Addon
By Jack Andersen <jackoalan@gmail.com>
This file provides a means to encode animation key-channels This file provides a means to encode animation key-channels
in an interleaved, sparse array for use by the runtime in an interleaved, sparse array for use by the runtime
''' '''
@ -11,10 +8,6 @@ import hashlib
import struct import struct
import mathutils import mathutils
# Hash bone name into truncated 28-bit integer
def hashbone(name):
return int.from_bytes(hashlib.sha1(name.encode()).digest()[:4], byteorder='big', signed=False) & 0xfffffff
# Regex RNA path matchers # Regex RNA path matchers
scale_matcher = re.compile(r'pose.bones\["(\S+)"\].scale') scale_matcher = re.compile(r'pose.bones\["(\S+)"\].scale')
rotation_matcher = re.compile(r'pose.bones\["(\S+)"\].rotation') rotation_matcher = re.compile(r'pose.bones\["(\S+)"\].rotation')

View File

@ -0,0 +1,211 @@
from . import SACTEvent
import bpy
# Action update (if anything important changes)
def active_action_update(self, context):
if not bpy.app.background:
if context.scene.hecl_type == 'ACTOR' and context.scene.hecl_auto_select:
if SACTAction_load.poll(context):
bpy.ops.scene.SACTAction_load()
# Action type update
def action_type_update(self, context):
if not bpy.app.background:
actor_data = context.scene.hecl_sact_data
active_action_update(self, context)
# Actor action class
class SACTAction(bpy.types.PropertyGroup):
name = bpy.props.StringProperty(name="Action Name")
action = bpy.props.StringProperty(name="Blender Action")
# Panel draw
def draw(layout, context):
actor_data = context.scene.hecl_sact_data
row = layout.row(align=True)
row.alignment = 'LEFT'
row.prop(actor_data, 'show_actions', text="Actions", icon='ACTION', emboss=False)
if actor_data.show_actions:
row = layout.row()
row.template_list("UI_UL_list", "SCENE_UL_SACTActions",
actor_data, 'actions', actor_data, 'active_action')
col = row.column(align=True)
col.operator("scene.sactaction_add", icon="ZOOMIN", text="")
col.operator("scene.sactaction_remove", icon="ZOOMOUT", text="")
if len(actor_data.actions) and actor_data.active_action >= 0:
action = actor_data.actions[actor_data.active_action]
# Load action operator
if not bpy.context.scene.hecl_auto_select:
layout.operator("scene.sactaction_load", icon='FILE_TICK', text="Load Action")
# Name edit field
layout.prop(action, 'name', text="Name")
layout.prop_search(action, 'action', bpy.data, 'actions', text="Action")
linked_action = None
if bpy.data.actions.find(action.action) != -1:
linked_action = bpy.data.actions[action.action]
# Validate
if linked_action is None:
layout.label("Source action not set", icon='ERROR')
else:
layout.prop(linked_action, 'hecl_index', text="Index")
layout.prop(linked_action, 'hecl_anim_props', text="Props")
layout.prop(linked_action, 'hecl_fps', text="Frame Rate")
layout.prop(context.scene, 'hecl_auto_remap', text="60-fps Remap")
# Action 'add' operator
class SACTAction_add(bpy.types.Operator):
bl_idname = "scene.sactaction_add"
bl_label = "New HECL Actor Action"
bl_description = "Add New HECL Actor Action to active scene"
@classmethod
def poll(cls, context):
return (context.scene is not None and
not context.scene.library and
context.scene.hecl_type == 'ACTOR')
def execute(self, context):
actor_data = context.scene.hecl_sact_data
action_name = 'ActorAction'
if action_name in actor_data.actions:
action_name = 'ActorAction.001'
action_idx = 1
while action_name in actor_data.actions:
action_idx += 1
action_name = 'ActorAction.{:0>3}'.format(action_idx)
action = actor_data.actions.add()
action.name = action_name
actor_data.active_action = len(actor_data.actions)-1
return {'FINISHED'}
# Action 'remove' operator
class SACTAction_remove(bpy.types.Operator):
bl_idname = "scene.sactaction_remove"
bl_label = "Remove HECL Actor Action"
bl_description = "Remove HECL Actor Action from active scene"
@classmethod
def poll(cls, context):
actor_data = context.scene.hecl_sact_data
return (context.scene is not None and
not context.scene.library and
context.scene.hecl_type == 'ACTOR' and
actor_data.active_action >= 0 and
len(actor_data.actions))
def execute(self, context):
actor_data = context.scene.hecl_sact_data
actor_data.actions.remove(actor_data.active_action)
actor_data.active_action -= 1
if actor_data.active_action == -1:
actor_data.active_action = 0
return {'FINISHED'}
# Action 'load' operator
class SACTAction_load(bpy.types.Operator):
bl_idname = "scene.sactaction_load"
bl_label = "Load HECL Actor Action"
bl_description = "Loads Action for playback in active scene"
@classmethod
def poll(cls, context):
return (context.scene is not None and
context.scene.hecl_type == 'ACTOR' and
len(context.scene.hecl_sact_data.actions) and
context.scene.hecl_sact_data.active_action >= 0)
def execute(self, context):
actor_data = context.scene.hecl_sact_data
if actor_data.active_action not in range(len(actor_data.actions)):
return {'CANCELLED'}
if actor_data.active_subtype not in range(len(actor_data.subtypes)):
return {'CANCELLED'}
action_data = actor_data.actions[actor_data.active_action]
subtype = actor_data.subtypes[actor_data.active_subtype]
# Refresh event markers
actor_event.clear_event_markers(actor_data, context)
actor_event.update_action_events(None)
actor_event.active_event_update(None, context)
# Clear animation data for all subtypes
for s in range(len(actor_data.subtypes)):
st = actor_data.subtypes[s]
if st.linked_armature in bpy.data.objects:
am = bpy.data.objects[st.linked_armature]
am.animation_data_clear()
# Set single action into armature
if subtype.linked_armature in bpy.data.objects:
armature_obj = bpy.data.objects[subtype.linked_armature]
if action_data.action in bpy.data.actions:
action_obj =\
bpy.data.actions[action_data.action]
armature_obj.animation_data_clear()
armature_obj.animation_data_create()
armature_obj.animation_data.action = action_obj
# Time remapping
if context.scene.hecl_auto_remap:
bpy.context.scene.render.fps = 60
bpy.context.scene.render.frame_map_old = action_obj.hecl_fps
bpy.context.scene.render.frame_map_new = 60
bpy.context.scene.frame_start = 2
bpy.context.scene.frame_end = action_obj.frame_range[1] * (60 / action_obj.hecl_fps)
else:
bpy.context.scene.render.fps = action_obj.hecl_fps
bpy.context.scene.render.frame_map_old = action_obj.hecl_fps
bpy.context.scene.render.frame_map_new = action_obj.hecl_fps
bpy.context.scene.frame_start = 1
bpy.context.scene.frame_end = action_obj.frame_range[1]
# Events
#actor_event.clear_action_events(self, context, actor_data)
#actor_event.load_action_events(self, context, action_obj, 0)
return {'FINISHED'}
else:
armature_obj.animation_data_clear()
self.report({'WARNING'}, "Unable to load action; check HECL panel")
return {'FINISHED'}
else:
self.report({'WARNING'}, "Unable to load armature; check HECL panel")
return {'FINISHED'}
# Registration
def register():
bpy.types.Action.hecl_fps = bpy.props.IntProperty(name="HECL Actor Sub-action Frame-rate",
description="Frame-rate at which action is authored; to be interpolated at 60-fps by runtime",
min=1, max=60, default=30,
update=active_action_update)
bpy.types.Action.hecl_anim_props = bpy.props.StringProperty(name="Animation Metadata")
bpy.types.Action.hecl_index = bpy.props.IntProperty(name="HECL Actor Action Index")
bpy.utils.register_class(SACTAction)
bpy.utils.register_class(SACTAction_add)
bpy.utils.register_class(SACTAction_load)
bpy.utils.register_class(SACTAction_remove)
def unregister():
bpy.utils.unregister_class(SACTAction)
bpy.utils.unregister_class(SACTAction_add)
bpy.utils.unregister_class(SACTAction_load)
bpy.utils.unregister_class(SACTAction_remove)

View File

@ -0,0 +1,444 @@
import bpy
# Loop event class
class hecl_actor_event_loop(bpy.types.PropertyGroup):
bool = bpy.props.BoolProperty(name="Loop Bool")
# UEVT event class
class hecl_actor_event_uevt(bpy.types.PropertyGroup):
type = bpy.props.IntProperty(name="UEVT Type")
bone_name = bpy.props.StringProperty(name="Bone Name")
# Effect event class
class hecl_actor_event_effect(bpy.types.PropertyGroup):
frame_count = bpy.props.IntProperty(name="Frame Count", min=0)
uid = bpy.props.StringProperty(name="Effect UID")
bone_name = bpy.props.StringProperty(name="Bone Name")
scale = bpy.props.FloatProperty(name="Scale", description="Proportional spacial scale")
transform_mode = bpy.props.EnumProperty(name="Transform Mode", description="How the bone will transform the effect",
items=[('STATIONARY', "Stationary", "Effect emitter will be transformed in bone-space, then retained"),
('WORLD', "World", "Effect emitter will be transformed in bone-space"),
('LOCAL', "Local", "Entire effect will be transformed in bone-space")])
# Sound event class
class hecl_actor_event_sound(bpy.types.PropertyGroup):
sound_id = bpy.props.StringProperty(name="Sound ID")
ref_amp = bpy.props.FloatProperty(name="Reference Amplitude")
ref_dist = bpy.props.FloatProperty(name="Reference Distance")
# Name update
def update_name(self, context):
if bpy.context.scene.hecl_type == 'ACTOR':
clear_event_markers(bpy.context.scene.hecl_sact_data, context)
update_action_events(None)
active_event_update(self, context)
# Actor event class
class hecl_actor_event(bpy.types.PropertyGroup):
name = bpy.props.StringProperty(name="Event Name",
update=update_name)
type = bpy.props.EnumProperty(name="Event Type",
items=[('LOOP', "Loop", "Loop Event"),
('UEVT', "UEVT", "UEVT Event"),
('EFFECT', "Effect", "Effect Event"),
('SOUND', "Sound", "Sound Event")],
default='LOOP')
index = bpy.props.IntProperty(name="Event Index")
time = bpy.props.FloatProperty(name="Event Time")
props = bpy.props.StringProperty(name="Event props")
loop_data = bpy.props.PointerProperty(name="Loop event data",
type=hecl_actor_event_loop)
uevt_data = bpy.props.PointerProperty(name="UEVT event data",
type=hecl_actor_event_uevt)
effect_data = bpy.props.PointerProperty(name="Effect event data",
type=hecl_actor_event_effect)
sound_data = bpy.props.PointerProperty(name="Sound event data",
type=hecl_actor_event_sound)
# Panel draw
def draw(layout, context):
actor_data = context.scene.hecl_sact_data
armature = None
if actor_data.active_subtype >= 0:
if actor_data.active_subtype in range(len(actor_data.subtypes)):
subtype = actor_data.subtypes[actor_data.active_subtype]
if subtype and subtype.linked_armature in bpy.data.objects:
armature = bpy.data.objects[subtype.linked_armature]
row = layout.row(align=True)
row.alignment = 'LEFT'
row.prop(actor_data, 'show_events', text="Events", icon='PREVIEW_RANGE', emboss=False)
if actor_data.show_events:
# Get action
action_data = None
subaction_data = None
if actor_data.active_action in range(len(actor_data.actions)):
action_data = actor_data.actions[actor_data.active_action]
if action_data.type == 'SINGLE':
subaction_data = action_data.subactions[0]
elif action_data.type == 'SEQUENCE' or action_data.type == 'RANDOM':
if action_data.active_subaction in range(len(action_data.subactions)):
subaction_data = action_data.subactions[action_data.active_subaction]
# Validate
if subaction_data is None:
layout.label("(Sub)action not selected in 'Actions'", icon='ERROR')
else:
if subaction_data.name == '':
layout.label("Action not set", icon='ERROR')
elif subaction_data.name not in bpy.data.actions:
layout.label("Action '"+subaction_data.name+"' not found", icon='ERROR')
else:
action = bpy.data.actions[subaction_data.name]
# Event list
row = layout.row()
row.template_list("UI_UL_list", "SCENE_UL_hecl_actor_subaction_events",
action, 'hecl_events', action, 'hecl_active_event')
col = row.column(align=True)
col.operator("scene.hecl_actor_subaction_event_add", icon="ZOOMIN", text="")
col.operator("scene.hecl_actor_subaction_event_remove", icon="ZOOMOUT", text="")
col.separator()
col.operator("scene.hecl_actor_subaction_event_move_up", icon="TRIA_UP", text="")
col.operator("scene.hecl_actor_subaction_event_move_down", icon="TRIA_DOWN", text="")
if len(action.hecl_events) and action.hecl_active_event >= 0:
event = action.hecl_events[action.hecl_active_event]
layout.prop(event, 'name', text="Name")
layout.prop(event, 'index', text="Index")
layout.prop(event, 'props', text="Props")
layout.label('Marker Time: ' + '{:g}'.format(event.time), icon='MARKER_HLT')
layout.label("Event Type:")
row = layout.row(align=True)
row.prop_enum(event, 'type', 'LOOP')
row.prop_enum(event, 'type', 'UEVT')
row.prop_enum(event, 'type', 'EFFECT')
row.prop_enum(event, 'type', 'SOUND')
if event.type == 'LOOP':
loop_data = event.loop_data
layout.prop(loop_data, 'bool')
elif event.type == 'UEVT':
uevt_data = event.uevt_data
layout.prop(uevt_data, 'type')
layout.prop(uevt_data, 'bone_name')
elif event.type == 'EFFECT':
effect_data = event.effect_data
layout.prop(effect_data, 'frame_count')
layout.prop(effect_data, 'uid')
if armature:
layout.prop_search(effect_data, 'bone_name', armature.data, 'bones')
else:
layout.prop(effect_data, 'bone_name')
layout.prop(effect_data, 'scale')
row = layout.row(align=True)
row.prop_enum(effect_data, 'transform_mode', 'STATIONARY')
row.prop_enum(effect_data, 'transform_mode', 'WORLD')
row.prop_enum(effect_data, 'transform_mode', 'LOCAL')
elif event.type == 'SOUND':
sound_data = event.sound_data
layout.prop(sound_data, 'sound_id')
layout.prop(sound_data, 'ref_amp')
layout.prop(sound_data, 'ref_dist')
# Clear event markers not in active event
def clear_event_markers(actor_data, context):
for marker in context.scene.timeline_markers:
if marker.name.startswith('hecl_'):
context.scene.timeline_markers.remove(marker)
# Event marker update
@bpy.app.handlers.persistent
def update_action_events(dummy):
context = bpy.context
if context.scene.hecl_type == 'ACTOR':
actor_data = context.scene.hecl_sact_data
if actor_data.active_action in range(len(actor_data.actions)):
action_data = actor_data.actions[actor_data.active_action]
if action_data.action in bpy.data.actions:
action_obj =\
bpy.data.actions[action_data.action]
for i in range(len(action_obj.hecl_events)):
event = action_obj.hecl_events[i]
marker_name = 'hecl_' + str(action_obj.hecl_index) + '_' + str(i) + '_' + event.name
if marker_name in context.scene.timeline_markers:
marker = context.scene.timeline_markers[marker_name]
event_time = marker.frame / action_obj.hecl_fps
if event_time != event.time:
event.time = event_time
else:
marker = context.scene.timeline_markers.new(marker_name)
marker.frame = event.time * action_obj.hecl_fps
marker.select = False
if i != action_obj.hecl_active_event and marker.select:
action_obj.hecl_active_event = i
# Event 'add' operator
class hecl_actor_subaction_event_add(bpy.types.Operator):
bl_idname = "scene.hecl_actor_subaction_event_add"
bl_label = "New HECL Actor Event"
bl_description = "Add New HECL Actor Event to active Sub-action"
@classmethod
def poll(cls, context):
actor_data = context.scene.hecl_sact_data
check = (context.scene is not None and
not context.scene.library and
context.scene.hecl_type == 'ACTOR' and
len(actor_data.actions) and
actor_data.active_action >= 0 and
len(actor_data.actions[actor_data.active_action].subactions) and
actor_data.actions[actor_data.active_action].active_subaction >= 0)
if not check:
return False
actor_data = context.scene.hecl_sact_data
action_data = actor_data.actions[actor_data.active_action]
subaction_data = action_data.subactions[action_data.active_subaction]
return subaction_data.name in bpy.data.actions
def execute(self, context):
actor_data = context.scene.hecl_sact_data
action_data = actor_data.actions[actor_data.active_action]
subaction_data = action_data.subactions[action_data.active_subaction]
blend_action = bpy.data.actions[subaction_data.name]
event_name = 'SubactionEvent'
if event_name in blend_action.hecl_events:
event_name = 'SubactionEvent.001'
event_idx = 1
while event_name in blend_action.hecl_events:
event_idx += 1
event_name = 'SubactionEvent.{:0>3}'.format(event_idx)
event = blend_action.hecl_events.add()
event.name = event_name
action_obj =\
bpy.data.actions[subaction_data.name]
event.time = (context.scene.frame_current / (context.scene.render.frame_map_new / context.scene.render.frame_map_old)) / action_obj.hecl_fps
blend_action.hecl_active_event = len(blend_action.hecl_events)-1
if not bpy.app.background:
update_action_events(None)
active_event_update(self, context)
return {'FINISHED'}
# Event 'remove' operator
class hecl_actor_subaction_event_remove(bpy.types.Operator):
bl_idname = "scene.hecl_actor_subaction_event_remove"
bl_label = "Remove HECL Actor Event"
bl_description = "Remove HECL Actor Event from active Sub-action"
@classmethod
def poll(cls, context):
actor_data = context.scene.hecl_sact_data
check = (context.scene is not None and
not context.scene.library and
context.scene.hecl_type == 'ACTOR' and
len(actor_data.actions) and
actor_data.active_action >= 0 and
len(actor_data.actions[actor_data.active_action].subactions) and
actor_data.actions[actor_data.active_action].active_subaction >= 0)
if not check:
return False
action_data = actor_data.actions[actor_data.active_action]
subaction_data = action_data.subactions[action_data.active_subaction]
if subaction_data.name not in bpy.data.actions:
return False
blend_action = bpy.data.actions[subaction_data.name]
return blend_action.hecl_active_event in range(len(blend_action.hecl_events))
def execute(self, context):
actor_data = context.scene.hecl_sact_data
action_data = actor_data.actions[actor_data.active_action]
subaction_data = action_data.subactions[action_data.active_subaction]
blend_action = bpy.data.actions[subaction_data.name]
event_name = blend_action.hecl_events[blend_action.hecl_active_event].name
blend_action.hecl_events.remove(blend_action.hecl_active_event)
marker_name = 'hecl_' + str(blend_action.hecl_index) + '_' + str(blend_action.hecl_active_event) + '_' + event_name
if marker_name in context.scene.timeline_markers:
context.scene.timeline_markers.remove(context.scene.timeline_markers[marker_name])
blend_action.hecl_active_event -= 1
if blend_action.hecl_active_event == -1:
blend_action.hecl_active_event = 0
clear_event_markers(actor_data, context)
return {'FINISHED'}
# Event 'move down' operator
class hecl_actor_subaction_event_move_down(bpy.types.Operator):
bl_idname = "scene.hecl_actor_subaction_event_move_down"
bl_label = "Move HECL Actor Event Down in Stack"
bl_description = "Move HECL Actor Event down in stack from active Sub-action"
@classmethod
def poll(cls, context):
actor_data = context.scene.hecl_sact_data
check = (context.scene is not None and
not context.scene.library and
context.scene.hecl_type == 'ACTOR' and
len(actor_data.actions) and
actor_data.active_action >= 0 and
len(actor_data.actions[actor_data.active_action].subactions) and
actor_data.actions[actor_data.active_action].active_subaction >= 0)
if not check:
return False
action_data = actor_data.actions[actor_data.active_action]
subaction_data = action_data.subactions[action_data.active_subaction]
if subaction_data.name not in bpy.data.actions:
return False
blend_action = bpy.data.actions[subaction_data.name]
return (blend_action.hecl_active_event in range(len(blend_action.hecl_events)) and
blend_action.hecl_active_event < len(blend_action.hecl_events) - 1)
def execute(self, context):
actor_data = context.scene.hecl_sact_data
action_data = actor_data.actions[actor_data.active_action]
subaction_data = action_data.subactions[action_data.active_subaction]
blend_action = bpy.data.actions[subaction_data.name]
event_name_a = blend_action.hecl_events[blend_action.hecl_active_event].name
event_name_b = blend_action.hecl_events[blend_action.hecl_active_event + 1].name
blend_action.hecl_events.move(blend_action.hecl_active_event, blend_action.hecl_active_event + 1)
marker_name_a = 'hecl_' + str(blend_action.hecl_index) + '_' + str(blend_action.hecl_active_event) + '_' + event_name_a
marker_a = None
if marker_name_a in context.scene.timeline_markers:
marker_a = context.scene.timeline_markers[marker_name_a]
marker_name_b = 'hecl_' + str(blend_action.hecl_index) + '_' + str(blend_action.hecl_active_event + 1) + '_' + event_name_b
marker_b = None
if marker_name_b in context.scene.timeline_markers:
marker_b = context.scene.timeline_markers[marker_name_b]
if marker_a and marker_b:
marker_a.name =\
'hecl_' + str(blend_action.hecl_index) + '_' + str(blend_action.hecl_active_event + 1) + '_' + event_name_a
marker_b.name =\
'hecl_' + str(blend_action.hecl_index) + '_' + str(blend_action.hecl_active_event) + '_' + event_name_b
blend_action.hecl_active_event += 1
return {'FINISHED'}
# Event 'move up' operator
class hecl_actor_subaction_event_move_up(bpy.types.Operator):
bl_idname = "scene.hecl_actor_subaction_event_move_up"
bl_label = "Move HECL Actor Event Up in Stack"
bl_description = "Move HECL Actor Event up in stack from active Sub-action"
@classmethod
def poll(cls, context):
actor_data = context.scene.hecl_sact_data
check = (context.scene is not None and
not context.scene.library and
context.scene.hecl_type == 'ACTOR' and
len(actor_data.actions) and
actor_data.active_action >= 0 and
len(actor_data.actions[actor_data.active_action].subactions) and
actor_data.actions[actor_data.active_action].active_subaction >= 0)
if not check:
return False
action_data = actor_data.actions[actor_data.active_action]
subaction_data = action_data.subactions[action_data.active_subaction]
if subaction_data.name not in bpy.data.actions:
return False
blend_action = bpy.data.actions[subaction_data.name]
return (blend_action.hecl_active_event in range(len(blend_action.hecl_events)) and
blend_action.hecl_active_event > 0)
def execute(self, context):
actor_data = context.scene.hecl_sact_data
action_data = actor_data.actions[actor_data.active_action]
subaction_data = action_data.subactions[action_data.active_subaction]
blend_action = bpy.data.actions[subaction_data.name]
event_name_a = blend_action.hecl_events[blend_action.hecl_active_event].name
event_name_b = blend_action.hecl_events[blend_action.hecl_active_event - 1].name
blend_action.hecl_events.move(blend_action.hecl_active_event, blend_action.hecl_active_event - 1)
marker_name_a = 'hecl_' + str(blend_action.hecl_index) + '_' + str(blend_action.hecl_active_event) + '_' + event_name_a
marker_a = None
if marker_name_a in context.scene.timeline_markers:
marker_a = context.scene.timeline_markers[marker_name_a]
marker_name_b = 'hecl_' + str(blend_action.hecl_index) + '_' + str(blend_action.hecl_active_event - 1) + '_' + event_name_b
marker_b = None
if marker_name_b in context.scene.timeline_markers:
marker_b = context.scene.timeline_markers[marker_name_b]
if marker_a and marker_b:
marker_a.name =\
'hecl_' + str(blend_action.hecl_index) + '_' + str(blend_action.hecl_active_event - 1) + '_' + event_name_a
marker_b.name =\
'hecl_' + str(blend_action.hecl_index) + '_' + str(blend_action.hecl_active_event) + '_' + event_name_b
blend_action.hecl_active_event -= 1
return {'FINISHED'}
def active_event_update(self, context):
actor_data = context.scene.hecl_sact_data
if actor_data.active_action in range(len(actor_data.actions)):
action_data = actor_data.actions[actor_data.active_action]
if action_data.active_subaction in range(len(action_data.subactions)):
subaction_data = action_data.subactions[action_data.active_subaction]
for marker in context.scene.timeline_markers:
if marker.name.startswith('hecl_'):
blend_action = bpy.data.actions[subaction_data.name]
event_name = blend_action.hecl_events[blend_action.hecl_active_event].name
if marker.name == 'hecl_' + str(blend_action.hecl_index) + '_' + str(blend_action.hecl_active_event) + '_' + event_name:
marker.select = True
else:
marker.select = False
# Registration
def register():
bpy.utils.register_class(hecl_actor_event_loop)
bpy.utils.register_class(hecl_actor_event_uevt)
bpy.utils.register_class(hecl_actor_event_effect)
bpy.utils.register_class(hecl_actor_event_sound)
bpy.utils.register_class(hecl_actor_event)
bpy.utils.register_class(hecl_actor_subaction_event_add)
bpy.utils.register_class(hecl_actor_subaction_event_remove)
bpy.utils.register_class(hecl_actor_subaction_event_move_down)
bpy.utils.register_class(hecl_actor_subaction_event_move_up)
bpy.types.Action.hecl_events = bpy.props.CollectionProperty(name="HECL action event",
type=hecl_actor_event)
bpy.types.Action.hecl_active_event = bpy.props.IntProperty(name="HECL active action event",
default=0,
update=active_event_update)
if not bpy.app.background and update_action_events not in bpy.app.handlers.scene_update_post:
bpy.app.handlers.scene_update_post.append(update_action_events)
def unregister():
if update_action_events in bpy.app.handlers.scene_update_post:
bpy.app.handlers.scene_update_post.remove(update_action_events)
bpy.utils.unregister_class(hecl_actor_event)
bpy.utils.unregister_class(hecl_actor_event_loop)
bpy.utils.unregister_class(hecl_actor_event_uevt)
bpy.utils.unregister_class(hecl_actor_event_effect)
bpy.utils.unregister_class(hecl_actor_event_sound)
bpy.utils.unregister_class(hecl_actor_subaction_event_add)
bpy.utils.unregister_class(hecl_actor_subaction_event_remove)
bpy.utils.unregister_class(hecl_actor_subaction_event_move_down)
bpy.utils.unregister_class(hecl_actor_subaction_event_move_up)

View File

@ -0,0 +1,184 @@
import bpy
# Subtype update (if anything important changes)
def active_subtype_update(self, context):
if context.scene.hecl_type == 'ACTOR' and context.scene.hecl_auto_select:
if SACTSubtype_load.poll(context):
bpy.ops.scene.SACTSubtype_load()
# Actor subtype class
class SACTSubtype(bpy.types.PropertyGroup):
name = bpy.props.StringProperty(name="Actor Mesh Name")
linked_armature = bpy.props.StringProperty(name="Linked Armature Object Source", update=active_subtype_update)
linked_mesh = bpy.props.StringProperty(name="Linked Mesh Object Source", update=active_subtype_update)
# Panel draw
def draw(layout, context):
actor_data = context.scene.hecl_sact_data
row = layout.row(align=True)
row.alignment = 'LEFT'
row.prop(actor_data, 'show_subtypes', text="Subtypes", icon='MESH_DATA', emboss=False)
if actor_data.show_subtypes:
row = layout.row()
row.template_list("UI_UL_list", "SCENE_UL_SACTSubtypes",
actor_data, 'subtypes', actor_data, 'active_subtype')
col = row.column(align=True)
col.operator("scene.sactsubtype_add", icon="ZOOMIN", text="")
col.operator("scene.sactsubtype_remove", icon="ZOOMOUT", text="")
if len(actor_data.subtypes) and actor_data.active_subtype >= 0:
subtype = actor_data.subtypes[actor_data.active_subtype]
# Load subtype operator
if not bpy.context.scene.hecl_auto_select:
layout.operator("scene.sactsubtype_load", icon='FILE_TICK', text="Load Subtype")
# Name edit field
layout.prop(subtype, 'name', text="Name")
# Link external armature search
layout.prop_search(subtype, 'linked_armature', bpy.data, 'objects', text="Armature")
linked_armature = None
if subtype.linked_armature in bpy.data.objects:
linked_armature = bpy.data.objects[subtype.linked_armature]
# Validate
if linked_armature is None:
layout.label("Source armature not set", icon='ERROR')
elif linked_armature is not None and linked_armature.type != 'ARMATURE':
layout.label("Source armature is not an 'ARMATURE'", icon='ERROR')
# Link external mesh search
layout.prop_search(subtype, 'linked_mesh', bpy.data, 'objects', text="Mesh")
linked_mesh = None
if subtype.linked_mesh in bpy.data.objects:
linked_mesh = bpy.data.objects[subtype.linked_mesh]
# Validate
if linked_mesh is None:
layout.label("Source mesh not set", icon='ERROR')
elif linked_mesh.type != 'MESH':
layout.label("Source mesh not 'MESH'", icon='ERROR')
elif linked_armature is not None and linked_mesh not in linked_armature.children:
layout.label(linked_mesh.name+" not a child of "+linked_armature.name, icon='ERROR')
elif linked_mesh.parent_type != 'ARMATURE':
layout.label("Source mesh not 'ARMATURE' parent type", icon='ERROR')
# Subtype 'add' operator
class SACTSubtype_add(bpy.types.Operator):
bl_idname = "scene.sactsubtype_add"
bl_label = "New HECL Actor Subtype"
bl_description = "Add New HECL Actor Subtype to active scene"
@classmethod
def poll(cls, context):
return (context.scene is not None and
not context.scene.library and
context.scene.hecl_type == 'ACTOR')
def execute(self, context):
actor_data = context.scene.hecl_sact_data
mesh_name = 'ActorMesh'
if mesh_name in actor_data.subtypes:
mesh_name = 'ActorMesh.001'
mesh_idx = 1
while mesh_name in actor_data.subtypes:
mesh_idx += 1
mesh_name = 'ActorMesh.{:0>3}'.format(mesh_idx)
mesh = actor_data.subtypes.add()
mesh.name = mesh_name
actor_data.active_subtype = len(actor_data.subtypes)-1
return {'FINISHED'}
# Subtype 'remove' operator
class SACTSubtype_remove(bpy.types.Operator):
bl_idname = "scene.sactsubtype_remove"
bl_label = "Remove HECL Actor Subtype"
bl_description = "Remove HECL Actor Subtype from active scene"
@classmethod
def poll(cls, context):
actor_data = context.scene.hecl_sact_data
return (context.scene is not None and
not context.scene.library and
context.scene.hecl_type == 'ACTOR' and
actor_data.active_subtype >= 0 and
len(actor_data.subtypes))
def execute(self, context):
actor_data = context.scene.hecl_sact_data
actor_data.subtypes.remove(actor_data.active_subtype)
actor_data.active_subtype -= 1
if actor_data.active_subtype == -1:
actor_data.active_subtype = 0
return {'FINISHED'}
# Subtype 'load' operator
class SACTSubtype_load(bpy.types.Operator):
bl_idname = "scene.sactsubtype_load"
bl_label = "Load HECL Actor Subtype"
bl_description = "Loads Subtype for viewing in active scene"
@classmethod
def poll(cls, context):
return (context.scene is not None and
context.scene.hecl_type == 'ACTOR' and
len(context.scene.hecl_sact_data.subtypes) and
context.scene.hecl_sact_data.active_subtype >= 0)
def execute(self, context):
actor_data = context.scene.hecl_sact_data
subtype = actor_data.subtypes[actor_data.active_subtype]
# Armature
linked_armature = None
if subtype.linked_armature in bpy.data.objects:
linked_armature = bpy.data.objects[subtype.linked_armature]
else:
return {'FINISHED'}
# Hide armature children
for object in linked_armature.children:
if object.name in context.scene.objects:
object.hide = True
# Hide all meshes
for mesh_name in actor_data.subtypes:
if mesh_name.linked_mesh in bpy.data.objects:
mesh = bpy.data.objects[mesh_name.linked_mesh]
if mesh.name in context.scene.objects:
mesh.hide = True
# Show only the chosen subtype
if subtype.linked_mesh in bpy.data.objects:
mesh_obj = bpy.data.objects[subtype.linked_mesh]
mesh_obj.hide = False
if mesh_obj != linked_armature:
mesh_obj.parent = linked_armature
mesh_obj.parent_type = 'ARMATURE'
return {'FINISHED'}
# Registration
def register():
bpy.utils.register_class(SACTSubtype)
bpy.utils.register_class(SACTSubtype_add)
bpy.utils.register_class(SACTSubtype_remove)
bpy.utils.register_class(SACTSubtype_load)
def unregister():
bpy.utils.unregister_class(SACTSubtype)
bpy.utils.unregister_class(SACTSubtype_add)
bpy.utils.unregister_class(SACTSubtype_remove)
bpy.utils.unregister_class(SACTSubtype_load)

View File

@ -0,0 +1,253 @@
from . import SACTSubtype, SACTAction, SACTEvent, ANIM
from .. import hmdl
import bpy
import re
import os.path
import posixpath
import struct
from mathutils import Vector
# Actor data class
class SACTData(bpy.types.PropertyGroup):
subtypes =\
bpy.props.CollectionProperty(type=SACTSubtype.SACTSubtype, name="Actor Subtype List")
active_subtype =\
bpy.props.IntProperty(name="Active Actor Subtype", default=0, update=SACTSubtype.active_subtype_update)
show_subtypes =\
bpy.props.BoolProperty()
actions =\
bpy.props.CollectionProperty(type=SACTAction.SACTAction, name="Actor Action List")
active_action =\
bpy.props.IntProperty(name="Active Actor Action", default=0, update=SACTAction.active_action_update)
show_actions =\
bpy.props.BoolProperty()
show_events =\
bpy.props.BoolProperty()
# A Routine to resolve HECL DAG-relative paths while ensuring database path-constraints
def resolve_local_path(blend_path, rel_path):
if not rel_path.startswith('//'):
raise RuntimeError("Files must have relative paths")
blend_dir = os.path.split(blend_path)[0]
image_comps = re.split('/|\\\\', rel_path[2:])
start_idx = 0
for comp in image_comps:
if comp == '..':
start_idx += 1
if not blend_dir:
raise RuntimeError("Relative file path has exceeded DAG root")
blend_dir = os.path.split(blend_dir)[0]
else:
break
retval = blend_dir
for i in range(len(image_comps)-start_idx):
if retval:
retval += '/'
retval += image_comps[start_idx+i]
return posixpath.relpath(retval)
# RARM Generator
def package_rarm(arm_obj, res_db, heclpak, arg_path, arg_package):
rarm_db_id, rarm_hash = res_db.register_resource(arg_path, arm_obj.name, arg_package)
if not rarm_hash:
skeleton_data = hecl_rmdl.generate_skeleton_info(arm_obj)
rarm_hash = heclpak.add_object(skeleton_data, b'RARM')
res_db.update_resource_stats(rarm_db_id, rarm_hash)
return rarm_db_id, rarm_hash
# RANI Generator
def package_rani(action_obj, res_db, heclpak, arg_path, arg_package):
rani_db_id, rani_hash = res_db.register_resource(arg_path, action_obj.name, arg_package)
if not rani_hash:
res_db.clear_dependencies(rani_db_id)
rani_hash = heclpak.add_object(hecl_rmdl.rmdl_anim.generate_animation_info(action_obj, res_db, rani_db_id, arg_package), b'RANI')
res_db.update_resource_stats(rani_db_id, rani_hash)
return rani_db_id, rani_hash
# Actor Ticket Generator
def package_actor(scene, res_db, heclpak, arg_path, arg_package, arg_res_name):
actor_data = scene.hecl_sact_data
act_db_id, act_hash = res_db.register_resource(arg_path, None, arg_package)
res_db.clear_dependencies(act_db_id)
with open(os.path.splitext(bpy.data.filepath)[0] + '.heclticket', 'wb') as ticket:
# Subtypes
ticket.write(struct.pack('I', len(actor_data.subtypes)))
for subtype_idx in range(len(actor_data.subtypes)):
subtype = actor_data.subtypes[subtype_idx]
scene.hecl_sact_data.active_subtype = subtype_idx
# Subtype name
ticket.write(subtype.name.encode() + b'\0')
# Mesh
if subtype.linked_mesh in bpy.data.objects:
mesh_obj = bpy.data.objects[subtype.linked_mesh]
if mesh_obj.library:
path = resolve_local_path(arg_path.split(';')[-1], mesh_obj.library.filepath)
mesh_db_id, mesh_hash = res_db.search_for_resource(path, arg_package)
if not mesh_hash:
raise RuntimeError("Error - unable to load mesh library '{0}'".format(path))
res_db.register_dependency(act_db_id, mesh_db_id)
ticket.write(mesh_hash)
else:
mesh_db_id, mesh_hash, _final_data = hecl_rmdl.to_rmdl(mesh_obj, mesh_obj.name, res_db, heclpak, arg_path, arg_package)
res_db.register_dependency(act_db_id, mesh_db_id)
ticket.write(mesh_hash)
else:
raise RuntimeError("Error - unable to load mesh '{0}'".format(mesh))
# Armature
if subtype.linked_armature in bpy.data.objects:
arm_obj = bpy.data.objects[subtype.linked_armature]
rarm_db_id, rarm_hash = package_rarm(arm_obj, res_db, heclpak, arg_path, arg_package)
res_db.register_dependency(act_db_id, rarm_db_id)
ticket.write(rarm_hash)
else:
raise RuntimeError("Error - unable to load armature '{0}'".format(subtype.linked_armature))
# Action AABBs
print('\nComputing Action AABBs for', subtype.name)
scene.hecl_auto_remap = False
ticket.write(struct.pack('I', len(actor_data.actions)))
for action_idx in range(len(actor_data.actions)):
action = actor_data.actions[action_idx]
print(action.name)
scene.hecl_sact_data.active_action = action_idx
bpy.ops.scene.SACTAction_load()
# Action name
ticket.write(action.name.encode() + b'\0')
# Frame 1
scene.frame_set(1)
# Transform against root
root_bone = arm_obj.pose.bones['root']
root_bone.location = (0.0,0.0,0.0)
if root_bone.rotation_mode == 'QUATERNION':
root_bone.rotation_quaternion = (1.0,0.0,0.0,0.0)
else:
root_bone.rotation_euler = (0.0,0.0,0.0)
root_aabb_min = Vector(mesh_obj.bound_box[0])
root_aabb_max = Vector(mesh_obj.bound_box[6])
# Accumulate AABB for each frame
for frame_idx in range(2, scene.frame_end + 1):
scene.frame_set(frame_idx)
root_bone.location = (0.0,0.0,0.0)
scene.update()
if root_bone.rotation_mode == 'QUATERNION':
root_bone.rotation_quaternion = (1.0,0.0,0.0,0.0)
else:
root_bone.rotation_euler = (0.0,0.0,0.0)
test_aabb_min = Vector(mesh_obj.bound_box[0])
test_aabb_max = Vector(mesh_obj.bound_box[6])
for comp in range(3):
if test_aabb_min[comp] < root_aabb_min[comp]:
root_aabb_min[comp] = test_aabb_min[comp]
for comp in range(3):
if test_aabb_max[comp] > root_aabb_max[comp]:
root_aabb_max[comp] = test_aabb_max[comp]
ticket.write(struct.pack('ffffff',
root_aabb_min[0], root_aabb_min[1], root_aabb_min[2],
root_aabb_max[0], root_aabb_max[1], root_aabb_max[2]))
# Actions
anim_hashes = dict()
ticket.write(struct.pack('I', len(actor_data.actions)))
for action in actor_data.actions:
# Action name
ticket.write(action.name.encode() + b'\0')
if action.type == 'SINGLE':
ticket.write(struct.pack('I', 0))
action_name = action.subactions[0].name
if action_name not in bpy.data.actions:
raise RuntimeError("Error - unable to load action '{0}'".format(action_name))
if action_name in anim_hashes:
rani_hash = anim_hashes[action_name]
else:
action_obj = bpy.data.actions[action_name]
rani_db_id, rani_hash = package_rani(action_obj, res_db, heclpak, arg_path, arg_package)
res_db.register_dependency(act_db_id, rani_db_id)
anim_hashes[action_name] = rani_hash
ticket.write(rani_hash)
elif action.type == 'SEQUENCE':
ticket.write(struct.pack('II', 1, len(action.subactions)))
for subaction in action.subactions:
action_name = subaction.name
if action_name not in bpy.data.actions:
raise RuntimeError("Error - unable to load action '{0}'".format(action_name))
if action_name in anim_hashes:
rani_hash = anim_hashes[action_name]
else:
action_obj = bpy.data.actions[action_name]
rani_db_id, rani_hash = package_rani(action_obj,
res_db, heclpak, arg_path, arg_package)
res_db.register_dependency(act_db_id, rani_db_id)
anim_hashes[action_name] = rani_hash
ticket.write(rani_hash)
elif action.type == 'RANDOM':
ticket.write(struct.pack('III', 2, len(action.subactions), action.random_val))
for subaction in action.subactions:
action_name = subaction.name
if action_name not in bpy.data.actions:
raise RuntimeError("Error - unable to load action '{0}'".format(action_name))
if action_name in anim_hashes:
rani_hash = anim_hashes[action_name]
else:
action_obj = bpy.data.actions[action_name]
rani_db_id, rani_hash = package_rani(action_obj,
res_db, heclpak, arg_path, arg_package)
res_db.register_dependency(act_db_id, rani_db_id)
anim_hashes[action_name] = rani_hash
ticket.write(rani_hash)
# Cook
def cook(writebuffunc, platform, endianchar):
print('COOKING SACT')
# Panel draw
def draw(layout, context):
SACTSubtype.draw(layout.box(), context)
SACTAction.draw(layout.box(), context)
SACTEvent.draw(layout.box(), context)
# Registration
def register():
SACTSubtype.register()
SACTEvent.register()
SACTAction.register()
bpy.utils.register_class(SACTData)
bpy.types.Scene.hecl_sact_data = bpy.props.PointerProperty(type=SACTData)
def unregister():
bpy.utils.unregister_class(SACTData)
SACTSubtype.unregister()
SACTAction.unregister()
SACTEvent.unregister()

View File

@ -19,9 +19,13 @@ DISTFILES += \
$$PWD/blendershell.py \ $$PWD/blendershell.py \
$$PWD/addon/__init__.py \ $$PWD/addon/__init__.py \
$$PWD/addon/hmdl/__init__.py \ $$PWD/addon/hmdl/__init__.py \
$$PWD/addon/hmdl/hmdl_anim.py \ $$PWD/addon/hmdl/HMDLMesh.py \
$$PWD/addon/hmdl/hmdl_mesh.py \ $$PWD/addon/hmdl/HMDLShader.py \
$$PWD/addon/hmdl/hmdl_shader.py \ $$PWD/addon/hmdl/HMDLSkin.py \
$$PWD/addon/hmdl/hmdl_skin.py \ $$PWD/addon/hmdl/HMDLTxtr.py \
$$PWD/addon/hmdl/hmdl_txtr.py $$PWD/addon/sact/__init__.py \
$$PWD/addon/sact/SACTAction.py \
$$PWD/addon/sact/SACTEvent.py \
$$PWD/addon/sact/SACTSubtype.py \
$$PWD/addon/sact/ANIM.py

View File

@ -7,7 +7,6 @@ if '--' not in sys.argv:
args = sys.argv[sys.argv.index('--')+1:] args = sys.argv[sys.argv.index('--')+1:]
readfd = int(args[0]) readfd = int(args[0])
writefd = int(args[1]) writefd = int(args[1])
print('READ', readfd, 'WRITE', writefd)
def readpipeline(): def readpipeline():
retval = bytearray() retval = bytearray()
@ -18,7 +17,11 @@ def readpipeline():
retval += ch retval += ch
def writepipeline(linebytes): def writepipeline(linebytes):
ch = os.write(writefd, linebytes + b'\n') os.write(writefd, linebytes + b'\n')
def writepipebuf(linebytes):
writepipeline(b'BUF')
os.write(writefd, struct.pack('I', len(linebytes)) + linebytes)
def quitblender(): def quitblender():
writepipeline(b'QUITTING') writepipeline(b'QUITTING')
@ -30,6 +33,9 @@ if 'hecl' not in bpy.context.user_preferences.addons:
writepipeline(b'NOADDON') writepipeline(b'NOADDON')
bpy.ops.wm.quit_blender() bpy.ops.wm.quit_blender()
# Make addon available to commands
import hecl
# Intro handshake # Intro handshake
writepipeline(b'READY') writepipeline(b'READY')
ackbytes = readpipeline() ackbytes = readpipeline()
@ -40,16 +46,15 @@ if ackbytes != b'ACK':
while True: while True:
cmdline = readpipeline().split(b' ') cmdline = readpipeline().split(b' ')
if not len(cmdline) or cmdline[0] == b'QUIT': if cmdline[0] == b'QUIT':
quitblender() quitblender()
elif cmdline[0] == b'OPEN': elif cmdline[0] == b'OPEN':
bpy.ops.wm.open_mainfile(filepath=cmdline[1].decode()) if 'FINISHED' in bpy.ops.wm.open_mainfile(filepath=cmdline[1].decode()):
writepipeline(b'SUCCESS') writepipeline(b'FINISHED')
else:
elif cmdline[0] == b'TYPE': writepipeline(b'CANCELLED')
objname = cmdline[1].decode()
else: else:
writepipeline(b'RESP ' + cmdline[0]) hecl.command(cmdline, writepipeline, writepipebuf)