Dynamic elements in a CYCLE, CYCLE empty after loading document?
-
Hello,
In a
ShaderData
, I am usingGetDDescription()
to add elements to aLONG CYCLE
description using this code:struct CycleElementData { Int32 elementId; ///< Element id maxon::String name; ///< Element name /// \brief Construct from element ID and name CycleElementData(Int32 t_elementId, const maxon::String& t_name) : elementId(t_elementId), name(t_name) {} /// \brief Copy constuctor CycleElementData(const CycleElementData& src) : elementId(src.elementId), name(src.name) {} }; Bool AddCycleElements(GeListNode* node, Description* descr, Int32 myDescId, maxon::BaseArray<CycleElementData>& elements, Bool addSeparator) { const DescID* const singleId = descr->GetSingleDescID(); if (IsSingleID(myDescId, singleId)) { BaseContainer* descSettings = descr->GetParameterI(myDescId, nullptr); if (!descSettings) return false; // Build BaseContainer with cycle elements BaseContainer cycleElements = descSettings->GetContainer(DESC_CYCLE); if (addSeparator) cycleElements.SetString(-1, ""_s); for (maxon::BaseArray<CycleElementData>::ConstIterator it = elements.Begin(); it != elements.End(); ++it) { const CycleElementData& currentElement = *it; cycleElements.SetString(currentElement.elementId, currentElement.name); } descSettings->SetContainer(DESC_CYCLE, cycleElements); return true; } return true; } // Later... maxon::BaseArray<CycleElementData> shaderModes; shaderModes.Append(CycleElementData(123456, "Some mode"_s)) iferr_ignore(); shaderModes.Append(CycleElementData(654321, "Some other mode"_s)) iferr_ignore(); AddCycleElements(node, description, MYSHADER_MODES, shaderModes, true);
So far, it seems to work fine. The additional entries appear in the
LONG CYCLE
in the shader's UI. I can also select them, and it works.However, when I save the scene, and then load it again, nothing is selected in the
LONG CYCLE
. I have to re-select the desired entry again. I guess, this is because the additional entries don't exist yet, when the scene is loaded, and are added later when the shader'sGetDDescription()
is called. So what might be the solution?Greetings & thanks in advance for help,
Frank -
OK, after taking a little break and then getting back to the code, it magically works. I didn't change anything. So, I change my question to: Are there any pitfalls with dynamic CYCLE elements, or dynamic description elements in general, when loading a scene?
-
Hi Franck, first of all, Happy new year!
Glad that it somehow works.The biggest issue is we don't have all your code, so we need to do some guesswork.
We don't know when ad how you are populating your BaseArray. Since it may be based on some other stuff from the scene, these other things may not be loaded yet.
We don't have your GetDDescription code, but be sure that LoadDescription is called and you return
DESCFLAGS_DESC::LOADED
Overwise your change may simply be overwritten. For more information see Description Manual - Access.Within the GetDDescription, once you populated the cycle, be sure also to call SetParameter to actually set the value (be sure to store the currently active value by overriding NodeData::Write/NodeDataRead)
Hope this help,
Cheers,
Maxime. -
Hi Adam, happy new year to you, too!
Since it works now, for some reason, I am pretty happy with what I have. However, since it might interest other plugin developers, I'll share more code. Maybe you have some tipps about improvements or potentially dangerous stuff, too.
The idea is that the shader has a
LINK
field where the user can link an object (which is also part of my plugin). The object can (but doesn't have to) provide a list of "custom outputs" that will be added to the shader'sCYCLE
. In the screenshot below it's the "Difference Map".When a rendering is started, the shader will request the according data from the linked object during
InitRender()
. But that's not part of this threadThe shader's
GetDDescription()
:Bool TerrainOperatorShader::GetDDescription(GeListNode *node, Description *description, DESCFLAGS_DESC &flags) { iferr_scope_handler { GePrint(err.GetMessage()); return false; }; if (!description->LoadDescription(node->GetType())) return false; flags |= DESCFLAGS_DESC::LOADED; BaseDocument* doc = node->GetDocument(); const BaseContainer& dataRef = static_cast<BaseShader*>(node)->GetDataInstanceRef(); // Hide or show attributes, depending on shader mode const Bool slopeMode = dataRef.GetInt32(XTERRAINOPERATORSHADER_DATA) == XTERRAINOPERATORSHADER_DATA_SLOPE; TF4D::GUI::ShowDescription(node, description, XTERRAINOPERATORSHADER_SLOPE_DIRECTION_ENABLE, slopeMode); TF4D::GUI::ShowDescription(node, description, XTERRAINOPERATORSHADER_SLOPE_DIRECTION, slopeMode); // Get linked object BaseObject *linkedObject = dataRef.GetObjectLink(XTERRAINOPERATORSHADER_OPERATORLINK, doc); if (linkedObject) { // Get linked object's NodeData TF4D::BaseTerrainOperatorData* linkedOperator = linkedObject->GetNodeData<TF4D::BaseTerrainOperatorData>(); // Get list of custom outputs (these are the elements to add to the CYCLE) maxon::BaseArray<TF4D::GUI::CycleElementData> customOutputs; if (linkedOperator->GetCustomOperatorOutputs(customOutputs)) { if (!TF4D::GUI::AddCycleElements(node, description, XTERRAINOPERATORSHADER_DATA, customOutputs, true)) iferr_throw(maxon::UnexpectedError(MAXON_SOURCE_LOCATION, "Could not add LONG CYCLE elements!"_s)); } } return SUPER::GetDDescription(node, description, flags); }
The linked object's
NodeData
'sGetCustomOperatorOutputs()
:Bool ErosionOperator::GetCustomOperatorOutputs(maxon::BaseArray<TF4D::GUI::CycleElementData>& customOperatorOutputs) const { iferr_scope_handler { GePrint(err.GetMessage()); return false; }; customOperatorOutputs.Reset(); // GetCustomOutputName() simply returns a maxon::String customOperatorOutputs.Append(TF4D::GUI::CycleElementData(TF4D_CUSTOMOUTPUT_EROSION_DIFFERENCE, GetCustomOutputName(TF4D_CUSTOMOUTPUT_EROSION_DIFFERENCE))) iferr_return; return true; }
Cheers,
FrankAh, damn. Now I've spoiled that I'm working on erosion for Terraform4D
-
@m_adam said in Dynamic elements in a CYCLE, CYCLE empty after loading document?:
Within the GetDDescription, once you populated the cycle, be sure also to call SetParameter to actually set the value (be sure to store the currently active value by overriding NodeData::Write/NodeDataRead)
What exactly do you mean? Why should I do that?
Do be clear: Just because the user links an object that adds extra items to the
CYCLE
doesn't mean any of those extra items should be automatically selected. They should just be available in theCYCLE
. -
@fwilleke80 said in Dynamic elements in a CYCLE, CYCLE empty after loading document?:
However, when I save the scene, and then load it again, nothing is selected in the LONG CYCLE.
What might happens is that the value is correctly set, but since the Cycle is not yet populated, the value is then reset.
So that's why a SetParameter, may be required once cycle data are loaded.Cheers,
Maxime. -
Ah, ok, yes that sounds reasonable. When would I do that? In Read(), Write(), and CopyTo()? And what exactly would I do? Simple getting the value and immediately setting it again probably wouldn’t change anything.
-
Hi sorry for the long waiting.
As demonstrated in the code snippet from Description - Iterate. I think in your GetDDescription, once the description is saved you should call SetParameter with the correct value.
This value can only be known by yourself. So you have to store the cycle value within the Hyperfile by overriding Write. Then when Cinema 4D loads the object the Read is called, here you can save in the previous value saved for the cycle. And finally in the GetDDescription, since Read would have been already called, you can call SetParameter with the correct value.Hope this clarifies a bit, and good work on Terraform looks solid!
Cheers,
Maxime. -
Thanks, Maxime! Looks like I have it running flawlessly now.
good work on Terraform looks solid!
Thank you! It is pretty solid, yesCheers & happy Easter,
Frank