From 1d4786344aebfb4914f14f1707cfa3e1a84f7fd3 Mon Sep 17 00:00:00 2001 From: Jack Andersen Date: Sun, 24 May 2015 12:19:28 -1000 Subject: [PATCH] initial actor panel integration --- hecl/blender/CBlenderConnection.cpp | 102 ++-- hecl/blender/CBlenderConnection.hpp | 24 +- hecl/blender/addon/__init__.py | 58 ++- .../addon/hmdl/{hmdl_mesh.py => HMDLMesh.py} | 1 - .../hmdl/{hmdl_shader.py => HMDLShader.py} | 0 .../addon/hmdl/{hmdl_skin.py => HMDLSkin.py} | 5 +- .../addon/hmdl/{hmdl_txtr.py => HMDLTxtr.py} | 0 hecl/blender/addon/hmdl/__init__.py | 20 +- .../addon/{hmdl/hmdl_anim.py => sact/ANIM.py} | 7 - hecl/blender/addon/sact/SACTAction.py | 211 +++++++++ hecl/blender/addon/sact/SACTEvent.py | 444 ++++++++++++++++++ hecl/blender/addon/sact/SACTSubtype.py | 184 ++++++++ hecl/blender/addon/sact/__init__.py | 253 ++++++++++ hecl/blender/blender.pro | 14 +- hecl/blender/blendershell.py | 23 +- 15 files changed, 1259 insertions(+), 87 deletions(-) rename hecl/blender/addon/hmdl/{hmdl_mesh.py => HMDLMesh.py} (99%) rename hecl/blender/addon/hmdl/{hmdl_shader.py => HMDLShader.py} (100%) rename hecl/blender/addon/hmdl/{hmdl_skin.py => HMDLSkin.py} (94%) rename hecl/blender/addon/hmdl/{hmdl_txtr.py => HMDLTxtr.py} (100%) rename hecl/blender/addon/{hmdl/hmdl_anim.py => sact/ANIM.py} (97%) create mode 100644 hecl/blender/addon/sact/SACTAction.py create mode 100644 hecl/blender/addon/sact/SACTEvent.py create mode 100644 hecl/blender/addon/sact/SACTSubtype.py create mode 100644 hecl/blender/addon/sact/__init__.py diff --git a/hecl/blender/CBlenderConnection.cpp b/hecl/blender/CBlenderConnection.cpp index d077f139c..bbd3b6978 100644 --- a/hecl/blender/CBlenderConnection.cpp +++ b/hecl/blender/CBlenderConnection.cpp @@ -1,12 +1,9 @@ -#if _WIN32 -#else #include #include #include #include #include #include -#endif #include "CBlenderConnection.hpp" @@ -20,7 +17,7 @@ #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; while (true) @@ -51,7 +48,7 @@ err: return 0; } -size_t CBlenderConnection::writeLine(const char* buf) +size_t CBlenderConnection::_writeLine(const char* buf) { ssize_t ret, nlerr; ret = write(m_writepipe[1], buf, strlen(buf)); @@ -65,7 +62,7 @@ err: 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); if (ret < 0) @@ -73,7 +70,7 @@ size_t CBlenderConnection::readBuf(char* buf, size_t len) 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); if (ret < 0) @@ -81,7 +78,7 @@ size_t CBlenderConnection::writeBuf(const char* buf, size_t len) return ret; } -void CBlenderConnection::closePipe() +void CBlenderConnection::_closePipe() { close(m_readpipe[0]); close(m_writepipe[1]); @@ -115,10 +112,11 @@ CBlenderConnection::CBlenderConnection(bool silenceBlender) char writefds[32]; snprintf(writefds, 32, "%d", m_readpipe[1]); - /* User-specified blender first */ + /* Try user-specified blender first */ if (blenderBin) { - execlp(blenderBin, blenderBin, "--background", "-P", TEMP_SHELLSCRIPT, + execlp(blenderBin, blenderBin, + "--background", "-P", TEMP_SHELLSCRIPT, "--", readfds, writefds, NULL); if (errno != ENOENT) { @@ -128,8 +126,9 @@ CBlenderConnection::CBlenderConnection(bool silenceBlender) } } - /* Default blender next */ - execlp(DEFAULT_BLENDER_BIN, DEFAULT_BLENDER_BIN, "--background", "-P", TEMP_SHELLSCRIPT, + /* Otherwise default blender */ + execlp(DEFAULT_BLENDER_BIN, DEFAULT_BLENDER_BIN, + "--background", "-P", TEMP_SHELLSCRIPT, "--", readfds, writefds, NULL); if (errno != ENOENT) { @@ -149,17 +148,18 @@ CBlenderConnection::CBlenderConnection(bool silenceBlender) /* Handle first response */ char lineBuf[256]; - readLine(lineBuf, sizeof(lineBuf)); + _readLine(lineBuf, sizeof(lineBuf)); if (!strcmp(lineBuf, "NOLAUNCH")) { - closePipe(); + _closePipe(); throw std::runtime_error("Unable to launch blender"); } else if (!strcmp(lineBuf, "NOBLENDER")) { - closePipe(); + _closePipe(); 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) + "'"); else throw std::runtime_error("Unable to find blender at '" + @@ -167,32 +167,76 @@ CBlenderConnection::CBlenderConnection(bool silenceBlender) } else if (!strcmp(lineBuf, "NOADDON")) { - closePipe(); + _closePipe(); throw std::runtime_error("HECL addon not installed within blender"); } else if (strcmp(lineBuf, "READY")) { - closePipe(); - throw std::runtime_error("read '" + std::string(lineBuf) + "' from blender; expected 'READY'"); + _closePipe(); + throw std::runtime_error("read '" + std::string(lineBuf) + + "' from blender; expected 'READY'"); } - writeLine("ACK"); - - writeLine("HELLOBLENDER!!"); - readLine(lineBuf, sizeof(lineBuf)); - printf("%s\n", lineBuf); - quitBlender(); + _writeLine("ACK"); } 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 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() { - writeLine("QUIT"); + _writeLine("QUIT"); char lineBuf[256]; - readLine(lineBuf, sizeof(lineBuf)); - printf("%s\n", lineBuf); + _readLine(lineBuf, sizeof(lineBuf)); } diff --git a/hecl/blender/CBlenderConnection.hpp b/hecl/blender/CBlenderConnection.hpp index 32af66020..8c7bcef3d 100644 --- a/hecl/blender/CBlenderConnection.hpp +++ b/hecl/blender/CBlenderConnection.hpp @@ -8,6 +8,9 @@ #include #endif +#include +#include + class CBlenderConnection { #if _WIN32 @@ -19,15 +22,26 @@ class CBlenderConnection int m_readpipe[2]; int m_writepipe[2]; #endif - size_t readLine(char* buf, size_t bufSz); - size_t writeLine(const char* buf); - size_t readBuf(char* buf, size_t len); - size_t writeBuf(const char* buf, size_t len); - void closePipe(); + std::string m_loadedBlend; + size_t _readLine(char* buf, size_t bufSz); + size_t _writeLine(const char* buf); + size_t _readBuf(char* buf, size_t len); + size_t _writeBuf(const char* buf, size_t len); + void _closePipe(); public: CBlenderConnection(bool silenceBlender=false); ~CBlenderConnection(); + bool openBlend(const std::string& path); + enum CookPlatform + { + CP_MODERN = 0, + CP_GX = 1, + }; + bool cookBlend(std::function bufGetter, + const std::string& expectedType, + const std::string& platform, + bool bigEndian=false); void quitBlender(); }; diff --git a/hecl/blender/addon/__init__.py b/hecl/blender/addon/__init__.py index 90325f9f9..96c47d41b 100644 --- a/hecl/blender/addon/__init__.py +++ b/hecl/blender/addon/__init__.py @@ -13,14 +13,15 @@ bl_info = { "category": "System"} # Package import -from . import hmdl +from . import hmdl, sact import bpy, os, sys from bpy.app.handlers import persistent # Appendable list allowing external addons to register additional resource types -hecl_export_types = [ +hecl_typeS = [ ('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 class hecl_scene_panel(bpy.types.Panel): @@ -37,46 +38,67 @@ class hecl_scene_panel(bpy.types.Panel): def draw(self, context): layout = self.layout 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: - if exp_type[0] == context.scene.hecl_export_type and callable(exp_type[3]): - exp_type[3](self, context) + for exp_type in hecl_typeS: + if exp_type[0] == context.scene.hecl_type and callable(exp_type[3]): + exp_type[3](self.layout, context) break -# Blender-selected polymorphism -def do_package(writefd, platform_type, endian_char): - for tp in hecl_export_types: - if tp[0] == bpy.context.scene.hecl_export_type: +# Blender-selected polymorphism cook +def do_cook(writebuf, platform_type, endian_char): + for tp in hecl_typeS: + if tp[0] == bpy.context.scene.hecl_type: 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 def register_export_type_enum(): - bpy.types.Scene.hecl_export_type = bpy.props.EnumProperty(items= - [tp[:3] for tp in hecl_export_types], + bpy.types.Scene.hecl_type = bpy.props.EnumProperty(items= + [tp[:3] for tp in hecl_typeS], name="HECL Export Type", description="Selects how active scene is exported by HECL") # Function for external addons to register export types with HECL def add_export_type(type_tuple): type_tup = tuple(type_tuple) - for tp in hecl_export_types: + for tp in hecl_typeS: if tp[0] == type_tup[0]: raise RuntimeError("Type already registered with HECL") - hecl_export_types.append(type_tup) + hecl_types.append(type_tup) 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 def register(): - hmdl.register() - bpy.utils.register_class(hecl_scene_panel) register_export_type_enum() + hmdl.register() + sact.register() + bpy.utils.register_class(hecl_scene_panel) def unregister(): hmdl.unregister() + sact.unregister() bpy.utils.unregister_class(hecl_scene_panel) if __name__ == "__main__": diff --git a/hecl/blender/addon/hmdl/hmdl_mesh.py b/hecl/blender/addon/hmdl/HMDLMesh.py similarity index 99% rename from hecl/blender/addon/hmdl/hmdl_mesh.py rename to hecl/blender/addon/hmdl/HMDLMesh.py index b9fd105d4..bfb8a9e4a 100644 --- a/hecl/blender/addon/hmdl/hmdl_mesh.py +++ b/hecl/blender/addon/hmdl/HMDLMesh.py @@ -528,4 +528,3 @@ class hmdl_mesh_operator(bpy.types.Operator): context.window_manager.clipboard = str_out self.report({'INFO'}, "Wrote mesh C to clipboard") return {'FINISHED'} - diff --git a/hecl/blender/addon/hmdl/hmdl_shader.py b/hecl/blender/addon/hmdl/HMDLShader.py similarity index 100% rename from hecl/blender/addon/hmdl/hmdl_shader.py rename to hecl/blender/addon/hmdl/HMDLShader.py diff --git a/hecl/blender/addon/hmdl/hmdl_skin.py b/hecl/blender/addon/hmdl/HMDLSkin.py similarity index 94% rename from hecl/blender/addon/hmdl/hmdl_skin.py rename to hecl/blender/addon/hmdl/HMDLSkin.py index ccdb3d0c3..be7851bf7 100644 --- a/hecl/blender/addon/hmdl/hmdl_skin.py +++ b/hecl/blender/addon/hmdl/HMDLSkin.py @@ -10,7 +10,6 @@ or have a new one established. import struct import bpy -from . import hmdl_anim class hmdl_skin: @@ -72,14 +71,14 @@ class hmdl_skin: # 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 = [] for bone_array in self.bone_arrays: skin_bytes = bytearray() skin_bytes += struct.pack(endian_char + 'I', len(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) # Generate skinning data diff --git a/hecl/blender/addon/hmdl/hmdl_txtr.py b/hecl/blender/addon/hmdl/HMDLTxtr.py similarity index 100% rename from hecl/blender/addon/hmdl/hmdl_txtr.py rename to hecl/blender/addon/hmdl/HMDLTxtr.py diff --git a/hecl/blender/addon/hmdl/__init__.py b/hecl/blender/addon/hmdl/__init__.py index ce8ab5ce5..5f69cf4a2 100644 --- a/hecl/blender/addon/hmdl/__init__.py +++ b/hecl/blender/addon/hmdl/__init__.py @@ -23,10 +23,7 @@ Positions, Normals, UV coordinates, and Weight Vectors import struct, bpy, bmesh from mathutils import Vector -from . import hmdl_shader -from . import hmdl_skin -from . import hmdl_mesh -from . import hmdl_anim +from . import HMDLShader, HMDLSkin, HMDLMesh, HMDLTxtr def get_3d_context(object_): 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) # and performs a one-shot conversion process to HMDL; packaging # 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] if mesh_obj.type != 'MESH': 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 - -def panel_draw(self, context): - layout = self.layout +def draw(layout, context): layout.prop_search(context.scene, 'hecl_mesh_obj', context.scene, 'objects') if not len(context.scene.hecl_mesh_obj): layout.label("Mesh not specified", icon='ERROR') @@ -301,8 +298,11 @@ def register(): bpy.types.Scene.hecl_mesh_obj = bpy.props.StringProperty( name='HECL Mesh Object', 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 def unregister(): - bpy.utils.unregister_class(hmdl_shader.hecl_shader_operator) + bpy.utils.unregister_class(HMDLShader.hecl_shader_operator) pass diff --git a/hecl/blender/addon/hmdl/hmdl_anim.py b/hecl/blender/addon/sact/ANIM.py similarity index 97% rename from hecl/blender/addon/hmdl/hmdl_anim.py rename to hecl/blender/addon/sact/ANIM.py index ee5e76068..d006bbd2f 100644 --- a/hecl/blender/addon/hmdl/hmdl_anim.py +++ b/hecl/blender/addon/sact/ANIM.py @@ -1,7 +1,4 @@ ''' -HMDL Export Blender Addon -By Jack Andersen - This file provides a means to encode animation key-channels in an interleaved, sparse array for use by the runtime ''' @@ -11,10 +8,6 @@ import hashlib import struct 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 scale_matcher = re.compile(r'pose.bones\["(\S+)"\].scale') rotation_matcher = re.compile(r'pose.bones\["(\S+)"\].rotation') diff --git a/hecl/blender/addon/sact/SACTAction.py b/hecl/blender/addon/sact/SACTAction.py new file mode 100644 index 000000000..b4bee3f1b --- /dev/null +++ b/hecl/blender/addon/sact/SACTAction.py @@ -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) diff --git a/hecl/blender/addon/sact/SACTEvent.py b/hecl/blender/addon/sact/SACTEvent.py new file mode 100644 index 000000000..dd8fb8519 --- /dev/null +++ b/hecl/blender/addon/sact/SACTEvent.py @@ -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) diff --git a/hecl/blender/addon/sact/SACTSubtype.py b/hecl/blender/addon/sact/SACTSubtype.py new file mode 100644 index 000000000..b69df2450 --- /dev/null +++ b/hecl/blender/addon/sact/SACTSubtype.py @@ -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) diff --git a/hecl/blender/addon/sact/__init__.py b/hecl/blender/addon/sact/__init__.py new file mode 100644 index 000000000..080d10c1b --- /dev/null +++ b/hecl/blender/addon/sact/__init__.py @@ -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() diff --git a/hecl/blender/blender.pro b/hecl/blender/blender.pro index 733dff348..014a6beeb 100644 --- a/hecl/blender/blender.pro +++ b/hecl/blender/blender.pro @@ -19,9 +19,13 @@ DISTFILES += \ $$PWD/blendershell.py \ $$PWD/addon/__init__.py \ $$PWD/addon/hmdl/__init__.py \ - $$PWD/addon/hmdl/hmdl_anim.py \ - $$PWD/addon/hmdl/hmdl_mesh.py \ - $$PWD/addon/hmdl/hmdl_shader.py \ - $$PWD/addon/hmdl/hmdl_skin.py \ - $$PWD/addon/hmdl/hmdl_txtr.py + $$PWD/addon/hmdl/HMDLMesh.py \ + $$PWD/addon/hmdl/HMDLShader.py \ + $$PWD/addon/hmdl/HMDLSkin.py \ + $$PWD/addon/hmdl/HMDLTxtr.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 diff --git a/hecl/blender/blendershell.py b/hecl/blender/blendershell.py index aa0e84f64..909d7f080 100644 --- a/hecl/blender/blendershell.py +++ b/hecl/blender/blendershell.py @@ -7,7 +7,6 @@ if '--' not in sys.argv: args = sys.argv[sys.argv.index('--')+1:] readfd = int(args[0]) writefd = int(args[1]) -print('READ', readfd, 'WRITE', writefd) def readpipeline(): retval = bytearray() @@ -18,7 +17,11 @@ def readpipeline(): retval += ch 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(): writepipeline(b'QUITTING') @@ -30,6 +33,9 @@ if 'hecl' not in bpy.context.user_preferences.addons: writepipeline(b'NOADDON') bpy.ops.wm.quit_blender() +# Make addon available to commands +import hecl + # Intro handshake writepipeline(b'READY') ackbytes = readpipeline() @@ -40,16 +46,15 @@ if ackbytes != b'ACK': while True: cmdline = readpipeline().split(b' ') - if not len(cmdline) or cmdline[0] == b'QUIT': + if cmdline[0] == b'QUIT': quitblender() elif cmdline[0] == b'OPEN': - bpy.ops.wm.open_mainfile(filepath=cmdline[1].decode()) - writepipeline(b'SUCCESS') - - elif cmdline[0] == b'TYPE': - objname = cmdline[1].decode() + if 'FINISHED' in bpy.ops.wm.open_mainfile(filepath=cmdline[1].decode()): + writepipeline(b'FINISHED') + else: + writepipeline(b'CANCELLED') else: - writepipeline(b'RESP ' + cmdline[0]) + hecl.command(cmdline, writepipeline, writepipebuf)