Update Attribute Manager after SetParameter

On 13/06/2018 at 10:00, xxxxxxxx wrote:

User Information:
Cinema 4D Version:   19 
Platform:   Windows  ;   
Language(s) :     C++  ;

---------
I'm sure I'm missing something obvious, but having not used the C++ SDK for quite a while...

I'm updating a few parameters from a custom function. This is an ObjectData plugin with a few string and long fields. NR_FRAMEINDEX in this case is LONG.

// a static callback function
void(__stdcall NeuronReader::BVHFrameDataReceived)(void* customObj, SOCKET_REF sender, BvhDataHeader* header, float* data) {
	NeuronReader* pthis = (NeuronReader* )customObj;
	BaseObject *op = (BaseObject* )(pthis->Get());
	pthis->ReadNeuronBvhData(sender, header, data, op);
}
  
// this should update the parameters
void NeuronReader::ReadNeuronBvhData(SOCKET_REF sender, BvhDataHeader* header, float* data, BaseObject* op) {
	if (sender) {
		Int32 frameIndex = (Int32)(header->FrameIndex);
		op->SetParameter(DescID(NR_FRAMEINDEX), GeData(frameIndex), DESCFLAGS_SET_0);
		//op->SetDirty(DIRTYFLAGS_DATA);
		//op->Message(MSG_CHANGE);
		//EventAdd(EVENT_ANIMATE);
	}
}

This  feels straightforward enough. Checking the NR_FRAMEINDEX parameter via GeConsoleOut shows me that it writes the correct values. However, the Attribute Manager attribute never gets updates.

Do I need to go through some SetDParameter magic or something? The various EventAdds and MSG_Change etc don't seem to do anything here.

On 14/06/2018 at 03:58, xxxxxxxx wrote:

I don't get it. I have now gone and implemented a (feeling somewhat convoluted) way to make sure that the SetParameter happens in the MainThread (by creating a MessageData plugin, sending a SpecialEventAdd() there containing a pointer to my object, sending a GePluginMessage() back (again containing the pointer) and then calling a function to apply the parameter.
I'm checking with GeIsMainThread() all along the way. Yet the UI will not update. Argh!

On 14/06/2018 at 09:50, xxxxxxxx wrote:

Hi,

to be honest I don't immediately see the issue, either.
Normally a EventAdd() (and also your EventAdd(EVENT_ANIMATE)) should do the trick.
You seem to have had the same sus**cion as I had in your second post. Nevertheless I'd like to ask in which context the functions you posted are called/used? Maybe a rough description of the structure of your ObjectData could help as well.
In any case I will discuss this tomorrow with the team.

On 15/06/2018 at 02:32, xxxxxxxx wrote:

This should be all the relevant code...

This is the output I get:

Registered the NeuronReader plugin
Connected to Neuron server at 127.0.0.1:7001
Demo message
IS MainThread
ApplyNeuronData running in MainThread // Modifying:1004
Before: 0
MSG_DESCRIPTION_POSTSETPARAMETER: 1004
POSTSETPARAMETER_VALUE:40
After: 40

So the parameter is set correctly internally, but somehow it does not get reflected in the UI at all.

// This is a callback function, being called from a threaded context
void(__stdcall ONeuronReader::BVHFrameDataReceived)(void* customObj, SOCKET_REF sender, BvhDataHeader* header, float* data) {
    ONeuronReader* pthis = (ONeuronReader* )customObj;
    BaseObject *op = (BaseObject* )(pthis->Get());
    pthis->ReadNeuronBvhData(sender, header, data, op);
}
  
// This does nothing other than send a MessageData, which in turn sends a GePluginMessage() to this plugin
void ONeuronReader::ReadNeuronBvhData(SOCKET_REF sender, BvhDataHeader* header, float* data, BaseObject* op) {
    if (sender) {
        SpecialEventAdd(MSG_NEURONMESSAGE, 0, (UInt)op);
    }
}
  
// Thanks to being called from PluginMessage, this is running in the MainThread (does it even have to?)
// Attempting to set the LONG parameter to a test value of "40"
// Value appears to be set correctly, however UI does not change
void ONeuronReader::ApplyNeuronData(BaseObject *op) {
    if (GeIsMainThread()) {
        GeConsoleOut("ApplyNeuronData running in MainThread // Modifying:" \+ String::IntToString(NR_FRAMEINDEX));
        GeData frameIndex;
        op->GetParameter(NR_FRAMEINDEX, frameIndex, DESCFLAGS_GET_0);
        GeConsoleOut("Before: " \+ String::IntToString(frameIndex.GetInt32()));
        op->SetParameter(DescID(NR_FRAMEINDEX), GeData(40), DESCFLAGS_SET_0);
        op->Message(MSG_CHANGE);
        EventAdd();
        op->GetParameter(NR_FRAMEINDEX, frameIndex, DESCFLAGS_GET_0);
        GeConsoleOut("After: " \+ String::IntToString(frameIndex.GetInt32()));
    }
    else {
        GeConsoleOut("ApplyNeuronData NOT running in MainThread");
    }
}
  
BaseObject* ONeuronReader::GetVirtualObjects(BaseObject* op, HierarchyHelp* hh) {
    return BaseObject::Alloc(Onull);
}
  
Bool ONeuronReader::Message(GeListNode* node, Int32 type, void* data) {
    BaseContainer* bc;
...
    else if (type == MSG_DESCRIPTION_POSTSETPARAMETER)
    {
        // Get message data
        DescriptionPostSetValue* dparm = (DescriptionPostSetValue* )data;
        Int32 parameterId = (*(dparm->descid))[0].id;
        bc = static_cast<BaseObject*>(node)->GetDataInstance();
        GeConsoleOut("MSG_DESCRIPTION_POSTSETPARAMETER: " \+ String::IntToString(parameterId));
        bc->SetInt32(NR_FRAMEINDEX, bc->GetInt32(NR_FRAMEINDEX));
  
        switch (parameterId)
        {
        case NR_FRAMEINDEX:
            GeConsoleOut("POSTSETPARAMETER_VALUE:" \+ String::IntToString(bc->GetInt32(NR_FRAMEINDEX)));
            break;
        }
    }
...
    return SUPER::Message(node, type, data);
}
  
Bool NeuronMessage::CoreMessage(Int32 id, const BaseContainer& bc)
{
    switch (id)
    {
    case MSG_NEURONMESSAGE:
        GeConsoleOut("Demo message");
        BaseObject* pointer_op = (BaseObject* )bc.GetVoid(BFM_CORE_PAR2);
        //GeConsoleOut(pointer_op->GetName()); // Just checking...
        GePluginMessage(MSG_NEURONMESSAGE, (void * )pointer_op);
    }
    return true;
}
  
Bool RegisterNeuronReaderObject() {
    return RegisterObjectPlugin(ID_NEURONREADER, GeLoadString(IDS_NEURONREADER), OBJECT_GENERATOR, ONeuronReader::Alloc, "Oneuronreader", AutoBitmap("atom.tif"), 0);
}
  
Bool PluginMessage(Int32 id, void *data)
{
    switch (id)
    {
        case C4DPL_INIT_SYS:
            if (!resource.Init())
                return false;       // don't start plugin without resource
            return true;
  
        case MSG_NEURONMESSAGE:
            if (GeIsMainThread()) {
                GeConsoleOut("IS MainThread");
                ONeuronReader* op = (ONeuronReader* )data;
                //GeConsoleOut(op->GetName()); // just to confirm it is the correct pointer
                op->ApplyNeuronData((BaseObject* )op);
            }
            else {
                GeConsoleOut("NotMainThread");
            }
    }
    return false;
}