How to access PLA data
-
Hello, I'm not sure if this is the correct place for this question, but what would be the correct way of doing this with the C++ SDK, instead of a python script?
Thanks, Facundo
forked from get spline points positions from pla keyframes by @ferdinand
-
Hello @facumh ,
Welcome to the Plugin Café forum and the Cinema 4D development community, it is great to have you with us!
Getting Started
Before creating your next postings, we would recommend making yourself accustomed with our Forum and Support Guidelines, as they line out details about the Maxon SDK Group support procedures. Of special importance are:
- Support Procedures: Scope of Support: Lines out the things we will do and what we will not do.
- Support Procedures: Confidential Data: Most questions should be accompanied by code but code cannot always be shared publicly. This section explains how to share code confidentially with Maxon.
- Forum Structure and Features: Lines out how the forum works.
- Structure of a Question: Lines out how to ask a good technical question. It is not mandatory to follow this exactly, but you should follow the idea of keeping things short and mentioning your primary question in a clear manner.
About your First Question
While your question was technically a continuation of the old question, I think your first question should be in its own thread. We also need more information, like for example which SDK you want to target, and which type of plugin hook you implement.
Because without all that, my answer will simply be that it is pretty much the same in C++ as it is in Python.
Cheers,
Ferdinand -
Hello Ferdinand, I was not sure whether to make a new thread or comment on the old one. thanks for taking time to do this and respond.
SDK 2023 and/or 2024 (I'm not sure how much they differ in this section), plugin hook::Compute( BaseObject* mod, BaseDocument* doc, BaseObject* op, BaseThread* bt, BaseObject* cacheNode )
.I basically tried something similar to what was shown in the python code
auto const time = doc->GetTime(); const DescID plaID = DescLevel( CTpla, CTpla, 0 ); CTrack* plaTrack = obj->FindCTrack( plaID ); if( plaTrack == nullptr ) { plaTrack = CTrack::Alloc( obj, plaID ); if( plaTrack == nullptr ) throw maxon::OutOfMemoryError( MAXON_SOURCE_LOCATION ); obj->InsertTrackSorted( plaTrack ); CCurve* curve1 = plaTrack->GetCurve(); if( curve1 == nullptr ) throw maxon::UnexpectedError( MAXON_SOURCE_LOCATION ); // set first key CKey* const key1 = curve1->AddKey( BaseTime( 0.0 ) ); if( key1 == nullptr ) throw maxon::OutOfMemoryError( MAXON_SOURCE_LOCATION ); plaTrack->FillKey( doc, obj, key1 ); } // set second key CCurve* curve = plaTrack->GetCurve(); auto animatedParams = curve->GetInfo(); CKey* const key2 = curve->AddKey( time ); if( key2 == nullptr ) throw maxon::OutOfMemoryError( MAXON_SOURCE_LOCATION ); plaTrack->FillKey( doc, obj, key2 ); GeData curveParams; auto foundParams = curve->GetParameter( plaID, curveParams, DESCFLAGS_GET::NONE ); auto geDataCurve = key2->GetGeData();
With this I expected to find either in
curveParams
or ingeDataCurve
a vector similar toAbsPos
orAbsRot
from SplineObject, but the former only has zeros in all fields, and the later has values that seem to be unintialized memory, or wrongly typed.What I want to obtain, much like in the other thread, is the position/rotation of the spline during the animation.
Since I'm new in this topic, maybe there is an easiere way of obtaining this information, but if that is the case I did not find it in the SDK doc.Thanks for your help and time,
Cheers, Facundo. -
Hello @facumh,
with plugin hook I meant something like
CommandData
orObjectData
, you could also say plugin type. I cannot recollect any method in our API that has the signature:::Compute(BaseObject* mod, BaseDocument* doc, BaseObject* op, BaseThread* bt, BaseObject* cacheNode)
But I assume you are implementing some kind of
NodeData
, probably anObjectData
, and this is your own method.When I look at your code I am a bit confused as to what you are trying to do¹. The key steps to accessing the raw PLA data are:
- Get the PLA track.
- Get a key in the curve of the track.
- Get the GeData value for the key and retrieve the PLA custom data type value for it.
- Then call PLAData::GetVariableTags to get the underlying
VariableTag
data. - ... and so on and so forth just as in the Python script.
Your code stops at point three, so you never access the PLA data in the first place. Since you have only provided a non-compileable fragment, I can only provide untested pseudo code, I do not have the time to write everything else around this example. Find the pseudo code below.
Regrading 2023 and 2024, yes, there are unfortunately some larger changes which will also impact you here (
DescID
allocation and custom data type access for example). I would recommend having a look at the migration guide for getting a sense of the changes.Cheers,
Ferdinand[1] As a minor side note - you are free to do whatever you like, but using
throw
in our API, especially in conjunction with the maxon API error interfaces is a bit odd. The maxon APi error handling exists for the very reason of avoiding usingthrow
. In the maxon API you return errors, see Error Handling for details.Pseudo-Code:
// This is not correct, the second value must be the data type, not the ID. // const DescID plaID = DescLevel( CTpla, CTpla, 0 ); // With 2024, DescID allocation has changed ... const DescID plaId = ConstDescID(DescLevel(CTpla, CUSTOMDATATYPE_PLA, 0)); // 2023.2 legacy code ... const DescID plaId = DescID(DescLevel(CTpla, CUSTOMDATATYPE_PLA, 0)); // ... // Access the PLA key. const GeData keyData; if (!curve->GetParameter(plaID, keyData, DESCFLAGS_GET::NONE)) return false; // This is 2024 code, the custom data type access has changed with 2024. const PLAData* const plaData = keyData.GetCustomDataType<const PLAData>(); // 2023.2 and earlier legacy code: const PLAData* const plaData = static_cast<PLAData*>(CUSTOMDATATYPE_PLA); if (!plaData) return false; // Get the two tags which hold the point and tangent data for the PLA data. PointTag* const pointTag; TangentTag* const tangentTag; plaData->GetVariableTags(pointTag, tangentTag); // All PLA data must at least carry point data. if (!pointTag) return false; // We could also take the scenic route over VariableTag::GetDataAddressR as we did in Python, but // we do not have to as we have here access to the specialization PointTag::GetDataAddressR. const Vector* points = pointTag->GetDataAddressR(); // Do stuff with the points ... // Tangent data not being populated is not an error event, some PlaData simply have none. if (tangentTag) const Tangent* tangents = tangentTag->GetDataAddressR();
-
Hi Ferdinand,
Thanks for taking the time to reply. I did not expect you to write compilable code, simply to help fill the gaps in my knowledge of the API, which you have done wonderfully. I will take a look at all the reading material you have suggested and take your comments into account when further writing plugin code.
Cheers, Facundo -
Hey @facumh,
Okay, great to hear! When you run into problems, just return with the concrete code you are stuck with (so that I can compile it), I will help you then. When you do not want to share your full code publicly, you can also use our contact form or send us a mail to
sdk_support(at)maxon(dot)net
.Cheers,
Ferdinand -
Hey ferdinand,
unfortunately I can not share the whole code, since it is a large and complex project. But hopefully with these small fractions you can help me identify where is the mistake.
Firstly, as you correctly guessed the plugin hook is an ObjectData.As for the mistake of using CTpla for the second argument of
DescLevel
, that is shows in the CTrack Manual, maybe a comment could be added whether that for an outdated version of the SDK, or something else.Thirdly, I've made the change you suggested to the parameters to getting
plaID
, but now bothFindCTrack
andCTrack::Alloc( obj, plaID );
are returning null. The first one is expected since on the first time executing this part of the code there shouldn't be any tracks but I find it odd that the Alloc is also returningnullptr
(I've checked that there is plenty of memory on the system, so at least that is not the case).Alternatively, if I use CTpla in both parameters (they way it's suggeested on the CTrack Manual), I noticed that when calling
CCurve* curve = plaTrack->GetCurve();
I get a non-nullptr back, but the underlying base2Dobject, is nullptr, is this also expected?Thanks for your time and response.
Cheers,
Facundo -
Hey @facumh,
sorry for sending you on the wrong track, the so called special tracks seem to pass the track ID and not its data type the data type argument of a
DescLevel
, so what you did was correct.Find below my Python code translated to C++ (for 2024),
Cheers,
FerdinandResult:
Code:
/// Demonstrates reading PLA data in 2024. #include "c4d_basedocument.h" #include "c4d_baselist.h" #include "c4d_canimation.h" #include "c4d_general.h" #include "customgui_pla.h" #include "lib_description.h" #include "ckpla.h" Bool GetPlaData(BaseDocument* doc) { // Input validation. if (!doc) return false; // Since this method is called "Get", and with the constness changes of 2024, we should get here // a const BaseObject* and subsequently, const track, curve, key, gedata, and pladata. But because // PlaData::GetVariableTags is not a const method this cascades back up to here, and we must get // mutable data although we only want to read. BaseObject* const node = doc->GetActiveObject(); if (!node || !node->IsInstanceOf(Opoint)) return false; // Construct the ID for the PLA track and get the track and its curve. const DescID plaTrackId = ConstDescID(DescLevel(CTpla, CTpla, 0)); CTrack* const track = node->FindCTrack(plaTrackId); if (!track) return false; CCurve* const curve = track->GetCurve(); if (!curve) return false; // Iterate over all keys in the curve, for (Int32 i = 0; i < curve->GetKeyCount(); i++) { CKey* const key = curve->GetKey(i); if (!key) continue; // Get the custom data type stored in this key, in this case the PLAData. Just calling CKey:: // GetGeData does not work here and instead we have to call this little ::GetParameter // monstrosity below. GeData data; if (!key->GetParameter(ConstDescID(DescLevel(CK_PLA_DATA, CUSTOMDATATYPE_PLA, 0)), data, DESCFLAGS_GET::NONE)) continue; PLAData* const pla = data.GetCustomDataTypeWritable<PLAData>(); // Get the two tags which hold the actual data. PointTag* pointTag = nullptr; TangentTag* tangentTag = nullptr; pla->GetVariableTags(pointTag, tangentTag); // This is a critical event, a PLA key should always hold point data, we should raise some kind // of error here. if (!pointTag) continue; // Get the read-only vectors and iterate over all of them. const Vector* points = pointTag->GetDataAddressR(); for (Int32 j = 0; j < pointTag->GetDataCount(); j++) ApplicationOutput( "Pla point data for t='@' and index='@': @", key->GetTime().Get(), j, points[j]); // This is not a critical event, only PLA data for splines holds tangent data. if (!tangentTag) continue; // Iterate over the read-only tangents. const Tangent* tangents = tangentTag->GetDataAddressR(); for (Int32 k = 0; k < tangentTag->GetDataCount(); k++) ApplicationOutput( "Pla tangent data for t='@' and index='@': (vl=@, vr=@)", key->GetTime().Get(), k, tangents[k].vl, tangents[k].vr); } return true; }
-
hey @ferdinand,
thanks for all the help. This code was very helpful, but I still get the same points, from different keys even if I create them with different times. Which makes me wonder if maybe I am creating them with the wrong parameters. I also tried using just one key and moving frame by frame, but still the points from the pointTag remained always the same.
Cheers, Facundo -
Hey @facumh,
As you can see in my screenshot, my code is reading the correct data for the two keys in the scene. Are you also creating the keys programmatically, or have the keys been created manually? Without your code, it will be hard for me to help you here.
Cheers,
Ferdinand -
Hey @ferdinand,
in my second comment you can see how I create the keys, programmaticallly. I know it's hard without the whole code, but so far this has being very educational for me as well, just wanted to thank you for that as well.
Cheers, Facundo -
Hey @facumh,
your code looks okay, at least when one wants to rely on
FillKey
(which we internally also do). The other route would be to do the inverse of what I showed in my code and manually write the arrays of the underlying PLAData.There are however two problems with your code, you use CTrack::FillKey which "Fills key with default values." But looking at our PLA baking code, this seems to respect the points and tangents of the passed
bl
, i.e., is smart enough to know what to do with the special track PLA.if ((flags & TL_FLAG_BAKE_PLA || flags & TL_FLAG_BAKE_ALL) && source->IsInstanceOf(Obase)) { desttrack = nullptr; if (bSearchTrack) desttrack = dest->FindCTrack(ConstDescID(DescLevel(CTpla, CTpla, 0))); if (!desttrack) { desttrack = CTrack::Alloc(dest, ConstDescID(DescLevel(CTpla, CTpla, 0))); GeListHead* head = dest->GetCTrackRoot(true); if (!head) return false; head->InsertLast(desttrack); } if (desttrack) { pKey = desttrack->GetCurve()->AddKey(time, &idx); if (pKey) { desttrack->FillKey(doc, source, pKey); if (flags & TL_FLAG_BAKE_CLEAN) CleanKeys(*desttrack, idx, *pKey, isLastFrame); } } }
There are however two other problems, first of all, you do not make sure to write to two unique time values, when
BaseDocument::GetTime()::Get() == 0.0
, then you will write to the same key. You also pass for both keysobj
as thebl
, creating the same default values for both keys.This is then likely the reason for you reading the same data on both keys.
Cheers,
Ferdinand -
hey @ferdinand ,
I did suspect usingobj
for both keys could be the issue, but then how would you suggest I extract the animated spline from the baseDoc ? -
Hey @facumh,
My point was that when you have an object
obj
whose points (and tangents) are in the stateX
and you use that object to fill the value(s) for a key, you will obviously end up with identical key values.So, you have to either manually write the key data, I already hinted at the fact that you could do that by doing the inverse of what I showed, to manually write the point and tangent data stored in a PLA key, or alternatively, you could change the state of
obj
, so that its points and tangents are not in the stateX
anymore. Otherwise, both keys will be filled with the stateX
.Cheers,
Ferdinand -
hey @ferdinand ,
sorry I should have explained a little more. So the first time this function is executed, there are no tracks, so I create the track and first key, based onObj
in state of time 0.0. On later executions, thedoc->getTime()
is no longer 0.0, and I thought this would mean that the baseObjectObj
would also have the point of the spline in this new time, and since now there is already a track, I create the new key usingObj
with the current time. This way and checking withkey = curve->FindKey(time)
and checking that it's not null, I make sure to not replicate keys with the same time hence (I thought), not two keys with the same state of `Obj.Thanks for your time and patience with all my doubts.
Cheers, FacundoEdit: sorry, not sure if it's clear, but what want is the position of the spline points in different moments of the animation and I thought with the pla tracks and keys this info could be retrieveed
-
Hey @facumh,
I understand what you want to do, you want to record a PLA key for the given object at the given document time. For which you could btw also just use the
CallCommand
for Record Active Objects:Just turn off position, rotation, scale, and parameter recording, and turn on PLA (just as shown in the screen above), then invoke "record active objects". Should be doable in less than 15 lines, including reverting to the previous animation recording state. You can use
CallCommand
to invoke all 6 buttons here. You can use IsCommandChecked to find out if a command is "blue" or not, so that you know when you must runCallCommand
to toggle the state and when not.Regarding your code, you showed us above that you write two keys:
auto const time = doc->GetTime(); const DescID plaID = DescLevel( CTpla, CTpla, 0 ); CTrack* plaTrack = obj->FindCTrack( plaID ); if( plaTrack == nullptr ) { plaTrack = CTrack::Alloc( obj, plaID ); if( plaTrack == nullptr ) throw maxon::OutOfMemoryError( MAXON_SOURCE_LOCATION ); obj->InsertTrackSorted( plaTrack ); CCurve* curve1 = plaTrack->GetCurve(); if( curve1 == nullptr ) throw maxon::UnexpectedError( MAXON_SOURCE_LOCATION ); // set first key CKey* const key1 = curve1->AddKey( BaseTime( 0.0 ) ); if( key1 == nullptr ) throw maxon::OutOfMemoryError( MAXON_SOURCE_LOCATION ); plaTrack->FillKey( doc, obj, key1 ); } // set second key CCurve* curve = plaTrack->GetCurve(); auto animatedParams = curve->GetInfo(); CKey* const key2 = curve->AddKey( time ); if( key2 == nullptr ) throw maxon::OutOfMemoryError( MAXON_SOURCE_LOCATION ); plaTrack->FillKey( doc, obj, key2 ); GeData curveParams; auto foundParams = curve->GetParameter( plaID, curveParams, DESCFLAGS_GET::NONE ); auto geDataCurve = key2->GetGeData();
If you use other code, you should show us this, in fact you should always post executable code, as things otherwise tend to get convoluted
Cheers,
FerdinandPS: When I have time I will squeeze in a code example for recording the PLA state of the selected object at the current document time. But it might take me some time, I am a bit busy at the moment.
-
Hi @ferdinand ,
thanks for the tip of using Record Active Element.
On one hand I couldn't find the command id on the sdk website.
On the other hand, playing around with the RAE and the spline, I realized that whenever the active element is the spline and I press play, it doesn't move, but when it is not the active element, and I press play, then the animation moves. I'm using a rope expresion tag to animate it and rope belt tag to fixate one end of the spline.
This behaviour of the spline not moving when it's the active element would explain why I always get the same points on the different keys, even when the animation moves. But could you maybe explain this behaviour ?Cheers, Facundo
-
Hey @facumh,
Not all command IDs are exposed as symbols. The easiest way to find out their ID is the so-called Script Log. Simply invoke the actions you are interested in, and the look into the log. The Script Log is by default part of the Script layout.
Regarding your other question(s), these are unfortunately out of scope of support here, as they are end-user questions. I must ask you to direct them towards our end-user support via Support Portal: Support Request.
Thank you for your understanding,
Ferdinand -
Hi @ferdinand ,
Thanks very much for all of your time and tips!!
Facundo -