HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
SOP_Expand.C
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2025
3  * Side Effects Software Inc. All rights reserved.
4  *
5  * Redistribution and use of Houdini Development Kit samples in source and
6  * binary forms, with or without modification, are permitted provided that the
7  * following conditions are met:
8  * 1. Redistributions of source code must retain the above copyright notice,
9  * this list of conditions and the following disclaimer.
10  * 2. The name of Side Effects Software may not be used to endorse or
11  * promote products derived from this software without specific prior
12  * written permission.
13  *
14  * THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE `AS IS' AND ANY EXPRESS
15  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
17  * NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
18  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
19  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
20  * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
21  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
22  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
23  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  *
25  *----------------------------------------------------------------------------
26  * The HDK Expand SOP
27  */
28 
29 #include "SOP_Expand.h"
30 
31 // This is an automatically generated header file based on theDsFile, below,
32 // to provide SOP_ExpandParms, an easy way to access parameter values from
33 // SOP_ExpandVerb::cook with the correct type.
34 #include "SOP_Expand.proto.h"
35 
36 #include <GA/GA_ElementGroup.h>
37 #include <GU/GU_Detail.h>
38 #include <GU/GU_Selection.h>
39 #include <GEO/GEO_Closure.h>
40 #include <GOP/GOP_Manager.h>
41 #include <OP/OP_Operator.h>
42 #include <OP/OP_OperatorTable.h>
43 #include <PRM/PRM_Include.h>
45 #include <UT/UT_DSOVersion.h>
46 #include <UT/UT_StringHolder.h>
47 
48 
49 using namespace UT::Literal;
50 using namespace HDK_Sample;
51 
52 //
53 // Help is stored in a "wiki" style text file. This text file should be copied
54 // to $HOUDINI_PATH/help/nodes/sop/hdk_expand.txt
55 //
56 // See the sample_install.sh file for an example.
57 //
58 
59 /// This is the internal name of the SOP type.
60 /// It isn't allowed to be the same as any other SOP's type name.
61 const UT_StringHolder SOP_Expand::theSOPTypeName("hdk_expand"_sh);
62 
63 /// newSopOperator is the hook that Houdini grabs from this dll
64 /// and invokes to register the SOP. In this case, we add ourselves
65 /// to the specified operator table.
66 void
68 {
69  table->addOperator(new OP_Operator(
70  SOP_Expand::theSOPTypeName, // Internal name
71  "HDK Expand", // UI name
72  SOP_Expand::myConstructor, // How to build the SOP
73  SOP_Expand::buildTemplates(), // My parameters
74  1, // Min # of sources
75  1, // Max # of sources
76  nullptr, // Custom local variables (none)
77  0, // No flags, not a generator, no merge input, not an output node.
78  0, // Not supplying custom input labels here.
79  1, // One output
80  "HDK Examples")); // Make the SOP appear in the HDK Examples tab submenu
81 }
82 
83 /// This is a multi-line raw string specifying the parameter interface
84 /// for this SOP.
85 
86 // For our purpose we will customize the string input parameter to
87 // more easily input the group.
88 // In SOP_Expand::buildTemplates we bind a drop down menu to the group field.
89 // Next, to get a selector button we use the 'script_action' parmtag to
90 // specify the script to execute when the button is pressed, the
91 // 'script_action_help' parmtag to create the hover tooltip, and the
92 // 'script_action_icon' parmtag to set the icon.
93 // All group selector buttons use the `soputils.selectGroupParm(kwargs)`
94 // function, and can use the kwargs['geometrytype'] to set the allowed
95 // geometry types, and the kwargs['indexindex'] to set the input
96 // to select on.
97 // Notice that we are able to pass a parameter to the kwargs['geometrytype'].
98 // If this is done the selector will treat the allowable menu options as
99 // the acceptable group types and will change the menu to the
100 // chosen group type when the selection has been accepted.
101 static const char *theDsFile = R"THEDSFILE(
102 {
103  name parameters
104  parm {
105  name "group"
106  label "Group"
107  type string
108  default { "" }
109  parmtag { "script_action" "import soputils\nkwargs['geometrytype'] = kwargs['node'].parmTuple('grouptype')\nkwargs['inputindex'] = 0\nsoputils.selectGroupParm(kwargs)" }
110  parmtag { "script_action_help" "Select geometry from an available viewport.\nShift-click to turn on Select Groups." }
111  parmtag { "script_action_icon" "BUTTONS_reselect" }
112  }
113  parm {
114  name "grouptype"
115  label "Group Type"
116  type ordinal
117  default { "0" }
118  menu {
119  "points" "Points"
120  "prims" "Primitives"
121  }
122  }
123  parm {
124  name "scale"
125  label "Uniform Scale"
126  type float
127  default { "1" }
128  range { 0! 10 }
129  }
130 }
131 )THEDSFILE";
132 
134 SOP_Expand::buildTemplates()
135 {
136  static PRM_TemplateBuilder templ("SOP_Expand.C"_sh, theDsFile);
137 
138  if (templ.justBuilt())
139  {
140  // Add drop down menu to the attribute string field.
141  // The menu type is a PRM_ChoiceList and SOP_Node provides a
142  // number of nice pre-built menus that you can use.
143 
144  // In this case we want to list all the primitive groups on the
145  // second input. We can use SOP_Node::groupMenu to generate the
146  // menu for us which will list all point
147  // and primitive groups.
148  templ.setChoiceListPtr("group", &SOP_Node::groupMenu);
149  }
150 
151  return templ.templates();
152 }
153 
154 // Define the verb for this SOP Node.
156 {
157 public:
159  virtual ~SOP_ExpandVerb() {}
160 
161  virtual SOP_NodeParms *allocParms() const { return new SOP_ExpandParms(); }
162  virtual UT_StringHolder name() const { return SOP_Expand::theSOPTypeName; }
163 
164  // There are a few choices for the cookMode.
165  // For SOPs with no inputs that only generate geometry use COOK_GENERATOR
166  // For SOPs that modify the input, COOK_DUPLICATE or COOK_INPLACE is usually
167  // the best option, with COOK_DUPLICATE creating a full copy of the input detail
168  // and COOK_INPLACE working on the input detail directly.
169  // The final option COOK_GENERIC allows any number of inputs, but the detail you
170  // get in ::cook will be empty by default. This mode is useful for SOPs like Sweep
171  // that combine two separate geometries in an interesting way.
172  virtual CookMode cookMode(const SOP_NodeParms *parms) const { return COOK_INPLACE; }
173 
174  virtual void cook(const CookParms &cookparms) const;
175 
176  /// This static data member automatically registers
177  /// this verb class at library load time.
179 };
180 
181 // The static member variable definition has to be outside the class definition.
182 // The declaration is inside the class.
184 
185 // The ::cookVerb member on the node must return an instance of the class
186 // we just defined above.
187 const SOP_NodeVerb *
188 SOP_Expand::cookVerb() const
189 {
190  return SOP_ExpandVerb::theVerb.get();
191 }
192 
193 /// This is the function that does the actual work.
194 void
196 {
197  auto &&sopparms = cookparms.parms<SOP_ExpandParms>();
198 
199  // The destination detail.
200  GU_Detail *detail = cookparms.gdh().gdpNC();
201 
202  // Get the input group pattern, grouptype and the scale.
203  UT_StringHolder groupname = sopparms.getGroup();
204  // When the proto file is generated, it will automatically
205  // generate enums for the different options in menus.
206  SOP_ExpandEnums::Grouptype grouptype = sopparms.getGrouptype();
207  fpreal scale = sopparms.getScale();
208 
209  // NOTE: Any detached groups created by the GOP_Manager
210  // get deleted when the GOP_Manager is destroyed.
211  GOP_Manager gop;
212  const GA_PointGroup * point_group = nullptr;
213  const GA_PrimitiveGroup * prim_group = nullptr;
214 
215  // If the input group type is primitives then we will parse the group
216  // as a primitive group and get the point group closure of it as a
217  // detached group. To ensure this group gets deleted when we're done
218  // with it we use a detached group returned as a unique pointer which
219  // will destroy the group when this unique pointer gets destroyed.
220  GA_PointGroupUPtr point_group_closure = nullptr;
221  if (groupname.isstring())
222  {
223  // To parse the group the GOP Manager needs at minimum the
224  // group string and the detail on which to create the groups.
225 
226  // We create a light wrapper around the detail with the
227  // GOP_Manager::GroupCreator. The wrapper is used to provide
228  // a common interface to GEO_Detail when using a const or
229  // non-const detail. If the detail is constant then any
230  // created group is detached, and if the detail is not
231  // constant then you have a choice when creating the
232  // GroupCreator whether to create detached groups or not.
233  GOP_Manager::GroupCreator group_creator(detail, /*detached=*/true);
234  switch (grouptype) {
235  case SOP_ExpandEnums::Grouptype::POINTS:
236  {
237  // Parse the group as points.
238  point_group = gop.parsePointGroups(groupname, group_creator);
239  break;
240  }
241  case SOP_ExpandEnums::Grouptype::PRIMS:
242  {
243  // Parse the group as prims and promote to points.
244  // We'll want to continue holding onto prim_group to set the
245  // cook selection later as well.
246  prim_group = gop.parsePrimitiveGroups(groupname, group_creator);
247 
248  if (prim_group)
249  {
250  // Use the GEO_Closure functions to get a detached point group
251  // containing all points on the input primitives.
252  // This is owned by this unique pointer and when this unique
253  // pointer is destroyed, the group will also be destroyed.
254  point_group_closure = GEO_Closure::getDetachedPointClosure(*detail, *prim_group);
255  point_group = point_group_closure.get();
256  }
257  break;
258  }
259  }
260 
261  // If the returned group is nullptr then it could not parse
262  // the input string.
263  if (!point_group)
264  cookparms.sopAddWarning(SOP_ERR_BADGROUP, groupname);
265  }
266  // Get the range of points to look at.
267  // If point_group is nullptr, this will give a point range
268  // with all points in the detail.
269  GA_Range point_range = detail->getPointRange(point_group);
270 
271  // Compute the centroid position by averaging the point positions.
272  UT_Vector3D centroid(0,0,0);
273  for (GA_Offset pt_off : point_range)
274  {
275  // Get the point position with GA_Detail::getPos3D.
276  const UT_Vector3D pos = detail->getPos3D(pt_off);
277  centroid += pos;
278  }
279 
280  if (!point_range.isEmpty())
281  centroid /= point_range.getEntries();
282 
283  // Now scale the point positions with respect to the centroid.
284  for (GA_Offset pt_off : point_range)
285  {
286  // Get the point position with GA_Detail::getPos3D.
287  UT_Vector3D pos = detail->getPos3D(pt_off);
288 
289  // First subtract the centroid position to get the offset from the centroid.
290  pos -= centroid;
291  // Then scale the offset from the centroid by the uniform scale.
292  pos *= scale;
293  // Then add the centroid position back in.
294  pos += centroid;
295 
296  // And finally set the point position using GA_Detail::setPos3.
297  detail->setPos3(pt_off, pos);
298  }
299 
300  // Set up a cook selection to show all the affected points/prims.
301  if (cookparms.selectionEnabled())
302  {
303  // We want the cook selection to match the input group type.
304  switch (grouptype) {
305  case SOP_ExpandEnums::Grouptype::POINTS:
306  {
307  // If we have a point group, set the cook selection to the point group
308  // Otherwise, set the cook selection to all points in the detail.
309  if (point_group)
310  cookparms.select(*point_group);
311  else
312  cookparms.select(GA_GROUP_POINT);
313  break;
314  }
315  case SOP_ExpandEnums::Grouptype::PRIMS:
316  {
317  // If we have a prim group, set the cook selection to the prim group.
318  // Otherwise, set the cook selection to all prims in the detail.
319  if (prim_group)
320  cookparms.select(*prim_group);
321  else
322  cookparms.select(GA_GROUP_PRIMITIVE);
323  break;
324  }
325  }
326  }
327 
328  // We have modified the 'P' attribute so we need to bump its data id.
329  // To bump all data ids you can use detail->bumpAllDataIds();
330  // For our case we only modified a single attribute so we can just bump
331  // that one attribute's data ids.
332  detail->getP()->bumpDataId();
333 }
void setChoiceListPtr(const UT_StringRef &name, PRM_ChoiceList *list)
UT_ErrorSeverity sopAddWarning(int code, const char *msg=0, const UT_SourceLocation *loc=0) const
Definition: SOP_NodeVerb.h:509
virtual CookMode cookMode(const SOP_NodeParms *parms) const
Definition: SOP_Expand.C:172
virtual void cook(const CookParms &cookparms) const
This is the function that does the actual work.
Definition: SOP_Expand.C:195
void newSopOperator(OP_OperatorTable *table)
Definition: SOP_Expand.C:67
bool addOperator(OP_Operator *op, std::ostream *err=nullptr)
A range of elements in an index-map.
Definition: GA_Range.h:42
GA_Size GA_Offset
Definition: GA_Types.h:646
GA_API const UT_StringHolder scale
const T & parms() const
Definition: SOP_NodeVerb.h:423
virtual UT_StringHolder name() const
Definition: SOP_Expand.C:162
Constructs a PRM_Template list from an embedded .ds file or an istream.
PRM_Template * templates() const
const GA_PrimitiveGroup * parsePrimitiveGroups(const char *pat, const GroupCreator &creator, bool numok=true, bool ordered=false, bool strict=false, GA_Index prim_offset=GA_Index(0), ParseInfo *info=0)
static const SOP_NodeVerb::Register< SOP_ExpandVerb > theVerb
Definition: SOP_Expand.C:178
static GA_PointGroupUPtr getDetachedPointClosure(const GEO_Detail &gdp, const GA_Group &group)
void select(GA_GroupType gtype=GA_GROUP_PRIMITIVE) const
Definition: SOP_NodeVerb.h:539
GU_Detail * gdpNC()
GLenum GLenum GLsizei void * table
Definition: glad.h:5129
static PRM_ChoiceList groupMenu
Definition: SOP_Node.h:1192
GA_API const UT_StringHolder parms
virtual SOP_NodeParms * allocParms() const
Definition: SOP_Expand.C:161
fpreal64 fpreal
Definition: SYS_Types.h:278
bool selectionEnabled() const
Definition: SOP_NodeVerb.h:610
virtual ~SOP_ExpandVerb()
Definition: SOP_Expand.C:159
UT_UniquePtr< GA_PointGroup > GA_PointGroupUPtr
const GA_PointGroup * parsePointGroups(const char *pat, const GroupCreator &creator, bool numok=true, bool ordered=false, bool strict=false, GA_Index point_offset=GA_Index(0), ParseInfo *info=0)
GU_DetailHandle & gdh() const
The initial state of gdh depends on the cookMode()
Definition: SOP_NodeVerb.h:341
SYS_FORCE_INLINE bool isstring() const