Dynamic elements in a CYCLE, CYCLE empty after loading document?



  • Hello,

    In a ShaderData, I am using GetDDescription() to add elements to a LONG 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's GetDDescription() 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's CYCLE. 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 thread ;-)

    Screenshot 2021-01-26 at 11.42.45.png

    The 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's GetCustomOperatorOutputs():

    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,
    Frank

    Ah, damn. Now I've spoiled that I'm working on erosion for Terraform4D :D



  • @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 the CYCLE.



  • @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.