1 // Copyright Contributors to the OpenVDB Project
2 // SPDX-License-Identifier: MPL-2.0
3 //
4 /// @file SOP_NodeVDB.cc
5 /// @author FX R&D OpenVDB team
6 
7 #include "SOP_NodeVDB.h"
8 
9 #include <houdini_utils/geometry.h>
10 #include <openvdb/points/PointDataGrid.h>
11 #include "PointUtils.h"
12 #include "Utils.h"
13 #include "GEO_PrimVDB.h"
14 #include "GU_PrimVDB.h"
15 #include <GU/GU_Detail.h>
16 #include <GU/GU_PrimPoly.h>
17 #include <OP/OP_NodeInfoParms.h>
18 #include <PRM/PRM_Parm.h>
19 #include <PRM/PRM_Type.h>
20 #include <SOP/SOP_Cache.h> // for stealable
21 #include <SYS/SYS_Version.h>
22 #include <UT/UT_InfoTree.h>
23 #include <UT/UT_SharedPtr.h>
24 #include <algorithm>
25 #include <cctype> // std::tolower
26 #include <iostream>
27 #include <map>
28 #include <memory>
29 #include <mutex>
30 #include <sstream>
31 #include <stdexcept>
32 
33 /// Enables custom UT_InfoTree data from SOP_NodeVDB::fillInfoTreeNodeSpecific()
34 /// which is used to populate the mako templates in Houdini 16 and greater.
35 /// The templates are used to provide MMB information on Houdini primitives and
36 /// are installed as part of the Houdini toolkit $HH/config/NodeInfoTemplates.
37 /// This code has since been absorbed by SideFX, but we continue to keep
38 /// it around to demonstrate how to extend the templates in Houdini. Note
39 /// that the current implementation is a close duplicate of the data populated
40 /// by Houdini, so this will clash with native Houdini names. The templates
41 /// may also change in future Houdini versions, so do not expect this to
42 /// produce valid results out the box.
43 ///
44 /// For users wishing to customize the .mako files, you can use python to
45 /// inspect the current mako structure.
46 ///
47 /// @code
48 /// infoTree = hou.node('/obj/geo1/vdbfrompolygons1').infoTree()
49 /// sopInfo  = infoTree.branches()['SOP Info']
50 /// sparseInfo = sopInfo.branches()['Sparse Volumes']
51 /// @endcode
52 ///
53 /// These mako branches are the paths that are populated by UT_InfoTree. The
54 /// mako files responsible for producing VDB specific data are geometry.mako,
55 /// called by sop.mako.
56 ///
57 //#define OPENVDB_CUSTOM_MAKO
58 
59 
60 namespace {
61 
62 const std::string&
getOpHidePolicy()63 getOpHidePolicy()
64 {
65     static std::string sOpHidePolicy;
66     static std::once_flag once;
67     std::call_once(once, []()
68     {
69         const char* opHidePolicy = std::getenv("OPENVDB_OPHIDE_POLICY");
70 
71 #ifdef OPENVDB_OPHIDE_POLICY
72         if (opHidePolicy == nullptr) {
73             opHidePolicy = OPENVDB_PREPROC_STRINGIFY(OPENVDB_OPHIDE_POLICY);
74         }
75 #endif
76 
77         if (opHidePolicy != nullptr) {
78             std::string opHidePolicyStr(opHidePolicy);
79 
80             // to lower-case
81 
82             std::transform(opHidePolicyStr.begin(), opHidePolicyStr.end(),
83                 opHidePolicyStr.begin(),
84                 [](unsigned char c) { return std::tolower(c); });
85 
86             sOpHidePolicy = opHidePolicy;
87         } else {
88             sOpHidePolicy = "";
89         }
90     });
91 
92     return sOpHidePolicy;
93 }
94 
95 } // anonymous namespace
96 
97 
98 namespace openvdb_houdini {
99 
100 namespace node_info_text {
101 
102 // map of function callbacks to grid types
103 using ApplyGridSpecificInfoTextMap = std::map<openvdb::Name, ApplyGridSpecificInfoText>;
104 
105 struct LockedInfoTextRegistry
106 {
LockedInfoTextRegistryopenvdb_houdini::node_info_text::LockedInfoTextRegistry107     LockedInfoTextRegistry() {}
~LockedInfoTextRegistryopenvdb_houdini::node_info_text::LockedInfoTextRegistry108     ~LockedInfoTextRegistry() {}
109 
110     std::mutex mMutex;
111     ApplyGridSpecificInfoTextMap mApplyGridSpecificInfoTextMap;
112 };
113 
114 // Declare this at file scope to ensure thread-safe initialization
115 static std::mutex theInitInfoTextRegistryMutex;
116 
117 // Global function for accessing the regsitry
118 static LockedInfoTextRegistry*
getInfoTextRegistry()119 getInfoTextRegistry()
120 {
121     std::lock_guard<std::mutex> lock(theInitInfoTextRegistryMutex);
122 
123     static LockedInfoTextRegistry *registry = nullptr;
124 
125     if (registry == nullptr) {
126 #if defined(__ICC)
127 __pragma(warning(disable:1711)) // disable ICC "assignment to static variable" warnings
128 #endif
129         registry = new LockedInfoTextRegistry();
130 #if defined(__ICC)
131 __pragma(warning(default:1711))
132 #endif
133     }
134 
135     return registry;
136 }
137 
138 
139 void registerGridSpecificInfoText(const std::string&, ApplyGridSpecificInfoText);
140 ApplyGridSpecificInfoText getGridSpecificInfoText(const std::string&);
141 
142 
143 void
registerGridSpecificInfoText(const std::string & gridType,ApplyGridSpecificInfoText callback)144 registerGridSpecificInfoText(const std::string& gridType, ApplyGridSpecificInfoText callback)
145 {
146     LockedInfoTextRegistry *registry = getInfoTextRegistry();
147     std::lock_guard<std::mutex> lock(registry->mMutex);
148 
149     if(registry->mApplyGridSpecificInfoTextMap.find(gridType) !=
150        registry->mApplyGridSpecificInfoTextMap.end()) return;
151 
152     registry->mApplyGridSpecificInfoTextMap[gridType] = callback;
153 }
154 
155 /// @brief Return a pointer to a grid information function, or @c nullptr
156 ///        if no specific function has been registered for the given grid type.
157 /// @note The defaultNodeSpecificInfoText() method is always returned prior to Houdini 14.
158 ApplyGridSpecificInfoText
getGridSpecificInfoText(const std::string & gridType)159 getGridSpecificInfoText(const std::string& gridType)
160 {
161     LockedInfoTextRegistry *registry = getInfoTextRegistry();
162     std::lock_guard<std::mutex> lock(registry->mMutex);
163 
164     const ApplyGridSpecificInfoTextMap::const_iterator iter =
165         registry->mApplyGridSpecificInfoTextMap.find(gridType);
166 
167     if (iter == registry->mApplyGridSpecificInfoTextMap.end() || iter->second == nullptr) {
168         return nullptr; // Native prim info is sufficient
169     }
170 
171     return iter->second;
172 }
173 
174 } // namespace node_info_text
175 
176 
177 ////////////////////////////////////////
178 
179 
SOP_NodeVDB(OP_Network * net,const char * name,OP_Operator * op)180 SOP_NodeVDB::SOP_NodeVDB(OP_Network* net, const char* name, OP_Operator* op):
181     SOP_Node(net, name, op)
182 {
183 #ifndef SESI_OPENVDB
184     // Initialize the OpenVDB library
185     openvdb::initialize();
186     // Forward OpenVDB log messages to the UT_ErrorManager (for all SOPs).
187     startLogForwarding(SOP_OPTYPE_ID);
188 #endif
189 
190     // Register grid-specific info text for Point Data Grids
191     node_info_text::registerGridSpecificInfoText<openvdb::points::PointDataGrid>(
192         &pointDataGridSpecificInfoText);
193 
194     // Set the flag to draw guide geometry
195     mySopFlags.setNeedGuide1(true);
196 }
197 
198 
199 ////////////////////////////////////////
200 
201 
202 const GA_PrimitiveGroup*
matchGroup(GU_Detail & aGdp,const std::string & pattern)203 SOP_NodeVDB::matchGroup(GU_Detail& aGdp, const std::string& pattern)
204 {
205     /// @internal Presumably, when a group name pattern matches multiple groups,
206     /// a new group must be created that is the union of the matching groups,
207     /// and therefore the detail must be non-const.  Since inputGeo() returns
208     /// a const detail, we can't match groups in input details; however,
209     /// we usually copy input 0 to the output detail, so we can in effect
210     /// match groups from input 0 by matching them in the output instead.
211 
212     const GA_PrimitiveGroup* group = nullptr;
213     if (!pattern.empty()) {
214         // If a pattern was provided, try to match it.
215         group = parsePrimitiveGroups(pattern.c_str(), GroupCreator(&aGdp, false));
216         if (!group) {
217             // Report an error if the pattern didn't match.
218             throw std::runtime_error(("Invalid group (" + pattern + ")").c_str());
219         }
220     }
221     return group;
222 }
223 
224 const GA_PrimitiveGroup*
matchGroup(const GU_Detail & aGdp,const std::string & pattern)225 SOP_NodeVDB::matchGroup(const GU_Detail& aGdp, const std::string& pattern)
226 {
227     const GA_PrimitiveGroup* group = nullptr;
228     if (!pattern.empty()) {
229         // If a pattern was provided, try to match it.
230         group = parsePrimitiveGroups(pattern.c_str(), GroupCreator(&aGdp));
231         if (!group) {
232             // Report an error if the pattern didn't match.
233             throw std::runtime_error(("Invalid group (" + pattern + ")").c_str());
234         }
235     }
236     return group;
237 }
238 
239 
240 ////////////////////////////////////////
241 
242 
243 void
fillInfoTreeNodeSpecific(UT_InfoTree & tree,const OP_NodeInfoTreeParms & parms)244 SOP_NodeVDB::fillInfoTreeNodeSpecific(UT_InfoTree& tree, const OP_NodeInfoTreeParms& parms)
245 {
246     SOP_Node::fillInfoTreeNodeSpecific(tree, parms);
247 
248     // Add the OpenVDB library version number to this node's
249     // extended operator information.
250     if (UT_InfoTree* child = tree.addChildMap("OpenVDB")) {
251         child->addProperties("OpenVDB Version", openvdb::getLibraryAbiVersionString());
252     }
253 
254 #ifdef OPENVDB_CUSTOM_MAKO
255     UT_StringArray sparseVolumeTreePath({"SOP Info", "Sparse Volumes"});
256     if (UT_InfoTree* sparseVolumes = tree.getDescendentPtr(sparseVolumeTreePath)) {
257         if (UT_InfoTree* info = sparseVolumes->addChildBranch("OpenVDB Points")) {
258 
259             OP_Context context(parms.getTime());
260             GU_DetailHandle gdHandle = getCookedGeoHandle(context);
261             if (gdHandle.isNull()) return;
262 
263             GU_DetailHandleAutoReadLock gdLock(gdHandle);
264             const GU_Detail* tmpGdp = gdLock.getGdp();
265             if (!tmpGdp) return;
266 
267             info->addColumnHeading("Point Count");
268             info->addColumnHeading("Point Groups");
269             info->addColumnHeading("Point Attributes");
270 
271             for (VdbPrimCIterator it(tmpGdp); it; ++it) {
272                 const openvdb::GridBase::ConstPtr grid = it->getConstGridPtr();
273                 if (!grid) continue;
274                 if (!grid->isType<openvdb::points::PointDataGrid>()) continue;
275 
276                 const openvdb::points::PointDataGrid& points =
277                     *openvdb::gridConstPtrCast<openvdb::points::PointDataGrid>(grid);
278 
279                 std::string countStr, groupStr, attributeStr;
280                 collectPointInfo(points, countStr, groupStr, attributeStr);
281 
282                 ut_PropertyRow* row = info->addProperties();
283                 row->append(countStr);
284                 row->append(groupStr);
285                 row->append(attributeStr);
286             }
287         }
288     }
289 #endif
290 }
291 
292 
293 void
getNodeSpecificInfoText(OP_Context & context,OP_NodeInfoParms & parms)294 SOP_NodeVDB::getNodeSpecificInfoText(OP_Context &context, OP_NodeInfoParms &parms)
295 {
296     SOP_Node::getNodeSpecificInfoText(context, parms);
297 
298 #ifdef SESI_OPENVDB
299     // Nothing needed since we will report it as part of native prim info
300 #else
301     // Get a handle to the geometry.
302     GU_DetailHandle gd_handle = getCookedGeoHandle(context);
303 
304    // Check if we have a valid detail handle.
305     if(gd_handle.isNull()) return;
306 
307     // Lock it for reading.
308     GU_DetailHandleAutoReadLock gd_lock(gd_handle);
309     // Finally, get at the actual GU_Detail.
310     const GU_Detail* tmp_gdp = gd_lock.getGdp();
311 
312     std::ostringstream infoStr;
313 
314     unsigned gridn = 0;
315 
316     for (VdbPrimCIterator it(tmp_gdp); it; ++it) {
317 
318         const openvdb::GridBase& grid = it->getGrid();
319 
320         node_info_text::ApplyGridSpecificInfoText callback =
321             node_info_text::getGridSpecificInfoText(grid.type());
322         if (callback) {
323             // Note, the output string stream for every new grid is initialized with
324             // its index and houdini primitive name prior to executing the callback
325             const UT_String gridName = it.getPrimitiveName();
326 
327             infoStr << "  (" << it.getIndex() << ")";
328             if(gridName.isstring()) infoStr << " name: '" << gridName << "',";
329 
330 
331             (*callback)(infoStr, grid);
332 
333             infoStr<<"\n";
334 
335             ++gridn;
336         }
337     }
338 
339     if (gridn > 0) {
340         std::ostringstream headStr;
341         headStr << gridn << " Custom VDB grid" << (gridn == 1 ? "" : "s") << "\n";
342 
343         parms.append(headStr.str().c_str());
344         parms.append(infoStr.str().c_str());
345     }
346 #endif
347 }
348 
349 
350 ////////////////////////////////////////
351 
352 
353 OP_ERROR
duplicateSourceStealable(const unsigned index,OP_Context & context,GU_Detail ** pgdp,GU_DetailHandle & gdh,bool clean)354 SOP_NodeVDB::duplicateSourceStealable(const unsigned index,
355     OP_Context& context, GU_Detail **pgdp, GU_DetailHandle& gdh, bool clean)
356 {
357     OPENVDB_NO_DEPRECATION_WARNING_BEGIN
358     // traverse upstream nodes, if unload is not possible, duplicate the source
359     if (!isSourceStealable(index, context)) {
360         duplicateSource(index, context, *pgdp, clean);
361         unlockInput(index);
362         return error();
363     }
364     OPENVDB_NO_DEPRECATION_WARNING_END
365 
366     // get the input GU_Detail handle and unlock the inputs
367     GU_DetailHandle inputgdh = inputGeoHandle(index);
368 
369     unlockInput(index);
370     SOP_Node *input = CAST_SOPNODE(getInput(index));
371 
372     if (!input) {
373         addError(SOP_MESSAGE, "Invalid input SOP Node when attempting to unload.");
374         return error();
375     }
376 
377     // explicitly unload the data from the input SOP
378     const bool unloadSuccessful = input->unloadData();
379 
380     // check if we only have one reference
381     const bool soleReference = (inputgdh.getRefCount() == 1);
382 
383     // if the unload was unsuccessful or the reference count is not one, we fall back to
384     // explicitly copying the input onto the gdp
385     if (!(unloadSuccessful && soleReference)) {
386         const GU_Detail *src = inputgdh.readLock();
387         UT_ASSERT(src);
388         if (src)  (*pgdp)->copy(*src);
389         inputgdh.unlock(src);
390         return error();
391     }
392 
393     // release our old write lock on gdp (setup by cookMe())
394     gdh.unlock(*pgdp);
395     // point to the input's old gdp and setup a write lock
396     gdh = inputgdh;
397     *pgdp = gdh.writeLock();
398 
399     return error();
400 }
401 
402 
403 bool
isSourceStealable(const unsigned index,OP_Context & context) const404 SOP_NodeVDB::isSourceStealable(const unsigned index, OP_Context& context) const
405 {
406     struct Local {
407         static inline OP_Node* nextStealableInput(
408             const unsigned idx, const fpreal now, const OP_Node* node)
409         {
410             OP_Node* input = node->getInput(idx);
411             while (input) {
412                 OP_Node* passThrough = input->getPassThroughNode(now);
413                 if (!passThrough) break;
414                 input = passThrough;
415             }
416             return input;
417         }
418     }; // struct Local
419 
420     const fpreal now = context.getTime();
421 
422     for (OP_Node* node = Local::nextStealableInput(index, now, this); node != nullptr;
423         node = Local::nextStealableInput(index, now, node))
424     {
425         // cont'd if it is a SOP_NULL.
426         std::string opname = node->getName().toStdString().substr(0, 4);
427         if (opname == "null") continue;
428 
429         // if the SOP is a cache SOP we don't want to try and alter its data without a deep copy
430         if (dynamic_cast<SOP_Cache*>(node))  return false;
431 
432         if (node->getUnload() != 0)
433             return true;
434         else
435             return false;
436     }
437     return false;
438 }
439 
440 
441 OP_ERROR
duplicateSourceStealable(const unsigned index,OP_Context & context)442 SOP_NodeVDB::duplicateSourceStealable(const unsigned index, OP_Context& context)
443 {
444     OPENVDB_NO_DEPRECATION_WARNING_BEGIN
445     auto error = this->duplicateSourceStealable(index, context, &gdp, myGdpHandle, true);
446     OPENVDB_NO_DEPRECATION_WARNING_END
447     return error;
448 }
449 
450 
451 ////////////////////////////////////////
452 
453 
454 const SOP_NodeVerb*
cookVerb() const455 SOP_NodeVDB::cookVerb() const
456 {
457     if (const auto* verb = SOP_NodeVerb::lookupVerb(getOperator()->getName())) {
458         return verb; ///< @todo consider caching this
459     }
460     return SOP_Node::cookVerb();
461 }
462 
463 
464 OP_ERROR
cookMySop(OP_Context & context)465 SOP_NodeVDB::cookMySop(OP_Context& context)
466 {
467     if (cookVerb()) {
468         return cookMyselfAsVerb(context);
469     }
470     return cookVDBSop(context);
471 }
472 
473 
474 ////////////////////////////////////////
475 
476 
477 namespace {
478 
479 void
createEmptyGridGlyph(GU_Detail & gdp,GridCRef grid)480 createEmptyGridGlyph(GU_Detail& gdp, GridCRef grid)
481 {
482     openvdb::Vec3R lines[6];
483 
484     lines[0].init(-0.5, 0.0, 0.0);
485     lines[1].init( 0.5, 0.0, 0.0);
486     lines[2].init( 0.0,-0.5, 0.0);
487     lines[3].init( 0.0, 0.5, 0.0);
488     lines[4].init( 0.0, 0.0,-0.5);
489     lines[5].init( 0.0, 0.0, 0.5);
490 
491     const openvdb::math::Transform &xform = grid.transform();
492     lines[0] = xform.indexToWorld(lines[0]);
493     lines[1] = xform.indexToWorld(lines[1]);
494     lines[2] = xform.indexToWorld(lines[2]);
495     lines[3] = xform.indexToWorld(lines[3]);
496     lines[4] = xform.indexToWorld(lines[4]);
497     lines[5] = xform.indexToWorld(lines[5]);
498 
499     UT_SharedPtr<GU_Detail> tmpGDP(new GU_Detail);
500 
501     UT_Vector3 color(0.1f, 1.0f, 0.1f);
502     tmpGDP->addFloatTuple(GA_ATTRIB_POINT, "Cd", 3, GA_Defaults(color.data(), 3));
503 
504     GU_PrimPoly *poly;
505 
506     for (int i = 0; i < 6; i += 2) {
507         poly = GU_PrimPoly::build(&*tmpGDP, 2, GU_POLY_OPEN);
508 
509         tmpGDP->setPos3(poly->getPointOffset(i % 2),
510             UT_Vector3(float(lines[i][0]), float(lines[i][1]), float(lines[i][2])));
511 
512         tmpGDP->setPos3(poly->getPointOffset(i % 2 + 1),
513             UT_Vector3(float(lines[i + 1][0]), float(lines[i + 1][1]), float(lines[i + 1][2])));
514     }
515 
516     gdp.merge(*tmpGDP);
517 }
518 
519 } // unnamed namespace
520 
521 
522 OP_ERROR
cookMyGuide1(OP_Context & context)523 SOP_NodeVDB::cookMyGuide1(OP_Context& context)
524 {
525 #ifndef SESI_OPENVDB
526     myGuide1->clearAndDestroy();
527     UT_Vector3 color(0.1f, 0.1f, 1.0f);
528     UT_Vector3 corners[8];
529 
530     // For each VDB primitive (with a non-null grid pointer) in the group...
531     for (VdbPrimIterator it(gdp); it; ++it) {
532         if (evalGridBBox(it->getGrid(), corners, /*expandHalfVoxel=*/true)) {
533             houdini_utils::createBox(*myGuide1, corners, &color);
534         } else {
535             createEmptyGridGlyph(*myGuide1, it->getGrid());
536         }
537     }
538 #endif
539     return SOP_Node::cookMyGuide1(context);
540 }
541 
542 
543 ////////////////////////////////////////
544 
545 
546 openvdb::Vec3f
evalVec3f(const char * name,fpreal time) const547 SOP_NodeVDB::evalVec3f(const char *name, fpreal time) const
548 {
549     return openvdb::Vec3f(float(evalFloat(name, 0, time)),
550                           float(evalFloat(name, 1, time)),
551                           float(evalFloat(name, 2, time)));
552 }
553 
554 openvdb::Vec3R
evalVec3R(const char * name,fpreal time) const555 SOP_NodeVDB::evalVec3R(const char *name, fpreal time) const
556 {
557     return openvdb::Vec3R(evalFloat(name, 0, time),
558                           evalFloat(name, 1, time),
559                           evalFloat(name, 2, time));
560 }
561 
562 openvdb::Vec3i
evalVec3i(const char * name,fpreal time) const563 SOP_NodeVDB::evalVec3i(const char *name, fpreal time) const
564 {
565     using ValueT = openvdb::Vec3i::value_type;
566     return openvdb::Vec3i(static_cast<ValueT>(evalInt(name, 0, time)),
567                           static_cast<ValueT>(evalInt(name, 1, time)),
568                           static_cast<ValueT>(evalInt(name, 2, time)));
569 }
570 
571 openvdb::Vec2R
evalVec2R(const char * name,fpreal time) const572 SOP_NodeVDB::evalVec2R(const char *name, fpreal time) const
573 {
574     return openvdb::Vec2R(evalFloat(name, 0, time),
575                           evalFloat(name, 1, time));
576 }
577 
578 openvdb::Vec2i
evalVec2i(const char * name,fpreal time) const579 SOP_NodeVDB::evalVec2i(const char *name, fpreal time) const
580 {
581     using ValueT = openvdb::Vec2i::value_type;
582     return openvdb::Vec2i(static_cast<ValueT>(evalInt(name, 0, time)),
583                           static_cast<ValueT>(evalInt(name, 1, time)));
584 }
585 
586 
587 std::string
evalStdString(const char * name,fpreal time,int index) const588 SOP_NodeVDB::evalStdString(const char* name, fpreal time, int index) const
589 {
590     UT_String str;
591     evalString(str, name, index, time);
592     return str.toStdString();
593 }
594 
595 
596 ////////////////////////////////////////
597 
598 
599 void
resolveRenamedParm(PRM_ParmList & obsoleteParms,const char * oldName,const char * newName)600 SOP_NodeVDB::resolveRenamedParm(PRM_ParmList& obsoleteParms,
601     const char* oldName, const char* newName)
602 {
603     PRM_Parm* parm = obsoleteParms.getParmPtr(oldName);
604     if (parm && !parm->isFactoryDefault()) {
605         if (this->hasParm(newName)) {
606             this->getParm(newName).copyParm(*parm);
607         }
608     }
609 }
610 
611 
612 ////////////////////////////////////////
613 
614 
615 namespace {
616 
617 
618 /// @brief Default OpPolicy for OpenVDB operator types
619 class DefaultOpenVDBOpPolicy: public houdini_utils::OpPolicy
620 {
621 public:
getValidName(const std::string & english)622     std::string getValidName(const std::string& english)
623     {
624         UT_String s(english);
625         // Remove non-alphanumeric characters from the name.
626         s.forceValidVariableName();
627         std::string name = s.toStdString();
628         // Remove spaces and underscores.
629         name.erase(std::remove(name.begin(), name.end(), ' '), name.end());
630         name.erase(std::remove(name.begin(), name.end(), '_'), name.end());
631         return name;
632     }
633 
getLowercaseName(const std::string & english)634     std::string getLowercaseName(const std::string& english)
635     {
636         UT_String s(english);
637         // Lowercase
638         s.toLower();
639         return s.toStdString();
640     }
641 
642     /// @brief OpenVDB operators of each flavor (SOP, POP, etc.) share
643     /// an icon named "SOP_OpenVDB", "POP_OpenVDB", etc.
getIconName(const houdini_utils::OpFactory & factory)644     std::string getIconName(const houdini_utils::OpFactory& factory) override
645     {
646         return factory.flavorString() + "_OpenVDB";
647     }
648 
649     /// @brief Return the name of the equivalent native operator as shipped with Houdini.
650     /// @details An empty string indicates that there is no equivalent native operator.
getNativeName(const houdini_utils::OpFactory &)651     virtual std::string getNativeName(const houdini_utils::OpFactory&)
652     {
653         return "";
654     }
655 };
656 
657 
658 /// @brief SideFX OpPolicy for OpenVDB operator types
659 class SESIOpenVDBOpPolicy: public DefaultOpenVDBOpPolicy
660 {
661 public:
getName(const houdini_utils::OpFactory &,const std::string & english)662     std::string getName(const houdini_utils::OpFactory&, const std::string& english) override
663     {
664         return this->getLowercaseName(this->getValidName(english));
665     }
666 
getTabSubMenuPath(const houdini_utils::OpFactory &)667     std::string getTabSubMenuPath(const houdini_utils::OpFactory&) override
668     {
669         return "VDB";
670     }
671 };
672 
673 
674 /// @brief ASWF OpPolicy for OpenVDB operator types
675 class ASWFOpenVDBOpPolicy: public DefaultOpenVDBOpPolicy
676 {
677 public:
getName(const houdini_utils::OpFactory &,const std::string & english)678     std::string getName(const houdini_utils::OpFactory&, const std::string& english) override
679     {
680         return "DW_Open" + this->getValidName(english);
681     }
682 
getLabelName(const houdini_utils::OpFactory & factory)683     std::string getLabelName(const houdini_utils::OpFactory& factory) override
684     {
685         return factory.english();
686     }
687 
getFirstName(const houdini_utils::OpFactory & factory)688     std::string getFirstName(const houdini_utils::OpFactory& factory) override
689     {
690         return this->getLowercaseName(this->getValidName(this->getLabelName(factory)));
691     }
692 
getNativeName(const houdini_utils::OpFactory & factory)693     std::string getNativeName(const houdini_utils::OpFactory& factory) override
694     {
695         return this->getLowercaseName(this->getValidName(factory.english()));
696     }
697 
getTabSubMenuPath(const houdini_utils::OpFactory &)698     std::string getTabSubMenuPath(const houdini_utils::OpFactory&) override
699     {
700         return "VDB/ASWF";
701     }
702 };
703 
704 
705 #ifdef SESI_OPENVDB
706 using OpenVDBOpPolicy = SESIOpenVDBOpPolicy;
707 #else
708 using OpenVDBOpPolicy = ASWFOpenVDBOpPolicy;
709 #endif // SESI_OPENVDB
710 
711 } // unnamed namespace
712 
713 
OpenVDBOpFactory(const std::string & english,OP_Constructor ctor,houdini_utils::ParmList & parms,OP_OperatorTable & table,houdini_utils::OpFactory::OpFlavor flavor)714 OpenVDBOpFactory::OpenVDBOpFactory(
715     const std::string& english,
716     OP_Constructor ctor,
717     houdini_utils::ParmList& parms,
718     OP_OperatorTable& table,
719     houdini_utils::OpFactory::OpFlavor flavor):
720     houdini_utils::OpFactory(OpenVDBOpPolicy(), english, ctor, parms, table, flavor)
721 {
722     setNativeName(OpenVDBOpPolicy().getNativeName(*this));
723 
724     std::stringstream ss;
725     ss << "vdb" << OPENVDB_LIBRARY_VERSION_STRING << " ";
726     ss << "houdini" << SYS_Version::full();
727 
728     // Define an operator version of the format "vdb6.1.0 houdini18.0.222",
729     // which can be returned by OP_OperatorDW::getVersion() and used to
730     // handle compatibility between specific versions of VDB or Houdini
731     addSpareData({{"operatorversion", ss.str()}});
732 }
733 
734 OpenVDBOpFactory&
setNativeName(const std::string & name)735 OpenVDBOpFactory::setNativeName(const std::string& name)
736 {
737     // SideFX nodes have no native equivalent.
738 #ifndef SESI_OPENVDB
739     addSpareData({{"nativename", name}});
740 
741     // if native name was previously defined and is present
742     // in the hidden table then remove it regardless of policy
743 
744     if (!mNativeName.empty() &&
745         this->table().isOpHidden(mNativeName.c_str())) {
746         this->table().delOpHidden(mNativeName.c_str());
747     }
748 
749     mNativeName = name;
750 
751     if (!name.empty()) {
752 
753         const std::string& opHidePolicy = getOpHidePolicy();
754 
755         if (opHidePolicy == "aswf") {
756             // set this SOP to be hidden (if a native equivalent exists)
757             this->setInvisible();
758         } else if (opHidePolicy == "native") {
759             // mark the native equivalent SOP to be hidden
760             this->table().addOpHidden(name.c_str());
761         }
762     }
763 #endif
764     return *this;
765 }
766 
767 } // namespace openvdb_houdini
768