diff --git a/hecl/blender/BlenderConnection.cpp b/hecl/blender/BlenderConnection.cpp index a6ef28ace..677a6aa4b 100644 --- a/hecl/blender/BlenderConnection.cpp +++ b/hecl/blender/BlenderConnection.cpp @@ -587,6 +587,7 @@ BlenderConnection::DataStream::Mesh::getContiguousSkinningVersion() const Mesh newMesh = *this; newMesh.pos.clear(); newMesh.norm.clear(); + newMesh.contiguousSkinVertCounts.clear(); newMesh.contiguousSkinVertCounts.reserve(skins.size()); for (size_t i=0 ; im_loadedType != TypeMesh) + BlenderLog.report(LogVisor::FatalError, _S("%s is not a MESH blend"), + m_parent->m_loadedBlend.getAbsolutePath().c_str()); + char req[128]; snprintf(req, 128, "MESHCOMPILE %s %d", MeshOutputModeString(outMode), skinSlotCount); @@ -418,10 +439,14 @@ public: return Mesh(*m_parent, outMode, skinSlotCount, surfProg); } - /* Compile mesh by name */ + /** Compile mesh by name (AREA blends only) */ Mesh compileMesh(const std::string& name, Mesh::OutputMode outMode, int skinSlotCount=10, Mesh::SurfProgFunc surfProg=[](int){}) { + if (m_parent->m_loadedType != TypeArea) + BlenderLog.report(LogVisor::FatalError, _S("%s is not an AREA blend"), + m_parent->m_loadedBlend.getAbsolutePath().c_str()); + char req[128]; snprintf(req, 128, "MESHCOMPILENAME %s %s %d", name.c_str(), MeshOutputModeString(outMode), skinSlotCount); @@ -435,10 +460,14 @@ public: return Mesh(*m_parent, outMode, skinSlotCount, surfProg); } - /* Compile all meshes into one */ + /** Compile all meshes into one (AREA blends only) */ Mesh compileAllMeshes(Mesh::OutputMode outMode, int skinSlotCount=10, float maxOctantLength=5.0, Mesh::SurfProgFunc surfProg=[](int){}) { + if (m_parent->m_loadedType != TypeArea) + BlenderLog.report(LogVisor::FatalError, _S("%s is not an AREA blend"), + m_parent->m_loadedBlend.getAbsolutePath().c_str()); + char req[128]; snprintf(req, 128, "MESHCOMPILEALL %s %d %f", MeshOutputModeString(outMode), @@ -452,6 +481,87 @@ public: return Mesh(*m_parent, outMode, skinSlotCount, surfProg); } + + /** Intermediate actor representation prepared by blender from a single HECL actor blend */ + struct Actor + { + struct Armature + { + std::string name; + struct Bone + { + std::string name; + Vector3f origin; + int32_t parent = -1; + std::vector children; + Bone(BlenderConnection& conn); + }; + std::vector bones; + Bone* lookupBone(const char* name) + { + for (Bone& b : bones) + if (!b.name.compare(name)) + return &b; + return nullptr; + } + Armature(BlenderConnection& conn); + }; + std::vector armatures; + + struct Subtype + { + std::string name; + ProjectPath mesh; + int32_t armature = -1; + std::vector> overlayMeshes; + Subtype(BlenderConnection& conn); + }; + std::vector subtypes; + + struct Action + { + std::string name; + float interval; + bool additive; + std::vector frames; + struct Channel + { + std::string boneName; + uint32_t attrMask; + struct Key + { + Vector4f rotation; + Vector3f position; + Vector3f scale; + Key(BlenderConnection& conn, uint32_t attrMask); + }; + std::vector keys; + Channel(BlenderConnection& conn); + }; + std::vector channels; + std::vector> subtypeAABBs; + Action(BlenderConnection& conn); + }; + std::vector actions; + + Actor(BlenderConnection& conn); + }; + + Actor compileActor() + { + if (m_parent->m_loadedType != TypeActor) + BlenderLog.report(LogVisor::FatalError, _S("%s is not an ACTOR blend"), + m_parent->m_loadedBlend.getAbsolutePath().c_str()); + + m_parent->_writeLine("ACTORCOMPILE"); + + char readBuf[256]; + m_parent->_readLine(readBuf, 256); + if (strcmp(readBuf, "OK")) + BlenderLog.report(LogVisor::FatalError, "unable to compile actor: %s", readBuf); + + return Actor(*m_parent); + } }; DataStream beginData() { diff --git a/hecl/blender/CMakeLists.txt b/hecl/blender/CMakeLists.txt index c319ca3f1..72213c640 100644 --- a/hecl/blender/CMakeLists.txt +++ b/hecl/blender/CMakeLists.txt @@ -5,10 +5,8 @@ list(APPEND PY_SOURCES hecl/hmdl/__init__.py hecl/hmdl/HMDLMesh.py hecl/hmdl/HMDLShader.py - hecl/hmdl/HMDLSkin.py hecl/sact/__init__.py hecl/sact/SACTAction.py - hecl/sact/SACTEvent.py hecl/sact/SACTSubtype.py hecl/srea/__init__.py) diff --git a/hecl/blender/hecl/__init__.py b/hecl/blender/hecl/__init__.py index 9f6598318..2b3029187 100644 --- a/hecl/blender/hecl/__init__.py +++ b/hecl/blender/hecl/__init__.py @@ -1,11 +1,9 @@ -'''Root HECL addon package for Blender''' - bl_info = { "name": "HECL", "author": "Jack Andersen ", "version": (1, 0), - "blender": (2, 75), - "tracker_url": "https://github.com/RetroView/hecl/issues/new", + "blender": (2, 76), + "tracker_url": "https://github.com/AxioDL/hecl/issues/new", "location": "Properties > Scene > HECL", "description": "Enables blender to gather meshes, materials, and textures for hecl", "category": "System"} diff --git a/hecl/blender/hecl/hmdl/HMDLMesh.py b/hecl/blender/hecl/hmdl/HMDLMesh.py index eecc9d71f..cb15ca065 100644 --- a/hecl/blender/hecl/hmdl/HMDLMesh.py +++ b/hecl/blender/hecl/hmdl/HMDLMesh.py @@ -1,8 +1,3 @@ -''' -HMDL Export Blender Addon -By Jack Andersen -''' - import bpy, bmesh, operator, struct from mathutils import Vector diff --git a/hecl/blender/hecl/hmdl/HMDLShader.py b/hecl/blender/hecl/hmdl/HMDLShader.py index 5958c3975..929b28adc 100644 --- a/hecl/blender/hecl/hmdl/HMDLShader.py +++ b/hecl/blender/hecl/hmdl/HMDLShader.py @@ -1,11 +1,3 @@ -''' -HMDL Export Blender Addon -By Jack Andersen - -Traces the 'Blender Internal' shader node structure to generate a -HECL combiner string -''' - import bpy, bpy.path, os.path def get_texmap_idx(tex_list, name): diff --git a/hecl/blender/hecl/hmdl/HMDLSkin.py b/hecl/blender/hecl/hmdl/HMDLSkin.py deleted file mode 100644 index be7851bf7..000000000 --- a/hecl/blender/hecl/hmdl/HMDLSkin.py +++ /dev/null @@ -1,97 +0,0 @@ -''' -HMDL Export Blender Addon -By Jack Andersen - -This file defines the `hmdl_skin` class to iteratively construct -a Skinning Info Section for HMDL files. Used by draw-format -generators to select an optimal skin entry for a draw primitive, -or have a new one established. -''' - -import struct -import bpy - -class hmdl_skin: - - # Set up with HMDL containing ARMATURE `object` - def __init__(self, max_bone_count, vertex_groups): - self.max_bone_count = max_bone_count - self.mesh_vertex_groups = vertex_groups - - self.bone_arrays = [] - - - # Augment bone array with loop vert and return weight array - # Returns 'None' if bone overflow - def augment_bone_array_with_lv(self, mesh_data, bone_array, loop_vert): - vertex = mesh_data.vertices[loop_vert[0].loop.vertex_index] - - # Loop-vert weight array - weight_array = [] - for i in range(len(bone_array)): - weight_array.append(0.0) - - # Tentative bone additions - new_bones = [] - - - # Determine which bones (vertex groups) belong to loop_vert - for group_elem in vertex.groups: - vertex_group = self.mesh_vertex_groups[group_elem.group] - - if vertex_group.name not in bone_array: - - # Detect bone overflow - if len(bone_array) + len(new_bones) >= self.max_bone_count: - return None - - # Add to array otherwise - new_bones.append(vertex_group.name) - - # Record bone weight - weight_array.append(group_elem.weight) - - else: - - # Record bone weight - weight_array[bone_array.index(vertex_group.name)] = group_elem.weight - - - # If we get here, no overflows; augment bone array and return weight array - bone_array.extend(new_bones) - return weight_array - - - # Augment triangle-strip bone array to rigging info - def augment_skin(self, bone_array): - if bone_array not in self.bone_arrays: - self.bone_arrays.append(bone_array) - return (len(self.bone_arrays)-1) - return self.bone_arrays.index(bone_array) - - - # Generate Rigging Info structure (call after all index-buffers generated) - 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', bone_dict[bone]) - skin_entries.append(skin_bytes) - - # Generate skinning data - info_bytes = bytearray() - info_bytes += struct.pack(endian_char + 'I', len(skin_entries)) - - cur_offset = len(skin_entries) * 4 + 4 - for entry in skin_entries: - info_bytes += struct.pack(endian_char + 'I', cur_offset) - cur_offset += len(entry) - - for entry in skin_entries: - info_bytes += entry - - return info_bytes - diff --git a/hecl/blender/hecl/hmdl/__init__.py b/hecl/blender/hecl/hmdl/__init__.py index 04aa48646..ee8d2ae90 100644 --- a/hecl/blender/hecl/hmdl/__init__.py +++ b/hecl/blender/hecl/hmdl/__init__.py @@ -1,69 +1,6 @@ -''' -HMDL Export Blender Addon -By Jack Andersen - -This Python module provides a generator implementation for -the 'HMDL' mesh format designed for use with HECL. - -The format features three main sections: -* Shader table -* Skin-binding table -* Mesh table (VBOs [array,element], VAO attribs, drawing index) - -The Shader table provides index-referenced binding points -for mesh-portions to use for rendering. - -The Skin-binding table provides the runtime with identifiers -to use in ensuring the correct bone-transformations are bound -to the shader when rendering a specific primitive. - -The Mesh table contains Vertex and Element buffers with interleaved -Positions, Normals, UV coordinates, and Weight Vectors -''' - import struct, bpy, bmesh from mathutils import Vector -from . import HMDLShader, HMDLSkin, HMDLMesh - -# Generate Skeleton Info structure (free-form tree structure) -def generate_skeleton_info(armature, endian_char='<'): - - bones = [] - for bone in armature.data.bones: - bone_bytes = bytearray() - - # Write bone hash - bone_bytes += struct.pack(endian_char + 'I', hmdl_anim.hashbone(bone.name)) - - for comp in bone.head_local: - bone_bytes += struct.pack(endian_char + 'f', comp) - - parent_idx = -1 - if bone.parent: - parent_idx = armature.data.bones.find(bone.parent.name) - bone_bytes += struct.pack(endian_char + 'i', parent_idx) - - bone_bytes += struct.pack(endian_char + 'I', len(bone.children)) - - for child in bone.children: - child_idx = armature.data.bones.find(child.name) - bone_bytes += struct.pack(endian_char + 'I', child_idx) - - bones.append(bone_bytes) - - # Generate bone tree data - info_bytes = bytearray() - info_bytes += struct.pack(endian_char + 'I', len(bones)) - - cur_offset = len(bones) * 4 + 4 - for bone in bones: - info_bytes += struct.pack(endian_char + 'I', cur_offset) - cur_offset += len(bone) - - for bone in bones: - info_bytes += bone - - return info_bytes +from . import HMDLShader, HMDLMesh def write_out_material(writebuf, mat, mesh_obj): hecl_str, texs = HMDLShader.shader(mat, mesh_obj) diff --git a/hecl/blender/hecl/sact/SACTEvent.py b/hecl/blender/hecl/sact/SACTEvent.py deleted file mode 100644 index e76f66d31..000000000 --- a/hecl/blender/hecl/sact/SACTEvent.py +++ /dev/null @@ -1,442 +0,0 @@ -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.name in bpy.data.actions: - action_obj =\ - bpy.data.actions[action_data.name] - 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] - for marker in context.scene.timeline_markers: - if marker.name.startswith('hecl_'): - blend_action = bpy.data.actions[action_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/hecl/sact/SACTSubtype.py b/hecl/blender/hecl/sact/SACTSubtype.py index 8198582c3..a004c4eb3 100644 --- a/hecl/blender/hecl/sact/SACTSubtype.py +++ b/hecl/blender/hecl/sact/SACTSubtype.py @@ -236,15 +236,15 @@ class SACTSubtypeOverlay_add(bpy.types.Operator): def execute(self, context): actor_data = context.scene.hecl_sact_data subtype = actor_data.subtypes[actor_data.active_subtype] - overlay_name = 'ActorMesh' + overlay_name = 'ActorOverlay' if overlay_name in subtype.overlays: - overlay_name = 'ActorMesh.001' + overlay_name = 'ActorOverlay.001' overlay_idx = 1 while overlay_name in subtype.overlays: overlay_idx += 1 - overlay_name = 'ActorMesh.{:0>3}'.format(overlay_idx) + overlay_name = 'ActorOverlay.{:0>3}'.format(overlay_idx) overlay = subtype.overlays.add() - mesh.name = overlay_name + overlay.name = overlay_name subtype.active_overlay = len(subtype.overlays)-1 return {'FINISHED'} diff --git a/hecl/blender/hecl/sact/__init__.py b/hecl/blender/hecl/sact/__init__.py index 43e484d19..6a682c9d2 100644 --- a/hecl/blender/hecl/sact/__init__.py +++ b/hecl/blender/hecl/sact/__init__.py @@ -2,11 +2,12 @@ from . import SACTSubtype, SACTAction, ANIM from .. import hmdl import bpy +import bpy.path import re import os.path import posixpath import struct -from mathutils import Vector +from mathutils import Vector, Quaternion, Euler # Actor data class class SACTData(bpy.types.PropertyGroup): @@ -25,217 +26,289 @@ class SACTData(bpy.types.PropertyGroup): show_actions =\ bpy.props.BoolProperty() - #show_events =\ - #bpy.props.BoolProperty() +# Regex RNA path matchers +scale_matcher = re.compile(r'pose.bones\["(\S+)"\].scale') +rotation_matcher = re.compile(r'pose.bones\["(\S+)"\].rotation') +location_matcher = re.compile(r'pose.bones\["(\S+)"\].location') -# 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] +def write_action_channels(writebuf, action): + # Set of frame indices + frame_set = set() + + # Set of unique bone names + bone_set = [] + + # Scan through all fcurves to build animated bone set + for fcurve in action.fcurves: + data_path = fcurve.data_path + scale_match = scale_matcher.match(data_path) + rotation_match = rotation_matcher.match(data_path) + location_match = location_matcher.match(data_path) + + if scale_match: + if scale_match.group(1) not in bone_set: + bone_set.append(scale_match.group(1)) + elif rotation_match: + if rotation_match.group(1) not in bone_set: + bone_set.append(rotation_match.group(1)) + elif location_match: + if location_match.group(1) not in bone_set: + bone_set.append(location_match.group(1)) 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) + continue + + # Count unified keyframes for interleaving channel data + for key in fcurve.keyframe_points: + frame_set.add(int(key.co[0])) + + # Build bone table + bone_list = [] + for bone in bone_set: + fc_dict = dict() + rotation_mode = None + property_bits = 0 + for fcurve in action.fcurves: + if fcurve.data_path == 'pose.bones["'+bone+'"].scale': + if 'scale' not in fc_dict: + fc_dict['scale'] = [None, None, None] + property_bits |= 4 + fc_dict['scale'][fcurve.array_index] = fcurve + elif fcurve.data_path == 'pose.bones["'+bone+'"].rotation_euler': + if 'rotation_euler' not in fc_dict: + fc_dict['rotation_euler'] = [None, None, None] + rotation_mode = 'rotation_euler' + property_bits |= 1 + fc_dict['rotation_euler'][fcurve.array_index] = fcurve + elif fcurve.data_path == 'pose.bones["'+bone+'"].rotation_quaternion': + if 'rotation_quaternion' not in fc_dict: + fc_dict['rotation_quaternion'] = [None, None, None, None] + rotation_mode = 'rotation_quaternion' + property_bits |= 1 + fc_dict['rotation_quaternion'][fcurve.array_index] = fcurve + elif fcurve.data_path == 'pose.bones["'+bone+'"].rotation_axis_angle': + if 'rotation_axis_angle' not in fc_dict: + fc_dict['rotation_axis_angle'] = [None, None, None, None] + rotation_mode = 'rotation_axis_angle' + property_bits |= 1 + fc_dict['rotation_axis_angle'][fcurve.array_index] = fcurve + elif fcurve.data_path == 'pose.bones["'+bone+'"].location': + if 'location' not in fc_dict: + fc_dict['location'] = [None, None, None] + property_bits |= 2 + fc_dict['location'][fcurve.array_index] = fcurve + bone_list.append((bone, rotation_mode, fc_dict, property_bits)) + + # Write out frame indices + sorted_frames = sorted(frame_set) + writebuf(struct.pack('I', len(sorted_frames))) + for frame in sorted_frames: + writebuf(struct.pack('i', frame)) + + # Interleave / interpolate keyframe data + writebuf(struct.pack('I', len(bone_list))) + for bone in bone_list: + + bone_name = bone[0] + rotation_mode = bone[1] + fc_dict = bone[2] + property_bits = bone[3] + + writebuf(struct.pack('I', len(bone_name))) + writebuf(bone_name.encode()) + + writebuf(struct.pack('I', property_bits)) + + writebuf(struct.pack('I', len(sorted_frames))) + for frame in sorted_frames: + + # Rotation curves + if rotation_mode == 'rotation_quaternion': + writevec = [0.0]*4 + for comp in range(4): + if fc_dict['rotation_quaternion'][comp]: + writevec[comp] = fc_dict['rotation_quaternion'][comp].evaluate(frame) + writebuf(struct.pack('ffff', writevec[0], writevec[1], writevec[2], writevec[3])) + + elif rotation_mode == 'rotation_euler': + euler = [0.0, 0.0, 0.0] + for comp in range(3): + if fc_dict['rotation_euler'][comp]: + euler[comp] = fc_dict['rotation_euler'][comp].evaluate(frame) + euler_o = Euler(euler, 'XYZ') + quat = euler_o.to_quaternion() + writebuf(struct.pack('ffff', quat[0], quat[1], quat[2], quat[3])) + + elif rotation_mode == 'rotation_axis_angle': + axis_angle = [0.0, 0.0, 0.0, 0.0] + for comp in range(4): + if fc_dict['rotation_axis_angle'][comp]: + axis_angle[comp] = fc_dict['rotation_axis_angle'][comp].evaluate(frame) + quat = Quaternion(axis_angle[1:4], axis_angle[0]) + writebuf(struct.pack('ffff', quat[0], quat[1], quat[2], quat[3])) + + # Location curves + if 'location' in fc_dict: + writevec = [0.0]*3 + for comp in range(3): + if fc_dict['location'][comp]: + writevec[comp] = fc_dict['location'][comp].evaluate(frame) + writebuf(struct.pack('fff', writevec[0], writevec[1], writevec[2])) + + # Scale curves + if 'scale' in fc_dict: + writevec = [1.0]*3 + for comp in range(3): + if fc_dict['scale'][comp]: + writevec[comp] = fc_dict['scale'][comp].evaluate(frame) + writebuf(struct.pack('fff', writevec[0], writevec[1], writevec[2])) -# RARM Generator -def package_rarm(arm_obj, res_db, heclpak, arg_path, arg_package): +def write_action_aabb(writebuf, arm_obj, mesh_obj): + scene = bpy.context.scene - rarm_db_id, rarm_hash = res_db.register_resource(arg_path, arm_obj.name, arg_package) - if not rarm_hash: + # Frame 1 + scene.frame_set(1) - 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) + # 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]) - return rarm_db_id, rarm_hash + # Accumulate AABB for each frame + for frame_idx in range(2, scene.frame_end + 1): + scene.frame_set(frame_idx) -# RANI Generator -def package_rani(action_obj, res_db, heclpak, arg_path, arg_package): + 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]) - 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) + 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] + writebuf(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])) # Cook -def cook(writebuffunc, platform, endianchar): - print('COOKING SACT') +def cook(writebuf): + sact_data = bpy.context.scene.hecl_sact_data + + # Output armatures + writebuf(struct.pack('I', len(bpy.data.armatures))) + for arm in bpy.data.armatures: + writebuf(struct.pack('I', len(arm.name))) + writebuf(arm.name.encode()) + + writebuf(struct.pack('I', len(arm.bones))) + for bone in arm.bones: + writebuf(struct.pack('I', len(bone.name))) + writebuf(bone.name.encode()) + + writebuf(struct.pack('fff', bone.head_local[0], bone.head_local[1], bone.head_local[2])) + + if bone.parent: + writebuf(struct.pack('i', arm.bones.find(bone.parent.name))) + else: + writebuf(struct.pack('i', -1)) + + writebuf(struct.pack('I', len(bone.children))) + for child in bone.children: + writebuf(struct.pack('i', arm.bones.find(child.name))) + + # Output subtypes + writebuf(struct.pack('I', len(sact_data.subtypes))) + for subtype in sact_data.subtypes: + writebuf(struct.pack('I', len(subtype.name))) + writebuf(subtype.name.encode()) + + mesh = None + if subtype.linked_mesh in bpy.data.objects: + mesh = bpy.data.objects[subtype.linked_mesh] + + if mesh and mesh.library: + mesh_path = bpy.path.abspath(mesh.library.filepath) + writebuf(struct.pack('I', len(mesh_path))) + writebuf(mesh_path.encode()) + else: + writebuf(struct.pack('I', 0)) + + arm = None + if subtype.linked_armature in bpy.data.objects: + arm = bpy.data.objects[subtype.linked_armature] + + arm_idx = -1 + if arm: + arm_idx = bpy.data.armatures.find(arm.name) + writebuf(struct.pack('i', arm_idx)) + + writebuf(struct.pack('I', len(subtype.overlays))) + for overlay in subtype.overlays: + writebuf(struct.pack('I', len(overlay.name))) + writebuf(overlay.name.encode()) + + mesh = None + if overlay.linked_mesh in bpy.data.objects: + mesh = bpy.data.objects[overlay.linked_mesh] + + if mesh and mesh.library: + mesh_path = bpy.path.abspath(mesh.library.filepath) + writebuf(struct.pack('I', len(mesh_path))) + writebuf(mesh_path.encode()) + else: + writebuf(struct.pack('I', 0)) + + + # Output actions + writebuf(struct.pack('I', len(sact_data.actions))) + for action_idx in range(len(sact_data.actions)): + sact_data.active_action = action_idx + action = sact_data.actions[action_idx] + writebuf(struct.pack('I', len(action.name))) + writebuf(action.name.encode()) + + bact = None + if action.name in bpy.data.actions: + bact = bpy.data.actions[action.name] + if not bact: + raise RuntimeError('action %s not found' % action.name) + + writebuf(struct.pack('f', 1.0 / bact.hecl_fps)) + writebuf(struct.pack('b', int(bact.hecl_additive))) + + write_action_channels(writebuf, bact) + writebuf(struct.pack('I', len(sact_data.subtypes))) + for subtype_idx in range(len(sact_data.subtypes)): + subtype = sact_data.subtypes[subtype_idx] + sact_data.active_subtype = subtype_idx + bpy.ops.scene.sactaction_load() + if subtype.linked_armature not in bpy.data.objects: + raise RuntimeError('armature %s not found' % subtype.linked_armature) + arm = bpy.data.objects[subtype.linked_armature] + if subtype.linked_mesh not in bpy.data.objects: + raise RuntimeError('mesh %s not found' % subtype.linked_mesh) + mesh = bpy.data.objects[subtype.linked_mesh] + write_action_aabb(writebuf, arm, mesh) + # Panel draw def draw(layout, context): SACTSubtype.draw(layout.box(), context) SACTAction.draw(layout.box(), context) - #SACTEvent.draw(layout.box(), context) # Time-remap option update @@ -247,7 +320,6 @@ def time_remap_update(self, 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) @@ -261,4 +333,3 @@ def unregister(): bpy.utils.unregister_class(SACTData) SACTSubtype.unregister() SACTAction.unregister() - #SACTEvent.unregister() diff --git a/hecl/blender/hecl_blendershell.py b/hecl/blender/hecl_blendershell.py index e94453034..6817b58fe 100644 --- a/hecl/blender/hecl_blendershell.py +++ b/hecl/blender/hecl_blendershell.py @@ -184,6 +184,10 @@ def dataout_loop(): bpy.data.objects.remove(join_obj) bpy.data.meshes.remove(join_mesh) + elif cmdargs[0] == 'ACTORCOMPILE': + writepipeline(b'OK') + hecl.sact.cook(writepipebuf) + # Command loop while True: