├── .gitattributes ├── .gitignore ├── .gitmodules ├── BlenderAssist ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── extract.cpp ├── extract.h ├── helper.cpp ├── helper.h ├── main.cpp ├── main.h ├── pack_anim.cpp ├── pack_anim.h ├── pack_skel.cpp └── pack_skel.h ├── BlenderAssistAddon ├── __init__.py ├── addon.py ├── anim.py ├── bin │ ├── blenderassist.exe │ └── tofbx.exe ├── data.py ├── exclude_bones.py ├── helper.py └── skl.py └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0ceal0t/BlenderAssist/5e30631f049911d45b966ac96cc29cc2e4a2e0ea/.gitignore -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0ceal0t/BlenderAssist/5e30631f049911d45b966ac96cc29cc2e4a2e0ea/.gitmodules -------------------------------------------------------------------------------- /BlenderAssist/.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | .idea/ 35 | cmake-*/ 36 | build/ 37 | __pycache__/ -------------------------------------------------------------------------------- /BlenderAssist/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | project(blenderassist) 3 | 4 | message(STATUS "havok sdk root local=${HAVOK_SDK_ROOT}") 5 | message(STATUS "havok sdk root env=$ENV{HAVOK_SDK_ROOT}") 6 | 7 | set( 8 | PROJ_LIBRARIES 9 | hkCompat.lib 10 | hkBase.lib 11 | hkSerialize.lib 12 | hkSceneData.lib 13 | hkVisualize.lib 14 | hkInternal.lib 15 | hkImageUtilities.lib 16 | hkaAnimation.lib 17 | hkaInternal.lib 18 | hkaPhysics2012Bridge.lib 19 | hkcdCollide.lib 20 | hkcdInternal.lib 21 | hkGeometryUtilities.lib 22 | ) 23 | 24 | include_directories( 25 | "$ENV{HAVOK_SDK_ROOT}/Source/" 26 | ) 27 | 28 | if (CMAKE_BUILD_TYPE STREQUAL "Debug") 29 | link_directories("$ENV{HAVOK_SDK_ROOT}/Lib/win32_vs2012_win7_noSimd/debug/") 30 | else() 31 | link_directories("$ENV{HAVOK_SDK_ROOT}/Lib/win32_vs2012_win7_noSimd/release/") 32 | endif() 33 | 34 | add_definitions(-DUNICODE -D_UNICODE) 35 | add_executable(blenderassist main.cpp pack_anim.cpp pack_anim.h helper.cpp helper.h extract.cpp extract.h pack_skel.cpp pack_skel.h) 36 | target_link_libraries(blenderassist ${PROJ_LIBRARIES}) 37 | 38 | set(CMAKE_CXX_STANDARD 14) -------------------------------------------------------------------------------- /BlenderAssist/LICENSE: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. -------------------------------------------------------------------------------- /BlenderAssist/extract.cpp: -------------------------------------------------------------------------------- 1 | #include "extract.h" 2 | #include "helper.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | int extract(const hkStringBuf skl_in_sklb, const hkStringBuf anim_in_pap, const hkStringBuf sklb_out, const hkStringBuf pap_out) { 10 | SklbFile sklbFile; 11 | sklbFile.read(skl_in_sklb); 12 | sklbFile.writeHavok(sklb_out); 13 | 14 | if(anim_in_pap.getLength() > 0 && pap_out.getLength() > 0) { // Only extract skeleton 15 | PapFile papFile; 16 | papFile.read(anim_in_pap); 17 | papFile.writeHavok(pap_out); 18 | } 19 | 20 | return 0; 21 | } -------------------------------------------------------------------------------- /BlenderAssist/extract.h: -------------------------------------------------------------------------------- 1 | #ifndef BLENDERASSIST_EXTRACT_H 2 | #define BLENDERASSIST_EXTRACT_H 3 | 4 | #include 5 | #include 6 | 7 | int extract(const hkStringBuf skl_in_sklb, const hkStringBuf anim_in_pap, const hkStringBuf sklb_out, const hkStringBuf pap_out); 8 | 9 | #endif //BLENDERASSIST_EXTRACT_H 10 | -------------------------------------------------------------------------------- /BlenderAssist/helper.cpp: -------------------------------------------------------------------------------- 1 | #include "helper.h" 2 | 3 | void read(hkIstream& stream, hkQsTransform& transform) { 4 | hkVector4 translation; 5 | hkQuaternion rotation; 6 | hkVector4 scale; 7 | 8 | stream.read(&translation, sizeof(hkVector4)); 9 | stream.read(&rotation.m_vec, sizeof(hkVector4)); 10 | stream.read(&scale, sizeof(hkVector4)); 11 | 12 | transform.setTranslation(translation); 13 | transform.setRotation(rotation); 14 | transform.setScale(scale); 15 | } 16 | 17 | int getBoneIdx(std::string trackName, hkRefPtr skl) { 18 | for(int boneIdx = 0; boneIdx < skl->m_bones.getSize(); boneIdx++) { 19 | auto boneName = skl->m_bones[boneIdx].m_name.cString(); 20 | if (trackName == boneName) { 21 | return boneIdx; 22 | } 23 | } 24 | return -1; 25 | } 26 | 27 | bool getBound(int boneIdx, hkRefPtr binding) { 28 | for(int i = 0; i < binding->m_transformTrackToBoneIndices.getSize(); i++) { 29 | if(binding->m_transformTrackToBoneIndices[i] == boneIdx) { 30 | return true; 31 | } 32 | } 33 | return false; 34 | } 35 | 36 | void writeInt(vector& buffer, int position, int value) { 37 | auto bytes = static_cast(static_cast(&value)); 38 | for(int i = 0; i < sizeof(int); i++) { 39 | buffer[position + i] = bytes[i]; 40 | } 41 | } 42 | 43 | std::streampos fileSize( const char* filePath ){ 44 | std::streampos fsize = 0; 45 | std::ifstream file( filePath, std::ios::binary ); 46 | 47 | fsize = file.tellg(); 48 | file.seekg( 0, std::ios::end ); 49 | fsize = file.tellg() - fsize; 50 | file.close(); 51 | 52 | return fsize; 53 | } 54 | 55 | void readIntoVector(vector& vec, ifstream &stream, int count) { 56 | vec.clear(); 57 | vec.reserve(count); 58 | for(int i = 0; i < count; i++) { 59 | char c; 60 | stream.read((char*)&c, 1); 61 | vec.push_back(c); 62 | } 63 | } 64 | 65 | void writeFromVector(vector& vec, ofstream& stream, int count) { 66 | for(int i = 0; i < count; i++) { 67 | stream.write(&vec[i], 1); 68 | } 69 | } 70 | 71 | // ============================ 72 | 73 | void SklbFile::read(hkStringBuf filename) { 74 | int fullSize = fileSize(filename.cString()); 75 | 76 | ifstream stream(filename, ios::binary); 77 | if (stream.is_open()) { 78 | int headerVersion; 79 | int havokOffset; 80 | 81 | stream.clear(); 82 | stream.seekg(4, ios::beg); 83 | stream.read((char*)&headerVersion, 4); 84 | 85 | headerType2 = (headerVersion == 0x31333030); 86 | 87 | if (!headerType2) { // Header 1 88 | short int offset2; 89 | 90 | // Only 2 bytes for this header format 91 | stream.clear(); 92 | stream.seekg(10, ios::beg); 93 | stream.read((char*)&offset2, 2); 94 | 95 | havokOffset = offset2; 96 | } 97 | else { // Header 2 98 | stream.clear(); 99 | stream.seekg(12, ios::beg); 100 | stream.read((char*)&havokOffset, 4); 101 | } 102 | 103 | preHavokSize = havokOffset; 104 | havokSize = fullSize - preHavokSize; 105 | 106 | stream.clear(); 107 | stream.seekg (0, ios::beg); 108 | readIntoVector(preHavok, stream, preHavokSize); 109 | 110 | stream.clear(); 111 | stream.seekg(havokOffset, ios::beg); 112 | readIntoVector(havok, stream, havokSize); 113 | 114 | stream.close(); 115 | } 116 | else { 117 | printf("Could not open stream %s\n", filename.cString()); 118 | } 119 | } 120 | 121 | void SklbFile::writeHavok(hkStringBuf filename) { 122 | ofstream stream(filename, ios::binary); 123 | if (stream.is_open()) { 124 | writeFromVector(havok, stream, havokSize); 125 | 126 | stream.close(); 127 | } 128 | else { 129 | printf("Could not open stream %s\n", filename.cString()); 130 | } 131 | } 132 | 133 | void SklbFile::writeSklb(hkStringBuf filename) { 134 | ofstream stream(filename, ios::binary); 135 | if (stream.is_open()) { 136 | writeFromVector(preHavok, stream, preHavokSize); 137 | writeFromVector(havok, stream, havokSize); 138 | 139 | stream.close(); 140 | } 141 | else { 142 | printf("Could not open stream %s\n", filename.cString()); 143 | } 144 | } 145 | 146 | void SklbFile::replaceHavok(hkStringBuf filename) { 147 | havokSize = fileSize(filename); 148 | ifstream stream(filename, ios::binary); 149 | if (stream.is_open()) { 150 | readIntoVector(havok, stream, havokSize); 151 | 152 | stream.close(); 153 | } 154 | else { 155 | printf("Could not open stream %s\n", filename.cString()); 156 | } 157 | } 158 | 159 | // ============================ 160 | 161 | void PapFile::read(hkStringBuf filename) { 162 | int fullSize = fileSize(filename.cString()); 163 | 164 | ifstream stream(filename, ios::binary); 165 | if (stream.is_open()) { 166 | int havokOffset; 167 | int timelineOffset; 168 | 169 | stream.clear(); 170 | stream.seekg(18, ios::beg); 171 | stream.read((char*)&havokOffset, 4); 172 | 173 | stream.clear(); 174 | stream.seekg(22, ios::beg); 175 | stream.read((char*)&timelineOffset, 4); 176 | 177 | preHavokSize = havokOffset; 178 | havokSize = timelineOffset - havokOffset; 179 | postHavokSize = fullSize - timelineOffset; 180 | 181 | stream.clear(); 182 | stream.seekg (0, ios::beg); 183 | readIntoVector(preHavok, stream, preHavokSize); 184 | 185 | stream.clear(); 186 | stream.seekg(havokOffset, ios::beg); 187 | readIntoVector(havok, stream, havokSize); 188 | 189 | stream.clear(); 190 | stream.seekg(timelineOffset, ios::beg); 191 | readIntoVector(postHavok, stream, postHavokSize); 192 | 193 | stream.close(); 194 | } 195 | else { 196 | printf("Could not open stream %s\n", filename.cString()); 197 | } 198 | } 199 | 200 | void PapFile::writeHavok(hkStringBuf filename) { 201 | ofstream stream(filename, ios::binary); 202 | if (stream.is_open()) { 203 | writeFromVector(havok, stream, havokSize); 204 | 205 | stream.close(); 206 | } 207 | else { 208 | printf("Could not open stream %s\n", filename.cString()); 209 | } 210 | } 211 | 212 | void PapFile::writePap(hkStringBuf filename) { 213 | ofstream stream(filename, ios::binary); 214 | if (stream.is_open()) { 215 | writeFromVector(preHavok, stream, preHavokSize); 216 | writeFromVector(havok, stream, havokSize); 217 | writeFromVector(postHavok, stream, postHavokSize); 218 | 219 | stream.close(); 220 | } 221 | else { 222 | printf("Could not open stream %s\n", filename.cString()); 223 | } 224 | } 225 | 226 | void PapFile::replaceHavok(hkStringBuf filename) { 227 | havokSize = fileSize(filename); 228 | ifstream stream(filename, ios::binary); 229 | if (stream.is_open()) { 230 | readIntoVector(havok, stream, havokSize); 231 | auto newTimelineOffset = 26 + 40 + havokSize; 232 | writeInt(preHavok, 22, newTimelineOffset); 233 | 234 | stream.close(); 235 | } 236 | else { 237 | printf("Could not open stream %s\n", filename.cString()); 238 | } 239 | } 240 | 241 | // ========================== 242 | 243 | void deleteFile(hkStringBuf path) { 244 | std::remove(path); 245 | } 246 | 247 | std::string dirnameOf(const std::string& fname) { 248 | size_t pos = fname.find_last_of("\\/"); 249 | return (std::string::npos == pos) 250 | ? "" 251 | : fname.substr(0, pos); 252 | } 253 | 254 | hkStringBuf concat(std::string baseDirStr, std::string fileName) { 255 | hkStringBuf baseDir(baseDirStr.c_str()); 256 | if (baseDir.getLength() == 0) { 257 | baseDir.append(fileName.c_str()); 258 | return baseDir; 259 | } 260 | 261 | baseDir.append("\\"); 262 | baseDir.append(fileName.c_str()); 263 | return baseDir; 264 | } -------------------------------------------------------------------------------- /BlenderAssist/helper.h: -------------------------------------------------------------------------------- 1 | #ifndef BLENDERASSIST_HELPER_H 2 | #define BLENDERASSIST_HELPER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | using namespace std; 19 | 20 | void read(hkIstream& stream, hkQsTransform& transform); 21 | int getBoneIdx(std::string trackName, hkRefPtr skl); 22 | bool getBound(int boneIdx, hkRefPtr binding); 23 | 24 | class SklbFile{ 25 | private: 26 | bool headerType2; 27 | vector preHavok; 28 | vector havok; 29 | 30 | int preHavokSize; 31 | int havokSize; 32 | public: 33 | void read(hkStringBuf filename); 34 | void writeHavok(hkStringBuf filename); 35 | void writeSklb(hkStringBuf filename); 36 | void replaceHavok(hkStringBuf filename); 37 | }; 38 | 39 | class PapFile{ 40 | private: 41 | vector preHavok; 42 | vector havok; 43 | vector postHavok; 44 | 45 | int preHavokSize; 46 | int havokSize; 47 | int postHavokSize; 48 | public: 49 | void read(hkStringBuf filename); 50 | void writeHavok(hkStringBuf filename); 51 | void writePap(hkStringBuf filename); 52 | void replaceHavok(hkStringBuf filename); 53 | }; 54 | 55 | void deleteFile(hkStringBuf path); 56 | std::string dirnameOf(const std::string& fname); 57 | hkStringBuf concat(std::string baseDirStr, std::string fileName); 58 | 59 | #endif //BLENDERASSIST_HELPER_H -------------------------------------------------------------------------------- /BlenderAssist/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include "Common/Base/System/Init/PlatformInit.cxx" 16 | #include "pack_anim.h" 17 | #include "pack_skel.h" 18 | #include "extract.h" 19 | 20 | static void HK_CALL errorReport(const char* msg, void* userContext) { 21 | using namespace std; 22 | printf("%s", msg); 23 | } 24 | 25 | void init() { 26 | PlatformInit(); 27 | hkMemoryRouter* memoryRouter = hkMemoryInitUtil::initDefault(hkMallocAllocator::m_defaultMallocAllocator, hkMemorySystem::FrameInfo(1024 * 1024)); 28 | hkBaseSystem::init(memoryRouter, errorReport); 29 | PlatformFileSystemInit(); 30 | hkSerializeDeprecatedInit::initDeprecated(); 31 | } 32 | 33 | inline std::string convert_from_wstring(const std::wstring &wstr) { 34 | std::wstring_convert, wchar_t> conv; 35 | return conv.to_bytes(wstr); 36 | } 37 | 38 | int main(int argc, const char** argv) { 39 | int nargc = 0; 40 | wchar_t** nargv; 41 | 42 | auto command_line = GetCommandLineW(); 43 | if (command_line == nullptr) { 44 | printf("Fatal error."); 45 | return 1; 46 | } 47 | nargv = CommandLineToArgvW(command_line, &nargc); 48 | if (nargv == nullptr) { 49 | printf("Fatal error."); 50 | return 1; 51 | } 52 | 53 | // =========================== 54 | 55 | init(); 56 | 57 | std::string operation(convert_from_wstring(nargv[1]).c_str()); 58 | 59 | // blenderassist.exe pack anim_idx .bin sklb pap out.pap 60 | if (operation.compare("pack_anim") == 0) { 61 | hkStringBuf anim_idx; // animation index 62 | hkStringBuf bin_in; // .bin from blender 63 | hkStringBuf skl_in; // original skeleton 64 | hkStringBuf anim_in; // original animation 65 | hkStringBuf anim_out; // pap animation out 66 | hkStringBuf check_if_bound; 67 | hkStringBuf compress_anim; 68 | 69 | anim_idx = convert_from_wstring(nargv[2]).c_str(); 70 | bin_in = convert_from_wstring(nargv[3]).c_str(); 71 | skl_in = convert_from_wstring(nargv[4]).c_str(); 72 | anim_in = convert_from_wstring(nargv[5]).c_str(); 73 | anim_out = convert_from_wstring(nargv[6]).c_str(); 74 | check_if_bound = convert_from_wstring(nargv[7]).c_str(); 75 | compress_anim = convert_from_wstring(nargv[8]).c_str(); 76 | 77 | return pack_anim(anim_idx, bin_in, skl_in, anim_in, anim_out, check_if_bound, compress_anim); 78 | } 79 | else if (operation.compare("pack_skel") == 0) { 80 | hkStringBuf bin_in; 81 | hkStringBuf skl_in; 82 | hkStringBuf skl_out; 83 | 84 | bin_in = convert_from_wstring(nargv[2]).c_str(); 85 | skl_in = convert_from_wstring(nargv[3]).c_str(); 86 | skl_out = convert_from_wstring(nargv[4]).c_str(); 87 | 88 | return pack_skel(bin_in, skl_in, skl_out); 89 | } 90 | else if (operation.compare("extract") == 0) { 91 | hkStringBuf skl_in_sklb; 92 | hkStringBuf anim_in_pap; 93 | hkStringBuf sklb_out; 94 | hkStringBuf pap_out; 95 | 96 | skl_in_sklb = convert_from_wstring(nargv[2]).c_str(); 97 | anim_in_pap = convert_from_wstring(nargv[3]).c_str(); 98 | sklb_out = convert_from_wstring(nargv[4]).c_str(); 99 | pap_out = convert_from_wstring(nargv[5]).c_str(); 100 | 101 | return extract(skl_in_sklb, anim_in_pap, sklb_out, pap_out); 102 | } 103 | 104 | printf("No operation given\n"); 105 | return 0; 106 | } 107 | 108 | #include 109 | 110 | #undef HK_FEATURE_PRODUCT_AI 111 | //#undef HK_FEATURE_PRODUCT_ANIMATION 112 | #undef HK_FEATURE_PRODUCT_CLOTH 113 | #undef HK_FEATURE_PRODUCT_DESTRUCTION_2012 114 | #undef HK_FEATURE_PRODUCT_DESTRUCTION 115 | #undef HK_FEATURE_PRODUCT_BEHAVIOR 116 | #undef HK_FEATURE_PRODUCT_PHYSICS_2012 117 | #undef HK_FEATURE_PRODUCT_SIMULATION 118 | #undef HK_FEATURE_PRODUCT_PHYSICS 119 | 120 | #define HK_SERIALIZE_MIN_COMPATIBLE_VERSION 201130r1 121 | 122 | #include -------------------------------------------------------------------------------- /BlenderAssist/main.h: -------------------------------------------------------------------------------- 1 | int main(int nargc, const char** nargv); -------------------------------------------------------------------------------- /BlenderAssist/pack_anim.cpp: -------------------------------------------------------------------------------- 1 | #include "pack_anim.h" 2 | #include "helper.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | void read(hkRefPtr anim, hkRefPtr binding, hkRefPtr skl, hkIstream stream, bool checkIfOriginalBound) { 14 | int numOriginalFrames; 15 | int numAllTransforms; 16 | hkReal duration; 17 | 18 | stream.read(&numOriginalFrames, sizeof(int)); 19 | numOriginalFrames += 1; // to account for t=0 20 | stream.read(&numAllTransforms, sizeof(int)); 21 | stream.read(&duration, sizeof(hkReal)); 22 | 23 | // Get all tracks 24 | 25 | std::vector allTrackNames; 26 | 27 | for(int trackIdx = 0; trackIdx < numAllTransforms; trackIdx++) { 28 | char trackNameBuffer[240]; 29 | stream.getline(trackNameBuffer, 240, '\0'); 30 | std::string trackName(trackNameBuffer); 31 | allTrackNames.push_back(trackName); 32 | } 33 | 34 | // Which ones are actually in the skeleton 35 | 36 | std::vector validTrackIndex; 37 | for(int trackIdx = 0; trackIdx < numAllTransforms; trackIdx++) { 38 | validTrackIndex.push_back(-1); // all not valid for now 39 | } 40 | 41 | printf("Check if bound %d\n", checkIfOriginalBound); 42 | 43 | int numTransforms = 0; // number of valid transforms 44 | 45 | for(int boneIdx = 0; boneIdx < skl->m_bones.getSize(); boneIdx++) { 46 | if (checkIfOriginalBound && !getBound(boneIdx, binding)) { continue; } // this bone was never bound 47 | auto boneName = skl->m_bones[boneIdx].m_name; 48 | 49 | for(int trackIdx = 0; trackIdx < numAllTransforms; trackIdx++) { 50 | auto trackName = allTrackNames[trackIdx]; 51 | 52 | if (trackName.compare(boneName.cString()) == 0) { 53 | validTrackIndex[trackIdx] = numTransforms; 54 | numTransforms++; 55 | break; 56 | } 57 | } 58 | } 59 | 60 | // Set up tracks 61 | 62 | printf("Number of frames %d\n", numOriginalFrames); 63 | printf("Number of tracks %d (%d)\n", numTransforms, numAllTransforms); 64 | 65 | anim->m_duration = duration; 66 | anim->m_annotationTracks.clear(); 67 | anim->m_annotationTracks.setSize(0); //anim->m_annotationTracks.setSize(numTransforms); 68 | anim->m_numberOfFloatTracks = 0; 69 | anim->m_numberOfTransformTracks = numTransforms; 70 | anim->m_floats.setSize(0); 71 | anim->m_transforms.setSize(numTransforms * numOriginalFrames, hkQsTransform::getIdentity()); 72 | 73 | // Set up bindings 74 | 75 | binding->m_transformTrackToBoneIndices.clear(); 76 | binding->m_transformTrackToBoneIndices.setSize(numTransforms); 77 | 78 | for(int trackIdx = 0; trackIdx < numAllTransforms; trackIdx++) { 79 | auto idx = validTrackIndex[trackIdx]; 80 | if (idx == -1) continue; 81 | auto trackName = allTrackNames[trackIdx]; 82 | auto boneIdx = getBoneIdx(trackName, skl); 83 | binding->m_transformTrackToBoneIndices[idx] = boneIdx; 84 | } 85 | 86 | // Read frames 87 | 88 | for(int frameIdx = 0; frameIdx < numOriginalFrames; frameIdx++) { 89 | int offTransforms = frameIdx * numTransforms; 90 | 91 | for(int trackIdx = 0; trackIdx < numAllTransforms; trackIdx++) { 92 | auto idx = validTrackIndex[trackIdx]; 93 | 94 | if(idx == -1) { 95 | hkQsTransformf dummyT; 96 | read(stream, dummyT); 97 | } 98 | else { 99 | read(stream, anim->m_transforms[idx + offTransforms]); 100 | } 101 | } 102 | } 103 | } 104 | 105 | int packHavok(const hkStringBuf anim_idx_str, const hkStringBuf bin_in, const hkStringBuf skl_in, const hkStringBuf anim_in, const hkStringBuf anim_out, const hkStringBuf check_if_bound_str, const hkStringBuf compress_anim_str) { 106 | int anim_idx = std::stoi(anim_idx_str.cString()); 107 | bool checkIfOriginalBound = std::stoi(check_if_bound_str.cString()) == 1; 108 | bool compress_anim = std::stoi(compress_anim_str.cString()) == 1; 109 | 110 | hkRootLevelContainer* skl_root_container; 111 | hkRootLevelContainer* anim_root_container; 112 | 113 | auto loader = new hkLoader(); 114 | 115 | hkIstream stream(bin_in); 116 | hkOstream out_stream(anim_out); 117 | 118 | skl_root_container = loader->load(skl_in); 119 | auto* skl_container = reinterpret_cast(skl_root_container->findObjectByType(hkaAnimationContainerClass.getName())); 120 | auto skl = skl_container->m_skeletons[0]; 121 | 122 | anim_root_container = loader->load(anim_in); 123 | auto* anim_container = reinterpret_cast(anim_root_container->findObjectByType(hkaAnimationContainerClass.getName())); 124 | 125 | auto anim_ptr = anim_container->m_animations[anim_idx]; 126 | auto binding_ptr = anim_container->m_bindings[anim_idx]; 127 | auto binding = hkRefPtr(binding_ptr); 128 | 129 | printf("Original number of tracks %d\n", binding->m_transformTrackToBoneIndices.getSize()); 130 | printf("Original number of frames %d\n", anim_ptr->getNumOriginalFrames()); 131 | printf("Original duration %f\n", anim_ptr->m_duration); 132 | 133 | // ======================== 134 | 135 | hkRefPtr storeAnim = new hkaInterleavedUncompressedAnimation(); 136 | 137 | read(storeAnim, binding, skl, stream, checkIfOriginalBound); 138 | 139 | printf("Compress animation %d\n", compress_anim); 140 | 141 | if (compress_anim) { 142 | hkaSplineCompressedAnimation::TrackCompressionParams tparams; 143 | hkaSplineCompressedAnimation::AnimationCompressionParams aparams; 144 | //tparams.m_rotationTolerance = 0.001f; 145 | //tparams.m_rotationQuantizationType = hkaSplineCompressedAnimation::TrackCompressionParams::THREECOMP40; 146 | auto final_anim = new hkaSplineCompressedAnimation( *storeAnim.val(), tparams, aparams ); 147 | binding->m_animation = final_anim; 148 | anim_container->m_animations[anim_idx] = final_anim; 149 | } 150 | else { 151 | binding->m_animation = storeAnim; 152 | anim_container->m_animations[anim_idx] = storeAnim; 153 | } 154 | 155 | // ======================== 156 | 157 | auto res = hkSerializeUtil::saveTagfile(anim_root_container, hkRootLevelContainer::staticClass(), out_stream.getStreamWriter(), nullptr, hkSerializeUtil::SAVE_DEFAULT); 158 | if (res.isSuccess()) { 159 | return 0; 160 | } else { 161 | std::cout << "\n\nAn error occurred while saving the HKX...\n"; 162 | return 1; 163 | } 164 | } 165 | 166 | int pack_anim(const hkStringBuf anim_idx_str, const hkStringBuf bin_in, const hkStringBuf skl_in_sklb, const hkStringBuf anim_in_pap, const hkStringBuf anim_out_pap, const hkStringBuf check_if_bound_str, const hkStringBuf compress_anim_str) { 167 | PapFile papFile; 168 | papFile.read(anim_in_pap); 169 | 170 | SklbFile sklbFile; 171 | sklbFile.read(skl_in_sklb); 172 | 173 | auto baseDir = dirnameOf(anim_out_pap.cString()); 174 | 175 | auto original_anim_temp = concat(baseDir, "original_anim_temp.hkx"); 176 | papFile.writeHavok(original_anim_temp); 177 | 178 | auto original_skl_temp = concat(baseDir, "original_skl_temp.hkx"); 179 | sklbFile.writeHavok(original_skl_temp); 180 | 181 | auto new_anim_temp = concat(baseDir, "new_anim_temp.hkx"); 182 | auto res = packHavok(anim_idx_str, bin_in, original_skl_temp, original_anim_temp, new_anim_temp, check_if_bound_str, compress_anim_str); 183 | 184 | papFile.replaceHavok(new_anim_temp); 185 | papFile.writePap(anim_out_pap); 186 | 187 | deleteFile(original_anim_temp); 188 | deleteFile(original_skl_temp); 189 | deleteFile(new_anim_temp); 190 | 191 | return res; 192 | } -------------------------------------------------------------------------------- /BlenderAssist/pack_anim.h: -------------------------------------------------------------------------------- 1 | #ifndef BLENDERASSIST_PACK_ANIM_H 2 | #define BLENDERASSIST_PACK_ANIM_H 3 | 4 | #include 5 | #include 6 | 7 | int pack_anim(const hkStringBuf anim_idx_str, const hkStringBuf bin_in, const hkStringBuf skl_in, const hkStringBuf anim_in, const hkStringBuf anim_out, const hkStringBuf check_if_bound_str, const hkStringBuf compress_anim_str); 8 | 9 | #endif //BLENDERASSIST_PACK_ANIM_H 10 | -------------------------------------------------------------------------------- /BlenderAssist/pack_skel.cpp: -------------------------------------------------------------------------------- 1 | #include "pack_skel.h" 2 | #include "helper.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | void read(hkRefPtr skl, hkIstream stream) { 12 | int numBones; 13 | stream.read(&numBones, sizeof(int)); 14 | 15 | for(int i = 0; i < numBones; i++) { 16 | char boneNameBuffer[240]; 17 | stream.getline(boneNameBuffer, 240, '\0'); 18 | std::string boneName(boneNameBuffer); 19 | 20 | char boneParentBuffer[240]; 21 | stream.getline(boneParentBuffer, 240, '\0'); 22 | std::string boneParent(boneParentBuffer); 23 | 24 | auto boneIdx = getBoneIdx(boneName, skl); 25 | auto newBone = boneIdx == -1; 26 | 27 | if (newBone) { 28 | printf("New bone: %s\n", boneName.c_str()); 29 | 30 | skl->m_bones.expandOne(); 31 | skl->m_parentIndices.expandOne(); 32 | skl->m_referencePose.expandOne(); 33 | 34 | boneIdx = skl->m_bones.getSize() - 1; 35 | 36 | hkStringBuf nameBuf(boneNameBuffer, boneName.length()); 37 | skl->m_bones[boneIdx].m_name = nameBuf; 38 | skl->m_bones[boneIdx].m_lockTranslation = false; 39 | } 40 | 41 | int boneParentIdx = -1; 42 | if (boneParent.compare("None") != 0) { 43 | boneParentIdx = getBoneIdx(boneParent, skl); 44 | } 45 | 46 | if(!newBone) { 47 | printf("Idx: %d Old parent %d New parent %d / %s\n", boneIdx, skl->m_parentIndices[boneIdx], boneParentIdx, boneName.c_str()); 48 | } 49 | 50 | skl->m_parentIndices[boneIdx] = boneParentIdx; 51 | 52 | read(stream, skl->m_referencePose[boneIdx]); 53 | } 54 | } 55 | 56 | int packHavok(const hkStringBuf bin_in, const hkStringBuf skl_in, const hkStringBuf skel_out) { 57 | hkRootLevelContainer* skl_root_container; 58 | 59 | auto loader = new hkLoader(); 60 | 61 | hkIstream stream(bin_in); 62 | hkOstream out_stream(skel_out); 63 | 64 | skl_root_container = loader->load(skl_in); 65 | auto* skl_container = reinterpret_cast(skl_root_container->findObjectByType(hkaAnimationContainerClass.getName())); 66 | auto skl = skl_container->m_skeletons[0]; 67 | 68 | read(skl, stream); 69 | 70 | auto res = hkSerializeUtil::saveTagfile(skl_root_container, hkRootLevelContainer::staticClass(), out_stream.getStreamWriter(), nullptr, hkSerializeUtil::SAVE_DEFAULT); 71 | if (res.isSuccess()) { 72 | return 0; 73 | } else { 74 | std::cout << "\n\nAn error occurred while saving the HKX...\n"; 75 | return 1; 76 | } 77 | } 78 | 79 | int pack_skel(const hkStringBuf bin_in, const hkStringBuf skl_in_sklb, const hkStringBuf skl_out_sklb) { 80 | SklbFile sklbFile; 81 | sklbFile.read(skl_in_sklb); 82 | 83 | auto baseDir = dirnameOf(skl_out_sklb.cString()); 84 | 85 | auto original_skl_temp = concat(baseDir, "original_skl_temp.hkx"); 86 | sklbFile.writeHavok(original_skl_temp); 87 | 88 | auto new_skl_temp = concat(baseDir, "new_skl_temp.hkx"); 89 | auto res = packHavok(bin_in, original_skl_temp, new_skl_temp); 90 | 91 | sklbFile.replaceHavok(new_skl_temp); 92 | sklbFile.writeSklb(skl_out_sklb); 93 | 94 | deleteFile(original_skl_temp); 95 | deleteFile(new_skl_temp); 96 | 97 | return res; 98 | } -------------------------------------------------------------------------------- /BlenderAssist/pack_skel.h: -------------------------------------------------------------------------------- 1 | #ifndef BLENDERASSIST_PACK_SKEL_H 2 | #define BLENDERASSIST_PACK_SKEL_H 3 | 4 | #include 5 | #include 6 | 7 | int pack_skel(const hkStringBuf bin_in, const hkStringBuf skl_in_sklb, const hkStringBuf skl_out_sklb); 8 | 9 | #endif //BLENDERASSIST_PACK_SKEL_H 10 | -------------------------------------------------------------------------------- /BlenderAssistAddon/__init__.py: -------------------------------------------------------------------------------- 1 | bl_info = { 2 | "name" : "BlenderAssist", 3 | "author" : "ocealot", 4 | "description" : "Export custom animations for FFXIV", 5 | "version": (1, 3, 2), 6 | "blender" : (2, 80, 0), 7 | "location" : "3D View > Tools (Right Side) > BlenderAssist", 8 | "warning" : "", 9 | "category" : "Animation", 10 | "wiki_url": 'https://github.com/0ceal0t/BlenderAssist', 11 | "tracker_url": 'https://github.com/0ceal0t/BlenderAssist/issues', 12 | } 13 | 14 | from . import addon 15 | 16 | def register(): 17 | addon.register() 18 | 19 | def unregister(): 20 | addon.unregister() -------------------------------------------------------------------------------- /BlenderAssistAddon/addon.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.props import (StringProperty, PointerProperty, IntProperty, CollectionProperty, BoolProperty) 3 | from bpy.types import (PropertyGroup, Operator) 4 | 5 | import os 6 | 7 | from . import anim 8 | from . import skl 9 | from . import data 10 | from . import exclude_bones 11 | 12 | import subprocess 13 | 14 | # ================================ 15 | 16 | class BlenderAssistProperties(PropertyGroup): 17 | # Import 18 | input_pap_import: StringProperty( 19 | name = "", 20 | default = "/tmp/original_animation.pap", 21 | maxlen = 1024, 22 | subtype = "FILE_PATH" 23 | ) 24 | input_sklb_import: StringProperty( 25 | name = "", 26 | default = "/tmp/original_skeleton.sklb", 27 | maxlen = 1024, 28 | subtype = "FILE_PATH" 29 | ) 30 | 31 | # Export Anim 32 | output_path: StringProperty( 33 | name = "", 34 | default = "/tmp/out.pap", 35 | maxlen = 1024, 36 | subtype = "FILE_PATH" 37 | ) 38 | input_pap: StringProperty( 39 | name = "", 40 | default = "/tmp/original_animation.pap", 41 | maxlen = 1024, 42 | subtype = "FILE_PATH" 43 | ) 44 | input_sklb: StringProperty( 45 | name = "", 46 | default = "/tmp/original_skeleton.sklb", 47 | maxlen = 1024, 48 | subtype = "FILE_PATH" 49 | ) 50 | start_frame: IntProperty( 51 | name = "Start Frame", 52 | default = 1, 53 | min = 1 54 | ) 55 | end_frame: IntProperty( 56 | name = "End Frame", 57 | default = 10, 58 | min = 10 59 | ) 60 | anim_idx: IntProperty( 61 | name = "Original Animation Index", 62 | default = 0, 63 | min = 0 64 | ) 65 | check_original_bound: BoolProperty( 66 | name = "Only animate same bones as original animation", 67 | default = False 68 | ) 69 | compress_anim: BoolProperty( 70 | name = "Compress animation data", 71 | default = True 72 | ) 73 | 74 | exclude_bones: CollectionProperty(type=data.ExcludeBone) 75 | editing_exclude_bones: BoolProperty(default=False) 76 | active_exclude_bone: bpy.props.IntProperty() 77 | 78 | # Export Skl 79 | input_sklb_skel: StringProperty( 80 | name = "", 81 | default = "/tmp/original_skeleton.sklb", 82 | maxlen = 1024, 83 | subtype = "FILE_PATH" 84 | ) 85 | output_path_sklb: StringProperty( 86 | name = "", 87 | default = "/tmp/out.sklb", 88 | maxlen = 1024, 89 | subtype = "FILE_PATH" 90 | ) 91 | 92 | # ================================ 93 | 94 | # note: uses a custom tofbx which exports to binary 95 | # https://github.com/lmcintyre/fbx2havok/blob/master/Core/FBXCommon.cxx#L75 96 | class BlenderAssistImport(Operator): 97 | bl_idname = "b_assist_props.blender_assist_import" 98 | bl_label = "Blender Assist Operator" 99 | 100 | def execute(self, context): 101 | scene = context.scene 102 | state = scene.b_assist_props 103 | 104 | anim_in = state.input_pap_import 105 | skl_in = state.input_sklb_import 106 | 107 | dirname = os.path.dirname(os.path.abspath(__file__)) 108 | 109 | skip_pap = len(anim_in) == 0 110 | 111 | skl_hkx = dirname + '/tmp/import_skl.hkx' 112 | 113 | anim_hkx = dirname + '/tmp/import_anim.hkx' 114 | if skip_pap: 115 | anim_in = "\"\"" 116 | anim_hkx = "\"\"" 117 | 118 | fbx_out = dirname + '/tmp/import.fbx' 119 | 120 | command = dirname + '/bin/blenderassist.exe' 121 | print(command + " " + skl_in + " " + anim_in + " " + skl_hkx + " " + anim_hkx) 122 | subprocess.run([command, 'extract', skl_in, anim_in, skl_hkx, anim_hkx]) 123 | 124 | command = dirname + '/bin/tofbx.exe' 125 | print(command + " " + skl_hkx + " " + anim_hkx + " " + fbx_out) 126 | 127 | if skip_pap: 128 | subprocess.run([command, '-hk_skeleton', skl_hkx, '-fbx', fbx_out]) 129 | else: 130 | subprocess.run([command, '-hk_skeleton', skl_hkx, '-hk_anim', anim_hkx, '-fbx', fbx_out]) 131 | 132 | bpy.ops.import_scene.fbx( filepath = fbx_out ) 133 | 134 | return {'FINISHED'} 135 | 136 | class BlenderAssistPanelImport(bpy.types.Panel): 137 | bl_idname = "BA_PT_Import" 138 | bl_label = "Import" 139 | bl_category = "BlenderAssist" 140 | bl_space_type = "VIEW_3D" 141 | bl_region_type = "UI" 142 | 143 | def draw(self, context): 144 | layout = self.layout 145 | scene = context.scene 146 | b_assist_props = scene.b_assist_props 147 | 148 | layout.label(text="Animation PAP (leave blank to only import skeleton)") 149 | col = layout.column(align=True) 150 | col.prop(b_assist_props, "input_pap_import", text="") 151 | 152 | layout.label(text="Skeleton SKLB") 153 | col = layout.column(align=True) 154 | col.prop(b_assist_props, "input_sklb_import", text="") 155 | 156 | layout.operator(BlenderAssistImport.bl_idname, text="Import", icon="PLAY") 157 | 158 | # ================================== 159 | 160 | class BlenderAssistExportAnim(Operator): 161 | bl_idname = "b_assist_props.blender_assist_export_anim" 162 | bl_label = "Blender Assist Operator Animation" 163 | 164 | def execute(self, context): 165 | scene = context.scene 166 | state = scene.b_assist_props 167 | 168 | output_pap = state.output_path 169 | anim_in = state.input_pap 170 | skl_in = state.input_sklb 171 | anim_idx = str(state.anim_idx) 172 | 173 | check_original_bound = "0" 174 | if state.check_original_bound: 175 | check_original_bound = "1" 176 | 177 | compress_anim = "0" 178 | if state.compress_anim: 179 | compress_anim = "1" 180 | 181 | dirname = os.path.dirname(os.path.abspath(__file__)) 182 | 183 | basename = os.path.basename(output_pap) 184 | basename, _ = os.path.splitext(basename) 185 | anim_bin_file = dirname + '/tmp/' + basename + '.bin' 186 | 187 | print("Starting exporting to bin: " + anim_bin_file) 188 | anim.export( 189 | state.start_frame, 190 | state.end_frame, 191 | anim_bin_file 192 | ) 193 | 194 | print("Finished exporting to bin") 195 | command = dirname + '/bin/blenderassist.exe' 196 | print(command + " " + str(anim_idx) + " " + anim_bin_file + " " + skl_in + " " + anim_in + " -> " + output_pap) 197 | print(check_original_bound, compress_anim) 198 | subprocess.run([command, 'pack_anim', str(anim_idx), anim_bin_file, skl_in, anim_in, output_pap, check_original_bound, compress_anim]) 199 | 200 | return {'FINISHED'} 201 | 202 | class BlenderAssistPanelExportAnim(bpy.types.Panel): 203 | bl_idname = "BA_PT_Export_Anim" 204 | bl_label = "Export Animation" 205 | bl_category = "BlenderAssist" 206 | bl_space_type = "VIEW_3D" 207 | bl_region_type = "UI" 208 | 209 | def draw(self, context): 210 | layout = self.layout 211 | scene = context.scene 212 | state = scene.b_assist_props 213 | 214 | if context.object != None and context.object.type == 'ARMATURE': 215 | split = layout.row().split(factor=0.244) 216 | split.column().label(text="Target") 217 | split.column().label(text=context.object.name, icon='ARMATURE_DATA') 218 | 219 | layout.label(text='Excluded Bones') 220 | exclude_bones.draw_panel(layout.box()) 221 | 222 | if not state.editing_exclude_bones: 223 | layout.separator() 224 | 225 | layout.label(text="Output PAP") 226 | col = layout.column(align=True) 227 | col.prop(state, "output_path", text="") 228 | 229 | layout.prop(state, "start_frame") 230 | layout.prop(state, "end_frame") 231 | 232 | layout.label(text="Original Animation PAP") 233 | col = layout.column(align=True) 234 | col.prop(state, "input_pap", text="") 235 | 236 | layout.prop(state, "anim_idx") 237 | 238 | layout.prop(state, "check_original_bound") 239 | 240 | layout.prop(state, "compress_anim") 241 | 242 | layout.label(text="Skeleton SKLB") 243 | col = layout.column(align=True) 244 | col.prop(state, "input_sklb", text="") 245 | 246 | layout.operator(BlenderAssistExportAnim.bl_idname, text="Export as .pap", icon="PLAY") 247 | else: 248 | layout.label(text='No armature selected', icon='ERROR') 249 | 250 | # ================================ 251 | 252 | class BlenderAssistExportSkel(Operator): 253 | bl_idname = "b_assist_props.blender_assist_export_skel" 254 | bl_label = "Blender Assist Operator Skeleton" 255 | 256 | def execute(self, context): 257 | scene = context.scene 258 | state = scene.b_assist_props 259 | 260 | output_sklb = state.output_path_sklb 261 | skl_in = state.input_sklb_skel 262 | 263 | dirname = os.path.dirname(os.path.abspath(__file__)) 264 | 265 | basename = os.path.basename(output_sklb) 266 | basename, _ = os.path.splitext(basename) 267 | skl_bin_file = dirname + '/tmp/' + basename + '.bin' 268 | 269 | print("Starting exporting to bin: " + skl_bin_file) 270 | skl.export( 271 | skl_bin_file 272 | ) 273 | 274 | print("Finished exporting to bin") 275 | command = dirname + '/bin/blenderassist.exe' 276 | print(command + " " + skl_bin_file + " " + skl_in + " -> " + output_sklb) 277 | subprocess.run([command, 'pack_skel', skl_bin_file, skl_in, output_sklb]) 278 | 279 | return {'FINISHED'} 280 | 281 | class BlenderAssistPanelExportSkel(bpy.types.Panel): 282 | bl_idname = "BA_PT_Export_Skel" 283 | bl_label = "Export Skeleton" 284 | bl_category = "BlenderAssist" 285 | bl_space_type = "VIEW_3D" 286 | bl_region_type = "UI" 287 | 288 | def draw(self, context): 289 | layout = self.layout 290 | scene = context.scene 291 | state = scene.b_assist_props 292 | 293 | if context.object != None and context.object.type == 'ARMATURE': 294 | split = layout.row().split(factor=0.244) 295 | split.column().label(text="Target") 296 | split.column().label(text=context.object.name, icon='ARMATURE_DATA') 297 | 298 | layout.label(text="Output SKLB") 299 | col = layout.column(align=True) 300 | col.prop(state, "output_path_sklb", text="") 301 | 302 | layout.label(text="Original Skeleton SKLB") 303 | col = layout.column(align=True) 304 | col.prop(state, "input_sklb_skel", text="") 305 | 306 | layout.operator(BlenderAssistExportSkel.bl_idname, text="Export as .sklb", icon="PLAY") 307 | else: 308 | layout.label(text='No armature selected', icon='ERROR') 309 | 310 | # ================================ 311 | 312 | classes = ( 313 | data.ExcludeBone, 314 | 315 | BlenderAssistProperties, 316 | 317 | BlenderAssistPanelImport, 318 | BlenderAssistImport, 319 | 320 | BlenderAssistPanelExportAnim, 321 | BlenderAssistExportAnim, 322 | 323 | BlenderAssistPanelExportSkel, 324 | BlenderAssistExportSkel, 325 | 326 | exclude_bones.RT_UL_exclude_bones, 327 | exclude_bones.ApplyOperator, 328 | exclude_bones.EditOperator, 329 | exclude_bones.ClearOperator, 330 | exclude_bones.ListActionOperator 331 | ) 332 | 333 | def register(): 334 | for cls in classes: 335 | bpy.utils.register_class(cls) 336 | bpy.types.Scene.b_assist_props = PointerProperty(type=BlenderAssistProperties) 337 | 338 | 339 | def unregister(): 340 | for cls in classes: 341 | bpy.utils.unregister_class(cls) 342 | del bpy.types.Scene.b_assist_props 343 | 344 | 345 | if __name__ == "__main__": 346 | register() 347 | -------------------------------------------------------------------------------- /BlenderAssistAddon/anim.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | from . import helper 4 | 5 | def export(startFrame, endFrame, out_bin_file): 6 | arm_ob = helper.detect_armature() 7 | bpy.context.view_layer.objects.active = arm_ob 8 | bpy.context.active_object.select_set(state=True) 9 | 10 | numOriginalFrames = endFrame - startFrame 11 | duration = float(numOriginalFrames - 1) * 0.0333333333333333 12 | 13 | tracks = {} 14 | for bone in arm_ob.data.bones: 15 | excluded = False 16 | for exclude_bone in bpy.context.scene.b_assist_props.exclude_bones: 17 | if bone.name == exclude_bone.bone: 18 | excluded = True 19 | print("Excluded: " + bone.name) 20 | break 21 | 22 | if excluded: 23 | continue 24 | 25 | tracks[bone.name] = [] 26 | 27 | need_to_add_n_root = "n_root" not in tracks 28 | tracks["n_root"] = [] 29 | 30 | numTracks = len(tracks) 31 | 32 | current_frame = 0 33 | for current_frame in range(numOriginalFrames + 1): 34 | #current_time = current_frame * 0.0333333333333333 35 | bpy.context.scene.frame_set(current_frame + startFrame) 36 | 37 | for pose_bone in arm_ob.pose.bones: 38 | if pose_bone.name not in tracks: 39 | continue 40 | bone = pose_bone.bone 41 | 42 | if pose_bone.parent: 43 | m = pose_bone.parent.matrix.inverted() @ pose_bone.matrix 44 | else: 45 | m = pose_bone.matrix 46 | 47 | location, rotation, scale = m.decompose() 48 | t = helper.Transform() 49 | t.translation = location 50 | t.rotation = rotation 51 | t.scale = scale 52 | tracks[pose_bone.name].append(t) 53 | 54 | if need_to_add_n_root: 55 | tracks["n_root"].append(helper.Transform()) 56 | 57 | with open(out_bin_file, 'wb') as file: 58 | helper.write_int(file, numOriginalFrames) 59 | helper.write_int(file, numTracks) 60 | helper.write_float(file, duration) 61 | 62 | for track_name in tracks: 63 | helper.write_cstring(file, track_name) 64 | 65 | for current_frame in range(numOriginalFrames + 1): 66 | for track_name in tracks: 67 | transform = tracks[track_name][current_frame] 68 | transform.write(file) -------------------------------------------------------------------------------- /BlenderAssistAddon/bin/blenderassist.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0ceal0t/BlenderAssist/5e30631f049911d45b966ac96cc29cc2e4a2e0ea/BlenderAssistAddon/bin/blenderassist.exe -------------------------------------------------------------------------------- /BlenderAssistAddon/bin/tofbx.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0ceal0t/BlenderAssist/5e30631f049911d45b966ac96cc29cc2e4a2e0ea/BlenderAssistAddon/bin/tofbx.exe -------------------------------------------------------------------------------- /BlenderAssistAddon/data.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | class ExcludeBone(bpy.types.PropertyGroup): 4 | bone: bpy.props.StringProperty() -------------------------------------------------------------------------------- /BlenderAssistAddon/exclude_bones.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | from . import helper 4 | 5 | def draw_panel(layout): 6 | if helper.detect_armature() == None: 7 | layout.row() 8 | return 9 | 10 | state = bpy.context.scene.b_assist_props 11 | n = len(state.exclude_bones) 12 | 13 | if not state.editing_exclude_bones: 14 | if n == 0: 15 | row = layout.row() 16 | row.label(text='No Excluded Bones', icon='INFO') 17 | row.operator('b_assist_exclude_bones.edit', text='Create', icon='PRESET_NEW') 18 | else: 19 | row = layout.row() 20 | row.label(text=str(n) + ' Excluded Bones', icon='GROUP_BONE') 21 | row.operator('b_assist_exclude_bones.edit', text='Edit', icon='TOOL_SETTINGS') 22 | row.operator('b_assist_exclude_bones.clear', text='', icon='X') 23 | else: 24 | layout.label(text='Edit Excluded Bones (%i):' % n, icon='TOOL_SETTINGS') 25 | 26 | row = layout.row() 27 | row.template_list('RT_UL_exclude_bones', '', state, 'exclude_bones', state, 'active_exclude_bone') 28 | col = row.column(align=True) 29 | col.operator('b_assist_exclude_bones.list_action', icon='ADD', text='').action = 'ADD' 30 | col.operator('b_assist_exclude_bones.list_action', icon='REMOVE', text='').action = 'REMOVE' 31 | layout.operator('b_assist_exclude_bones.apply', text='Done') 32 | 33 | class RT_UL_exclude_bones(bpy.types.UIList): 34 | def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index, flt_flag): 35 | arm = helper.detect_armature() 36 | if arm == None: 37 | return 38 | layout.prop_search(item, 'bone', arm.data, 'bones', text='', icon='BONE_DATA') 39 | 40 | def draw_filter(self, context, layout): 41 | pass 42 | 43 | def filter_items(self, context, data, propname): 44 | flt_flags = [] 45 | flt_neworder = [] 46 | 47 | return flt_flags, flt_neworder 48 | 49 | # ========================= 50 | 51 | class EditOperator(bpy.types.Operator): 52 | bl_idname = 'b_assist_exclude_bones.edit' 53 | bl_label = 'Create' 54 | 55 | def execute(self, context): 56 | bpy.context.scene.b_assist_props.editing_exclude_bones = True 57 | return {'FINISHED'} 58 | 59 | class ApplyOperator(bpy.types.Operator): 60 | bl_idname = 'b_assist_exclude_bones.apply' 61 | bl_label = 'Apply' 62 | 63 | def execute(self, context): 64 | bpy.context.scene.b_assist_props.editing_exclude_bones = False 65 | return {'FINISHED'} 66 | 67 | class ListActionOperator(bpy.types.Operator): 68 | bl_idname = 'b_assist_exclude_bones.list_action' 69 | bl_label = 'Apply' 70 | action: bpy.props.StringProperty() 71 | 72 | def execute(self, context): 73 | state = bpy.context.scene.b_assist_props 74 | 75 | if self.action == 'ADD': 76 | exclude_bones = state.exclude_bones.add() 77 | state.active_exclude_bone = len(state.exclude_bones) - 1 78 | elif self.action == 'REMOVE': 79 | if len(state.exclude_bones) > 0: 80 | state.exclude_bones.remove(state.active_exclude_bone) 81 | state.active_exclude_bone = min(state.active_exclude_bone, len(state.exclude_bones) - 1) 82 | 83 | 84 | return {'FINISHED'} 85 | 86 | class ClearOperator(bpy.types.Operator): 87 | bl_idname = 'b_assist_exclude_bones.clear' 88 | bl_label = 'Reset Excluded Bones' 89 | bl_options = {'REGISTER', 'INTERNAL'} 90 | 91 | def invoke(self, context, event): 92 | return context.window_manager.invoke_confirm(self, event) 93 | 94 | def execute(self, context): 95 | state = bpy.context.scene.b_assist_props 96 | state.exclude_bones.clear() 97 | return {'FINISHED'} -------------------------------------------------------------------------------- /BlenderAssistAddon/helper.py: -------------------------------------------------------------------------------- 1 | from struct import pack 2 | import bpy 3 | from mathutils import Quaternion, Vector 4 | 5 | class Transform(object): 6 | def __init__(self): 7 | self.translation = Vector((0, 0, 0)) 8 | self.rotation = Quaternion() 9 | self.scale = Vector((1, 1, 1)) 10 | 11 | def write(self, file): 12 | v = self.translation 13 | v = (v.x, v.y, v.z, 0) 14 | q = self.rotation 15 | q = (q.x, q.y, q.z, q.w) 16 | s = self.scale 17 | s = (s.x, s.y, s.z, 1) 18 | 19 | write_vector4_raw(file, v) 20 | write_vector4_raw(file, q) 21 | write_vector4_raw(file, s) 22 | 23 | def write_headerstring(file, value): 24 | bytes = value.encode('utf-8') 25 | file.write(bytes) 26 | file.write(b'\x0a') # '\n' 27 | 28 | def write_cstring(file, value): 29 | bytes = value.encode('ascii') 30 | file.write(bytes) 31 | file.write(b'\x00') 32 | 33 | def write_int(file, value): 34 | file.write(pack(' Preferences > Add-ons`, press "Install" and select the entire `.zip` file. Make sure to enable the add-on as well. 13 | 14 | > Note on updating: you may need to uninstall the add-on, restart Blender, and then re-install it 15 | 16 | ![image](https://user-images.githubusercontent.com/18051158/162598790-56386e08-6182-4691-90b3-ebfc0f88cf9f.png) 17 | 18 | ## Usage 19 | 20 | First, find the `.pap` file of an animation that you want to replace (using a tool like [FFXIVExplorer](https://github.com/goaaats/ffxiv-explorer-fork/tree/index2)), as well as the corresponding `.sklb` skeleton file. For example: 21 | 22 | ``` 23 | chara/human/c1101/skeleton/base/b0001/skl_c1101b0001.sklb 24 | chara/human/c1101/animation/a0001/bt_common/emote/joy.pap 25 | ``` 26 | Make sure that the ids of the skeleton and the animation match up (in this case `c1101`), and extract both of these files using Textools, FFXIVExplorer, etc. 27 | 28 | Once you have an animation you want to export, select the armature and open the "BlenderAssist" menu in 3D view (default keybind is "N"), or press the left-facing arrow at the top-right of the 3D view. 29 | 30 | Configure the parameters to your liking, **making sure to select the original .pap and .sklb files**. Make sure the animation index also matches that of the animation you want to replace. Most `.pap` files only have a single animation, so leaving it at `0` is fine, but make sure to double-check. 31 | 32 | ![image](https://user-images.githubusercontent.com/18051158/164119440-961a48fc-d1dd-44f6-be33-561dd30bfb3a.png) 33 | 34 | And import the outputed `.pap` using your modding tool of choice. 35 | 36 | https://user-images.githubusercontent.com/18051158/162326495-ab9ba1c2-fc88-4068-a53d-bf8dd50c83e2.mp4 37 | 38 | ## Porting Animations 39 | 40 | When importing animations from MMD, other games, etc. it's generally a good idea to use a [bone remapping tool](https://github.com/Mwni/blender-animation-retargeting). A small caveat for this specific ones is that it will not work unless just adjust the "Rest alignment" of one of the bones you have mapped. For example, my mapping is: 41 | 42 | ![image](https://user-images.githubusercontent.com/18051158/162326747-87837006-276d-4436-ba15-6abe2b23652f.png) 43 | 44 | So when setting the "Rest alignment", I just wiggled `n_hara` slightly. Also make sure to resize the animation source armature so that is rougly the same size as your target: 45 | 46 | ![image](https://user-images.githubusercontent.com/18051158/162326875-74f1c72d-999c-4cd8-a882-4d382ec65c92.png) 47 | 48 | ## TMB and PAP Files 49 | 50 | There are parameters in both the `.pap` and `.tmb` files which determine how long an animation is allowed to play, so you may need to adjust them. In addition `.tmb` files often have facial expressions which you may want to remove or adjust using [VFXEditor](https://github.com/0ceal0t/Dalamud-VFXEditor) 51 | 52 | ## Notes on Building 53 | 54 | This is taken verbatim from [AnimAssist](https://github.com/lmcintyre/AnimAssist#building): 55 | 56 | > Building animassist.exe requires the Havok 2014 SDK and an env var of HAVOK_SDK_ROOT set to the directory, as well as the Visual C++ Platform Toolset v110. This is included in any install of VS2012, including the Community edition. You can find the Havok SDK to compile with in the description of [this video](https://www.youtube.com/watch?v=U88C9K-mSHs). Please note that is NOT a download I control, just a random one from online. 57 | 58 | Make sure to set your `HAVOK_SDK_ROOT` like this: 59 | 60 | ![image](https://user-images.githubusercontent.com/18051158/162323294-f6eacc56-7efc-4cf4-9247-ac3888ee865a.png) 61 | --------------------------------------------------------------------------------