HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
SOP_CopyPacked.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  * This SOP copies packed primitives from the first input onto the specified
27  * points in the second input.
28  */
29 
30 // A .proto.h file is an automatically generated header file based on theDsFile,
31 // below, to provide SOP_CopyPackedParms, an easy way to access parameter
32 // values from SOP_CopyPackedVerb::cook with the correct type, and
33 // SOP_CopyPackedEnums, a namespace containing enum types for any ordinal
34 // menu parameters.
35 #include "SOP_CopyPacked.proto.h"
36 
37 #include <SOP/SOP_Node.h>
38 #include <SOP/SOP_NodeVerb.h>
39 #include <GU/GU_Detail.h>
40 #include <GU/GU_PrimPacked.h>
41 #include <GOP/GOP_Manager.h>
42 #include <GA/GA_ATITopology.h>
43 #include <GA/GA_AttributeRefMap.h>
44 #include <GA/GA_ElementGroup.h>
45 #include <GA/GA_Handle.h>
46 #include <GA/GA_Names.h>
47 #include <GA/GA_SplittableRange.h>
48 #include <GA/GA_Types.h>
49 #include <OP/OP_Operator.h>
50 #include <OP/OP_OperatorTable.h>
52 #include <UT/UT_Assert.h>
53 #include <UT/UT_DSOVersion.h>
54 #include <UT/UT_Interrupt.h>
55 #include <UT/UT_Lock.h>
56 #include <UT/UT_ParallelUtil.h>
57 #include <UT/UT_StringHolder.h>
58 #include <UT/UT_StringMap.h>
59 #include <UT/UT_UniquePtr.h>
60 #include <SYS/SYS_MemoryOrder.h>
61 
62 using namespace UT::Literal;
63 
64 namespace HDK_Sample {
65 
66 //******************************************************************************
67 //* Setup *
68 //******************************************************************************
69 
71 {
72 public:
74  {}
75 };
76 
77 
79 {
80 public:
81  virtual SOP_NodeParms *allocParms() const { return new SOP_CopyPackedParms(); }
82  virtual SOP_NodeCache *allocCache() const { return new SOP_CopyPackedCache(); }
83  virtual UT_StringHolder name() const { return theSOPTypeName; }
84 
85  virtual CookMode cookMode(const SOP_NodeParms *parms) const { return COOK_GENERIC; }
86 
87  virtual void cook(const CookParms &cookparms) const;
88 
89  /// This is the internal name of the SOP type.
90  /// It isn't allowed to be the same as any other SOP's type name.
92 
93  /// This static data member automatically registers
94  /// this verb class at library load time.
96 
97  /// This is the parameter interface string, below.
98  static const char *const theDsFile;
99 };
100 
101 // The static member variable definitions have to be outside the class definition.
102 // The declarations are inside the class.
103 const UT_StringHolder SOP_CopyPackedVerb::theSOPTypeName("hdk_copypacked"_sh);
104 const SOP_NodeVerb::Register<SOP_CopyPackedVerb> SOP_CopyPackedVerb::theVerb;
105 
106 /// This is the SOP class definition.
107 class SOP_CopyPacked : public SOP_Node
108 {
109 public:
110  static inline PRM_Template *buildTemplates(const UT_StringHolder &filename, const char *ds_file)
111  {
112  static PRM_TemplateBuilder templ(filename, ds_file);
113  if (templ.justBuilt())
114  {
115  templ.setChoiceListPtr("pointgroup", &SOP_Node::pointGroupMenu);
116  }
117  return templ.templates();
118  }
119  static OP_Node *myConstructor(OP_Network *net, const char *name, OP_Operator *op)
120  {
121  return new SOP_CopyPacked(net, name, op);
122  }
123 
124 protected:
125  virtual const SOP_NodeVerb *cookVerb() const override
126  {
127  return SOP_CopyPackedVerb::theVerb.get();
128  }
129 
131  : SOP_Node(net, name, op)
132  {
133  // All verb SOPs must manage data IDs, to track what's changed
134  // from cook to cook.
135  mySopFlags.setManagesDataIDs(true);
136  }
137 
138  virtual ~SOP_CopyPacked() {}
139 
140  /// Since this SOP implements a verb, cookMySop just delegates to the verb.
141  virtual OP_ERROR cookMySop(OP_Context &context) override
142  {
143  return cookMyselfAsVerb(context);
144  }
145 
146  /// These are the labels that appear when hovering over the inputs.
147  virtual const char *inputLabel(OP_InputIdx idx) const override
148  {
149  UT_ASSERT(idx >= 0);
150  switch (idx)
151  {
152  case 0: return "Packed Primitives";
153  case 1: return "Points";
154  default: return "Invalid Source";
155  }
156  }
157 
158  /// This just indicates whether an input wire gets drawn with a dotted line
159  /// in the network editor. If something is usually copied directly
160  /// into the output, a solid line (false) is used, but this SOP very often
161  /// doesn't do that for either input.
162  virtual int isRefInput(OP_InputIdx i) const override
163  {
164  UT_ASSERT(i >= 0);
165  // First or second input both use dotted lines
166  return (i == 0 || i == 1);
167  }
168 };
169 } // End HDK_Sample namespace
170 
171 /// newSopOperator is the hook that Houdini grabs from this dll
172 /// and invokes to register the SOP. In this case, we add ourselves
173 /// to the specified operator table.
174 void
176 {
177  using namespace HDK_Sample;
178  table->addOperator(new OP_Operator(
179  SOP_CopyPackedVerb::theSOPTypeName, // Internal name
180  "Copy Packed", // UI name
181  SOP_CopyPacked::myConstructor, // How to build the SOP
182  SOP_CopyPacked::buildTemplates( // My parameters
183  "SOP_CopyPacked.C"_sh,
184  SOP_CopyPackedVerb::theDsFile),
185  2, 2, // Min, Max # of inputs
186  nullptr, // Custom local variables (none)
187  0)); // No special flags (OP_FLAG_UNORDERED to have a multi-input like Merge SOP)
188 }
189 
190 namespace HDK_Sample {
191 
192 //******************************************************************************
193 //* Parameters *
194 //******************************************************************************
195 
196 /// This is a multi-line raw string specifying the parameter interface for this SOP.
197 const char *const SOP_CopyPackedVerb::theDsFile = R"THEDSFILE(
198 {
199  name parameters
200  parm {
201  name "pointgroup"
202  cppname "PointGroup"
203  label "Point Group"
204  type string
205  default { "" }
206  parmtag { "script_action" "import soputils\nkwargs['geometrytype'] = (hou.geometryType.Points,)\nkwargs['inputindex'] = 1\nsoputils.selectGroupParm(kwargs)" }
207  parmtag { "script_action_help" "Select geometry from an available viewport.\nShift-click to turn on Select Groups." }
208  parmtag { "script_action_icon" "BUTTONS_reselect" }
209  parmtag { "sop_input" "1" }
210  }
211 }
212 )THEDSFILE";
213 
214 //******************************************************************************
215 //* Cooking *
216 //******************************************************************************
217 
218 template<typename FUNCTOR>
219 static void
220 copyPrimitiveData(
221  GEO_Detail *output_geo,
222  const GEO_Detail *packed_prims_input,
223  GA_PrimitiveGroupUPtr &bad_prim_group_deleter,
224  const GA_AttributeRefMap &primitive_attribs,
225  const GA_AttributeRefMap &vertex_attribs,
226  FUNCTOR &&pt_to_prim_functor)
227 {
228  GA_PrimitiveGroup *volatile bad_prim_group = nullptr;
229  UT_Lock bad_prim_group_lock;
230 
231  // NOTE: We're using a GA_SplittableRange over the destination primitives so that we can
232  // safely write to unordered primitive groups in parallel without locking around the writes.
234  [output_geo, packed_prims_input,
235  &bad_prim_group, &bad_prim_group_deleter, &bad_prim_group_lock,
236  &primitive_attribs, &vertex_attribs, &pt_to_prim_functor](const GA_SplittableRange &r) {
238  GA_Offset end;
239  for (GA_Iterator it(r); it.blockAdvance(start, end); ) {
240  GA_Index i = output_geo->primitiveIndex(start);
241  for (GA_Offset dest_primoff = start; dest_primoff < end; ++dest_primoff, ++i) {
242  GA_Primitive *dest_prim = output_geo->getPrimitive(dest_primoff);
243  // There can only be packed primitives here, so this should be safe.
244  GEO_PrimPacked *dest_packed_prim = UTverify_cast<GEO_PrimPacked *>(dest_prim);
245  GA_Offset dest_ptoff = output_geo->pointOffset(i);
246  GA_Offset source_primoff = pt_to_prim_functor(dest_ptoff);
247  if (GAisValid(source_primoff)) {
248  const GA_Primitive *source_prim = packed_prims_input->getPrimitive(source_primoff);
249  const GEO_PrimPacked *source_packed_prim = UTverify_cast<const GEO_PrimPacked *>(source_prim);
250  dest_packed_prim->copyMemberDataFrom(*source_packed_prim);
251 
252  if (primitive_attribs.entries()) {
253  primitive_attribs.copyValue(GA_ATTRIB_PRIMITIVE, dest_primoff, GA_ATTRIB_PRIMITIVE, source_primoff);
254  }
255  if (vertex_attribs.entries()) {
256  vertex_attribs.copyValue(GA_ATTRIB_VERTEX, dest_prim->getVertexOffset(0), GA_ATTRIB_VERTEX, source_prim->getVertexOffset(0));
257  }
258  }
259  else {
260  if (!bad_prim_group) {
261  UT_Lock::Scope lock(bad_prim_group_lock);
262  if (!bad_prim_group) {
263  // Making a detached primitive group (owned by us, not output_geo).
264  bad_prim_group_deleter = UTmakeUnique<GA_PrimitiveGroup>(*output_geo);
265  SYSstoreFence();
266  bad_prim_group = bad_prim_group_deleter.get();
267  }
268  }
269  bad_prim_group->addOffset(dest_primoff);
270  }
271  }
272  }
273  });
274 }
275 
276 /// This is the function that does the actual work.
277 void SOP_CopyPackedVerb::cook(const CookParms &cookparms) const
278 {
279  // This gives easy access to all of the current parameter values
280  auto &&sopparms = cookparms.parms<SOP_CopyPackedParms>();
281  auto sopcache = (SOP_CopyPackedCache *)cookparms.cache();
282 
283  // The output detail
284  GEO_Detail *output_geo = cookparms.gdh().gdpNC();
285 
286  const GEO_Detail *const packed_prims_input = cookparms.inputGeo(0);
287  const GEO_Detail *const points_input = cookparms.inputGeo(1);
288 
289  // GOP_Manager will own any temporary groups it creates
290  // and automatically destroy them when it goes out of scope.
291  GOP_Manager group_manager;
292  const GA_PointGroup *input_point_group = nullptr;
293  if (sopparms.getPointGroup().isstring()) {
294  // Parse point group on points_input detail.
295  bool success;
296  input_point_group = group_manager.parsePointDetached(
297  sopparms.getPointGroup().c_str(),
298  points_input, true, success);
299  }
300  if (input_point_group) {
301  output_geo->mergePoints(*points_input, input_point_group, true, false);
302  }
303  else {
304  output_geo->replaceWithPoints(*points_input);
305  }
306 
307  if (packed_prims_input->getNumPrimitives() == 0) {
308  return;
309  }
310 
311  GA_PrimitiveTypeId primitive_type_id(packed_prims_input->getPrimitiveTypeId(packed_prims_input->primitiveOffset(GA_Index(0))));
312  if (!GU_PrimPacked::isPackedPrimitive(primitive_type_id)) {
313  cookparms.sopAddError(SOP_MESSAGE, "All input primitives for copying must be packed primitives, at the moment.");
314  return;
315  }
316  if (packed_prims_input->countPrimitiveType(primitive_type_id) != packed_prims_input->getNumPrimitives()) {
317  cookparms.sopAddError(SOP_MESSAGE, "All input packed primitives must be the same primitive type, at the moment.");
318  return;
319  }
320 
321  GA_Size npoints = output_geo->getNumPoints();
322  GA_Offset start_primoff = output_geo->appendPrimitiveBlock(primitive_type_id, npoints);
323  GA_Offset start_vtxoff = output_geo->appendVertexBlock(npoints);
324 
325  output_geo->bumpDataIdsForAddOrRemove(false, true, true);
326 
327  // Initialize the vertex lists in parallel
328  UTparallelForLightItems(UT_BlockedRange<exint>(0, npoints), [output_geo, start_vtxoff, start_primoff](const UT_BlockedRange<exint> &r) {
329  for (exint i = r.begin(), n = r.end(); i < n; ++i) {
330  GA_Primitive *prim = output_geo->getPrimitive(start_primoff+i);
331  // There can only be packed primitives here, so this should be safe.
332  GEO_PrimPacked *packed_prim = UTverify_cast<GEO_PrimPacked *>(prim);
333  packed_prim->assignVertex(start_vtxoff+i, false);
334  }
335  });
336 
337  // Initialize the vertex-to-primitive topology attribute in parallel
338  GA_ATITopology *vtx_to_prim = output_geo->getTopology().getPrimitiveRef();
339  vtx_to_prim->hardenAllPages();
340  UTparallelForLightItems(UT_BlockedRange<exint>(0, npoints), [vtx_to_prim, start_vtxoff, start_primoff](const UT_BlockedRange<exint> &r) {
341  for (exint i = r.begin(), n = r.end(); i < n; ++i) {
342  vtx_to_prim->setLink(start_vtxoff+i, start_primoff+i);
343  }
344  });
345 
346  // Initialize the vertex-to-point and point-to-vertex topology attributes in parallel.
347  // NOTE: vertex-to-next-vertex and vertex-to-prev-vertex topology attributes
348  // don't need to be updated in this case, since their default GA_INVALID_OFFSET (-1)
349  // is what we want.
350  GA_ATITopology *vtx_to_pt = output_geo->getTopology().getPointRef();
351  vtx_to_pt->hardenAllPages();
352  GA_ATITopology *pt_to_vtx = output_geo->getTopology().getVertexRef();
353  const GA_IndexMap &pt_map = output_geo->getPointMap();
354  if (pt_map.isTrivialMap()) {
355  UTparallelForLightItems(UT_BlockedRange<exint>(0, npoints), [vtx_to_pt, start_vtxoff](const UT_BlockedRange<exint> &r) {
356  for (exint i = r.begin(), n = r.end(); i < n; ++i) {
357  vtx_to_pt->setLink(start_vtxoff+i, GA_Offset(i));
358  }
359  });
360  pt_to_vtx->hardenAllPages();
361  UTparallelForLightItems(UT_BlockedRange<exint>(0, npoints), [pt_to_vtx, start_vtxoff](const UT_BlockedRange<exint> &r) {
362  for (exint i = r.begin(), n = r.end(); i < n; ++i) {
363  pt_to_vtx->setLink(GA_Offset(i), start_vtxoff+i);
364  }
365  });
366  }
367  else {
368  UTparallelForLightItems(UT_BlockedRange<exint>(0, npoints), [vtx_to_pt, start_vtxoff, &pt_map](const UT_BlockedRange<exint> &r) {
369  for (exint i = r.begin(), n = r.end(); i < n; ++i) {
370  vtx_to_pt->setLink(start_vtxoff+i, pt_map.offsetFromIndex(GA_Index(i)));
371  }
372  });
373  pt_to_vtx->hardenAllPages();
374  UTparallelForLightItems(GA_SplittableRange(output_geo->getPointRange()), [output_geo, pt_to_vtx, start_vtxoff](const GA_SplittableRange &r) {
375  GA_Offset start;
376  GA_Offset end;
377  for (GA_Iterator it(r); it.blockAdvance(start, end); ) {
378  exint i = (exint)output_geo->pointIndex(start);
379  for (GA_Offset ptoff = start; ptoff < end; ++ptoff, ++i) {
380  pt_to_vtx->setLink(ptoff, start_vtxoff+i);
381  }
382  }
383  });
384  }
385 
386  GA_AttributeRefMap primitive_attribs(*output_geo, packed_prims_input);
388 
389  GA_AttributeRefMap vertex_attribs(*output_geo, packed_prims_input);
391 
392  // Below, we're relying on that the vertex attribute pages and primitive attribute pages
393  // line up, in case there are vertex groups we're copying, since it's not safe
394  // for multiple threads to write to the same page of the same group at the same time,
395  // even if we were hardening the pages. Luckily, the vertex and primitive attribute pages
396  // should line up in this case, since there's exactly one vertex per primitive here and
397  // they should both start at GA_Offset(0) and the offsets of each are in a contiguous block.
398  UT_ASSERT_MSG(start_vtxoff == GA_Offset(0) && start_primoff == GA_Offset(0),
399  "start_vtxoff and start_primoff should both be zero, since we appended a block to an empty detail.");
400 
401  // This will automatically delete the detached primitive group
402  // (if one is created) when this goes out of scope.
403  GA_PrimitiveGroupUPtr bad_prim_group(nullptr);
404 
405  // It might seem like overkill to read the id attribute as int64, since
406  // primitive numbers usually don't exceed 2 billion, but if the IDs are
407  // incremented over the course of a simulation, or anything like that,
408  // they could conceivably need integers greater than 2 billion.
409  // Most of the time, the attribute will be int32, and its values will be
410  // automatically converted to int64 upon reading, which is fine.
411  GA_ROHandleID pt_id_attrib(output_geo->findPointAttribute(GA_Names::id));
412  if (pt_id_attrib.isValid()) {
413  GA_ROHandleID prim_id_attrib(packed_prims_input->findPrimitiveAttribute(GA_Names::id));
414  GA_AttributeOwner prim_id_owner = GA_ATTRIB_PRIMITIVE;
415  if (!prim_id_attrib.isValid()) {
416  prim_id_attrib.bind(packed_prims_input->findVertexAttribute(GA_Names::id));
417  prim_id_owner = GA_ATTRIB_VERTEX;
418  if (!prim_id_attrib.isValid()) {
419  prim_id_attrib.bind(packed_prims_input->findPointAttribute(GA_Names::id));
420  prim_id_owner = GA_ATTRIB_POINT;
421  }
422  }
423 
424  if (prim_id_attrib.isValid()) {
425  // Record the mapping from ID to packed primitive offset
426  UT::ArrayMap<exint,GA_Offset> id_to_primoff;
427  GA_Offset start;
428  GA_Offset end;
429  for (GA_Iterator it(packed_prims_input->getPrimitiveRange()); it.blockAdvance(start, end); ) {
430  for (GA_Offset primoff = start; primoff < end; ++primoff) {
431  GA_Offset attriboff = primoff;
432  if (prim_id_owner != GA_ATTRIB_PRIMITIVE) {
433  // NOTE: Packed primitives in the input should always have one vertex,
434  // so this should be safe.
435  attriboff = packed_prims_input->getPrimitiveVertexOffset(primoff, 0);
436  if (prim_id_owner == GA_ATTRIB_POINT) {
437  attriboff = packed_prims_input->vertexPoint(attriboff);
438  }
439  }
440  exint id = prim_id_attrib.get(attriboff);
442  // UT::ArrayMap<exint,...> doesn't support
443  // 0x8000000000000000LL as a key. Hopefully that's okay.
444  continue;
445  }
446 
447  // NOTE: This will not overwrite if another primitive with the same ID has already been inserted,
448  // so it only keeps the first match.
449  id_to_primoff.insert(id, primoff);
450  }
451  }
452 
453  copyPrimitiveData(output_geo, packed_prims_input, bad_prim_group, primitive_attribs, vertex_attribs,
454  [&pt_id_attrib, &id_to_primoff](GA_Offset dest_ptoff) -> GA_Offset {
455  const exint id = pt_id_attrib.get(dest_ptoff);
457  // UT::ArrayMap<exint,...> doesn't support
458  // 0x8000000000000000LL as a key. Hopefully that's okay.
459  return GA_INVALID_OFFSET;
460  }
461  auto &&it = id_to_primoff.find(id);
462  if (it != id_to_primoff.end()) {
463  return it->second;
464  }
465  return GA_INVALID_OFFSET;
466  });
467  }
468  else {
469  // There's no primitive id attribute, so id represents the primitive number (index).
470  copyPrimitiveData(output_geo, packed_prims_input, bad_prim_group, primitive_attribs, vertex_attribs,
471  [&pt_id_attrib, packed_prims_input](GA_Offset dest_ptoff) -> GA_Offset {
472  const GA_Index source_primind(pt_id_attrib.get(dest_ptoff));
473  if (GAisValid(source_primind) && source_primind < packed_prims_input->getNumPrimitives()) {
474  // Primitive number is in range
475  return packed_prims_input->primitiveOffset(source_primind);
476  }
477  return GA_INVALID_OFFSET;
478  });
479  }
480  }
481  else {
482  GA_ROHandleS pt_name_attrib(output_geo->findPointAttribute(GA_Names::name));
483  GA_ROHandleS prim_name_attrib(output_geo->findPrimitiveAttribute(GA_Names::name));
484  GA_AttributeOwner prim_name_owner = GA_ATTRIB_PRIMITIVE;
485  if (!prim_name_attrib.isValid()) {
486  prim_name_attrib.bind(packed_prims_input->findVertexAttribute(GA_Names::name));
487  prim_name_owner = GA_ATTRIB_VERTEX;
488  if (!prim_name_attrib.isValid()) {
489  prim_name_attrib.bind(packed_prims_input->findPointAttribute(GA_Names::name));
490  prim_name_owner = GA_ATTRIB_POINT;
491  }
492  }
493  if (pt_name_attrib.isValid() && prim_name_attrib.isValid()) {
494  // Record the mapping from name to packed primitive offset
495  UT_ArrayStringMap<GA_Offset> name_to_primoff;
496  GA_Offset start;
497  GA_Offset end;
498  for (GA_Iterator it(packed_prims_input->getPrimitiveRange()); it.blockAdvance(start, end); ) {
499  for (GA_Offset primoff = start; primoff < end; ++primoff) {
500  GA_Offset attriboff = primoff;
501  if (prim_name_owner != GA_ATTRIB_PRIMITIVE) {
502  // NOTE: Packed primitives in the input should always have one vertex,
503  // so this should be safe.
504  attriboff = packed_prims_input->getPrimitiveVertexOffset(primoff, 0);
505  if (prim_name_owner == GA_ATTRIB_POINT) {
506  attriboff = packed_prims_input->vertexPoint(attriboff);
507  }
508  }
509  const UT_StringHolder &name = prim_name_attrib.get(attriboff);
510  if (!name.isstring()) {
511  // UT_ArrayStringMap doesn't support "" as a key in
512  // Houdini 16.5 and earlier, and if
513  // "" appears in the matching attribute, the user is
514  // probably indicating that they don't want to copy
515  // anything there, so it should be okay.
516  continue;
517  }
518 
519  // NOTE: This will not overwrite if another primitive with the same name has already been inserted,
520  // so it only keeps the first match.
521  name_to_primoff.insert(name, primoff);
522  }
523  }
524 
525  copyPrimitiveData(output_geo, packed_prims_input, bad_prim_group, primitive_attribs, vertex_attribs,
526  [&pt_name_attrib, &name_to_primoff](GA_Offset dest_ptoff) -> GA_Offset {
527  const UT_StringHolder &name = pt_name_attrib.get(dest_ptoff);
528  if (!name.isstring()) {
529  // UT_ArrayStringMap doesn't support "" in 16.5 and earlier,
530  // and it probably means that we're not trying to match
531  // anything, anyway.
532  return GA_INVALID_OFFSET;
533  }
534  auto &&it = name_to_primoff.find(name);
535  if (it != name_to_primoff.end()) {
536  return it->second;
537  }
538  return GA_INVALID_OFFSET;
539  });
540 
541  }
542  else {
543  output_geo->clearAndDestroy();
544  cookparms.sopAddError(SOP_MESSAGE, "Either the points must have an id attribute, or both the points and the packed primitives must have a name attribute.");
545  return;
546  }
547  }
548 
549  if (bad_prim_group) {
550  // If there were any points that didn't match any source primitives,
551  // delete the incompletely initialized primitives we've made.
552  output_geo->deletePrimitives(*bad_prim_group);
553  }
554 }
555 
556 } // End HDK_Sample namespace
This is the SOP class definition.
A class to manage an ordered array which has fixed offset handles.
Definition: GA_IndexMap.h:63
SYS_FORCE_INLINE GA_Offset getPrimitiveVertexOffset(GA_Offset primoff, GA_Size i) const
Definition: GA_Primitive.h:909
GT_API const UT_StringHolder filename
SYS_FORCE_INLINE GA_Primitive * getPrimitive(GA_Offset prim_off)
Definition: GA_Detail.h:429
void hardenAllPages(GA_Offset start_offset=GA_Offset(0), GA_Offset end_offset=GA_INVALID_OFFSET) override
Harden data pages.
Iteration over a range of elements.
Definition: GA_Iterator.h:29
void setChoiceListPtr(const UT_StringRef &name, PRM_ChoiceList *list)
SYS_FORCE_INLINE int getPrimitiveTypeId(GA_Offset primoff) const
Definition: GA_Primitive.h:916
SYS_FORCE_INLINE const GA_Attribute * findVertexAttribute(GA_AttributeScope s, const UT_StringRef &name) const
Definition: GA_Detail.h:1055
std::pair< iterator, bool > insert(const UT_StringRef &key, const ITEM_T &val)
bool blockAdvance(GA_Offset &start, GA_Offset &end)
GLuint start
Definition: glcorearb.h:475
void clearAndDestroy()
Clear all the points/primitives out of this detail.
Definition: GEO_Detail.h:268
virtual UT_StringHolder name() const
int OP_InputIdx
Definition: OP_DataTypes.h:184
int64 exint
Definition: SYS_Types.h:125
UT_ErrorSeverity
Definition: UT_Error.h:25
void UTparallelForLightItems(const Range &range, const Body &body, const bool force_use_task_scope=true)
SYS_FORCE_INLINE TO_T UTverify_cast(FROM_T from)
Definition: UT_Assert.h:229
void newSopOperator(OP_OperatorTable *table)
bool addOperator(OP_Operator *op, std::ostream *err=nullptr)
virtual OP_ERROR cookMySop(OP_Context &context) override
Since this SOP implements a verb, cookMySop just delegates to the verb.
static PRM_ChoiceList pointGroupMenu
Definition: SOP_Node.h:1194
void assignVertex(GA_Offset vtx, bool update_topology)
Called when loading to set the vertex.
SOP_CopyPacked(OP_Network *net, const char *name, OP_Operator *op)
SYS_FORCE_INLINE bool GAisValid(GA_Size v)
Definition: GA_Types.h:655
exint GA_Size
Defines the bit width for index and offset types in GA.
Definition: GA_Types.h:236
GA_Size deletePrimitives(const GA_Range &range, bool and_points=false)
Definition: GEO_Detail.h:1240
virtual const char * inputLabel(OP_InputIdx idx) const override
These are the labels that appear when hovering over the inputs.
#define GA_INVALID_OFFSET
Definition: GA_Types.h:687
iterator find(const Key &key)
Definition: UT_ArrayMap.h:158
GA_Size countPrimitiveType(const GA_PrimitiveTypeId &type) const
Definition: GA_Detail.h:2311
UT_ErrorSeverity sopAddError(int code, const char *msg=0, const UT_SourceLocation *loc=0) const
Definition: SOP_NodeVerb.h:516
GA_API const UT_StringHolder name
#define UT_ASSERT_MSG(ZZ,...)
Definition: UT_Assert.h:159
GA_Size GA_Offset
Definition: GA_Types.h:646
const T & parms() const
Definition: SOP_NodeVerb.h:423
GLdouble n
Definition: glcorearb.h:2008
SYS_FORCE_INLINE GA_Index primitiveIndex(GA_Offset offset) const
Given a primitive's data offset, return its index.
Definition: GA_Detail.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
const GA_IndexMap & getPointMap() const
Definition: GA_Detail.h:744
Constructs a PRM_Template list from an embedded .ds file or an istream.
GA_Offset appendPrimitiveBlock(const GA_PrimitiveTypeId &type, GA_Size nprimitives)
Append a contiguous block of primitives by GA_PrimitiveTypeId.
PRM_Template * templates() const
std::pair< iterator, bool > insert(const Key &key, const T &val)
Definition: UT_ArrayMap.h:117
GLuint GLuint end
Definition: glcorearb.h:475
static const SOP_NodeVerb::Register< SOP_CopyPackedVerb > theVerb
virtual SOP_NodeParms * allocParms() const
virtual SOP_NodeCache * allocCache() const
UT_UniquePtr< GA_PrimitiveGroup > GA_PrimitiveGroupUPtr
A handle to simplify manipulation of multiple attributes.
#define SYSstoreFence()
SYS_FORCE_INLINE GA_Offset vertexPoint(GA_Offset vertex) const
Given a vertex, return the point it references.
Definition: GA_Detail.h:529
GLuint const GLchar * name
Definition: glcorearb.h:786
static PRM_Template * buildTemplates(const UT_StringHolder &filename, const char *ds_file)
GA_API const UT_StringHolder id
GA_Size GA_Index
Define the strictness of GA_Offset/GA_Index.
Definition: GA_Types.h:640
SYS_FORCE_INLINE const GA_ATITopology * getPrimitiveRef() const
Definition: GA_Topology.h:223
GU_Detail * gdpNC()
GLenum GLenum GLsizei void * table
Definition: glad.h:5129
GA_Topology & getTopology()
Definition: GA_Detail.h:801
SYS_FORCE_INLINE const GA_Attribute * findPrimitiveAttribute(GA_AttributeScope s, const UT_StringRef &name) const
Definition: GA_Detail.h:1062
SYS_FORCE_INLINE GA_Index pointIndex(GA_Offset offset) const
Given a point's data offset, return its index.
Definition: GA_Detail.h:349
SYS_FORCE_INLINE bool isTrivialMap() const
Definition: GA_IndexMap.h:291
void mergePoints(const GEO_Detail &src, const GA_PointGroup *ptGrp=0, bool merge_groups=true, bool keep_internal_groups=true)
const GA_PointGroup * parsePointDetached(const char *pat, const GEO_Detail *pgdp, bool forceexistence, bool &success)
GA_AttributeOwner
Definition: GA_Types.h:35
GA_API const UT_StringHolder parms
virtual CookMode cookMode(const SOP_NodeParms *parms) const
void replaceWithPoints(const GA_Detail &src, const GA_AttributeFilter *skip=nullptr)
SYS_FORCE_INLINE GA_Offset primitiveOffset(GA_Index index) const
Given a primitive's index (in append order), return its data offset.
Definition: GA_Detail.h:419
SYS_FORCE_INLINE const GA_ATITopology * getVertexRef() const
Definition: GA_Topology.h:225
static const UT_StringHolder theSOPTypeName
SYS_FORCE_INLINE GA_Offset offsetFromIndex(GA_Index ordered_index) const
Definition: GA_IndexMap.h:118
SYS_FORCE_INLINE GA_Size getNumPrimitives() const
Return the number of primitives.
Definition: GA_Detail.h:408
SYS_FORCE_INLINE GA_Offset getVertexOffset(GA_Size primvertexnum) const
Definition: GA_Primitive.h:244
const GU_Detail * inputGeo(exint idx) const
Definition: SOP_NodeVerb.h:387
SOP_NodeCache * cache() const
Definition: SOP_NodeVerb.h:428
virtual int isRefInput(OP_InputIdx i) const override
void appendAndCreateAllSource(GA_AttributeOwner destowner, GA_AttributeOwner sourceowner, const char *matchpattern, UT_ArraySet< const GA_Attribute * > *alreadymappeddest=nullptr, bool includegroups=true)
void copyMemberDataFrom(const GEO_PrimPacked &src)
#define UT_ASSERT(ZZ)
Definition: UT_Assert.h:156
GLboolean r
Definition: glcorearb.h:1222
virtual const SOP_NodeVerb * cookVerb() const override
static const char *const theDsFile
This is the parameter interface string, below.
void copyValue(GA_AttributeOwner downer, GA_Offset doffset, GA_AttributeOwner sowner, GA_Offset soffset, Cache *cache=0) const
Automatically expand attribute data pages for threading.
SYS_FORCE_INLINE const GA_Attribute * findPointAttribute(GA_AttributeScope s, const UT_StringRef &name) const
Definition: GA_Detail.h:1048
static OP_Node * myConstructor(OP_Network *net, const char *name, OP_Operator *op)
GA_Range getPrimitiveRange(const GA_PrimitiveGroup *group=0) const
Get a range of all primitives in the detail.
Definition: GA_Detail.h:1752
void bumpDataIdsForAddOrRemove(bool added_or_removed_points, bool added_or_removed_vertices, bool added_or_removed_primitives)
SYS_FORCE_INLINE GA_Offset pointOffset(GA_Index index) const
Given a point's index (in append order), return its data offset.
Definition: GA_Detail.h:345
Declare prior to use.
SYS_FORCE_INLINE const GA_ATITopology * getPointRef() const
Definition: GA_Topology.h:221
SYS_FORCE_INLINE void addOffset(GA_Offset ai)
GU_DetailHandle & gdh() const
The initial state of gdh depends on the cookMode()
Definition: SOP_NodeVerb.h:341
SYS_FORCE_INLINE bool isstring() const
SYS_FORCE_INLINE GA_Offset appendVertexBlock(GA_Size nvertices)
Append new vertices, returning the first offset of the contiguous block.
Definition: GA_Detail.h:500
static bool isPackedPrimitive(const GA_PrimitiveDefinition &pdef)
SYS_FORCE_INLINE GA_Size getNumPoints() const
Return the number of points.
Definition: GA_Detail.h:334
SYS_FORCE_INLINE void setLink(GA_Offset ai, GA_Offset v)