hello,
It's taking more time that i tough. I'll probably work more on it to add it to our sdk example but for now it should give you enough things to play with.The BakeTextureTag is really bigger than this but this should give you an idea.
This will only display a rainbow gradient in the preview gadget.
Important parts are GetDParameterPreview
where you init your previewData with your own function (callback) (in my example named RenderPreviewImage
)
This function have to manage MatPreview Message
the dirtycount is really important.
Use the MSG_DESCRIPTION_COMMAND
inside Message
to react to button interaction and start the thread.
Define a MessageData to react when the thread is finished and do some cleaning there.
You can use your own UserData to pass data to the thread.
I've added some comment but you should just play with this code.
pc11836.h
#ifndef __pc11836_h__
#define __pc11836_h__
class pc11836_TAG;
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Thread that will calculate the gradient
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
class CalculateGradientThread_11836 : public C4DThread
{
public:
virtual void Main(void) override;
virtual const Char* GetThreadName(void) override;
maxon::Result<void> Start(pc11836_TAG* tag);
void Stop();
maxon::JobRef jobRef_;
pc11836_TAG* tag_;
};
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// User Data to pass information to the Init function of the MaterialPreviewData that will be use in RenderPreviewImage (callback)
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
class pc11868_UserData
{
public:
pc11868_UserData();
~pc11868_UserData();
Bool Init();
Bool InitFrom(pc11868_UserData &d);
void Free();
BaseLink* tagLink_;
maxon::Int32 dirtyCount_;
};
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MessageData to clean the thread once it has finished
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
class MyPreviewMessageData : public MessageData
{
public:
virtual maxon::Bool CoreMessage(maxon::Int32 id, const BaseContainer& bc) override;
};
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// The tag itself
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
class pc11836_TAG : public TagData
{
INSTANCEOF(pc11836_TAG, TagData);
public:
static NodeData* Alloc() { return NewObjClear(pc11836_TAG); }
pc11836_TAG();
~pc11836_TAG();
virtual Bool GetDDescription(GeListNode *node, Description *description, DESCFLAGS_DESC &flags);
virtual Bool Init(GeListNode* node) override;
virtual void Free(GeListNode* node) override;
virtual Bool Message(GeListNode* node, Int32 type, void* data) override;
virtual Bool GetDParameter(GeListNode* node, const DescID& id, GeData& t_data, DESCFLAGS_GET& flags) override;
Bool GetDParameterPreview(BaseContainer* pContainer, GeData* pData, DESCFLAGS_GET &lFlags, Int32 lID, BaseObject* pObject);
virtual Bool SetDParameter(GeListNode* node, const DescID& id, const GeData& t_data, DESCFLAGS_SET& flags) override;
// this function have to handle MATPREVIEW message.
static Bool RenderPreviewImage(maxon::Int32 lMessage, void* data, void* userData);
maxon::Int32 dirtycount_;
BaseBitmap* previewBmp_;
CalculateGradientThread_11836 thread_;
maxon::Bool isCalculating_;
BAKE_TEX_ERR bakeResult_;
};
#endif // !"__pc11836.h__"
pc11836.cpp
include "c4d_basedocument.h"
#include "c4d_basebitmap.h"
#include "c4d_general.h"
#include "c4d_tagplugin.h"
#include "c4d_basetag.h"
#include "c4d_customdatatypeplugin.h"
#include "customgui_matpreview.h"
#include "tbase.h"
#include "c4d_messageplugin.h"
#include "lib_description.h"
#include "maxon/job.h"
#include "pc11836.h"
static maxon::Int32 g_PreviewTagID = 1053526;
static maxon::Int32 g_previewMessage = 1053527;
static const maxon::Int32 g_msg_mypreview = 1053519;
static const maxon::Int32 g_msg_mypreview_done = 1053520;
#define RENDER_PREVIEW_ID 1000
#define RENDER_BUTTON_PREVIEW_ID 1001
#define RENDER_BUTTON_DELETE 1002
#define RENDER_GROUP 1003
static const maxon::Int32 g_width = 1920;
static const maxon::Int32 g_height = 720;
static maxon::Spinlock g_myPreviewSpineLock;
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// free job queue
static maxon::SerialJobQueueRef g_serialJobsQueue;
static maxon::UInt g_serialJobsQueueOwnerId;
// free resources at program end
static void FreeQueue()
{
if (g_serialJobsQueue)
{
g_serialJobsQueue.CancelAndWait(g_serialJobsQueueOwnerId);
g_serialJobsQueue = nullptr;
}
}
MAXON_INITIALIZATION(nullptr, FreeQueue);
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Thread
void CalculateGradientThread_11836::Main(void)
{
if (tag_->previewBmp_ == nullptr)
{
tag_->previewBmp_ = BaseBitmap::Alloc();
tag_->previewBmp_->Init(g_width, g_height, 24);
}
BaseBitmap* target = tag_->previewBmp_;
// Calculate the rainbow gradient
maxon::Float widthf = maxon::Float(g_width);
for (auto i = 0; i < g_width; i++)
{
maxon::Vector col = Vector(maxon::Float(i) / widthf, 1, 1);
maxon::Vector colConvert = HSVToRGB(col);
colConvert = colConvert * 255.0;
for (auto j = 0; j < g_height; j++)
{
// set the image
target->SetPixel(i, j, colConvert.x, colConvert.y, colConvert.z);
for (auto k = 0; k < 1000; k++)
{
auto delay = k;
}
tag_->dirtycount_++;
EventAdd(EVENT::NOEXPRESSION);
}
}
// Send a message to specify thread is finished
SpecialEventAdd(g_msg_mypreview, g_msg_mypreview_done, (maxon::UInt)tag_);
}
const Char* CalculateGradientThread_11836::GetThreadName(void)
{
return "CalculateTheRainbowGradientThread_11836";
}
maxon::Result<void> CalculateGradientThread_11836::Start(pc11836_TAG* tag)
{
iferr_scope;
DebugAssert(tag_ == nullptr);
// Prepares the thread setting all informations needed to execute the thread.
tag_ = tag;
BaseTag* basetag = static_cast<BaseTag*>(tag->Get());
BaseObject* obj = basetag->GetObject();
if (obj == nullptr)
{
tag->bakeResult_ = BAKE_TEX_ERR::NO_OBJECT;
tag_ = nullptr;
return maxon::IllegalArgumentError(MAXON_SOURCE_LOCATION);
}
BaseDocument* doc = obj->GetDocument();
if (doc == nullptr)
{
tag->bakeResult_ = BAKE_TEX_ERR::NO_DOC;
tag_ = nullptr;
return maxon::IllegalArgumentError(MAXON_SOURCE_LOCATION);;
}
// creates the queue for the thread
g_serialJobsQueue = maxon::SerialJobQueueRef::Create(maxon::JOBQUEUETYPE::LOWPRIORITY, maxon::JOBQUEUEMODE::PSEUDO_THREADS, "Preview example with thread queue", &g_serialJobsQueueOwnerId) iferr_return;
maxon::JobQueueRef destqueue = maxon::JobQueueInterface::GetDestinationQueue();
jobRef_ = maxon::JobRef::Enqueue(
[this, destqueue]
{
MAXON_WARN_MUTE_UNUSED maxon::JobQueueInterface::SetDestinationQueue(destqueue);
if (!this->C4DThread::Start(THREADMODE::ASYNC, THREADPRIORITYEX::BELOW))
{
CriticalStop();
}
else
{
if (maxon::JobRef::IsCurrentJobCancelled())
this->C4DThread::End(false);
this->C4DThread::Wait();
}
}, g_serialJobsQueue) iferr_return;
return maxon::OK;
}
void CalculateGradientThread_11836::Stop()
{
jobRef_.Cancel();
if (IsRunning())
{
End();
}
jobRef_ = nullptr;
tag_ = nullptr;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// User Data
pc11868_UserData::pc11868_UserData()
{
tagLink_ = nullptr;
}
pc11868_UserData::~pc11868_UserData()
{
Free();
}
Bool pc11868_UserData::Init()
{
if (tagLink_)
return true;
tagLink_ = BaseLink::Alloc();
return tagLink_ != nullptr;
}
Bool pc11868_UserData::InitFrom(pc11868_UserData &d)
{
if (!Init())
return false;
if (d.tagLink_)
d.tagLink_->CopyTo(tagLink_, COPYFLAGS::NONE, nullptr);
return true;
}
void pc11868_UserData::Free()
{
BaseLink::Free(tagLink_);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Tag itself
pc11836_TAG::pc11836_TAG()
{
previewBmp_ = nullptr;
}
pc11836_TAG::~pc11836_TAG()
{
BaseBitmap::Free(previewBmp_);
}
Bool pc11836_TAG::GetDDescription(GeListNode *node, Description *description, DESCFLAGS_DESC &flags)
{
// Creates the UI for the tag, a preview area and two buttons
if (!description->LoadDescription(Tbase))
return false;
const DescID* singleid = description->GetSingleDescID();
DescID cid = DescLevel(RENDER_PREVIEW_ID, CUSTOMDATATYPE_MATPREVIEW, 0);
if (!singleid || cid.IsPartOf(*singleid, nullptr))
{
BaseContainer bc = GetCustomDataTypeDefault(CUSTOMDATATYPE_MATPREVIEW);
bc.SetString(DESC_NAME, "my preview"_s);
bc.SetData(DESC_GUIOPEN, true);
description->SetParameter(cid, bc, DescLevel(ID_TAGPROPERTIES));
}
cid = DescLevel(RENDER_GROUP, DTYPE_GROUP, 0);
if (!singleid || cid.IsPartOf(*singleid, nullptr))
{
BaseContainer bc = GetCustomDataTypeDefault(DTYPE_GROUP);
bc.SetString(DESC_NAME, ""_s);
bc.SetInt32(DESC_COLUMNS, 2);
description->SetParameter(cid, bc, DescLevel(ID_TAGPROPERTIES));
}
cid = DescLevel(RENDER_BUTTON_PREVIEW_ID, DTYPE_BUTTON, 0);
if (!singleid || cid.IsPartOf(*singleid, nullptr))
{
BaseContainer bc = GetCustomDataTypeDefault(DTYPE_BUTTON);
bc.SetInt32(DESC_CUSTOMGUI, CUSTOMGUI_BUTTON);
bc.SetString(DESC_NAME, "Show gradient"_s);
bc.SetInt32(DESC_ANIMATE, DESC_ANIMATE_OFF);
bc.SetInt32(DESC_SCALEH, 1);
description->SetParameter(cid, bc, DescLevel(RENDER_GROUP));
}
cid = DescLevel(RENDER_BUTTON_DELETE, DTYPE_BUTTON, 0);
if (!singleid || cid.IsPartOf(*singleid, nullptr))
{
BaseContainer bc = GetCustomDataTypeDefault(DTYPE_BUTTON);
bc.SetInt32(DESC_CUSTOMGUI, CUSTOMGUI_BUTTON);
bc.SetString(DESC_NAME, "Clear Preview"_s);
bc.SetInt32(DESC_ANIMATE, DESC_ANIMATE_OFF);
bc.SetInt32(DESC_SCALEH, 1);
description->SetParameter(cid, bc, DescLevel(RENDER_GROUP));
}
flags |= DESCFLAGS_DESC::LOADED;
return true;
}
Bool pc11836_TAG::Init(GeListNode* node)
{
BaseList2D* bl = static_cast<BaseList2D*>(node);
if (bl == nullptr)
return false;
BaseContainer* bcData = bl->GetDataInstance();
isCalculating_ = false;
dirtycount_ = 1;
return true;
}
void pc11836_TAG::Free(GeListNode* node)
{
if (isCalculating_)
{
thread_.Stop();
isCalculating_ = false;
}
}
Bool pc11836_TAG::Message(GeListNode* node, Int32 type, void* data)
{
iferr_scope_handler
{
err.DiagOutput();
return true;
};
if (type == MSG_DESCRIPTION_COMMAND)
{
DescriptionCommand* cd = static_cast<DescriptionCommand*>(data);
if (cd == nullptr)
return false;
switch (cd->_descId[0].id)
{
case RENDER_BUTTON_PREVIEW_ID:
{
GeShowMouse(MOUSE_BUSY);
finally
{
GeShowMouse(MOUSE_NORMAL);
};
isCalculating_ = true;
iferr(thread_.Start(this))
{
isCalculating_ = false;
thread_.Stop();
}
dirtycount_++;
break;
}
case RENDER_BUTTON_DELETE:
{
isCalculating_ = false;
dirtycount_++;
thread_.Stop();
BaseBitmap::Free(previewBmp_);
previewBmp_ = nullptr;
EventAdd();
break;
}
}
}
return TagData::Message(node, type, data);
}
Bool pc11836_TAG::GetDParameter(GeListNode* node, const DescID& id, GeData& t_data, DESCFLAGS_GET& flags)
{
if (id[0].id == RENDER_PREVIEW_ID)
{
BaseObject *op = static_cast<BaseObject*>(node);
if (op == nullptr)
return false;
BaseContainer *bc = op->GetDataInstance();
return GetDParameterPreview(bc, (GeData*)&t_data, flags, RENDER_PREVIEW_ID, op);
}
return TagData::GetDParameter(node, id, t_data, flags);
}
Bool pc11836_TAG::GetDParameterPreview(BaseContainer* pContainer, GeData* pData, DESCFLAGS_GET &lFlags, Int32 lID, BaseObject* pObject)
{
MaterialPreviewData* previewData = (MaterialPreviewData*)pContainer->GetCustomDataType(RENDER_PREVIEW_ID, CUSTOMDATATYPE_MATPREVIEW);
// if the materialPreviewData doesn't exit, create and init them.
if (previewData == nullptr)
{
*pData = GeData(CUSTOMDATATYPE_MATPREVIEW, DEFAULTVALUE);
pContainer->SetData(RENDER_PREVIEW_ID, *pData);
previewData = (MaterialPreviewData*)pData->GetCustomDataType(CUSTOMDATATYPE_MATPREVIEW);
if (previewData == nullptr)
return false;
}
else
{
*pData = GeData(CUSTOMDATATYPE_MATPREVIEW, *previewData);
previewData = (MaterialPreviewData*)pData->GetCustomDataType(CUSTOMDATATYPE_MATPREVIEW);
if (previewData == nullptr)
return false;
}
pc11868_UserData pud;
if (!pud.Init())
return false;
pud.tagLink_->SetLink((BaseList2D*)(Get()));
pud.dirtyCount_ = dirtycount_;
if (!previewData->Init(RenderPreviewImage, &pud, RENDER_PREVIEW_ID, dirtycount_))
{
return false;
}
lFlags |= DESCFLAGS_GET::PARAM_GET;
return true;
};
Bool pc11836_TAG::RenderPreviewImage(maxon::Int32 lMessage, void* data, void* userData)
{
switch (lMessage)
{
case MATPREVIEW_GET_OBJECT_INFO:
{
MatPreviewObjectInfo* mpoi = static_cast<MatPreviewObjectInfo*>(data);
mpoi->bHandlePreview = true;
mpoi->bNoStandardScene = true;
// hide all option from popup menu except MATPREVIEW_FLAG_HIDE_OPEN
mpoi->lFlags = MATPREVIEW_FLAG_HIDE_ROTATION | MATPREVIEW_FLAG_HIDE_SCENES | MATPREVIEW_FLAG_HIDE_SCENE_SETTINGS | MATPREVIEW_FLAG_HIDE_SIZE | MATPREVIEW_FLAG_HIDE_ANIMATE;
break;
}
case MATPREVIEW_GENERATE_IMAGE:
{
MatPreviewGenerateImage* mpgi = static_cast<MatPreviewGenerateImage*>(data);
pc11868_UserData* previewData = static_cast<pc11868_UserData*>(userData);
BaseBitmap* bmp = mpgi->pDest;
if (!previewData)
{
mpgi->lResult = RENDERRESULT::OUTOFMEMORY;
return false;
}
if (!previewData->tagLink_)
{
mpgi->lResult = RENDERRESULT::OUTOFMEMORY;
return false;
}
BaseTag* tag = (BaseTag*)(previewData->tagLink_->GetLink(GetActiveDocument(), g_PreviewTagID));
g_myPreviewSpineLock.Lock();
if (tag)
{
pc11836_TAG* tagData = tag->GetNodeData<pc11836_TAG>();
// previewBmp is not nullptr so show the information.
if (tagData->previewBmp_)
{
tagData->previewBmp_->ScaleIt(bmp, 255, true, true);
}
else
{
// show by default a gradient of red
maxon::Float withf = maxon::Float(bmp->GetBw());
for (auto i = 0; i < bmp->GetBw(); i++)
{
maxon::Int32 color = (Float(i) / withf) * 255;
for (auto j = 0; j < bmp->GetBh(); j++)
{
// set the image
bmp->SetPixel(i, j, color, 0, 0);
}
}
}
}
g_myPreviewSpineLock.Unlock();
mpgi->lResult = RENDERRESULT::OK;
break;
}
case MATPREVIEW_MODIFY_CACHE_SCENE:
break;
case MATPREVIEW_PREPARE_SCENE:
{
MatPreviewPrepareScene* mpps = static_cast<MatPreviewPrepareScene*>(data);
mpps->bScenePrepared = true;
break;
}
case MATPREVIEW_GET_PREVIEW_ID:
break;
case MATPREVIEW_GET_POPUP_OPTIONS:
break;
case MATPREVIEW_HANDLE_POPUP_MSG:
break;
case MATPREVIEW_FREE_USERDATA:
{
pc11868_UserData* renderData = static_cast<pc11868_UserData*>(userData);
DeleteObj(renderData);
break;
}
case MATPREVIEW_COPY_USERDATA:
{
MatPreviewCopyUserData* copyData = static_cast<MatPreviewCopyUserData*>(data);
pc11868_UserData* src = static_cast<pc11868_UserData*>(copyData->src);
pc11868_UserData* clone = NewObjClear(pc11868_UserData);
if (clone == nullptr)
return false;
if (!clone->InitFrom(*src))
return false;
copyData->dst = clone;
break;
}
case MATPREVIEW_GET_DIRTY_COUNT:
{
pc11868_UserData* previewData = (pc11868_UserData*)userData;
BaseTag* tag = (BaseTag*)(previewData->tagLink_->GetLink(GetActiveDocument(), g_PreviewTagID));
if (tag)
{
pc11836_TAG* tagData = tag->GetNodeData<pc11836_TAG>();
*((Int32*)data) = tagData->dirtycount_;
}
break;
}
}
return true;
}
Bool pc11836_TAG::SetDParameter(GeListNode* node, const DescID& id, const GeData& t_data, DESCFLAGS_SET& flags)
{
switch (id[0].id)
{
case RENDER_PREVIEW_ID:
{
BaseTag* btag = static_cast<BaseTag*>(node);
BaseContainer* data = btag->GetDataInstance();
return SetDParameterPreview(data, &t_data, flags, RENDER_PREVIEW_ID);
}
break;
}
return TagData::SetDParameter(node, id, t_data, flags);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MessageData to clean the thread once it has finished
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
maxon::Bool MyPreviewMessageData::CoreMessage(maxon::Int32 id, const BaseContainer& bc)
{
if (id == g_msg_mypreview)
{
void *p1 = bc.GetVoid(BFM_CORE_PAR1);
void *p2 = bc.GetVoid(BFM_CORE_PAR2);
// Checks if the message sent is from the thread that have finished.
if ((Int)p1 == g_msg_mypreview_done)
{
pc11836_TAG* tag = (pc11836_TAG*)p2;
if (tag == nullptr)
return false;
StopAllThreads();
tag->thread_.Stop();
tag->isCalculating_ = false;
}
}
return true;
}
static maxon::Result<void> RegisterGeDialogPlugins()
{
iferr_scope;
if (!(RegisterTagPlugin(g_PreviewTagID, "PC11836 tag with preview"_s, TAG_VISIBLE, pc11836_TAG::Alloc, ""_s, nullptr, 0) &&
RegisterMessagePlugin(g_previewMessage, "mypreviewmessages"_s, PLUGINFLAG_HIDEPLUGINMENU, NewObjClear(MyPreviewMessageData))))
return maxon::UnexpectedError(MAXON_SOURCE_LOCATION);
return maxon::OK;
}
Cheers,
Manuel