HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
SOP_MergePrimitives.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 Merge Primitives SOP
27  */
28 
29 #include "SOP_MergePrimitives.h"
30 
31 // This is an automatically generated header file based on theDsFile, below,
32 // to provide SOP_MergePrimitivesParms, an easy way to access parameter values from
33 // SOP_MergePrimitivesVerb::cook with the correct type.
34 #include "SOP_MergePrimitives.proto.h"
35 
36 #include <GA/GA_ElementGroup.h>
37 #include <GU/GU_Detail.h>
38 #include <GOP/GOP_Manager.h>
39 #include <OP/OP_Operator.h>
40 #include <OP/OP_OperatorTable.h>
41 #include <PRM/PRM_Include.h>
43 #include <UT/UT_DSOVersion.h>
44 #include <UT/UT_StringHolder.h>
45 
46 
47 using namespace UT::Literal;
48 using namespace HDK_Sample;
49 
50 constexpr static int theDestInput = 0;
51 constexpr static int theSrcInput = 1;
52 
53 //
54 // Help is stored in a "wiki" style text file. This text file should be copied
55 // to $HOUDINI_PATH/help/nodes/sop/hdk_simplemerge.txt
56 //
57 // See the sample_install.sh file for an example.
58 //
59 
60 /// This is the internal name of the SOP type.
61 /// It isn't allowed to be the same as any other SOP's type name.
62 const UT_StringHolder SOP_MergePrimitives::theSOPTypeName("hdk_mergeprimitives"_sh);
63 
64 /// newSopOperator is the hook that Houdini grabs from this dll
65 /// and invokes to register the SOP. In this case, we add ourselves
66 /// to the specified operator table.
67 void
69 {
70  table->addOperator(new OP_Operator(
71  SOP_MergePrimitives::theSOPTypeName, // Internal name
72  "HDK Merge Primitives", // UI name
73  SOP_MergePrimitives::myConstructor, // How to build the SOP
74  SOP_MergePrimitives::buildTemplates(), // My parameters
75  2, // Min # of sources
76  2, // Max # of sources
77  nullptr, // Custom local variables (none)
78  0, // No flags, not a generator, no merge input, not an output node.
79  0, // Not supplying custom input labels here. See SOP_MergePrimitives::inputLabel.
80  1, // One output
81  "HDK Examples")); // Make the SOP appear in the HDK Examples tab submenu
82 }
83 
84 /// This is a multi-line raw string specifying the parameter interface
85 /// for this SOP.
86 
87 // For our purpose we only need the primitive group, but we will customize
88 // the string input parameter to more easily input the group.
89 // This group is on the second input. In SOP_MergePrimitives::buildTemplates
90 // we bind a drop down menu to the group field. In order to get the menu
91 // to gather the gro ups on the second input we must specify the
92 // 'sop_input' parmtag to index '1'.
93 // Next, to get a selector button we use the 'script_action' parmtag to
94 // specify the script to execute when the button is pressed, the
95 // 'script_action_help' parmtag to create the hover tooltip, and the
96 // 'script_action_icon' parmtag to set the icon.
97 // All group selector buttons use the `soputils.selectGroupParm(kwargs)`
98 // function, and can use the kwargs['geometrytype'] to set the allowed
99 // geometry types, and the kwargs['indexindex'] to set the input
100 // to select on.
101 static const char *theDsFile = R"THEDSFILE(
102 {
103  name parameters
104  parm {
105  name "group"
106  label "Primitive Group"
107  type string
108  default { "" }
109  parmtag { "sop_input" "1" }
110  parmtag { "script_action" "import soputils\nkwargs['geometrytype'] = (hou.geometryType.Primitives,)\nkwargs['inputindex'] = 1\nsoputils.selectGroupParm(kwargs)" }
111  parmtag { "script_action_help" "Select geometry from an available viewport.\nShift-click to turn on Select Groups." }
112  parmtag { "script_action_icon" "BUTTONS_reselect" }
113  }
114 }
115 )THEDSFILE";
116 
118 SOP_MergePrimitives::buildTemplates()
119 {
120  static PRM_TemplateBuilder templ("SOP_MergePrimitives.C"_sh, theDsFile);
121 
122  if (templ.justBuilt())
123  {
124  // Add drop down menu to the attribute string field.
125  // The menu type is a PRM_ChoiceList and SOP_Node provides a
126  // number of nice pre-built menus that you can use.
127  //
128  // In this case we want to list all the primitive groups on the
129  // second input. We can use SOP_Node::primGroupMenu to generate
130  // the menu for us, and use the `parmtag` `sop_input` with an
131  // index of 1 to specify the menu to gather the groups on the
132  // input with index 1. Since the inputs are 0-indexed the first
133  // input has an index of 0 and the second has an index of 1.
134  templ.setChoiceListPtr("group", &SOP_Node::primGroupMenu);
135  }
136 
137  return templ.templates();
138 }
139 
140 // Set custom input labels for the source and destination inputs.
141 const char *
142 SOP_MergePrimitives::inputLabel(OP_InputIdx idx) const
143 {
144  switch (idx)
145  {
146  case theDestInput:
147  return "Destination Geometry";
148  case theSrcInput:
149  return "Source Geometry";
150  default:
151  return "Invalid Source";
152  }
153 }
154 
155 // Define the verb for this SOP Node.
157 {
158 public:
161 
162  virtual SOP_NodeParms *allocParms() const { return new SOP_MergePrimitivesParms(); }
163  virtual UT_StringHolder name() const { return SOP_MergePrimitives::theSOPTypeName; }
164 
165  // There are a few choices for the cookMode.
166  // For SOPs with no inputs that only generate geometry use COOK_GENERATOR
167  // For SOPs that modify the input, COOK_DUPLICATE or COOK_INPLACE is usually
168  // the best option, with COOK_DUPLICATE creating a full copy of the input detail
169  // and COOK_INPLACE working on the input detail directly.
170  // The final option COOK_GENERIC allows any number of inputs, but the detail you
171  // get in ::cook will be empty by default. This mode is useful for SOPs like Sweep
172  // that combine two separate geometries in an interesting way.
173  virtual CookMode cookMode(const SOP_NodeParms *parms) const { return COOK_INPLACE; }
174 
175  virtual void cook(const CookParms &cookparms) const;
176 
177  /// This static data member automatically registers
178  /// this verb class at library load time.
180 };
181 
182 // The static member variable definition has to be outside the class definition.
183 // The declaration is inside the class.
185 
186 // The ::cookVerb member on the node must return an instance of the class
187 // we just defined above.
188 const SOP_NodeVerb *
189 SOP_MergePrimitives::cookVerb() const
190 {
192 }
193 
194 /// This is the function that does the actual work.
195 void
197 {
198  auto &&sopparms = cookparms.parms<SOP_MergePrimitivesParms>();
199 
200  // The destination detail.
201  GU_Detail *detail = cookparms.gdh().gdpNC();
202 
203  // Get the second input for the source detail.
204  const GU_Detail * src_detail = cookparms.inputGeo(theSrcInput);
205 
206  // Get the input group pattern.
207  UT_StringHolder groupname = sopparms.getGroup();
208 
209  // NOTE: Any detached groups created by the GOP_Manager
210  // get deleted when the GOP_Manager is destroyed.
211  // Since the source detail is const, the groups created
212  // WILL be detached and so will be destroyed at the
213  // end of this scope when 'gop' is destroyed.
214  GOP_Manager gop;
215 
216  // Parse the primitive group from the supplied string.
217  // If the string is empty then leave the group as nullptr.
218  const GA_PrimitiveGroup * group = nullptr;
219  if (groupname.isstring())
220  {
221  // To parse the group the GOP Manager needs at minimum the
222  // group string and the detail on which to create the groups.
223 
224  // We create a light wrapper around the detail with the
225  // GOP_Manager::GroupCreator. The wrapper is used to provide
226  // a common interface to GEO_Detail when using a const or
227  // non-const detail. If the detail is constant then any
228  // created group is detached, and if the detail is not
229  // constant then you have a choice when creating the
230  // GroupCreator whether to create detached groups or not.
231  group = gop.parsePrimitiveGroups(
232  /*pat=*/groupname,
233  /*creator=*/GOP_Manager::GroupCreator(/*detail=*/src_detail));
234 
235  // If the returned group is nullptr then it could not parse
236  // the input string.
237  // If this happens merge the whole detail and show a warning
238  // saying that the group was not valid.
239  if (!group)
240  {
241  cookparms.sopAddWarning(SOP_ERR_BADGROUP, groupname);
242  }
243  }
244 
245  // GEO_Detail::merge to merge the primitives in the source detail into
246  // the destination detail. If the group field was left blank then
247  // nullptr is given to the primGrp input resulting in all primitives
248  // being copied.
249  // There are a few different ways you are able to merge geometry.
250  // 1. GEO_Detail::merge with a detail and a group can be used to merge a
251  // full detail or a group of primitives.
252  // 2. GEO_Detail::mergePrimitives can be used to merge only a selection
253  // of primitives defined by a GA_Range.
254  // 3. GEO_Detail::mergePoints with a detail and a group can be used to
255  // merge only a group of points.
256  // 4. GEO_Detail::mergePoints with a detail and a GA_Range can be used to
257  // merge only a range of points.
258  // 5. GEO_Detail::merge with only a GEO_Primitive can be used to merge
259  // a single primitive from the source detail.
260  // Note: The GEO_Detail::mergePoints member functions will ONLY merge the
261  // points from the source and not the primitives.
262  detail->merge(/*src=*/*src_detail, /*primGrp=*/group);
263 }
static PRM_ChoiceList primGroupMenu
Definition: SOP_Node.h:1193
virtual SOP_NodeParms * allocParms() const
virtual UT_StringHolder name() const
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
int OP_InputIdx
Definition: OP_DataTypes.h:184
bool addOperator(OP_Operator *op, std::ostream *err=nullptr)
const T & parms() const
Definition: SOP_NodeVerb.h:423
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)
virtual CookMode cookMode(const SOP_NodeParms *parms) const
static const SOP_NodeVerb::Register< SOP_MergePrimitivesVerb > theVerb
GU_Detail * gdpNC()
GLenum GLenum GLsizei void * table
Definition: glad.h:5129
GA_API const UT_StringHolder parms
const GU_Detail * inputGeo(exint idx) const
Definition: SOP_NodeVerb.h:387
virtual void cook(const CookParms &cookparms) const
This is the function that does the actual work.
void newSopOperator(OP_OperatorTable *table)
GU_DetailHandle & gdh() const
The initial state of gdh depends on the cookMode()
Definition: SOP_NodeVerb.h:341
SYS_FORCE_INLINE bool isstring() const