hi,
I'm a bit surprised that your render can be triggered from a NodeData (but why not)
But i don't understand why you can't use GetDDescription to update your node.
That's what i did (in a probably too naive way) but you got the idea. I just got a boolean display_aboard  on my ObjectData and i switch it as i needed.
If i presse the button, i switch and i set dirty my node, if i receive the message that the render is finished (or stopped) i set my bool to false and i SetDirty my node.
static const Int32 g_pc12584ButtonID = 1000;
	
class pc12584_object : public ObjectData
{
	INSTANCEOF(pc12584_object, ObjectData);
public:
	static NodeData* Alloc() { return NewObjClear(pc12584_object); };
	Bool Init(GeListNode* node) override
	{
		display_aboard = false;
		return true;
	}
	BaseObject* GetVirtualObjects(BaseObject* op, HierarchyHelp* hh) override
	{
		BaseObject* null = BaseObject::Alloc(Onull);
		return null;
	}
	Bool GetDDescription(GeListNode* node, Description* description, DESCFLAGS_DESC& flags) override
	{
		
		if (!description->LoadDescription(Obase))
			return false;
		
		const DescID* singleid = description->GetSingleDescID();
		const Int32 ID = g_pc12584ButtonID;
		const DescID cid = DescLevel(ID, DTYPE_BUTTON, 0);
		if (!singleid || cid.IsPartOf(*singleid, nullptr))
		{
			BaseContainer bc = GetCustomDataTypeDefault(DTYPE_BUTTON);
			bc.SetInt32(DESC_CUSTOMGUI, CUSTOMGUI_BUTTON);
			if (display_aboard)
				bc.SetString(DESC_NAME, "Aboard Render"_s);
			else
				bc.SetString(DESC_NAME, "Render"_s);
			bc.SetInt32(DESC_ANIMATE, DESC_ANIMATE_OFF);
			bc.SetInt32(DESC_SCALEH, 1);
			description->SetParameter(cid, bc, DescLevel(ID_OBJECTPROPERTIES));
		}
		flags |= DESCFLAGS_DESC::LOADED;
		
		return SUPER::GetDDescription(node, description, flags);
	}
	Bool Message(GeListNode* node, Int32 type, void* data) override
	{
		switch (type)
		{
		case MSG_DESCRIPTION_COMMAND:
		{
			DescriptionCommand* dc = (DescriptionCommand*)data;
			const Int32 id = dc->_descId[0].id;
			if (id == g_pc12584ButtonID)
			{
				display_aboard = !display_aboard;
				node->SetDirty(DIRTYFLAGS::DESCRIPTION);
			}
			break;
		}
		case g_pc12584Message:
		{
			display_aboard = false;
			node->SetDirty(DIRTYFLAGS::DESCRIPTION);
			break;
		}
		}
		return SUPER::Message(node, type, data);
	};
private:
	Bool display_aboard = false;
};
// function to send a message to the ObjectData.
static maxon::Result<void> PC12854_Message(BaseDocument* doc)
{
	iferr_scope;
	// update the first object 
	BaseObject* op = doc->GetFirstObject();
	if (op == nullptr)
		return maxon::NullptrError(MAXON_SOURCE_LOCATION);
	op->Message(g_pc12584Message);
	return maxon::OK;
}
Cheers,
Manuel