HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
SOP_MaxPromote.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 MaxPromote SOP
27  */
28 
29 #include "SOP_MaxPromote.h"
30 
31 // This is an automatically generated header file based on theDsFile, below,
32 // to provide SOP_MaxPromoteParms, an easy way to access parameter values from
33 // SOP_MaxPromoteVerb::cook with the correct type.
34 #include "SOP_MaxPromote.proto.h"
35 
36 #include <GU/GU_Detail.h>
37 #include <GU/GU_Promote.h>
38 #include <OP/OP_Operator.h>
39 #include <OP/OP_OperatorTable.h>
40 #include <PRM/PRM_Include.h>
42 #include <UT/UT_DSOVersion.h>
43 #include <UT/UT_StringHolder.h>
44 
45 
46 using namespace UT::Literal;
47 using namespace HDK_Sample;
48 
49 //
50 // Help is stored in a "wiki" style text file. This text file should be copied
51 // to $HOUDINI_PATH/help/nodes/sop/hdk_maxpromote.txt
52 //
53 // See the sample_install.sh file for an example.
54 //
55 
56 /// This is the internal name of the SOP type.
57 /// It isn't allowed to be the same as any other SOP's type name.
58 const UT_StringHolder SOP_MaxPromote::theSOPTypeName("hdk_maxpromote"_sh);
59 
60 /// newSopOperator is the hook that Houdini grabs from this dll
61 /// and invokes to register the SOP. In this case, we add ourselves
62 /// to the specified operator table.
63 void
65 {
66  table->addOperator(new OP_Operator(
67  SOP_MaxPromote::theSOPTypeName, // Internal name
68  "HDK Max Promote", // UI name
69  SOP_MaxPromote::myConstructor, // How to build the SOP
70  SOP_MaxPromote::buildTemplates(), // My parameters
71  1, // Min # of sources
72  1, // Max # of sources
73  nullptr, // Custom local variables (none)
74  0, // No flags, not a generator, no merge input, not an output node.
75  0, // No custom input labels
76  1, // One output
77  "HDK Examples")); // Make the SOP appear in the Attribute tab submenu
78 }
79 
80 /// This is a multi-line raw string specifying the parameter interface
81 /// for this SOP.
82 static const char *theDsFile = R"THEDSFILE(
83 {
84  name parameters
85  parm {
86  name "attrib"
87  label "Point Attribute"
88  type string
89  default { "" }
90  }
91  parm {
92  name "keepsource"
93  label "Keep Source Attribute"
94  type toggle
95  default { "1" }
96  }
97 }
98 )THEDSFILE";
99 
101 SOP_MaxPromote::buildTemplates()
102 {
103  static PRM_TemplateBuilder templ("SOP_MaxPromote.C"_sh, theDsFile);
104 
105  if (templ.justBuilt())
106  {
107  // Add drop down menu to the attribute string field.
108  // The menu type is a PRM_ChoiceList and SOP_Node provides a
109  // number of nice pre-built menus that you can use. For our case
110  // we would like to list all of the point attributes so we can
111  // choose between 'pointAttribMenu' or 'pointAttribReplaceMenu'
112  // for whether we want a toggle or a replace menu.
113  // Since the SOP only handles one attribute name, the
114  // SOP_Node::pointAttribReplaceMenu is the best choice.
116  }
117 
118  return templ.templates();
119 }
120 
121 // Define the verb for this SOP Node.
123 {
124 public:
126  virtual ~SOP_MaxPromoteVerb() {}
127 
128  virtual SOP_NodeParms *allocParms() const { return new SOP_MaxPromoteParms(); }
129  virtual UT_StringHolder name() const { return SOP_MaxPromote::theSOPTypeName; }
130 
131  // There are a few choices for the cookMode.
132  // For SOPs with no inputs that only generate geometry use COOK_GENERATOR
133  // For SOPs that modify the input, COOK_DUPLICATE or COOK_INPLACE is usually
134  // the best option, with COOK_DUPLICATE creating a full copy of the input detail
135  // and COOK_INPLACE working on the input detail directly.
136  // The final option COOK_GENERIC allows any number of inputs, but the detail you
137  // get in ::cook will be empty by default. This mode is useful for SOPs like Sweep
138  // that combine two separate geometries in an interesting way.
139  virtual CookMode cookMode(const SOP_NodeParms *parms) const { return COOK_INPLACE; }
140 
141  virtual void cook(const CookParms &cookparms) const;
142 
143  /// This static data member automatically registers
144  /// this verb class at library load time.
146 };
147 
148 // The static member variable definition has to be outside the class definition.
149 // The declaration is inside the class.
151 
152 // The ::cookVerb member on the node must return an instance of the class
153 // we just defined above.
154 const SOP_NodeVerb *
155 SOP_MaxPromote::cookVerb() const
156 {
157  return SOP_MaxPromoteVerb::theVerb.get();
158 }
159 
160 template <typename T>
161 void
163  GU_Detail & detail,
164  const GA_Attribute & source_attrib,
165  GA_Attribute & dest_attrib)
166 {
167  // Use a read-only handle for the source attribute.
168  GA_ROHandleT<T> source_handle(&source_attrib);
169 
170  // Ensure the source handle got bound correctly.
171  if (source_handle.isInvalid())
172  {
173  cookparms.sopAddError(SOP_MESSAGE, "Unable to bind read-only handle to source attribute.");
174  return;
175  }
176 
177  // Use a read-write handle for the dest attribute.
178  GA_RWHandleT<T> dest_handle(&dest_attrib);
179 
180  // Ensure dest_handle got bound correctly.
181  if (dest_handle.isInvalid())
182  {
183  cookparms.sopAddError(SOP_MESSAGE, "Unable to bind read-write handle to output attribute.");
184  return;
185  }
186 
187  // Scan all the points, find the maximal value.
188  // If there are no points, then max_val will get the value 0.
189  T max_val = 0;
190  T cur_val = 0;
191  bool first_set = false;
192  for (GA_Offset pt_off : detail.getPointRange())
193  {
194  cur_val = source_handle.get(pt_off);
195  if (!first_set || cur_val > max_val)
196  {
197  first_set = true;
198  max_val = cur_val;
199  }
200  }
201 
202  // Store the max into the detail attribute.
203  dest_handle.set(GA_DETAIL_OFFSET, max_val);
204 }
205 
206 /// This is the function that does the actual work.
207 void
209 {
210  auto &&sopparms = cookparms.parms<SOP_MaxPromoteParms>();
211  GU_Detail *detail = cookparms.gdh().gdpNC();
212 
213  // Get the attribute name.
214  UT_StringHolder attrib_name = sopparms.getAttrib();
215 
216  // Find the attribute, if not valid then display a warning.
217  GA_Attribute * source_attrib = detail->findPointAttribute(attrib_name);
218  if (!source_attrib)
219  {
220  // There are a number of common error messages you can use defined in
221  // SOP_Error.h. The SOP_ATTRIBUTE_INVALID will take the attribute's
222  // name and format a warning for us that the attribute is not valid.
223  cookparms.sopAddWarning(SOP_ATTRIBUTE_INVALID, attrib_name);
224  return;
225  }
226 
227  bool keep_existing = sopparms.getKeepsource();
228 
229  // NOTE: All of the following can be done more efficiently and with more
230  // generality using GU_Promote::promote.
231  // GU_Promote::promote(
232  // /*gdp=*/*detail, // The geometry detail to modify
233  // /*attrib=*/source_attrib, // The source attribute to promote
234  // /*new_owner=*/GA_ATTRIB_DETAIL, // Promote to detail
235  // /*destroy_existing=*/!keep_existing, // Drive from "keepsource" toggle.
236  // /*method=*/GU_Promote::GU_PROMOTE_MAX, // Use max promotion method
237  // /*new_name=*/nullptr, // Keep existing name
238  // /*piece_attrib=*/nullptr); // No point attribute
239 
240  GA_ATINumeric * source_numerical_attrib = GA_ATINumeric::cast(source_attrib);
241  if (!source_numerical_attrib || source_numerical_attrib->getTupleSize() != 1)
242  {
243  // SOP_MESSAGE allows you to specify any custom message to display
244  // as either a warning using cookparms.sopAddWarning or an error
245  // using cookparms.sopAddError.
246  cookparms.sopAddError(SOP_MESSAGE, "Invalid attribute type. Only numerical attributes with one component are supported.");
247  return;
248  }
249 
250  // Destroy any existing attribute on the detail with the same name.
251  detail->destroyAttribute(GA_ATTRIB_GLOBAL, source_attrib->getName());
252 
253  // Clone the source attribute but set the owner to the detail so we get the
254  // output attribute with the same storage type, options, tuple size,
255  // and scope.
256  GA_Attribute * out_attrib = detail->getAttributes().cloneAttribute(
257  /*owner=*/GA_ATTRIB_GLOBAL,
258  /*name=*/source_attrib->getName(),
259  /*src=*/*source_attrib,
260  /*clone_options=*/true);
261  if (!out_attrib)
262  {
263  cookparms.sopAddError(SOP_MESSAGE, "Failed to create output attribute.");
264  return;
265  }
266 
267  // Delegate the promotion to a templated function based on the storage type.
268  // We can read and write to attributes using a GA_ROHandleT<> (write only)
269  // or a GA_RWHandleT<> (read+write). There are a number of
270  // common handles defined in GA_Handle.h.
271  switch (source_numerical_attrib->getStorage())
272  {
273  case GA_STORE_INT32:
274  {
275  // Use a GA_ROHandleI and GA_RWHandleI
276  sopPromote<int32>(cookparms, *detail, *source_attrib, *out_attrib);
277  break;
278  }
279  case GA_STORE_INT64:
280  {
281  // Use a GA_ROHandleID and GA_RWHandleID
282  sopPromote<int64>(cookparms, *detail, *source_attrib, *out_attrib);
283  break;
284  }
285  case GA_STORE_REAL32:
286  {
287  // Use a GA_ROHandleF and GA_RWHandleF
288  sopPromote<fpreal32>(cookparms, *detail, *source_attrib, *out_attrib);
289  break;
290  }
291  case GA_STORE_REAL64:
292  {
293  // Use a GA_ROHandleD and GA_RWHandleD
294  sopPromote<fpreal64>(cookparms, *detail, *source_attrib, *out_attrib);
295  break;
296  }
297  default:
298  {
299  cookparms.sopAddError(SOP_MESSAGE, "Invalid attribute storage type. Only 32 and 64 bit integer or floating point attributes are supported.");
300  return;
301  }
302  }
303 
304  // Destroy existing if specified.
305  if (!keep_existing)
306  {
307  detail->destroyAttribute(source_attrib->getOwner(), source_attrib->getName());
308  }
309 
310  // NOTE: Whenever an existing attribute is modified, you must bump the data
311  // IDs on the attribute to ensure the various parts of houdini know that
312  // the attribute, and the detail it is on, have changed.
313  // To bump a single attribute data Id, you can call GA_Attribute::bumpDataId(),
314  // and to bump all attribute data Ids on a detail you can call
315  // GA_Detail::bumpAllDataIds().
316  // In this case, we are not modifying any existing attributes so we are okay,
317  // but the regular SOP Attribute Promote using GU_Promote::promote *may* alter
318  // existing attributes, and will automatically call GA_Attribute::bumpDataId().
319 }
Definition of a geometry attribute.
Definition: GA_Attribute.h:198
static PRM_ChoiceList pointAttribReplaceMenu
Definition: SOP_Node.h:1230
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 getTupleSize() const
bool addOperator(OP_Operator *op, std::ostream *err=nullptr)
void sopPromote(const SOP_NodeVerb::CookParms &cookparms, GU_Detail &detail, const GA_Attribute &source_attrib, GA_Attribute &dest_attrib)
virtual CookMode cookMode(const SOP_NodeParms *parms) const
UT_ErrorSeverity sopAddError(int code, const char *msg=0, const UT_SourceLocation *loc=0) const
Definition: SOP_NodeVerb.h:516
SYS_FORCE_INLINE const UT_StringHolder & getName() const
Definition: GA_Attribute.h:283
GA_Size GA_Offset
Definition: GA_Types.h:646
const T & parms() const
Definition: SOP_NodeVerb.h:423
GA_Range getPointRange(const GA_PointGroup *group=0) const
Get a range of all points in the detail.
Definition: GA_Detail.h:1749
Constructs a PRM_Template list from an embedded .ds file or an istream.
PRM_Template * templates() const
static SYS_FORCE_INLINE GA_ATINumeric * cast(GA_Attribute *attrib)
Definition: GA_ATINumeric.h:65
SYS_FORCE_INLINE T get(GA_Offset off, int comp=0) const
Definition: GA_Handle.h:203
SYS_FORCE_INLINE bool isInvalid() const
Definition: GA_Handle.h:191
GU_Detail * gdpNC()
GLenum GLenum GLsizei void * table
Definition: glad.h:5129
GA_API const UT_StringHolder parms
virtual UT_StringHolder name() const
virtual ~SOP_MaxPromoteVerb()
SYS_FORCE_INLINE void set(GA_Offset off, const T &val) const
Definition: GA_Handle.h:362
static const SOP_NodeVerb::Register< SOP_MaxPromoteVerb > theVerb
virtual SOP_NodeParms * allocParms() const
SYS_FORCE_INLINE GA_AttributeOwner getOwner() const
Definition: GA_Attribute.h:210
#define GA_DETAIL_OFFSET
Definition: GA_Types.h:691
void newSopOperator(OP_OperatorTable *table)
GU_DetailHandle & gdh() const
The initial state of gdh depends on the cookMode()
Definition: SOP_NodeVerb.h:341
virtual void cook(const CookParms &cookparms) const
This is the function that does the actual work.
GA_Storage getStorage() const