#include "hecl/Blender/FindBlender.hpp" #include "hecl/SteamFinder.hpp" #include #include namespace hecl::blender { namespace { struct SBlenderVersion { uint32_t Major; uint32_t Minor; }; // Supported blender versions in reverse order, with the most recently supported version first constexpr std::array SupportedVersions{ SBlenderVersion{3, 0}, SBlenderVersion{2, 93}, SBlenderVersion{2, 92}, SBlenderVersion{2, 91}, SBlenderVersion{2, 90}, SBlenderVersion{2, 83}, }; // The most recent version with the most testing constexpr SBlenderVersion RecommendedVersion{2, 93}; static std::string OverridePath; } // namespace #ifdef __APPLE__ #define DEFAULT_BLENDER_BIN "/Applications/Blender.app/Contents/MacOS/blender" #else #define DEFAULT_BLENDER_BIN "blender" #endif static const std::regex regBlenderVersion(R"(Blender (\d+)\.(\d+)(?:\.(\d+))?)", std::regex::ECMAScript | std::regex::optimize); static bool RegFileExists(const char* path) { if (!path) return false; #if !defined(WIN32) if (path[0] != '/') { auto envPath = hecl::GetEnv("PATH"); if (envPath) { std::istringstream iss(*envPath); std::string item; while (std::getline(iss, item, ':')) { if (RegFileExists((item + "/" + path).c_str())) { return true; } } } } #endif hecl::Sstat theStat; return !hecl::Stat(path, &theStat) && S_ISREG(theStat.st_mode); } std::optional FindBlender(int& major, int& minor) { major = 0; minor = 0; std::optional blenderBin; if (!OverridePath.empty()) { blenderBin = {OverridePath}; } else { /* User-specified blender path */ blenderBin = GetEnv("BLENDER_BIN"); } if (blenderBin && !RegFileExists(blenderBin->c_str())) { blenderBin.reset(); } /* Child process of blender */ #if _WIN32 if (!blenderBin) { /* Environment not set; try steam */ std::string steamBlender = hecl::FindCommonSteamApp("Blender"); if (!steamBlender.empty()) { steamBlender += "\\blender.exe"; if (RegFileExists(steamBlender.c_str())) { blenderBin = std::move(steamBlender); } } if (!blenderBin) { /* No steam; try default */ wchar_t wProgFiles[256]; if (GetEnvironmentVariableW(L"ProgramFiles", wProgFiles, 256)) { auto progFiles = nowide::narrow(wProgFiles); for (const auto& version : SupportedVersions) { std::string blenderBinBuf = fmt::format(FMT_STRING("{}\\Blender Foundation\\Blender {}.{}\\blender.exe"), progFiles, version.Major, version.Minor); if (RegFileExists(blenderBinBuf.c_str())) { blenderBin = std::move(blenderBinBuf); break; } } } } } #else if (!blenderBin) { /* Try steam */ std::string steamBlender = hecl::FindCommonSteamApp("Blender"); if (steamBlender.size()) { #ifdef __APPLE__ steamBlender += "/blender.app/Contents/MacOS/blender"; #else steamBlender += "/blender"; #endif if (RegFileExists(steamBlender.c_str())) { blenderBin = std::move(steamBlender); } else if (RegFileExists(DEFAULT_BLENDER_BIN)) { blenderBin = DEFAULT_BLENDER_BIN; } } else if (RegFileExists(DEFAULT_BLENDER_BIN)) { blenderBin = DEFAULT_BLENDER_BIN; } } #endif if (!blenderBin) { return {}; } #if _WIN32 const nowide::wstackstring wblenderBin(blenderBin.value()); DWORD handle = 0; DWORD infoSize = GetFileVersionInfoSizeW(wblenderBin.get(), &handle); if (infoSize != NULL) { auto* infoData = new char[infoSize]; if (GetFileVersionInfoW(wblenderBin.get(), handle, infoSize, infoData)) { UINT size = 0; LPVOID lpBuffer = nullptr; if (VerQueryValueW(infoData, L"\\", &lpBuffer, &size) && size != 0u) { auto* verInfo = static_cast(lpBuffer); if (verInfo->dwSignature == 0xfeef04bd) { major = static_cast((verInfo->dwFileVersionMS >> 16) & 0xffff); minor = static_cast((verInfo->dwFileVersionMS >> 0 & 0xffff) * 10 + (verInfo->dwFileVersionLS >> 16 & 0xffff)); } } } delete[] infoData; } #else std::string command = std::string("\"") + blenderBin.value() + "\" --version"; FILE* fp = popen(command.c_str(), "r"); char versionBuf[256]; size_t rdSize = fread(&versionBuf[0], 1, 255, fp); versionBuf[rdSize] = '\0'; pclose(fp); std::cmatch match; if (std::regex_search(versionBuf, match, regBlenderVersion)) { major = atoi(match[1].str().c_str()); minor = atoi(match[2].str().c_str()); } #endif return blenderBin; } bool IsVersionSupported(int major, int minor) { auto it = std::find_if(SupportedVersions.cbegin(), SupportedVersions.cend(), [&major, &minor](const auto& version) { return version.Major == major && version.Minor == minor; }); return it != SupportedVersions.cend(); } std::pair GetLatestSupportedVersion() { return {SupportedVersions.front().Major, SupportedVersions.front().Minor}; } std::pair GetEarliestSupportedVersion() { return {SupportedVersions.back().Major, SupportedVersions.back().Minor}; } std::pair GetRecommendedVersion() { return {RecommendedVersion.Major, RecommendedVersion.Minor}; } void SetOverridePath(std::string_view overridePath) { OverridePath = overridePath; } } // namespace hecl::blender