metaforce/hecl/blender/BlenderConnection.cpp

469 lines
14 KiB
C++
Raw Normal View History

2015-05-24 04:51:16 +00:00
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
2015-08-31 19:47:59 +00:00
#include <inttypes.h>
2015-05-24 04:51:16 +00:00
#include <system_error>
#include <string>
2015-07-22 19:14:50 +00:00
#include <HECL/HECL.hpp>
#include <LogVisor/LogVisor.hpp>
2015-07-08 04:26:29 +00:00
#include "BlenderConnection.hpp"
2015-05-24 04:51:16 +00:00
2015-08-31 03:36:24 +00:00
#if _WIN32
#include <io.h>
#include <fcntl.h>
#endif
2015-07-28 02:25:33 +00:00
namespace HECL
{
LogVisor::LogModule BlenderLog("BlenderConnection");
BlenderConnection* SharedBlenderConnection = nullptr;
2015-07-22 19:14:50 +00:00
2015-05-24 04:51:16 +00:00
#ifdef __APPLE__
#define DEFAULT_BLENDER_BIN "/Applications/Blender.app/Contents/MacOS/blender"
#else
#define DEFAULT_BLENDER_BIN "blender"
#endif
2015-08-13 07:30:23 +00:00
extern "C" uint8_t HECL_BLENDERSHELL[];
extern "C" size_t HECL_BLENDERSHELL_SZ;
2015-05-24 04:51:16 +00:00
2015-08-31 03:36:24 +00:00
extern "C" uint8_t HECL_ADDON[];
extern "C" size_t HECL_ADDON_SZ;
2015-09-06 20:08:23 +00:00
extern "C" uint8_t HECL_STARTUP[];
extern "C" size_t HECL_STARTUP_SZ;
2015-08-31 03:36:24 +00:00
2015-09-06 20:08:23 +00:00
static void InstallBlendershell(const SystemChar* path)
{
FILE* fp = HECL::Fopen(path, _S("w"));
if (!fp)
BlenderLog.report(LogVisor::FatalError, _S("unable to open %s for writing"), path);
fwrite(HECL_BLENDERSHELL, 1, HECL_BLENDERSHELL_SZ, fp);
fclose(fp);
}
static void InstallAddon(const SystemChar* path)
2015-08-31 03:36:24 +00:00
{
FILE* fp = HECL::Fopen(path, _S("wb"));
if (!fp)
BlenderLog.report(LogVisor::FatalError, _S("Unable to install blender addon at '%s'"), path);
fwrite(HECL_ADDON, 1, HECL_ADDON_SZ, fp);
fclose(fp);
}
2015-09-06 20:08:23 +00:00
static void InstallStartup(const char* path)
{
FILE* fp = fopen(path, "wb");
if (!fp)
BlenderLog.report(LogVisor::FatalError, "Unable to place hecl_startup.blend at '%s'", path);
fwrite(HECL_STARTUP, 1, HECL_STARTUP_SZ, fp);
fclose(fp);
}
2015-07-22 19:14:50 +00:00
size_t BlenderConnection::_readLine(char* buf, size_t bufSz)
2015-05-24 04:51:16 +00:00
{
size_t readBytes = 0;
while (true)
{
if (readBytes >= bufSz)
2015-05-25 01:34:31 +00:00
{
2015-08-08 04:52:20 +00:00
BlenderLog.report(LogVisor::FatalError, "Pipe buffer overrun");
2015-05-25 01:34:31 +00:00
*(buf-1) = '\0';
return bufSz - 1;
}
2015-08-31 03:36:24 +00:00
int ret = read(m_readpipe[0], buf, 1);
2015-05-24 04:51:16 +00:00
if (ret < 0)
goto err;
else if (ret == 1)
{
if (*buf == '\n')
{
*buf = '\0';
return readBytes;
}
++readBytes;
++buf;
}
else
{
*buf = '\0';
return readBytes;
}
}
err:
2015-07-28 02:25:33 +00:00
BlenderLog.report(LogVisor::FatalError, strerror(errno));
2015-05-24 04:51:16 +00:00
return 0;
}
2015-07-22 19:14:50 +00:00
size_t BlenderConnection::_writeLine(const char* buf)
2015-05-24 04:51:16 +00:00
{
2015-08-31 03:36:24 +00:00
int ret, nlerr;
2015-05-24 04:51:16 +00:00
ret = write(m_writepipe[1], buf, strlen(buf));
if (ret < 0)
goto err;
nlerr = write(m_writepipe[1], "\n", 1);
if (nlerr < 0)
goto err;
return (size_t)ret;
err:
2015-07-28 02:25:33 +00:00
BlenderLog.report(LogVisor::FatalError, strerror(errno));
2015-07-26 02:52:02 +00:00
return 0;
2015-05-24 04:51:16 +00:00
}
2015-07-22 19:14:50 +00:00
size_t BlenderConnection::_readBuf(char* buf, size_t len)
2015-05-24 04:51:16 +00:00
{
2015-08-31 03:36:24 +00:00
int ret = read(m_readpipe[0], buf, len);
2015-05-24 04:51:16 +00:00
if (ret < 0)
2015-07-22 19:14:50 +00:00
goto err;
2015-05-24 04:51:16 +00:00
return ret;
2015-07-22 19:14:50 +00:00
err:
2015-07-28 02:25:33 +00:00
BlenderLog.report(LogVisor::FatalError, strerror(errno));
2015-07-22 19:14:50 +00:00
return 0;
2015-05-24 04:51:16 +00:00
}
2015-07-22 19:14:50 +00:00
size_t BlenderConnection::_writeBuf(const char* buf, size_t len)
2015-05-24 04:51:16 +00:00
{
2015-08-31 03:36:24 +00:00
int ret = write(m_writepipe[1], buf, len);
2015-05-24 04:51:16 +00:00
if (ret < 0)
2015-07-22 19:14:50 +00:00
goto err;
2015-05-24 04:51:16 +00:00
return ret;
2015-07-22 19:14:50 +00:00
err:
2015-07-28 02:25:33 +00:00
BlenderLog.report(LogVisor::FatalError, strerror(errno));
2015-07-22 19:14:50 +00:00
return 0;
2015-05-24 04:51:16 +00:00
}
2015-07-22 19:14:50 +00:00
void BlenderConnection::_closePipe()
2015-05-24 04:51:16 +00:00
{
close(m_readpipe[0]);
close(m_writepipe[1]);
}
2015-07-22 19:14:50 +00:00
BlenderConnection::BlenderConnection(bool silenceBlender)
2015-05-24 04:51:16 +00:00
{
2015-09-02 02:31:33 +00:00
BlenderLog.report(LogVisor::Info, "Establishing BlenderConnection...");
2015-08-13 07:30:23 +00:00
/* Put hecl_blendershell.py in temp dir */
#ifdef _WIN32
wchar_t* TMPDIR = _wgetenv(L"TEMP");
if (!TMPDIR)
TMPDIR = (wchar_t*)L"\\Temp";
2015-09-06 21:00:50 +00:00
m_startupBlend = HECL::WideToUTF8(TMPDIR);
#else
char* TMPDIR = getenv("TMPDIR");
if (!TMPDIR)
TMPDIR = (char*)"/tmp";
2015-09-06 21:00:50 +00:00
m_startupBlend = TMPDIR;
#endif
HECL::SystemString blenderShellPath(TMPDIR);
2015-08-13 07:30:23 +00:00
blenderShellPath += _S("/hecl_blendershell.py");
2015-09-06 20:08:23 +00:00
InstallBlendershell(blenderShellPath.c_str());
2015-08-31 03:36:24 +00:00
HECL::SystemString blenderAddonPath(TMPDIR);
blenderAddonPath += _S("/hecl_blenderaddon.zip");
2015-09-02 02:31:33 +00:00
InstallAddon(blenderAddonPath.c_str());
2015-09-06 20:08:23 +00:00
2015-09-06 21:00:50 +00:00
m_startupBlend += "/hecl_startup.blend";
2015-09-06 20:08:23 +00:00
InstallStartup(m_startupBlend.c_str());
2015-08-31 03:36:24 +00:00
int installAttempt = 0;
while (true)
{
/* Construct communication pipes */
2015-07-22 19:14:50 +00:00
#if _WIN32
2015-08-31 03:36:24 +00:00
_pipe(m_readpipe, 2048, _O_BINARY);
_pipe(m_writepipe, 2048, _O_BINARY);
HANDLE writehandle = HANDLE(_get_osfhandle(m_writepipe[0]));
SetHandleInformation(writehandle, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
HANDLE readhandle = HANDLE(_get_osfhandle(m_readpipe[1]));
SetHandleInformation(readhandle, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
2015-07-22 19:14:50 +00:00
#else
2015-08-31 03:36:24 +00:00
pipe(m_readpipe);
pipe(m_writepipe);
2015-07-22 19:14:50 +00:00
#endif
2015-05-24 04:51:16 +00:00
2015-08-31 03:36:24 +00:00
/* User-specified blender path */
2015-07-22 19:14:50 +00:00
#if _WIN32
2015-08-31 03:36:24 +00:00
wchar_t BLENDER_BIN_BUF[2048];
wchar_t* blenderBin = _wgetenv(L"BLENDER_BIN");
2015-07-22 19:14:50 +00:00
#else
2015-08-31 03:36:24 +00:00
char* blenderBin = getenv("BLENDER_BIN");
2015-07-22 19:14:50 +00:00
#endif
2015-05-24 04:51:16 +00:00
2015-08-31 03:36:24 +00:00
/* Child process of blender */
2015-07-22 19:14:50 +00:00
#if _WIN32
2015-08-31 03:36:24 +00:00
if (!blenderBin)
2015-07-22 19:14:50 +00:00
{
2015-08-31 03:36:24 +00:00
/* Environment not set; use default */
wchar_t progFiles[256];
if (!GetEnvironmentVariableW(L"ProgramFiles", progFiles, 256))
BlenderLog.report(LogVisor::FatalError, L"unable to determine 'Program Files' path");
_snwprintf(BLENDER_BIN_BUF, 2048, L"%s\\Blender Foundation\\Blender\\blender.exe", progFiles);
blenderBin = BLENDER_BIN_BUF;
2015-07-22 19:14:50 +00:00
}
2015-08-31 03:36:24 +00:00
wchar_t cmdLine[2048];
2015-09-02 02:31:33 +00:00
_snwprintf(cmdLine, 2048, L" --background -P \"%s\" -- %" PRIuPTR " %" PRIuPTR " \"%s\"",
blenderShellPath.c_str(), uintptr_t(writehandle), uintptr_t(readhandle), blenderAddonPath.c_str());
2015-07-22 19:14:50 +00:00
2015-08-31 03:36:24 +00:00
STARTUPINFO sinfo = {sizeof(STARTUPINFO)};
HANDLE nulHandle = NULL;
if (silenceBlender)
{
SECURITY_ATTRIBUTES sattrs = {sizeof(SECURITY_ATTRIBUTES), NULL, TRUE};
nulHandle = CreateFileW(L"nul", GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, &sattrs, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
sinfo.hStdError = nulHandle;
sinfo.hStdOutput = nulHandle;
sinfo.dwFlags = STARTF_USESTDHANDLES;
}
2015-07-22 19:14:50 +00:00
2015-08-31 03:36:24 +00:00
PROCESS_INFORMATION pinfo;
if (!CreateProcessW(blenderBin, cmdLine, NULL, NULL, TRUE, NORMAL_PRIORITY_CLASS, NULL, NULL, &sinfo, &pinfo))
{
LPWSTR messageBuffer = nullptr;
size_t size = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&messageBuffer, 0, NULL);
BlenderLog.report(LogVisor::FatalError, L"unable to launch blender from %s: %s", blenderBin, messageBuffer);
}
2015-07-22 19:14:50 +00:00
2015-08-31 03:36:24 +00:00
close(m_writepipe[0]);
close(m_readpipe[1]);
2015-07-22 19:14:50 +00:00
2015-08-31 03:36:24 +00:00
if (nulHandle)
CloseHandle(nulHandle);
2015-07-22 19:14:50 +00:00
#else
2015-08-31 03:36:24 +00:00
pid_t pid = fork();
if (!pid)
2015-05-24 04:51:16 +00:00
{
2015-08-31 03:36:24 +00:00
close(m_writepipe[1]);
close(m_readpipe[0]);
2015-05-24 04:51:16 +00:00
2015-08-31 03:36:24 +00:00
if (silenceBlender)
{
int devNull = open("/dev/null", O_WRONLY);
dup2(devNull, STDOUT_FILENO);
dup2(devNull, STDERR_FILENO);
}
2015-05-24 04:51:16 +00:00
2015-08-31 03:36:24 +00:00
char errbuf[256];
char readfds[32];
snprintf(readfds, 32, "%d", m_writepipe[0]);
char writefds[32];
snprintf(writefds, 32, "%d", m_readpipe[1]);
/* Try user-specified blender first */
if (blenderBin)
{
2015-09-02 02:31:33 +00:00
execlp(blenderBin, blenderBin,
"--background", "-P", blenderShellPath.c_str(),
"--", readfds, writefds, blenderAddonPath.c_str(), NULL);
2015-08-31 03:36:24 +00:00
if (errno != ENOENT)
{
snprintf(errbuf, 256, "NOLAUNCH %s\n", strerror(errno));
write(m_writepipe[1], errbuf, strlen(errbuf));
exit(1);
}
}
/* Otherwise default blender */
2015-09-02 02:31:33 +00:00
execlp(DEFAULT_BLENDER_BIN, DEFAULT_BLENDER_BIN,
"--background", "-P", blenderShellPath.c_str(),
"--", readfds, writefds, blenderAddonPath.c_str(), NULL);
2015-05-24 04:51:16 +00:00
if (errno != ENOENT)
{
snprintf(errbuf, 256, "NOLAUNCH %s\n", strerror(errno));
write(m_writepipe[1], errbuf, strlen(errbuf));
exit(1);
}
2015-08-31 03:36:24 +00:00
/* Unable to find blender */
write(m_writepipe[1], "NOBLENDER\n", 10);
2015-05-24 04:51:16 +00:00
exit(1);
2015-08-31 03:36:24 +00:00
}
close(m_writepipe[0]);
close(m_readpipe[1]);
m_blenderProc = pid;
2015-07-22 19:14:50 +00:00
#endif
2015-05-24 04:51:16 +00:00
2015-08-31 03:36:24 +00:00
/* Handle first response */
char lineBuf[256];
_readLine(lineBuf, sizeof(lineBuf));
if (!strcmp(lineBuf, "NOLAUNCH"))
{
_closePipe();
BlenderLog.report(LogVisor::FatalError, "Unable to launch blender");
}
else if (!strcmp(lineBuf, "NOBLENDER"))
{
_closePipe();
if (blenderBin)
BlenderLog.report(LogVisor::FatalError, _S("Unable to find blender at '%s' or '%s'"),
blenderBin, DEFAULT_BLENDER_BIN);
else
BlenderLog.report(LogVisor::FatalError, _S("Unable to find blender at '%s'"),
DEFAULT_BLENDER_BIN);
}
else if (!strcmp(lineBuf, "NOADDON"))
{
_closePipe();
InstallAddon(blenderAddonPath.c_str());
++installAttempt;
if (installAttempt >= 2)
BlenderLog.report(LogVisor::FatalError, "unable to install blender addon using '%s'", blenderAddonPath.c_str());
continue;
}
else if (strcmp(lineBuf, "READY"))
{
_closePipe();
BlenderLog.report(LogVisor::FatalError, "read '%s' from blender; expected 'READY'", lineBuf);
}
_writeLine("ACK");
2015-05-24 04:51:16 +00:00
2015-08-31 03:36:24 +00:00
break;
}
2015-05-24 04:51:16 +00:00
}
2015-07-22 19:14:50 +00:00
BlenderConnection::~BlenderConnection()
2015-05-24 04:51:16 +00:00
{
2015-05-24 22:19:28 +00:00
_closePipe();
}
2015-07-28 02:25:33 +00:00
bool BlenderConnection::createBlend(const SystemString& path)
{
2015-08-16 23:01:35 +00:00
if (m_lock)
2015-08-05 22:59:59 +00:00
{
BlenderLog.report(LogVisor::FatalError,
"BlenderConnection::createBlend() musn't be called with stream active");
return false;
}
HECL::SystemUTF8View pathView(path);
2015-09-06 20:08:23 +00:00
_writeLine(("CREATE \"" + pathView.str() + "\" \"" + m_startupBlend + "\"").c_str());
2015-07-28 02:25:33 +00:00
char lineBuf[256];
_readLine(lineBuf, sizeof(lineBuf));
if (!strcmp(lineBuf, "FINISHED"))
{
2015-08-31 03:36:24 +00:00
m_loadedBlend = path;
2015-07-28 02:25:33 +00:00
return true;
}
return false;
}
bool BlenderConnection::openBlend(const SystemString& path)
2015-05-24 22:19:28 +00:00
{
2015-08-16 23:01:35 +00:00
if (m_lock)
2015-08-05 22:59:59 +00:00
{
BlenderLog.report(LogVisor::FatalError,
"BlenderConnection::openBlend() musn't be called with stream active");
return false;
}
HECL::SystemUTF8View pathView(path);
_writeLine(("OPEN \"" + pathView.str() + "\"").c_str());
2015-05-24 22:19:28 +00:00
char lineBuf[256];
_readLine(lineBuf, sizeof(lineBuf));
if (!strcmp(lineBuf, "FINISHED"))
{
2015-08-31 03:36:24 +00:00
m_loadedBlend = path;
2015-05-24 22:19:28 +00:00
return true;
}
return false;
}
bool BlenderConnection::saveBlend()
{
2015-08-16 23:01:35 +00:00
if (m_lock)
2015-08-05 22:59:59 +00:00
{
BlenderLog.report(LogVisor::FatalError,
"BlenderConnection::saveBlend() musn't be called with stream active");
return false;
}
_writeLine("SAVE");
char lineBuf[256];
_readLine(lineBuf, sizeof(lineBuf));
if (!strcmp(lineBuf, "FINISHED"))
return true;
return false;
}
2015-08-05 22:59:59 +00:00
void BlenderConnection::deleteBlend()
{
if (m_loadedBlend.size())
{
HECL::Unlink(m_loadedBlend.c_str());
2015-08-31 03:36:24 +00:00
BlenderLog.report(LogVisor::Info, _S("Deleted '%s'"), m_loadedBlend.c_str());
2015-08-05 22:59:59 +00:00
m_loadedBlend.clear();
}
}
2015-08-31 03:36:24 +00:00
void BlenderConnection::PyOutStream::linkBlend(const std::string& target, const std::string& objName,
bool link)
{
format("if '%s' not in bpy.data.scenes:\n"
2015-08-08 04:52:20 +00:00
" with bpy.data.libraries.load('''%s''', link=%s, 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 == '%s':\n"
" obj_scene = scene\n"
" break\n"
2015-08-13 07:30:23 +00:00
" if not obj_scene:\n"
2015-08-15 04:12:57 +00:00
" raise RuntimeError('''unable to find %s in %s. 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['%s']\n"
"\n",
2015-08-05 22:59:59 +00:00
objName.c_str(), target.c_str(), link?"True":"False",
2015-08-13 07:30:23 +00:00
objName.c_str(), objName.c_str(), target.c_str(), objName.c_str());
}
2015-07-22 19:14:50 +00:00
bool BlenderConnection::cookBlend(std::function<char*(uint32_t)> bufGetter,
2015-08-08 04:52:20 +00:00
const std::string& expectedType,
const std::string& platform,
bool bigEndian)
2015-05-24 22:19:28 +00:00
{
char lineBuf[256];
char reqLine[512];
snprintf(reqLine, 512, "COOK %s %c", platform.c_str(), bigEndian?'>':'<');
_writeLine(reqLine);
_readLine(lineBuf, sizeof(lineBuf));
if (strcmp(expectedType.c_str(), lineBuf))
{
2015-07-28 02:25:33 +00:00
BlenderLog.report(LogVisor::Error, "expected '%s' to contain '%s' not '%s'",
2015-07-26 02:52:02 +00:00
m_loadedBlend.c_str(), expectedType.c_str(), lineBuf);
2015-05-24 22:19:28 +00:00
return false;
}
_writeLine("ACK");
for (_readLine(lineBuf, sizeof(lineBuf));
!strcmp("BUF", lineBuf);
_readLine(lineBuf, sizeof(lineBuf)))
{
uint32_t sz;
2015-05-26 04:42:20 +00:00
_readBuf((char*)&sz, 4);
char* buf = bufGetter(sz);
2015-05-24 22:19:28 +00:00
_readBuf(buf, sz);
}
if (!strcmp("SUCCESS", lineBuf))
return true;
else if (!strcmp("EXCEPTION", lineBuf))
2015-07-28 02:25:33 +00:00
BlenderLog.report(LogVisor::FatalError, "blender script exception");
2015-05-24 22:19:28 +00:00
return false;
2015-05-24 04:51:16 +00:00
}
2015-07-22 19:14:50 +00:00
void BlenderConnection::quitBlender()
2015-05-24 04:51:16 +00:00
{
2015-05-24 22:19:28 +00:00
_writeLine("QUIT");
2015-05-24 04:51:16 +00:00
char lineBuf[256];
2015-05-24 22:19:28 +00:00
_readLine(lineBuf, sizeof(lineBuf));
2015-05-24 04:51:16 +00:00
}
2015-07-28 02:25:33 +00:00
}