Displaying the texture baking progress preview.



  • @zipit said in Displaying the texture baking progress preview.:

    c4d.utils.BakeTexture()

    Hi,
    I'm actually using the github repository: py-texture_baker_r18.

    def BakeTextureHook(self, info):
        print(info)
        #pass
    
    def Main(self):
        # Bake Texture Thread Main routine
        self.bakeError = c4d.utils.BakeTexture(self.bakeDoc, self.bakeData, self.bakeBmp, self.Get(), self.BakeTextureHook)
    
        # Sends core message once baking has finished
        c4d.SpecialEventAdd(PLUGIN_ID)
    
    """
    # Print result
    {'state': 10004, 'version': 2, 'r': 0.0, 'starttime': 47079360, 'timedelta': 0}
    {'state': 10004, 'version': 2, 'r': 0.0, 'starttime': 47079360, 'timedelta': 40}
    {'state': 10002, 'version': 2, 'r': 0.0, 'starttime': 47079360, 'timedelta': 55}
    {'state': 10002, 'version': 2, 'r': 0.125, 'starttime': 47079360, 'timedelta': 108}
    {'state': 10002, 'version': 2, 'r': 0.125, 'starttime': 47079360, 'timedelta': 115}
    {'state': 10002, 'version': 2, 'r': 0.25, 'starttime': 47079360, 'timedelta': 144}
    {'state': 10002, 'version': 2, 'r': 0.25, 'starttime': 47079360, 'timedelta': 162}
    {'state': 10002, 'version': 2, 'r': 0.3125, 'starttime': 47079360, 'timedelta': 174}
    {'state': 10002, 'version': 2, 'r': 0.4375, 'starttime': 47079360, 'timedelta': 195}
    {'state': 10002, 'version': 2, 'r': 0.4375, 'starttime': 47079360, 'timedelta': 212}
    {'state': 10002, 'version': 2, 'r': 0.5625, 'starttime': 47079360, 'timedelta': 233}
    {'state': 10002, 'version': 2, 'r': 0.625, 'starttime': 47079360, 'timedelta': 249}
    {'state': 10002, 'version': 2, 'r': 0.625, 'starttime': 47079360, 'timedelta': 251}
    {'state': 10002, 'version': 2, 'r': 0.6875, 'starttime': 47079360, 'timedelta': 270}
    {'state': 10002, 'version': 2, 'r': 0.75, 'starttime': 47079360, 'timedelta': 322}
    {'state': 10002, 'version': 2, 'r': 0.8125, 'starttime': 47079360, 'timedelta': 339}
    {'state': 10002, 'version': 2, 'r': 0.8125, 'starttime': 47079360, 'timedelta': 340}
    {'state': 10002, 'version': 2, 'r': 0.875, 'starttime': 47079360, 'timedelta': 343}
    {'state': 10002, 'version': 2, 'r': 1.0, 'starttime': 47079360, 'timedelta': 348}
    {'state': 10002, 'version': 2, 'r': 1.0, 'starttime': 47079360, 'timedelta': 350}
    {'state': 10003, 'version': 2, 'r': 0.0, 'starttime': 47079360, 'timedelta': 350}
    """
    

    The hook returning the following info: {'state': , 'version': , 'r': , 'starttime': , 'timedelta': } I can use it with to display a Spinning bar BFM_STATUSBAR_PROGRESSSPIN, but that I want to showing is: The baking render progress image.



  • Hi,

    I was aware that you wanted to show the rendering progress. What I meant was, that you can read the data from the MultipassBitmap you own and c4d.utils.BakeTexture() is writing the data to in the callback function. I assumed that BakeTexture() is writing its output directly into the bitmap. But I just tried it and it does not, the bitmap stays empty until the end of the call to BakeTexture().

    The only solution i can think of now is to bake the texture(s) in chunks and then display these chunks. But that seems an awful lot of work for such minor convenience feature.

    Cheers
    zipit



  • @zipit
    Hi,

    Can you can give me a bit of an idea how that technique is working? I really need this, because the baking texture has important role in my plugin. exist examples or similar examples? to understand this technique, and then perhaps I find an intermediary solution.

    Best regards,
    Mustapha



  • Hi,

    you can define the range of uv coordinates in the baking data container. The idea would be to render in chunks (or buckets if this term does make more sense to you). I have used that feature (only baking a region of a texture) before, so I am certain that you can do it. But never in that iterative approach that would be necessary here. It would go something like this:

    """
    We have defined somewhere our baking data container (bake_data), a
     MultipassBitmap (mp_bmp), a BitmapButtonCutomGui (bitmap_button), and a
     BaseBitmap (bmp) to be displayed in that button. This all is meant to take
     place within a GeDialog.
    """
    
    # we want a 10 by 10 bucket grid
    buckets_x, buckets_y = 10, 10
    # just flattening the nested loop to save some horizontal space
    chunks = [(x, y) for x in range(buckets_x) for y in range(buckets_y)]
    # our cell size in half range (i.e. uv space)
    cell_size_u, cell_size_v = 1/buckets_x, 1/buckets_y
    
    # loop over all chunks
    for x, y in chunks:
        # calculate our bucket coordinates
        u_start, v_start = x / buckets_x, y / buckets_y
        u_end, v_end = u_start + cell_size_u, v_start + cell_size_v
        # set the uv data in the baking container
        bake_data[c4d.BAKE_TEX_UV_LEFT] = u_start
        bake_data[c4d.BAKE_TEX_UV_RIGHT] = u_end
        bake_data[c4d.BAKE_TEX_UV_TOP] = v_start
        bake_data[c4d.BAKE_TEX_UV_BOTTOM] = v_end
    
        # do other stuff you might have to do.
    
        # I am not sure if you have to reinit the baker for just chaning the
        # uv coords, you might be able to skip this step
        bake_doc, msg = c4d.utils.InitBakeTexture(
            doc, tex_tag, uvw_tag, my_thread, bake_data)
    
        if not bake_doc:
            print msg
            return
    
        # bake the data
        msg = c4d.utils.BakeTexture(
            bake_doc, bake_data, mp_bmp, my_thread, my_callback)
    
        if msg != c4d.BAKE_TEX_ERR_NONE:
            print msg
            return
    
        # copy the part from the MultipassBitmap (mp_bmp) that just has been
        # written into the BaseBitmap (bmp) for our bitmap button. If you are
        # dealing with multiple channels, you have to get the correct layer from
        # mp_bmp first.
    
        # here you have to do the conversion from half range to pixel coords
        x, y = convert_to_pixel_coords(u_start, v_start)
        w, h = convert_to_pixel_coords(u_end, v_end)
        # copy the data
        mp_bmp.CopyPartTo(bmp, x, y, w, h)
        # display the bitmap, this step is not neccesarry when you did not let
        # SetImage() copy the passed bitmap.
        bitmap_button.SetImage(bmp)
    

    So this would be quite some compromise, as you would introduce more and more overhead as you did increase the granularity of the preview. You might be able to optimize out some of that overhead (see code above), but I am still not convinced that this a good solution.

    Also figuring out how to make the baked result truly seamless, might require some legwork. Just consider the above code a general concept.

    Cheers
    zipit



  • hello,

    this is probably not possible in python, there's no MaterialPreviewData for example.

    You also have to launch your bake in a separate thread and than display the result on another one, you really have to be careful to be thread safe.

    I'm trying to write an example in c++ as short as possible but there's quiet some stuff to be set down.

    Cheers,
    Manuel



  • @m_magalhaes
    Hello,

    I'm waiting your example, I'm searching this from long time. Thank you so much.

    Regards,
    Mustapha



  • 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



  • @m_magalhaes
    Hello Manuel,

    I don't know how much I can thank you for this advanced example and the enormous work. Good job!

    I'm trying to compiling The Cinema 4D R20 C++ SDK Examples, I get some difficulty to setup Visual Studio. Actually I'm using R19 to developing c4d plugins.

    I will return when testing your example

    Thanks again!

    Regards,
    Mustapha



  • @m_magalhaes
    Hello,

    I tried to use this example but I get the error: cannot open source file "maxon/job.h". And many other errors, I suppose because of the job.h that not included.

    I'm using Cinema 4D R19 and Visual Studio 2015, should I include an additional library. Sorry I'm new in C ++ dev and there's a lot of things that I don't know.

    Regards,
    Mustapha



  • You cannot compile R20/R21 stuff directly for R19 and with the R19 libraries. The whole "Maxon" namespace has been added in the recent releases. The whole thread/job stuff has changed, thus I'm afraid that the example is only of limited use to you.
    (Also, the error handling and everything else that starts with maxon::)

    I only skimmed over the code but it looks as if you would have to replace all job stuff with classic threading stuff for R19.



  • hello,

    sorry i forgot the version you were using.
    As this could serve as an example, we must use the new API as much as we can.

    As @Cairyn said you can change the error handling and come back to the old thread system.

    Cheers
    Manuel



  • hello,

    I will passed this thread as solved tomorrow if nothing to add.

    Cheers,
    Manuel