Jump to content
Guest mantragora

Update attribute menu

Recommended Posts

Guest mantragora

I want to have possibility to list point or primitive attributes in my menu according to type user will pick. Lets say that my operator default state for attribute type is Point attrbute which I got covered by 

PRM_Template(PRM_STRING, 1, &attributeParm_Name, PRMzeroDefaults, &SOP_Mantragora::pointAttribMenu);
and user switched type to Primitive. Should I update the menu with 

PRM_Template::setChoiceListPtr(&SOP_Mantragora::primAttribMenu)

 

If so, where I should do this? Putting it in cook() method didn't worked.

 

 

EDIT: The same case is for groups. There is

SOP_Mantragora::groupMenu
But it lists all group types at once. And I want to have listed only those specified by user and switch between

SOP_Mantragora::pointGroupMenu
and

SOP_Mantragora::primGroupMenu
 

Thanks

Edited by mantragora

Share this post


Link to post
Share on other sites

Make your own callback to decide which one (primGroupMenu or pointGroupMenu) to call.

Share this post


Link to post
Share on other sites
Guest mantragora

You mean something similar to example for dynamically generated items that we can find -> http://www.sidefx.com/docs/hdk13.0/_h_d_k__op_basics__overview__parameters.html#HDK_OpBasics_Overview_Parameters_Types ?

 

From Help:

static void
sopBuildRestGroup(void *data, PRM_Name *choicenames, int listsize,
const PRM_SpareData *spare, PRM_Parm *parm)
{
SOP_Node *sop = CAST_SOPNODE((OP_Node *)data);
 
if( sop )
{
sop->buildInputGroups( SOP_REST_INPUT, choicenames, listsize,
PRIM_GROUP, 0, true, parm );
}
else
{
choicenames[0].setToken(0);
choicenames[0].setLabel(0);
}
}
 
static PRM_ChoiceList sopRestGroupMenu(PRM_CHOICELIST_TOGGLE,
sopBuildRestGroup);
Is this the way to go?

WTF is this "void *data" or "const PRM_SpareData *spare"?

EDIT: I don't get how I should build attributes list. When Parameters are created they don't know on which operator they will land. So I think I need to update them after they are added to list of parameters of the operator so I can evaluate the Type the user picked. Are there any other examples somewhere in HDK examples?

Edited by mantragora

Share this post


Link to post
Share on other sites
Guest mantragora

This is what I got:

static auto Update_GroupMenu(void* data, PRM_Name* choicenames, int listsize, const PRM_SpareData* spare, PRM_Parm* parm)
-> void
{

}

static auto Update_AttributeMenu(void* data, PRM_Name* choicenames, int listsize, const PRM_SpareData* spare, PRM_Parm* parm)
-> void
{

}

static auto templateGroupParm_ChoiceList = PRM_ChoiceList(PRM_CHOICELIST_TOGGLE, Update_GroupMenu);
static auto attributeParm_ChoiceList = PRM_ChoiceList(PRM_CHOICELIST_TOGGLE, Update_AttributeMenu);

auto templateGroup_Parameter = PRM_Template(PRM_STRING, 1, &templateGroupParm_Name, PRMzeroDefaults, &templateGroupParm_ChoiceList);
auto attribute_Parameter = PRM_Template(PRM_STRING, 1, &attributeParm_Name, PRMzeroDefaults, &attributeParm_ChoiceList);
I'm getting error "No instance of constructor PRM_ChoiceList... maches the argument list". There are two constructors I think i can target:

1. PRM_ChoiceList (PRM_ChoiceListType thetype, PRM_ChoiceItemGenFunc thefunc)

2. PRM_ChoiceList (PRM_ChoiceListType thetype, PRM_ChoiceGenFunc thefunc)

I looked at typedefs of PRM_ChoiceItemGenFunc()/PRM_ChoiceGenFunc() and it looks that my functions matched argument list correctly and the return type is also good.

So what I'm doing wrong?

Edited by mantragora

Share this post


Link to post
Share on other sites

Your signature doesn't look like it matches to me.

 

I think you're misunderstanding the nature of the callback. It doesn't "update" a menu, it "generates" the items for the menu when the user opens it. For node PRM_ChoiceList's, the void *data will always be the owning node pointer.

Share this post


Link to post
Share on other sites
Guest mantragora

Oh, come on. Not my fault that there is an error in HDK example. It's YOURS signature that doesn't match :P

Missed const keyword in last *PRM_Parm argument of both functions. Now it compiles. Will see what happens next.

Thanks!

Edited by mantragora

Share this post


Link to post
Share on other sites

Which HDK example are you talking about? We have regression tests for the HDK samples so I'd be very surprised that there's one that doesn't compile.

Share this post


Link to post
Share on other sites
Guest mantragora

Which HDK example are you talking about? We have regression tests for the HDK samples so I'd be very surprised that there's one that doesn't compile.

 

 The one below. Code example is straight from there. No const in last argument.

You mean something similar to example for dynamically generated items that we can find -> http://www.sidefx.com/docs/hdk13.0/_h_d_k__op_basics__overview__parameters.html#HDK_OpBasics_Overview_Parameters_Types ?

 

From Help:

static void
sopBuildRestGroup(void *data, PRM_Name *choicenames, int listsize,
const PRM_SpareData *spare, PRM_Parm *parm)
{
}
 
static PRM_ChoiceList sopRestGroupMenu(PRM_CHOICELIST_TOGGLE,
sopBuildRestGroup);

Share this post


Link to post
Share on other sites
Guest mantragora

Now I got this:

auto
SOP_Mantragora::Update_GroupMenu(void* data, PRM_Name* choicenames, int listsize, const PRM_SpareData* spare, const PRM_Parm* parm)
-> void
{
	auto sop = CAST_SOPNODE((OP_Node*) data);

	if (sop)
	{
		const PRM_Name* names;

		cout << parm->getToken() << endl;
		
		SOP_Mantragora::pointGroupMenu.getChoiceNames(names, data, spare, parm);
		SOP_Mantragora::primGroupMenu.getChoiceNames(names, data, spare, parm);

		choicenames[0].setToken(names->getToken());
		choicenames[0].setLabel(names->getLabel());		
	}
	else
	{
		choicenames[0].setToken(0);
		choicenames[0].setLabel(0);
	}
}

auto
SOP_Mantragora::Update_AttributeMenu(void* data, PRM_Name* choicenames, int listsize, const PRM_SpareData* spare, const PRM_Parm* parm)
-> void
{
	auto sop = CAST_SOPNODE((OP_Node*) data);

	if (sop)
	{
		const PRM_Name* names;

		cout << parm->getToken() << endl;

		SOP_Mantragora::pointAttribMenu.getChoiceNames(names, data, spare, parm);
		SOP_Mantragora::primAttribMenu.getChoiceNames(names, data, spare, parm);

		choicenames[0].setToken(names->getToken());
		choicenames[0].setLabel(names->getLabel());		
	}
	else
	{
		choicenames[0].setToken(0);
		choicenames[0].setLabel(0);
	}
}
SOP_Mantragora is my abstract base class from which I derive all my operators and this is the class in which I place all methods that I can reuse between other operators. At this point I know for sure that I will use the same functions in one other operator.

Now, I can get the name of the parameter that calls those callback functions, but how do I know that I have to switch between SOP_Mantragora::pointAttribMenu and SOP_Mantragora::primAttribMenu if I can't evaluate my other parameter on which users sets the class of attribute/group?

My SOP_Mantragora doesn't know about parameters that are on its child classes.

Any tips?

Edited by mantragora

Share this post


Link to post
Share on other sites

Oh, well, that's just a docs bug then. :P The source code files all compile.

 

Your base class should have a simplified method that takes the parameter name to evaluate. Put the actual callbacks themselves in the subclass.

Share this post


Link to post
Share on other sites
Guest mantragora

Your base class should have a simplified method that takes the parameter name to evaluate. Put the actual callbacks themselves in the subclass.

So, since I do have this layer above SOP_Node thanks to my SOP_Mantragora, and because

void* data
is always the owning node I could safely make something like this:

auto sop = CAST_SOPNODE((OP_Node*) data);

if (sop)
{
   auto sop_mantragora = (SOP_Mantragora*) sop;
}
to access all methods from my base class that are not static.

So instead of moving Update_GroupMenu() and Update_AttributeMenu() to subclass I left them in SOP_Mantragora and just made additional helper methods which I can override in my subclass, that will just give correct data to those callback methods by evaluating correct parameter on this subclassed node.

virtual 
auto Eval_IntParmForUpdateGroupMenu(exint& val, fpreal time = 0) -> void;

auto
SOP_Mantragora::Get_IntParmForUpdateGroupMenu(exint& val, fpreal time /* = 0 */)
-> void
{
	// I don't wont it to be pure virtual method so lets add some error to remind myself that it needs to be implemented.	
	cout << "Get_IntParmForUpdateGroupMenu() method need to be overriden in childclass before it can be called!" << endl;

	val = -1;
}
So finished version of Callback Update_GroupMenu() method:

auto
SOP_Mantragora::Update_GroupMenu(void* data, PRM_Name* choicenames, int listsize, const PRM_SpareData* spare, const PRM_Parm* parm)
-> void
{
	// We need to get our SOP node
	auto sop = CAST_SOPNODE((OP_Node*) data);

	if (sop)
	{
		// Since we know that we have SOP, we can safely cast it to my Base node class
		auto sop_mantragora = (SOP_Mantragora*) sop;

		// Extract data based on group class type
		fpreal currentTime = CHgetEvalTime();
		const PRM_Name* names;
		exint groupType;

		sop_mantragora->Get_IntParmForUpdateGroupMenu(groupType, currentTime);
		switch (groupType)
		{
		case 0:
			SOP_Mantragora::pointGroupMenu.getChoiceNames(names, data, spare, parm);
			break;

		case 1:
			SOP_Mantragora::primGroupMenu.getChoiceNames(names, data, spare, parm);
			break;

		default:
			cout << "WTF? Maybe Get_IntParmForUpdateGroupMenu() is not implemented in child class?" << endl;
			break;
		}
				
		// Set data
		choicenames[0].setToken(names->getToken());
		choicenames[0].setLabel(names->getLabel());	
	}
	else
	{
		choicenames[0].setToken(0);
		choicenames[0].setLabel(0);
	}
}
Edited by mantragora

Share this post


Link to post
Share on other sites
Guest mantragora

Hcuston, we got a problem.

I got small method for finding point/primitive attributes to which I can pass PRM_Template which is used to extract string from UI, then there are some string checks performed and attribute test performed. I have something similar for groups too.

First on my operator I'm checking group, and if there is no text in group field (empty) operator turns red, but the Update_GroupMenu() method still correctly fills dropdown menu so I can pick group.

Second check is for attributes, and if there is no text in attribute text field operator also turn red, but the dropdown menu is not filled. If I instead change it from addError() to addWarning() it fills dropdown correctly. But I don't want to have warning there because attribute is kinda very, very needed for rest of the code in the operator. So I want to have RED error but I also would like to have dropdown working, just like with group field.

And yes. String checking method is the same for both, group and attribute text field.

Any ideas why it works for groups and not for attributes in error state?

Edited by mantragora

Share this post


Link to post
Share on other sites
Guest mantragora

I looked at other operators and it looks that standard behavior for those that requires some group or attribute is to just set warning state, not error. Error seems to be for things like missing input.

So I changed my code and I'm reporting warnings everywhere and allowing execution of code if it's anything below UT_ERROR_WARNING.

Share this post


Link to post
Share on other sites

First, you shouldn't take the list of attributes from your own geometry. You should call getCookedGeoHandle() on your input node.

 

Secondly, empty group parameters are usually taken to mean "everything" and so they wouldn't trigger errors nor warnings.

 

Depending on your use case, you may or may not want to have the "empty semantic" for your attributes parameter as well.

  • Like 1

Share this post


Link to post
Share on other sites
Guest mantragora

First, you shouldn't take the list of attributes from your own geometry. You should call getCookedGeoHandle() on your input node.

I'm lost. Where I should call it? In my Update_AttributeMenu() method? Then I have to pass context there, or is there some other way it shoudl be done?

And, if I have to pass something to Update_AttributeMenu(), wouldn't it be easier to just pass there GA_AttributeDict instead of playing with Handle and locking unlocking geometry there?

Last thing, I'm not modyfing geometry (so only duplicatePointSource()) in this operator so the attributes in inputGeo and in this operator geo are exactly the same. So do I have to always get attributes from input geo even in this example?

And if I shouldn't take attributes from my geometry than in what situations SOP_Node::pointAttribMenu and SOP_Node::primAttribMenu should be used?

Edited by mantragora

Share this post


Link to post
Share on other sites

If you're just calling pointAttribMenu(), then it already cooks from the first input. So offhand, I don't see why adding an error on your node will prevent the menu contents from being filled out.

Share this post


Link to post
Share on other sites
Guest mantragora

Ok, I stripped the code as much as I could.

This is my cook():

auto
SOP_Test_Operator::cookMySop(OP_Context& context)
-> OP_ERROR
{
	UT_AutoInterrupt progress("Performing Operation");
	fpreal currentTime = context.getTime();

	// to access input we need to lock it
	if (lockInputs(context) >= UT_ERROR_ABORT) return error();
	// to modify geometry we need to make copy of it
	duplicatePointSource(0, context);

	// action!
	UT_String groupName;
	evalString(groupName, SOP_Test_PARAMETERS::group_Parameter.getToken(), 0, currentTime);

	UT_String attributeName;
	evalString(attributeName, SOP_Test_PARAMETERS::attribute_Parameter.getToken(), 0, currentTime);

	cout << "Group name: " << groupName << endl;
	cout << "Attribute name: " << attributeName << endl;
	cout << "Error() before string check: " << error() << endl;

	exint reportType = evalInt(SOP_Test_PARAMETERS::reportTypeMenu_Parameter.getToken(), 0, currentTime);

	if (!groupName.isstring())
	{
		switch (reportType)
		{
		case 0:
			addWarning(SOP_ERR_BADGROUP, "FAIL");
			break;

		default:
			addError(SOP_ERR_BADGROUP, "FAIL");
			break;
		}
	}

	if (!attributeName.isstring())
	{		
		switch (reportType)
		{
		case 0:
			addWarning(SOP_ERR_INVALID_ATTRIBUTE_NAME, "FAIL");
			break;

		default:
			addError(SOP_ERR_INVALID_ATTRIBUTE_NAME, "FAIL");
			break;
		}	
	}

	cout << "Error() after string check: " << error() << endl;

	if (error() < UT_ERROR_WARNING) cout << "Now we can test more!" << endl; 

	unlockInputs();
	return error();
}
My parameters:

namespace SOP_Test_PARAMETERS
{
	static auto reportTypeMenuParm_Name = PRM_Name("reporttype", "Report Type");
	static auto groupParm_Name = PRM_Name("group", "Group");
	static auto attributeParm_Name = PRM_Name("attribute", "Attribute");

	static PRM_Name reportTypeMenuParm_Choices[] =
	{
		PRM_Name("addwarning", "AddWarning"),
		PRM_Name("adderror", "AddError"),
		PRM_Name(0)
	};
	static auto reportTypeMenuParm_ChoiceList = PRM_ChoiceList(PRM_CHOICELIST_SINGLE, reportTypeMenuParm_Choices);

	// -------------------------- Parameters //

	auto reportTypeMenu_Parameter = PRM_Template(PRM_ORD, 1, &reportTypeMenuParm_Name, 0, &reportTypeMenuParm_ChoiceList);
	auto group_Parameter = PRM_Template(PRM_STRING, 1, &groupParm_Name, PRMzeroDefaults, &SOP_Node::pointGroupMenu);
	auto attribute_Parameter = PRM_Template(PRM_STRING, 1, &attributeParm_Name, PRMzeroDefaults, &SOP_Node::pointAttribMenu);
}
If I change error report type to AddWarning it fills menu in both, group and attribute menu, with AddError it fill it only with group menu while attribute menu is blank. Edited by mantragora

Share this post


Link to post
Share on other sites
Guest mantragora

Could anyone confirm that he gets the same wrong behavior when using SOP_Node::pointAttribMenu and correct when using SOP_Node::pointGroupMenu? So I could log error to SESI.

Stalkerx777, R U THERE? :)

Edited by mantragora

Share this post


Link to post
Share on other sites
Guest mantragora

Well, it looks that we got a bug. Ticket #22613.

This should be fixed in Houdini 13.0.586

All the best,

Support.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×