Hi @d_schmidt,
thank for your video, that helped quite a lot. Your conclusion was right, there are multiple documents at play here. In the case of your DoubleCircleData
node representation being linked in a Cloner and having a empty spline next to it, the Cloner bugs out and something like visualized by console output below happens:
GetContour() called with document at 00000231f8fd4240
CheckDirty() and dirty requirements met with doc: 00000231a99c1e40 and nextNode: 0000000000000000.
GetContour() called with document at 00000231a99c1e40
CheckDirty() and dirty requirements met with doc: 00000231a99c1e40 and nextNode: 0000000000000000.
GetContour() called with document at 00000231a99c1e40
There are at least two documents at play here and the amount of calls, here three in total, are more than we would expect here. I also had cases where I had five calls in a row or just two. Also the next node in that copied document is not present, i.e., the call op->GetNext()
in your GetContour
will return a nullptr
then; we can see this in line 2 where it says in CheckDirty
nextNode: 0000000000000000.
When we instead either do not link your plugin in a cloner or put something else than an empty spline object next to it, the behaviour will then be correct and take a form similar to:
GetContour() called with document at 00000231f8fd4240 and _documentTransferCache at 0000000000000000`
You might be wondering at this point what that _documentTransferCache
is. This is part of my little work around I did provide, which basically relies on Noddedata::CopyTo
and manually curating this very special case. You can read a bit more about it in the file provided below. At least for me this works now like I would expect it to work. I also did provide a demo file. As stated in the code below, I would however not really recommend doing this, unless you REALLY REALLY need this. My solution is rather hacky, but I do not see another way of fixing the mess the cloner causes. I would either inform my users about that known problem or look for an alternative way to design my plugin. I will report this behaviour of the Cloner as a bug, but it might take some time until someone will fix it.
Cheers,
Ferdinand
The test-scene:
naughty-cloner.c4d
The code:
/*
A rather hacky way to deal with your Cloner & empty spline problem.
I actually would not suggest using this, because this solution is so unnecessarily complicated for a
special corner case that is does not seem to be worth it. This is definitely caused by an, *cough*,
*oddness* of the Cloner object in that special empty spline object case.
I would rather suggest to either:
- Reject empty spline objects as links in your BaseLink.
- Simply tell your users that this is a known problem and warn them.
While this solution here works, there are so many ifs and buts at play, that it seems rather unlikely
that this will work in every conceivable scenario. I will file a bug report for the Cloner about this,
but due to its niche-case nature I would not hold my breath for a short term fix.
As discussed in:
https://plugincafe.maxon.net/topic/13231/
*/
#include "c4d.h"
#include "c4d_symbols.h"
#include "odoublecircle.h"
#include "main.h"
#define ID_CIRCLEOBJECT 1001154
class DoubleCircleData : public ObjectData
{
INSTANCEOF(DoubleCircleData, ObjectData)
public:
virtual Bool Init(GeListNode* node);
virtual SplineObject* GetContour(BaseObject* op, BaseDocument* doc, Float lod, BaseThread* bt);
virtual void CheckDirty(BaseObject* op, BaseDocument* doc);
virtual Bool CopyTo(NodeData* dest, GeListNode* snode, GeListNode* dnode, COPYFLAGS flags, AliasTrans* trn);
static NodeData* Alloc() { return NewObjClear(DoubleCircleData); }
private:
BaseObject* _nextNodeTransferCache = nullptr;
BaseDocument* _docmentTransferCache = nullptr;
BaseObject* _nextNodeCache = nullptr;
Int32 _prevFrameCache = 0;
Bool _hasInitalised = false;
};
Bool DoubleCircleData::Init(GeListNode* node)
{
BaseObject* op = (BaseObject*)node;
BaseContainer* data = op->GetDataInstance();
if (!data)
return false;
data->SetFloat(CIRCLEOBJECT_RAD, 200.0);
data->SetInt32(PRIM_PLANE, 0);
data->SetBool(PRIM_REVERSE, false);
data->SetInt32(SPLINEOBJECT_INTERPOLATION, SPLINEOBJECT_INTERPOLATION_ADAPTIVE);
data->SetInt32(SPLINEOBJECT_SUB, 8);
data->SetFloat(SPLINEOBJECT_ANGLE, DegToRad(5.0));
data->SetFloat(SPLINEOBJECT_MAXIMUMLENGTH, 5.0);
return true;
}
// We are implementing CopyTo to copy over data for that buggy Cloner case.
Bool DoubleCircleData::CopyTo(NodeData* dest, GeListNode* snode, GeListNode* dnode, COPYFLAGS flags, AliasTrans* trn)
{
// In case this node is getting copied, we copy over the old document and the next node.
DoubleCircleData* impl = static_cast<DoubleCircleData*>(dest);
// This the "source" case, i.e., when we are actually in the document we *want* to be in.
if (_nextNodeTransferCache == nullptr)
{
impl->_nextNodeTransferCache = _nextNodeCache;
impl->_docmentTransferCache = snode->GetDocument();
}
// Here we are copying a copy, so this will run when our node has been cloned and is then getting cloned again.
else
{
impl->_nextNodeTransferCache = _nextNodeTransferCache;
impl->_docmentTransferCache = _docmentTransferCache;
}
return NodeData::CopyTo(dest, snode, dnode, flags, trn);
}
void DoubleCircleData::CheckDirty(BaseObject* op, BaseDocument* doc)
{
// The same as yours, it only can also handle documents with a negative document time, specifically frame -1.
BaseObject* nextNode = op->GetNext();
Int32 currentFrame = doc->GetTime().GetFrame(doc->GetFps());
if (!_hasInitalised || nextNode != _nextNodeCache || _prevFrameCache != currentFrame)
{
_prevFrameCache = currentFrame;
_nextNodeCache = nextNode;
_hasInitalised = true;
op->SetDirty(DIRTYFLAGS::DATA);
ApplicationOutput("CheckDirty() and dirty requirements met with doc: @ and nextNode: @.", (void*)doc, (void*)nextNode);
}
}
SplineObject* DoubleCircleData::GetContour(BaseObject* op, BaseDocument* doc, Float lod, BaseThread* bt)
{
BaseContainer* bc = op->GetDataInstance();
if (!bc)
return nullptr;
// Some console chirping to see what is going on here. The cloner copies stuff all over the place in case we have an
// empty spline next to our DoubleCircleData GeListNode representation.
ApplicationOutput("GetContour() called with document at @ and _documentTransferCache at @", (void*)doc, (void*)_docmentTransferCache);
ApplicationOutput("_nextNodeCache: @, _nextNodeTransferCache: @.", (void*)_nextNodeCache, (void*)_nextNodeTransferCache);
// op->GetNext() will "fail" in the case our DoubleCircleData GeListNode representation being linked in a Cloner as a cloning
// "surface". next will then be a nullptr, although it should point to the empty spline. This has nothing to do with node order or
// other things one might suspect, but only with the fact if the spline has vertices or not. For a spline with vertices evrything will
// behave normally.
// I have not dug around in the depth of the Cloner implementation to see what is happening in detail there, but this would not
// help us much here for a short term solution anyways.
// BaseObject* next = op->GetNext();
// Instead we fall back onto our _nextNodeTransferCache when this document has a _docmentTransferCache which is not null.
BaseObject* next = nullptr;
// We have a document transfer cache, so we fall back onto our "actual" node.
if (_docmentTransferCache != nullptr)
{
next = _nextNodeTransferCache;
}
// We are in the original document.
else
{
next = op->GetNext();
}
if (next == nullptr || next->GetType() != Ospline)
{
SplineObject* rs = SplineObject::Alloc(4, SPLINETYPE::LINEAR);
if (!rs)
{
return nullptr;
}
Vector* padr = rs->GetPointW();
padr[0] = Vector(100, 0, 0);
padr[1] = Vector(0, 100, 0);
padr[2] = Vector(-100, 0, 0);
padr[3] = Vector(0, -100, 0);
rs->Message(MSG_UPDATE);
//ApplicationOutput("NextNoSplineCondition, returned dummy geom."_s);
return rs;
}
// else
SplineObject* inputspline = static_cast<SplineObject*>(next);
Int32 pointcount = inputspline->GetPointCount();
const Vector* points = inputspline->GetPointR();
SplineObject* rs = SplineObject::Alloc(pointcount, SPLINETYPE::LINEAR);
if (!rs)
{
return nullptr;
}
Vector* padr = rs->GetPointW();
for (Int32 x = 0; x < pointcount; x++)
{
padr[x] = points[x];
}
rs->Message(MSG_UPDATE);
//ApplicationOutput("IsSplineCondition, returned copied spline."_s);
return rs;
}
Bool RegisterCircle()
{
return RegisterObjectPlugin(ID_CIRCLEOBJECT, GeLoadString(IDS_CIRCLE), OBJECT_GENERATOR | OBJECT_ISSPLINE, DoubleCircleData::Alloc, "Odoublecircle"_s, AutoBitmap("circle.tif"_s), 0);
}