2015-07-22 19:14:50 +00:00
|
|
|
#ifndef BLENDERCONNECTION_HPP
|
|
|
|
#define BLENDERCONNECTION_HPP
|
2015-05-23 21:59:40 +00:00
|
|
|
|
2015-05-24 04:51:16 +00:00
|
|
|
#if _WIN32
|
2015-08-31 03:36:24 +00:00
|
|
|
#ifndef WIN32_LEAN_AND_MEAN
|
|
|
|
#define WIN32_LEAN_AND_MEAN 1
|
|
|
|
#endif
|
2015-09-27 04:35:36 +00:00
|
|
|
#ifndef NOMINMAX
|
|
|
|
#define NOMINMAX
|
|
|
|
#endif
|
2015-05-24 04:51:16 +00:00
|
|
|
#include <windows.h>
|
|
|
|
#else
|
2015-05-23 21:59:40 +00:00
|
|
|
#include <unistd.h>
|
2015-05-24 04:51:16 +00:00
|
|
|
#endif
|
2015-05-23 21:59:40 +00:00
|
|
|
|
2015-07-22 19:14:50 +00:00
|
|
|
#include <stdint.h>
|
2015-08-03 02:05:04 +00:00
|
|
|
#include <stdio.h>
|
2015-05-24 22:19:28 +00:00
|
|
|
#include <string>
|
|
|
|
#include <functional>
|
|
|
|
|
2015-07-28 02:25:33 +00:00
|
|
|
#include "HECL/HECL.hpp"
|
|
|
|
|
|
|
|
namespace HECL
|
|
|
|
{
|
|
|
|
|
|
|
|
extern LogVisor::LogModule BlenderLog;
|
|
|
|
extern class BlenderConnection* SharedBlenderConnection;
|
|
|
|
|
2015-07-22 19:14:50 +00:00
|
|
|
class BlenderConnection
|
2015-05-23 21:59:40 +00:00
|
|
|
{
|
2015-08-31 03:36:24 +00:00
|
|
|
bool m_lock = false;
|
2015-05-24 04:51:16 +00:00
|
|
|
#if _WIN32
|
|
|
|
HANDLE m_blenderProc;
|
|
|
|
#else
|
2015-05-23 21:59:40 +00:00
|
|
|
pid_t m_blenderProc;
|
2015-08-31 03:36:24 +00:00
|
|
|
#endif
|
2015-05-24 04:51:16 +00:00
|
|
|
int m_readpipe[2];
|
|
|
|
int m_writepipe[2];
|
2015-08-31 03:36:24 +00:00
|
|
|
SystemString m_loadedBlend;
|
2015-09-06 20:08:23 +00:00
|
|
|
std::string m_startupBlend;
|
2015-05-24 22:19:28 +00:00
|
|
|
size_t _readLine(char* buf, size_t bufSz);
|
|
|
|
size_t _writeLine(const char* buf);
|
2015-10-02 04:06:45 +00:00
|
|
|
size_t _readBuf(void* buf, size_t len);
|
|
|
|
size_t _writeBuf(const void* buf, size_t len);
|
2015-05-24 22:19:28 +00:00
|
|
|
void _closePipe();
|
2015-05-23 21:59:40 +00:00
|
|
|
public:
|
2015-09-22 01:41:38 +00:00
|
|
|
BlenderConnection(int verbosityLevel=1);
|
2015-07-22 19:14:50 +00:00
|
|
|
~BlenderConnection();
|
2015-05-24 04:51:16 +00:00
|
|
|
|
2015-10-01 00:40:06 +00:00
|
|
|
enum BlendType
|
|
|
|
{
|
|
|
|
TypeNone,
|
|
|
|
TypeMesh,
|
|
|
|
TypeActor,
|
|
|
|
TypeArea
|
|
|
|
};
|
|
|
|
|
|
|
|
bool createBlend(const SystemString& path, BlendType type);
|
|
|
|
BlendType getBlendType();
|
2015-07-28 02:25:33 +00:00
|
|
|
bool openBlend(const SystemString& path);
|
2015-08-04 21:37:12 +00:00
|
|
|
bool saveBlend();
|
2015-08-05 22:59:59 +00:00
|
|
|
void deleteBlend();
|
2015-07-25 23:01:02 +00:00
|
|
|
|
|
|
|
class PyOutStream : public std::ostream
|
|
|
|
{
|
|
|
|
friend class BlenderConnection;
|
|
|
|
BlenderConnection* m_parent;
|
2015-08-05 22:59:59 +00:00
|
|
|
bool m_deleteOnError;
|
2015-07-25 23:01:02 +00:00
|
|
|
struct StreamBuf : std::streambuf
|
|
|
|
{
|
2015-08-05 22:59:59 +00:00
|
|
|
PyOutStream& m_parent;
|
2015-07-28 02:25:33 +00:00
|
|
|
std::string m_lineBuf;
|
2015-08-05 22:59:59 +00:00
|
|
|
bool m_deleteOnError;
|
|
|
|
StreamBuf(PyOutStream& parent, bool deleteOnError)
|
|
|
|
: m_parent(parent), m_deleteOnError(deleteOnError) {}
|
2015-07-25 23:01:02 +00:00
|
|
|
StreamBuf(const StreamBuf& other) = delete;
|
|
|
|
StreamBuf(StreamBuf&& other) = default;
|
|
|
|
int_type overflow(int_type ch)
|
|
|
|
{
|
2015-08-16 23:01:35 +00:00
|
|
|
if (!m_parent.m_parent || !m_parent.m_parent->m_lock)
|
2015-08-05 22:59:59 +00:00
|
|
|
BlenderLog.report(LogVisor::FatalError, "lock not held for PyOutStream writing");
|
2015-08-31 03:36:24 +00:00
|
|
|
if (ch != traits_type::eof() && ch != '\n' && ch != '\0')
|
2015-07-25 23:01:02 +00:00
|
|
|
{
|
2015-07-28 02:25:33 +00:00
|
|
|
m_lineBuf += char_type(ch);
|
|
|
|
return ch;
|
2015-07-25 23:01:02 +00:00
|
|
|
}
|
2015-08-31 03:36:24 +00:00
|
|
|
//printf("FLUSHING %s\n", m_lineBuf.c_str());
|
2015-08-05 22:59:59 +00:00
|
|
|
m_parent.m_parent->_writeLine(m_lineBuf.c_str());
|
2015-07-28 02:25:33 +00:00
|
|
|
char readBuf[16];
|
2015-08-05 22:59:59 +00:00
|
|
|
m_parent.m_parent->_readLine(readBuf, 16);
|
2015-07-28 02:25:33 +00:00
|
|
|
if (strcmp(readBuf, "OK"))
|
2015-08-05 22:59:59 +00:00
|
|
|
{
|
|
|
|
if (m_deleteOnError)
|
|
|
|
m_parent.m_parent->deleteBlend();
|
2015-07-28 02:25:33 +00:00
|
|
|
BlenderLog.report(LogVisor::FatalError, "error sending '%s' to blender", m_lineBuf.c_str());
|
2015-08-05 22:59:59 +00:00
|
|
|
}
|
2015-07-28 02:25:33 +00:00
|
|
|
m_lineBuf.clear();
|
2015-07-25 23:01:02 +00:00
|
|
|
return ch;
|
|
|
|
}
|
|
|
|
} m_sbuf;
|
2015-08-05 22:59:59 +00:00
|
|
|
PyOutStream(BlenderConnection* parent, bool deleteOnError)
|
2015-09-03 01:43:20 +00:00
|
|
|
: std::ostream(&m_sbuf),
|
|
|
|
m_parent(parent),
|
2015-08-05 22:59:59 +00:00
|
|
|
m_deleteOnError(deleteOnError),
|
2015-09-03 01:43:20 +00:00
|
|
|
m_sbuf(*this, deleteOnError)
|
2015-07-25 23:01:02 +00:00
|
|
|
{
|
2015-08-16 23:01:35 +00:00
|
|
|
m_parent->m_lock = true;
|
2015-07-28 02:25:33 +00:00
|
|
|
m_parent->_writeLine("PYBEGIN");
|
|
|
|
char readBuf[16];
|
|
|
|
m_parent->_readLine(readBuf, 16);
|
|
|
|
if (strcmp(readBuf, "READY"))
|
|
|
|
BlenderLog.report(LogVisor::FatalError, "unable to open PyOutStream with blender");
|
2015-07-25 23:01:02 +00:00
|
|
|
}
|
|
|
|
public:
|
|
|
|
PyOutStream(const PyOutStream& other) = delete;
|
|
|
|
PyOutStream(PyOutStream&& other)
|
2015-09-03 01:43:20 +00:00
|
|
|
: std::ostream(&m_sbuf), m_parent(other.m_parent), m_sbuf(std::move(other.m_sbuf))
|
2015-07-25 23:01:02 +00:00
|
|
|
{other.m_parent = nullptr;}
|
2015-08-05 22:59:59 +00:00
|
|
|
~PyOutStream() {close();}
|
|
|
|
void close()
|
2015-07-25 23:01:02 +00:00
|
|
|
{
|
2015-08-16 23:01:35 +00:00
|
|
|
if (m_parent && m_parent->m_lock)
|
2015-07-28 02:25:33 +00:00
|
|
|
{
|
2015-08-16 23:01:35 +00:00
|
|
|
m_parent->_writeLine("PYEND");
|
|
|
|
char readBuf[16];
|
|
|
|
m_parent->_readLine(readBuf, 16);
|
|
|
|
if (strcmp(readBuf, "DONE"))
|
|
|
|
BlenderLog.report(LogVisor::FatalError, "unable to close PyOutStream with blender");
|
|
|
|
m_parent->m_lock = false;
|
2015-07-28 02:25:33 +00:00
|
|
|
}
|
2015-07-25 23:01:02 +00:00
|
|
|
}
|
2015-09-24 01:00:30 +00:00
|
|
|
#if __GNUC__
|
|
|
|
__attribute__((__format__ (__printf__, 2, 3)))
|
|
|
|
#endif
|
2015-08-03 02:05:04 +00:00
|
|
|
void format(const char* fmt, ...)
|
|
|
|
{
|
2015-08-16 23:01:35 +00:00
|
|
|
if (!m_parent || !m_parent->m_lock)
|
2015-08-05 22:59:59 +00:00
|
|
|
BlenderLog.report(LogVisor::FatalError, "lock not held for PyOutStream::format()");
|
2015-08-03 02:05:04 +00:00
|
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
|
|
char* result = nullptr;
|
2015-08-31 03:36:24 +00:00
|
|
|
#ifdef _WIN32
|
|
|
|
int length = _vscprintf(fmt, ap);
|
|
|
|
result = (char*)malloc(length);
|
|
|
|
vsnprintf(result, length, fmt, ap);
|
|
|
|
#else
|
2015-08-03 02:05:04 +00:00
|
|
|
int length = vasprintf(&result, fmt, ap);
|
2015-08-31 03:36:24 +00:00
|
|
|
#endif
|
|
|
|
va_end(ap);
|
2015-08-03 02:05:04 +00:00
|
|
|
if (length > 0)
|
|
|
|
this->write(result, length);
|
|
|
|
free(result);
|
|
|
|
}
|
2015-08-31 03:36:24 +00:00
|
|
|
void linkBlend(const std::string& target, const std::string& objName, bool link=true);
|
2015-09-01 00:26:35 +00:00
|
|
|
|
|
|
|
class ANIMOutStream
|
|
|
|
{
|
|
|
|
BlenderConnection* m_parent;
|
|
|
|
unsigned m_curCount = 0;
|
|
|
|
unsigned m_totalCount = 0;
|
|
|
|
bool m_inCurve = false;
|
|
|
|
public:
|
|
|
|
enum CurveType
|
|
|
|
{
|
|
|
|
CurveRotate,
|
|
|
|
CurveTranslate,
|
|
|
|
CurveScale
|
|
|
|
};
|
|
|
|
ANIMOutStream(BlenderConnection* parent)
|
|
|
|
: m_parent(parent)
|
|
|
|
{
|
|
|
|
m_parent->_writeLine("PYANIM");
|
|
|
|
char readBuf[16];
|
|
|
|
m_parent->_readLine(readBuf, 16);
|
|
|
|
if (strcmp(readBuf, "ANIMREADY"))
|
|
|
|
BlenderLog.report(LogVisor::FatalError, "unable to open ANIMOutStream");
|
|
|
|
}
|
|
|
|
~ANIMOutStream()
|
|
|
|
{
|
|
|
|
char tp = -1;
|
|
|
|
m_parent->_writeBuf(&tp, 1);
|
|
|
|
char readBuf[16];
|
|
|
|
m_parent->_readLine(readBuf, 16);
|
|
|
|
if (strcmp(readBuf, "ANIMDONE"))
|
|
|
|
BlenderLog.report(LogVisor::FatalError, "unable to close ANIMOutStream");
|
|
|
|
}
|
|
|
|
void changeCurve(CurveType type, unsigned crvIdx, unsigned keyCount)
|
|
|
|
{
|
|
|
|
if (m_curCount != m_totalCount)
|
|
|
|
BlenderLog.report(LogVisor::FatalError, "incomplete ANIMOutStream for change");
|
|
|
|
m_curCount = 0;
|
|
|
|
m_totalCount = keyCount;
|
|
|
|
char tp = char(type);
|
|
|
|
m_parent->_writeBuf(&tp, 1);
|
|
|
|
struct
|
|
|
|
{
|
|
|
|
uint32_t ci;
|
|
|
|
uint32_t kc;
|
|
|
|
} info = {uint32_t(crvIdx), uint32_t(keyCount)};
|
|
|
|
m_parent->_writeBuf(reinterpret_cast<const char*>(&info), 8);
|
|
|
|
m_inCurve = true;
|
|
|
|
}
|
|
|
|
void write(unsigned frame, float val)
|
|
|
|
{
|
|
|
|
if (!m_inCurve)
|
|
|
|
BlenderLog.report(LogVisor::FatalError, "changeCurve not called before write");
|
|
|
|
if (m_curCount < m_totalCount)
|
|
|
|
{
|
|
|
|
struct
|
|
|
|
{
|
|
|
|
uint32_t frm;
|
|
|
|
float val;
|
|
|
|
} key = {uint32_t(frame), val};
|
|
|
|
m_parent->_writeBuf(reinterpret_cast<const char*>(&key), 8);
|
|
|
|
++m_curCount;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
BlenderLog.report(LogVisor::FatalError, "ANIMOutStream keyCount overflow");
|
|
|
|
}
|
|
|
|
};
|
|
|
|
ANIMOutStream beginANIMCurve()
|
|
|
|
{
|
|
|
|
return ANIMOutStream(m_parent);
|
|
|
|
}
|
2015-07-25 23:01:02 +00:00
|
|
|
};
|
2015-10-02 04:06:45 +00:00
|
|
|
PyOutStream beginPythonOut(bool deleteOnError=false)
|
2015-07-25 23:01:02 +00:00
|
|
|
{
|
2015-08-16 23:01:35 +00:00
|
|
|
if (m_lock)
|
|
|
|
BlenderLog.report(LogVisor::FatalError, "lock already held for BlenderConnection::beginPythonOut()");
|
2015-08-05 22:59:59 +00:00
|
|
|
return PyOutStream(this, deleteOnError);
|
2015-07-25 23:01:02 +00:00
|
|
|
}
|
|
|
|
|
2015-10-02 04:06:45 +00:00
|
|
|
class DataStream
|
|
|
|
{
|
|
|
|
friend class BlenderConnection;
|
|
|
|
BlenderConnection* m_parent;
|
|
|
|
DataStream(BlenderConnection* parent)
|
|
|
|
: m_parent(parent)
|
|
|
|
{
|
|
|
|
m_parent->m_lock = true;
|
|
|
|
m_parent->_writeLine("DATABEGIN");
|
|
|
|
char readBuf[16];
|
|
|
|
m_parent->_readLine(readBuf, 16);
|
|
|
|
if (strcmp(readBuf, "READY"))
|
|
|
|
BlenderLog.report(LogVisor::FatalError, "unable to open DataStream with blender");
|
|
|
|
}
|
|
|
|
public:
|
|
|
|
DataStream(const DataStream& other) = delete;
|
|
|
|
DataStream(DataStream&& other)
|
|
|
|
: m_parent(other.m_parent) {other.m_parent = nullptr;}
|
|
|
|
~DataStream() {close();}
|
|
|
|
void close()
|
|
|
|
{
|
|
|
|
if (m_parent && m_parent->m_lock)
|
|
|
|
{
|
|
|
|
m_parent->_writeLine("DATAEND");
|
|
|
|
char readBuf[16];
|
|
|
|
m_parent->_readLine(readBuf, 16);
|
|
|
|
if (strcmp(readBuf, "DONE"))
|
|
|
|
BlenderLog.report(LogVisor::FatalError, "unable to close DataStream with blender");
|
|
|
|
m_parent->m_lock = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<std::string> getMeshList()
|
|
|
|
{
|
|
|
|
m_parent->_writeLine("MESHLIST");
|
|
|
|
uint32_t count;
|
|
|
|
m_parent->_readBuf(&count, 4);
|
|
|
|
std::vector<std::string> retval;
|
|
|
|
retval.reserve(count);
|
|
|
|
for (int i=0 ; i<count ; ++i)
|
|
|
|
{
|
|
|
|
char name[128];
|
|
|
|
m_parent->_readLine(name, 128);
|
|
|
|
retval.push_back(name);
|
|
|
|
}
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Intermediate mesh representation prepared by blender from a single mesh object */
|
|
|
|
struct Mesh
|
|
|
|
{
|
|
|
|
/* HECL source of each material */
|
|
|
|
std::vector<std::string> materials;
|
|
|
|
|
|
|
|
/* Encapsulates mesh data up to maximum indexing space,
|
|
|
|
* overflowing to additional Submeshes as needed */
|
|
|
|
struct Submesh
|
|
|
|
{
|
|
|
|
/* Vertex buffer data */
|
|
|
|
struct Vector2f
|
|
|
|
{
|
|
|
|
float val[2];
|
|
|
|
Vector2f(BlenderConnection& conn) {conn._readBuf(val, 8);}
|
|
|
|
};
|
|
|
|
struct Vector3f
|
|
|
|
{
|
|
|
|
float val[3];
|
|
|
|
Vector3f(BlenderConnection& conn) {conn._readBuf(val, 12);}
|
|
|
|
};
|
|
|
|
struct Vector4f
|
|
|
|
{
|
|
|
|
float val[4];
|
|
|
|
Vector4f(BlenderConnection& conn) {conn._readBuf(val, 16);}
|
|
|
|
};
|
|
|
|
struct Index
|
|
|
|
{
|
|
|
|
uint32_t val;
|
|
|
|
Index(BlenderConnection& conn) {conn._readBuf(&val, 4);}
|
|
|
|
};
|
|
|
|
std::vector<Vector3f> pos;
|
|
|
|
std::vector<Vector3f> norm;
|
|
|
|
uint32_t colorLayerCount = 0;
|
|
|
|
std::vector<Vector4f> color[4];
|
|
|
|
uint32_t uvLayerCount = 0;
|
|
|
|
std::vector<Vector2f> uv[8];
|
|
|
|
|
|
|
|
/* Skinning data */
|
|
|
|
std::vector<std::string> boneNames;
|
|
|
|
struct SkinBind
|
|
|
|
{
|
|
|
|
uint32_t boneIdx;
|
|
|
|
float weight;
|
|
|
|
SkinBind(BlenderConnection& conn) {conn._readBuf(&boneIdx, 8);}
|
|
|
|
};
|
|
|
|
std::vector<std::vector<SkinBind>> skins;
|
|
|
|
std::vector<std::vector<Index>> skinBanks;
|
|
|
|
|
|
|
|
/* Islands of the same material/skinBank are represented here */
|
|
|
|
struct Surface
|
|
|
|
{
|
|
|
|
Vector3f centroid;
|
|
|
|
Index materialIdx;
|
|
|
|
Vector3f aabbMin;
|
|
|
|
Vector3f aabbMax;
|
|
|
|
Vector3f reflectionNormal;
|
|
|
|
Index skinBankIdx;
|
|
|
|
|
|
|
|
/* Vertex indexing data */
|
|
|
|
struct Vert
|
|
|
|
{
|
|
|
|
uint32_t iPos;
|
|
|
|
uint32_t iNorm;
|
|
|
|
uint32_t iColor[4] = {uint32_t(-1)};
|
|
|
|
uint32_t iUv[8] = {uint32_t(-1)};
|
|
|
|
uint32_t iSkin;
|
|
|
|
|
|
|
|
Vert(BlenderConnection& conn, const Submesh& parent);
|
|
|
|
};
|
|
|
|
std::vector<Vert> verts;
|
|
|
|
|
|
|
|
Surface(BlenderConnection& conn, const Submesh& parent);
|
|
|
|
};
|
|
|
|
std::vector<Surface> surfaces;
|
|
|
|
|
|
|
|
Submesh(BlenderConnection& conn);
|
|
|
|
};
|
|
|
|
std::vector<Submesh> submeshes;
|
|
|
|
|
|
|
|
Mesh(BlenderConnection& conn);
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Compile mesh by name */
|
|
|
|
Mesh compileMesh(const std::string& name, int maxIdx=65535, int maxSkinBanks=10)
|
|
|
|
{
|
|
|
|
char req[128];
|
|
|
|
snprintf(req, 128, "MESHCOMPILE %s %d %d", name.c_str(), maxIdx, maxSkinBanks);
|
|
|
|
m_parent->_writeLine(req);
|
|
|
|
|
|
|
|
char readBuf[256];
|
|
|
|
m_parent->_readLine(readBuf, 256);
|
|
|
|
if (strcmp(readBuf, "OK"))
|
|
|
|
BlenderLog.report(LogVisor::FatalError, "unable to cook mesh '%s': %s", name.c_str(), readBuf);
|
|
|
|
|
|
|
|
return Mesh(*m_parent);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Compile all meshes into one */
|
|
|
|
Mesh compileAllMeshes(int maxIdx=65535, int maxSkinBanks=10)
|
|
|
|
{
|
|
|
|
char req[128];
|
|
|
|
snprintf(req, 128, "MESHCOMPILEALL %d %d", maxIdx, maxSkinBanks);
|
|
|
|
m_parent->_writeLine(req);
|
|
|
|
|
|
|
|
char readBuf[256];
|
|
|
|
m_parent->_readLine(readBuf, 256);
|
|
|
|
if (strcmp(readBuf, "OK"))
|
|
|
|
BlenderLog.report(LogVisor::FatalError, "unable to cook all meshes: %s", readBuf);
|
|
|
|
|
|
|
|
return Mesh(*m_parent);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
DataStream beginData()
|
|
|
|
{
|
|
|
|
if (m_lock)
|
|
|
|
BlenderLog.report(LogVisor::FatalError, "lock already held for BlenderConnection::beginDataIn()");
|
|
|
|
return DataStream(this);
|
|
|
|
}
|
|
|
|
|
2015-05-24 04:51:16 +00:00
|
|
|
void quitBlender();
|
2015-07-28 02:25:33 +00:00
|
|
|
|
2015-10-02 04:06:45 +00:00
|
|
|
static BlenderConnection& SharedConnection()
|
2015-07-28 02:25:33 +00:00
|
|
|
{
|
|
|
|
if (!SharedBlenderConnection)
|
2015-09-22 01:41:38 +00:00
|
|
|
SharedBlenderConnection = new BlenderConnection(HECL::VerbosityLevel);
|
2015-07-28 02:25:33 +00:00
|
|
|
return *SharedBlenderConnection;
|
|
|
|
}
|
|
|
|
|
2015-10-02 04:06:45 +00:00
|
|
|
void closeStream()
|
2015-08-16 23:01:35 +00:00
|
|
|
{
|
|
|
|
if (m_lock)
|
|
|
|
deleteBlend();
|
|
|
|
}
|
|
|
|
|
2015-10-02 04:06:45 +00:00
|
|
|
static void Shutdown()
|
2015-07-28 02:25:33 +00:00
|
|
|
{
|
2015-08-16 23:01:35 +00:00
|
|
|
if (SharedBlenderConnection)
|
|
|
|
{
|
|
|
|
SharedBlenderConnection->closeStream();
|
|
|
|
SharedBlenderConnection->quitBlender();
|
|
|
|
delete SharedBlenderConnection;
|
|
|
|
SharedBlenderConnection = nullptr;
|
|
|
|
BlenderLog.report(LogVisor::Info, "BlenderConnection Shutdown Successful");
|
|
|
|
}
|
2015-07-28 02:25:33 +00:00
|
|
|
}
|
2015-08-16 23:01:35 +00:00
|
|
|
|
2015-05-23 21:59:40 +00:00
|
|
|
};
|
|
|
|
|
2015-07-28 02:25:33 +00:00
|
|
|
}
|
|
|
|
|
2015-07-22 19:14:50 +00:00
|
|
|
#endif // BLENDERCONNECTION_HPP
|