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