diff --git a/hecl/blender/hecl_blendershell.py b/hecl/blender/hecl_blendershell.py index 0c005fba6..f3454e7e8 100644 --- a/hecl/blender/hecl_blendershell.py +++ b/hecl/blender/hecl_blendershell.py @@ -57,7 +57,7 @@ class PathHasher: read_str = readpipestr() return int(read_str[0:8], 16) -# Ensure Blender 2.90 is being used +# Ensure Blender 2.90+ is being used if bpy.app.version < (2, 90, 0): writepipestr(b'NOT290') _quitblender() diff --git a/hecl/extern/athena b/hecl/extern/athena index 180556880..1d0727fae 160000 --- a/hecl/extern/athena +++ b/hecl/extern/athena @@ -1 +1 @@ -Subproject commit 1805568808e8c78450365fef7d55cf7d0c985ab0 +Subproject commit 1d0727fae08bf0c8260174015e3b74679b2eb6f0 diff --git a/hecl/lib/Blender/Connection.cpp b/hecl/lib/Blender/Connection.cpp index c00714523..c8e37d115 100644 --- a/hecl/lib/Blender/Connection.cpp +++ b/hecl/lib/Blender/Connection.cpp @@ -48,6 +48,11 @@ using namespace std::literals; namespace hecl::blender { +static const uint32_t MinBlenderMajorSearch = 2; +static const uint32_t MaxBlenderMajorSearch = 2; +static const uint32_t MinBlenderMinorSearch = 90; +static const uint32_t MaxBlenderMinorSearch = 91; + logvisor::Module BlenderLog("hecl::blender::Connection"); Token SharedBlenderToken; @@ -342,6 +347,7 @@ Connection::Connection(int verbosityLevel) { /* User-specified blender path */ #if _WIN32 + wchar_t BLENDER_BIN_BUF[2048]; std::wstring blenderBinBuf; const wchar_t* blenderBin = _wgetenv(L"BLENDER_BIN"); #else @@ -364,12 +370,24 @@ Connection::Connection(int verbosityLevel) { if (!RegFileExists(blenderBin)) { /* No steam; try default */ wchar_t progFiles[256]; - if (!GetEnvironmentVariableW(L"ProgramFiles", progFiles, 256)) - BlenderLog.report(logvisor::Fatal, FMT_STRING(L"unable to determine 'Program Files' path")); - blenderBinBuf = fmt::format(FMT_STRING(L"{}\\Blender Foundation\\Blender 2.90\\blender.exe"), progFiles); - blenderBin = blenderBinBuf.c_str(); - if (!RegFileExists(blenderBin)) - BlenderLog.report(logvisor::Fatal, FMT_STRING(L"unable to find {}"), blenderBin); + if (GetEnvironmentVariableW(L"ProgramFiles", progFiles, 256)) { + for (size_t major = MinBlenderMajorSearch; major <= MaxBlenderMajorSearch; ++major) { + bool found = false; + for (size_t minor = MinBlenderMinorSearch; minor <= MaxBlenderMinorSearch; ++minor) { + _snwprintf(BLENDER_BIN_BUF, 2048, L"%s\\Blender Foundation\\Blender %i.%i\\blender.exe", progFiles, major, + minor); + if (RegFileExists(BLENDER_BIN_BUF)) { + blenderBin = BLENDER_BIN_BUF; + found = true; + break; + } + } + + if (found) { + break; + } + } + } } } @@ -395,7 +413,8 @@ Connection::Connection(int verbosityLevel) { FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&messageBuffer, 0, nullptr); - BlenderLog.report(logvisor::Fatal, FMT_STRING(L"unable to launch blender from {}: {}"), blenderBin, messageBuffer); + BlenderLog.report(logvisor::Fatal, FMT_STRING(L"unable to launch blender from {}: {}"), blenderBin, + messageBuffer); } close(m_writepipe[0]); @@ -417,7 +436,8 @@ Connection::Connection(int verbosityLevel) { if (err == ERROR_BROKEN_PIPE) break; // pipe done - normal exit path. else - BlenderLog.report(logvisor::Error, FMT_STRING("Error with ReadFile: {:08X}"), err); // Something bad happened. + BlenderLog.report(logvisor::Error, FMT_STRING("Error with ReadFile: {:08X}"), + err); // Something bad happened. } // Display the character read on the screen. @@ -623,14 +643,14 @@ std::streamsize PyOutStream::StreamBuf::xsputn(const char_type* __first, std::st return __n; } -constexpr std::array BlendTypeStrs{ - "NONE"sv, "MESH"sv, "CMESH"sv, "ARMATURE"sv, "ACTOR"sv, "AREA"sv, - "WORLD"sv, "MAPAREA"sv, "MAPUNIVERSE"sv, "FRAME"sv, "PATH"sv -}; +constexpr std::array BlendTypeStrs{"NONE"sv, "MESH"sv, "CMESH"sv, "ARMATURE"sv, + "ACTOR"sv, "AREA"sv, "WORLD"sv, "MAPAREA"sv, + "MAPUNIVERSE"sv, "FRAME"sv, "PATH"sv}; bool Connection::createBlend(const ProjectPath& path, BlendType type) { if (m_lock) { - BlenderLog.report(logvisor::Fatal, FMT_STRING("BlenderConnection::createBlend() musn't be called with stream active")); + BlenderLog.report(logvisor::Fatal, + FMT_STRING("BlenderConnection::createBlend() musn't be called with stream active")); return false; } _writeStr(fmt::format(FMT_STRING("CREATE \"{}\" {}"), path.getAbsolutePathUTF8(), BlendTypeStrs[int(type)])); @@ -646,7 +666,8 @@ bool Connection::createBlend(const ProjectPath& path, BlendType type) { bool Connection::openBlend(const ProjectPath& path, bool force) { if (m_lock) { - BlenderLog.report(logvisor::Fatal, FMT_STRING("BlenderConnection::openBlend() musn't be called with stream active")); + BlenderLog.report(logvisor::Fatal, + FMT_STRING("BlenderConnection::openBlend() musn't be called with stream active")); return false; } if (!force && path == m_loadedBlend) @@ -678,7 +699,8 @@ bool Connection::openBlend(const ProjectPath& path, bool force) { bool Connection::saveBlend() { if (m_lock) { - BlenderLog.report(logvisor::Fatal, FMT_STRING("BlenderConnection::saveBlend() musn't be called with stream active")); + BlenderLog.report(logvisor::Fatal, + FMT_STRING("BlenderConnection::saveBlend() musn't be called with stream active")); return false; } _writeStr("SAVE"); @@ -710,28 +732,30 @@ void PyOutStream::close() { } void PyOutStream::linkBlend(std::string_view target, std::string_view objName, bool link) { - format(FMT_STRING("if '{}' not in bpy.data.scenes:\n" - " with bpy.data.libraries.load('''{}''', link={}, relative=True) as (data_from, data_to):\n" - " data_to.scenes = data_from.scenes\n" - " obj_scene = None\n" - " for scene in data_to.scenes:\n" - " if scene.name == '{}':\n" - " obj_scene = scene\n" - " break\n" - " if not obj_scene:\n" - " raise RuntimeError('''unable to find {} in {}. try deleting it and restart the extract.''')\n" - " obj = None\n" - " for object in obj_scene.objects:\n" - " if object.name == obj_scene.name:\n" - " obj = object\n" - "else:\n" - " obj = bpy.data.objects['{}']\n" - "\n"), - objName, target, link ? "True" : "False", objName, objName, target, objName); + format( + FMT_STRING("if '{}' not in bpy.data.scenes:\n" + " with bpy.data.libraries.load('''{}''', link={}, relative=True) as (data_from, data_to):\n" + " data_to.scenes = data_from.scenes\n" + " obj_scene = None\n" + " for scene in data_to.scenes:\n" + " if scene.name == '{}':\n" + " obj_scene = scene\n" + " break\n" + " if not obj_scene:\n" + " raise RuntimeError('''unable to find {} in {}. try deleting it and restart the extract.''')\n" + " obj = None\n" + " for object in obj_scene.objects:\n" + " if object.name == obj_scene.name:\n" + " obj = object\n" + "else:\n" + " obj = bpy.data.objects['{}']\n" + "\n"), + objName, target, link ? "True" : "False", objName, objName, target, objName); } void PyOutStream::linkArmature(std::string_view target, std::string_view armName) { - format(FMT_STRING("target_arm_name = '{}'\n" + format(FMT_STRING( + "target_arm_name = '{}'\n" "if target_arm_name not in bpy.data.armatures:\n" " with bpy.data.libraries.load('''{}''', link=True, relative=True) as (data_from, data_to):\n" " if target_arm_name not in data_from.armatures:\n" @@ -745,7 +769,8 @@ void PyOutStream::linkArmature(std::string_view target, std::string_view armName } void PyOutStream::linkMesh(std::string_view target, std::string_view meshName) { - format(FMT_STRING("target_mesh_name = '{}'\n" + format(FMT_STRING( + "target_mesh_name = '{}'\n" "if target_mesh_name not in bpy.data.objects:\n" " with bpy.data.libraries.load('''{}''', link=True, relative=True) as (data_from, data_to):\n" " if target_mesh_name not in data_from.objects:\n" @@ -759,18 +784,19 @@ void PyOutStream::linkMesh(std::string_view target, std::string_view meshName) { void PyOutStream::linkBackground(std::string_view target, std::string_view sceneName) { if (sceneName.empty()) { format(FMT_STRING("with bpy.data.libraries.load('''{}''', link=True, relative=True) as (data_from, data_to):\n" - " data_to.scenes = data_from.scenes\n" - "obj_scene = None\n" - "for scene in data_to.scenes:\n" - " obj_scene = scene\n" - " break\n" - "if not obj_scene:\n" - " raise RuntimeError('''unable to find {}. try deleting it and restart the extract.''')\n" - "\n" - "bpy.context.scene.background_set = obj_scene\n"), + " data_to.scenes = data_from.scenes\n" + "obj_scene = None\n" + "for scene in data_to.scenes:\n" + " obj_scene = scene\n" + " break\n" + "if not obj_scene:\n" + " raise RuntimeError('''unable to find {}. try deleting it and restart the extract.''')\n" + "\n" + "bpy.context.scene.background_set = obj_scene\n"), target, target); } else { - format(FMT_STRING("if '{}' not in bpy.data.scenes:\n" + format(FMT_STRING( + "if '{}' not in bpy.data.scenes:\n" " with bpy.data.libraries.load('''{}''', link=True, relative=True) as (data_from, data_to):\n" " data_to.scenes = data_from.scenes\n" " obj_scene = None\n" @@ -790,27 +816,27 @@ void PyOutStream::AABBToBMesh(const atVec3f& min, const atVec3f& max) { athena::simd_floats minf(min.simd); athena::simd_floats maxf(max.simd); format(FMT_STRING("bm = bmesh.new()\n" - "bm.verts.new(({},{},{}))\n" - "bm.verts.new(({},{},{}))\n" - "bm.verts.new(({},{},{}))\n" - "bm.verts.new(({},{},{}))\n" - "bm.verts.new(({},{},{}))\n" - "bm.verts.new(({},{},{}))\n" - "bm.verts.new(({},{},{}))\n" - "bm.verts.new(({},{},{}))\n" - "bm.verts.ensure_lookup_table()\n" - "bm.edges.new((bm.verts[0], bm.verts[1]))\n" - "bm.edges.new((bm.verts[0], bm.verts[2]))\n" - "bm.edges.new((bm.verts[0], bm.verts[4]))\n" - "bm.edges.new((bm.verts[3], bm.verts[1]))\n" - "bm.edges.new((bm.verts[3], bm.verts[2]))\n" - "bm.edges.new((bm.verts[3], bm.verts[7]))\n" - "bm.edges.new((bm.verts[5], bm.verts[1]))\n" - "bm.edges.new((bm.verts[5], bm.verts[4]))\n" - "bm.edges.new((bm.verts[5], bm.verts[7]))\n" - "bm.edges.new((bm.verts[6], bm.verts[2]))\n" - "bm.edges.new((bm.verts[6], bm.verts[4]))\n" - "bm.edges.new((bm.verts[6], bm.verts[7]))\n"), + "bm.verts.new(({},{},{}))\n" + "bm.verts.new(({},{},{}))\n" + "bm.verts.new(({},{},{}))\n" + "bm.verts.new(({},{},{}))\n" + "bm.verts.new(({},{},{}))\n" + "bm.verts.new(({},{},{}))\n" + "bm.verts.new(({},{},{}))\n" + "bm.verts.new(({},{},{}))\n" + "bm.verts.ensure_lookup_table()\n" + "bm.edges.new((bm.verts[0], bm.verts[1]))\n" + "bm.edges.new((bm.verts[0], bm.verts[2]))\n" + "bm.edges.new((bm.verts[0], bm.verts[4]))\n" + "bm.edges.new((bm.verts[3], bm.verts[1]))\n" + "bm.edges.new((bm.verts[3], bm.verts[2]))\n" + "bm.edges.new((bm.verts[3], bm.verts[7]))\n" + "bm.edges.new((bm.verts[5], bm.verts[1]))\n" + "bm.edges.new((bm.verts[5], bm.verts[4]))\n" + "bm.edges.new((bm.verts[5], bm.verts[7]))\n" + "bm.edges.new((bm.verts[6], bm.verts[2]))\n" + "bm.edges.new((bm.verts[6], bm.verts[4]))\n" + "bm.edges.new((bm.verts[6], bm.verts[7]))\n"), minf[0], minf[1], minf[2], maxf[0], minf[1], minf[2], minf[0], maxf[1], minf[2], maxf[0], maxf[1], minf[2], minf[0], minf[1], maxf[2], maxf[0], minf[1], maxf[2], minf[0], maxf[1], maxf[2], maxf[0], maxf[1], maxf[2]); } @@ -900,9 +926,7 @@ void Mesh::normalizeSkinBinds() { Mesh::Mesh(Connection& conn, HMDLTopology topologyIn, int skinSlotCount, bool useLuvs) : topology(topologyIn), sceneXf(conn), aabbMin(conn), aabbMax(conn) { - conn._readVectorFunc(materialSets, [&]() { - conn._readVector(materialSets.emplace_back()); - }); + conn._readVectorFunc(materialSets, [&]() { conn._readVector(materialSets.emplace_back()); }); MeshOptimizer opt(conn, materialSets[0], useLuvs); opt.optimize(*this, skinSlotCount); @@ -1137,9 +1161,7 @@ World::Area::Area(Connection& conn) { conn._readVector(docks); } -World::World(Connection& conn) { - conn._readVector(areas); -} +World::World(Connection& conn) { conn._readVector(areas); } Light::Light(Connection& conn) : sceneXf(conn), color(conn) { conn._readBuf(&layer, 29); @@ -1197,9 +1219,7 @@ Actor::Actor(Connection& conn) { conn._readVector(actions); } -PathMesh::PathMesh(Connection& conn) { - conn._readVector(data); -} +PathMesh::PathMesh(Connection& conn) { conn._readVector(data); } const Bone* Armature::lookupBone(const char* name) const { for (const Bone& b : bones) @@ -1230,9 +1250,7 @@ const Bone* Armature::getRoot() const { return nullptr; } -Armature::Armature(Connection& conn) { - conn._readVector(bones); -} +Armature::Armature(Connection& conn) { conn._readVector(bones); } Bone::Bone(Connection& conn) { name = conn._readStdString(); @@ -1452,9 +1470,7 @@ std::vector DataStream::getTextures() { m_parent->_checkOk("unable to get textures"sv); std::vector texs; - m_parent->_readVectorFunc(texs, [&]() { - texs.push_back(m_parent->_readPath()); - }); + m_parent->_readVectorFunc(texs, [&]() { texs.push_back(m_parent->_readPath()); }); return texs; }