From a864184325abbcf56b08ef0cae4b1abc9896dc95 Mon Sep 17 00:00:00 2001 From: Ken Museth Date: Sat, 29 Nov 2025 14:06:04 -0800 Subject: [PATCH 001/275] improvements to vdb_tool Signed-off-by: Ken Museth --- openvdb_cmd/vdb_tool/include/Parser.h | 36 ++++++++++++++++++++------- openvdb_cmd/vdb_tool/include/Util.h | 8 +++--- pendingchanges/openvdb_cmd.txt | 5 ++++ 3 files changed, 36 insertions(+), 13 deletions(-) create mode 100644 pendingchanges/openvdb_cmd.txt diff --git a/openvdb_cmd/vdb_tool/include/Parser.h b/openvdb_cmd/vdb_tool/include/Parser.h index 08b08599f5..a8848f5d02 100644 --- a/openvdb_cmd/vdb_tool/include/Parser.h +++ b/openvdb_cmd/vdb_tool/include/Parser.h @@ -105,7 +105,7 @@ class Memory } else if (name=="e") { return std::to_string(2.718281828459); } else { - throw std::invalid_argument("Storrage::get: undefined variable \""+name+"\""); + throw std::invalid_argument("Storage::get: undefined variable \""+name+"\""); } } return it->second; @@ -242,17 +242,17 @@ class Processor [&](){mCallStack.top()=getExt(mCallStack.top());}); // boolean operations - add("==","returns true if the two top enteries on the stack compare equal, e.g. {1:2:==} -> {0}", + add("==","returns true if the two top entries on the stack compare equal, e.g. {1:2:==} -> {0}", [&](){this->boolean(std::equal_to<>());}); - add("!=","returns true if the two top enteries on the stack are not equal, e.g. {1:2:!=} -> {1}", + add("!=","returns true if the two top entries on the stack are not equal, e.g. {1:2:!=} -> {1}", [&](){this->boolean(std::not_equal_to<>());}); - add("<=","returns true if the two top enteries on the stack are less than or equal, e.g. {1:2:<=} -> {1}", + add("<=","returns true if the two top entries on the stack are less than or equal, e.g. {1:2:<=} -> {1}", [&](){this->boolean(std::less_equal<>());}); - add(">=","returns true if the two top enteries on the stack are greater than or equal, e.g. {1:2:>=} -> {0}", + add(">=","returns true if the two top entries on the stack are greater than or equal, e.g. {1:2:>=} -> {0}", [&](){this->boolean(std::greater_equal<>());}); - add("<","returns true if the two top enteries on the stack are less than, e.g. {1:2:<} -> {1}", + add("<","returns true if the two top entries on the stack are less than, e.g. {1:2:<} -> {1}", [&](){this->boolean(std::less<>());}); - add(">","returns true if the two top enteries on the stack are less than or equal, e.g. {1:2:<=} -> {1}", + add(">","returns true if the two top entries on the stack are less than or equal, e.g. {1:2:<=} -> {1}", [&](){this->boolean(std::greater<>());}); add("!","logical negation, e.g. {1:!} -> {0}", [&](){this->set(!strToBool(mCallStack.top()));}); @@ -695,6 +695,7 @@ struct Parser { Action& getAction() {return *iter;} const Action& getAction() const {return *iter;} void printAction() const {if (verbose>1) iter->print();} + std::vector closeMatches(const std::string &str) const;// returns available actions that contain str ActListT available, actions; ActIterT iter;// iterator pointing to the current actions being processed @@ -755,6 +756,20 @@ math::Vec3 Parser::getVec3(const std::string &name, const char* delimiters) c // ============================================================================================================== +std::vector Parser::closeMatches(const std::string &str) const +{//returns vector of available actions that contain str, while ignoring character case and leading '-' + std::vector match; + size_t pos = str.find_first_not_of("-"); + if (pos==std::string::npos) return match; + std::string pattern = toLowerCase(str.substr(pos));//remove all leading "-" and convert to lower case + for (auto it = available.begin(); it != available.end(); ++it) { + if (it->name.find(pattern) != std::string::npos) match.push_back(it->name); + } + return match; +} + +// ============================================================================================================== + void Action::setOption(const std::string &str) { const size_t pos = str.find_first_of("={");// since expressions are only evaluated for values and not for names of values, we only search for '=' before expressions, which start with '{' @@ -970,7 +985,10 @@ void Parser::parse(int argc, char *argv[]) while(i+1setOption(argv[++i]); iter->init();// optional callback function unique to action } else { - throw std::invalid_argument("Parser: unsupported action \""+str+"\"\n"); + std::stringstream ss; + ss << "Parser: unsupported action \"" << str << "\"\n"; + for (const std::string &action : this->closeMatches(str)) ss << "Did you mean: \"" << action << "\"\n"; + throw std::invalid_argument(ss.str()); } }// loop over all input arguments if (counter!=0) throw std::invalid_argument("Parser: Unmatched pairing of {-for,-each,-if} and -end"); @@ -995,7 +1013,7 @@ std::string Parser::usage(const Action &action, bool brief) const const static int w = 17; auto op = [&](std::string line, size_t width, bool isSentence) { if (isSentence) { - line[0] = static_cast(std::toupper(line[0]));// capitalizestd::string name, value, example, documentation;');// punctuate + line[0] = static_cast(std::toupper(line[0]));// capitalize std::string name, value, example, documentation;');// punctuate } width += w; const VecS words = tokenize(line, " "); diff --git a/openvdb_cmd/vdb_tool/include/Util.h b/openvdb_cmd/vdb_tool/include/Util.h index 9d084fb22e..c3e20fb50b 100644 --- a/openvdb_cmd/vdb_tool/include/Util.h +++ b/openvdb_cmd/vdb_tool/include/Util.h @@ -120,7 +120,7 @@ inline std::string toUpperCase(const std::string &str) return toUpperCase(tmp); } -/// @brief return the 1-based index of the first matching word in a string-vector with comma-seperated words. +/// @brief return the 1-based index of the first matching word in a string-vector with comma-separated words. /// @details findMatch("ba", {"abc,a", "aab,c,ba"}) returns 2 since "ba" is a word in the second entry of vec inline int findMatch(const std::string &word, const std::vector &vec) { @@ -177,7 +177,7 @@ inline bool endsWith(const std::string &str, const std::string &pattern) inline bool isInt(float x) {return floorf(x) == x;} /// @brief Returns true if the string contains an integer, in which case @b i contains the value. -/// @note Leading and trailing whilespaces in the input string are not allowed +/// @note Leading and trailing whitespaces in the input string are not allowed inline bool isInt(const std::string &s, int &i) { size_t pos = 0; @@ -253,7 +253,7 @@ template <> inline bool strTo(const std::string &str){return strToBool(str);} /// @brief Returns true if the string contains a float, in which case @b v contains the value. -/// @note Leading and trailing whilespaces in the input string are not allowed +/// @note Leading and trailing whitespaces in the input string are not allowed /// @warning In this context integers are a subset of floats so be sure to test for integers first! inline bool isFloat(const std::string &s, float &v) { @@ -268,7 +268,7 @@ inline bool isFloat(const std::string &s, float &v) /// @brief Returns 1 if the string is an integer, 2 if it's a float and otherwise 0. In the first two cases /// the respective values are updated. -/// @note Leading and trailing whilespaces in the input string are not allowed +/// @note Leading and trailing whitespaces in the input string are not allowed inline int isNumber(const std::string &s, int &i, float &v) { if (isInt(s, i)) { diff --git a/pendingchanges/openvdb_cmd.txt b/pendingchanges/openvdb_cmd.txt new file mode 100644 index 0000000000..bf12bba9f2 --- /dev/null +++ b/pendingchanges/openvdb_cmd.txt @@ -0,0 +1,5 @@ +vdb_tool: + Improvements: + - Improved error reporting when using unsupported actions + Fixes: + - Fixed several typos in vdb_tool \ No newline at end of file From 2b8a2d5a72c41f39e6008b416ecab59db87808dc Mon Sep 17 00:00:00 2001 From: Ken Museth Date: Sat, 29 Nov 2025 14:23:49 -0800 Subject: [PATCH 002/275] candidate list of actions is now sorted Signed-off-by: Ken Museth --- openvdb_cmd/vdb_tool/include/Parser.h | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/openvdb_cmd/vdb_tool/include/Parser.h b/openvdb_cmd/vdb_tool/include/Parser.h index a8848f5d02..997a056908 100644 --- a/openvdb_cmd/vdb_tool/include/Parser.h +++ b/openvdb_cmd/vdb_tool/include/Parser.h @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -695,7 +696,7 @@ struct Parser { Action& getAction() {return *iter;} const Action& getAction() const {return *iter;} void printAction() const {if (verbose>1) iter->print();} - std::vector closeMatches(const std::string &str) const;// returns available actions that contain str + std::map closeMatches(const std::string &str) const;// returns available actions that contain str ActListT available, actions; ActIterT iter;// iterator pointing to the current actions being processed @@ -756,16 +757,17 @@ math::Vec3 Parser::getVec3(const std::string &name, const char* delimiters) c // ============================================================================================================== -std::vector Parser::closeMatches(const std::string &str) const -{//returns vector of available actions that contain str, while ignoring character case and leading '-' - std::vector match; +std::map Parser::closeMatches(const std::string &str) const +{//returns sorted map of available actions that contain str, while ignoring character case and leading '-' + std::map matches; size_t pos = str.find_first_not_of("-"); - if (pos==std::string::npos) return match; + if (pos==std::string::npos) return matches;// special case when str only contains one or more '-' std::string pattern = toLowerCase(str.substr(pos));//remove all leading "-" and convert to lower case for (auto it = available.begin(); it != available.end(); ++it) { - if (it->name.find(pattern) != std::string::npos) match.push_back(it->name); + pos = it->name.find(pattern); + if (pos != std::string::npos) matches.emplace(pos, it->name); } - return match; + return matches; } // ============================================================================================================== @@ -987,7 +989,7 @@ void Parser::parse(int argc, char *argv[]) } else { std::stringstream ss; ss << "Parser: unsupported action \"" << str << "\"\n"; - for (const std::string &action : this->closeMatches(str)) ss << "Did you mean: \"" << action << "\"\n"; + for (auto &match : this->closeMatches(str)) ss << "Did you mean: \"" << match.second << "\"\n"; throw std::invalid_argument(ss.str()); } }// loop over all input arguments From be5c73c730f3665bd307ca323501a727532fe4e4 Mon Sep 17 00:00:00 2001 From: Ken Museth Date: Sat, 29 Nov 2025 14:34:30 -0800 Subject: [PATCH 003/275] snapshot Signed-off-by: Ken Museth --- openvdb_cmd/vdb_tool/include/Parser.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openvdb_cmd/vdb_tool/include/Parser.h b/openvdb_cmd/vdb_tool/include/Parser.h index 997a056908..490d9841c5 100644 --- a/openvdb_cmd/vdb_tool/include/Parser.h +++ b/openvdb_cmd/vdb_tool/include/Parser.h @@ -975,7 +975,7 @@ void Parser::parse(int argc, char *argv[]) { OPENVDB_ASSERT(!hashMap.empty()); if (argc <= 1) throw std::invalid_argument("Parser: No arguments provided, try " + getFile(argv[0]) + " -help\""); - counter = 0;// reset to check for matching {for,each}/end loops + counter = 0;// reset to check for matching {for,each,if}/end loops for (int i=1; icloseMatches(str)) ss << "Did you mean: \"" << match.second << "\"\n"; + for (auto &match : this->closeMatches(str)) ss << "Did you mean the action: \"-" << match.second << "\"?\n"; throw std::invalid_argument(ss.str()); } }// loop over all input arguments From 937514f8853f100b3c682059f6999d52645eebcc Mon Sep 17 00:00:00 2001 From: Ken Museth Date: Sat, 29 Nov 2025 15:21:54 -0800 Subject: [PATCH 004/275] fixed bug when reading ascii ply files Signed-off-by: Ken Museth --- openvdb_cmd/vdb_tool/include/Geometry.h | 2 +- openvdb_cmd/vdb_tool/include/Tool.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openvdb_cmd/vdb_tool/include/Geometry.h b/openvdb_cmd/vdb_tool/include/Geometry.h index ebd497b3bb..18535917d0 100644 --- a/openvdb_cmd/vdb_tool/include/Geometry.h +++ b/openvdb_cmd/vdb_tool/include/Geometry.h @@ -760,7 +760,7 @@ void Geometry::readPLY(std::istream &is) for (auto &v : mVtx) { tokens = tokenize_line(); if (int(tokens.size()) != vtxProps) error("vdb_tool::readPLY: error reading ascii vertex coordinates"); - for (int i = 0; i<3; ++i) v[i] = std::stof(tokens[xyz[0].id]); + for (int i = 0; i<3; ++i) v[i] = std::stof(tokens[xyz[i].id]); }// loop over vertices } diff --git a/openvdb_cmd/vdb_tool/include/Tool.h b/openvdb_cmd/vdb_tool/include/Tool.h index e558be1923..91c81051ef 100644 --- a/openvdb_cmd/vdb_tool/include/Tool.h +++ b/openvdb_cmd/vdb_tool/include/Tool.h @@ -437,7 +437,7 @@ void Tool::init() mParser.addAction( "points2ls", "p2l", "Convert geometry points into a narrow-band level set", - {{"dim", "", "256", "largest dimension in voxel units of the bbox of all the points (defaults to 256). In \"voxel\" is defined \"dim\" is ignored"}, + {{"dim", "", "256", "largest dimension in voxel units of the bbox of all the points (defaults to 256). If \"voxel\" is defined \"dim\" is ignored"}, {"voxel", "", "0.01", "voxel size in world units (by defaults \"dim\" is used to derive \"voxel\"). If specified this option takes precedence over \"dim\""}, {"width", "", "3.0", "half-width in voxel units of the output narrow-band level set (defaults to 3 units on either side of the zero-crossing)"}, {"radius", "2.0", "2.0", "radius in voxel units of the input points"}, From 1ea2961fb7584418da2f044016dcd0bb37062732 Mon Sep 17 00:00:00 2001 From: Ken Museth Date: Sat, 29 Nov 2025 15:22:53 -0800 Subject: [PATCH 005/275] fixed bug when reading ascii ply files Signed-off-by: Ken Museth --- pendingchanges/openvdb_cmd.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/pendingchanges/openvdb_cmd.txt b/pendingchanges/openvdb_cmd.txt index bf12bba9f2..424e64263b 100644 --- a/pendingchanges/openvdb_cmd.txt +++ b/pendingchanges/openvdb_cmd.txt @@ -2,4 +2,5 @@ vdb_tool: Improvements: - Improved error reporting when using unsupported actions Fixes: + - Fixed bug when reading ascii ply files - Fixed several typos in vdb_tool \ No newline at end of file From 0b5a185e4b6a5cc1743f87170616775e7a359b8c Mon Sep 17 00:00:00 2001 From: Ken Museth Date: Sat, 29 Nov 2025 18:54:27 -0800 Subject: [PATCH 006/275] improved error message for invalid options Signed-off-by: Ken Museth --- openvdb_cmd/vdb_tool/include/Parser.h | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/openvdb_cmd/vdb_tool/include/Parser.h b/openvdb_cmd/vdb_tool/include/Parser.h index 490d9841c5..6da21469a8 100644 --- a/openvdb_cmd/vdb_tool/include/Parser.h +++ b/openvdb_cmd/vdb_tool/include/Parser.h @@ -768,7 +768,7 @@ std::map Parser::closeMatches(const std::string &str) const if (pos != std::string::npos) matches.emplace(pos, it->name); } return matches; -} +}// Parser::closeMatches // ============================================================================================================== @@ -792,7 +792,14 @@ void Action::setOption(const std::string &str) opt.value = str.substr(pos+1); return;// done } - throw std::invalid_argument(name + ": invalid option \"" + str + "\""); + std::stringstream ss; + ss << name << ": Invalid option: \"" << str << "\"\n"; + for (auto it = options.begin(); it != options.end();) { + ss << "Valid options: \"" << it->name << "=" << (it++)->example << "\""; + while (it != options.end()) ss << " or \"" << it->name << "=" << (it++)->example << "\""; + ss << "\n"; + } + throw std::invalid_argument(ss.str()); } }// Action::setOption @@ -974,7 +981,7 @@ void Parser::finalize() void Parser::parse(int argc, char *argv[]) { OPENVDB_ASSERT(!hashMap.empty()); - if (argc <= 1) throw std::invalid_argument("Parser: No arguments provided, try " + getFile(argv[0]) + " -help\""); + if (argc <= 1) throw std::invalid_argument("Parser: No arguments provided, try \"" + getFile(argv[0]) + " -help\""); counter = 0;// reset to check for matching {for,each,if}/end loops for (int i=1; iinit();// optional callback function unique to action } else { std::stringstream ss; - ss << "Parser: unsupported action \"" << str << "\"\n"; - for (auto &match : this->closeMatches(str)) ss << "Did you mean the action: \"-" << match.second << "\"?\n"; + ss << "Parser: Unsupported action: \"" << str << "\"\n"; + auto matches = this->closeMatches(str); + for (auto it = matches.begin(); it != matches.end();) { + ss << "Did you mean: \"-" << (it++)->second << "\""; + while (it != matches.end()) ss << " or \"-" << (it++)->second << "\""; + ss << "?\n"; + } throw std::invalid_argument(ss.str()); } }// loop over all input arguments From 108743b94272cdf4900c868f7c48ad70bb1f9d86 Mon Sep 17 00:00:00 2001 From: Ken Museth Date: Sat, 29 Nov 2025 18:55:34 -0800 Subject: [PATCH 007/275] improved error message for invalid options Signed-off-by: Ken Museth --- pendingchanges/openvdb_cmd.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pendingchanges/openvdb_cmd.txt b/pendingchanges/openvdb_cmd.txt index 424e64263b..bb556de098 100644 --- a/pendingchanges/openvdb_cmd.txt +++ b/pendingchanges/openvdb_cmd.txt @@ -1,6 +1,6 @@ vdb_tool: Improvements: - - Improved error reporting when using unsupported actions + - Improved error reporting when using unsupported actions and options Fixes: - Fixed bug when reading ascii ply files - Fixed several typos in vdb_tool \ No newline at end of file From 4558ef3bcc2d7f6a76d8e96d97f4ac8913819921 Mon Sep 17 00:00:00 2001 From: Ken Museth Date: Sun, 30 Nov 2025 17:16:47 -0800 Subject: [PATCH 008/275] cleaned up PDAL integration Signed-off-by: Ken Museth --- openvdb_cmd/vdb_tool/include/Geometry.h | 42 ++++++++++++------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/openvdb_cmd/vdb_tool/include/Geometry.h b/openvdb_cmd/vdb_tool/include/Geometry.h index 18535917d0..b67f184367 100644 --- a/openvdb_cmd/vdb_tool/include/Geometry.h +++ b/openvdb_cmd/vdb_tool/include/Geometry.h @@ -125,7 +125,7 @@ class Geometry void readPTS(const std::string &fileName); void readGEO(const std::string &fileName); void readABC(const std::string &fileName); - void readPDAL(const std::string &fileName); + bool readPDAL(const std::string &fileName); void readVDB(const std::string &fileName); void readNVDB(const std::string &fileName); @@ -428,16 +428,10 @@ void Geometry::read(const std::string &fileName) break; default: #if VDB_TOOL_USE_PDAL - pdal::StageFactory factory; - const std::string driver = factory.inferReaderDriver(fileName); - if (driver != "") { - this->readPDAL(fileName); - break; - } + if (this->readPDAL(fileName)) break;// note, this only reads vertices #endif throw std::invalid_argument("Geometry::read: File \""+fileName+"\" has an invalid extension"); - break; - } + }// end switch over file extensions }// Geometry::read void Geometry::readOBJ(const std::string &fileName) @@ -484,14 +478,20 @@ void Geometry::readOBJ(std::istream &is) mBBox = BBoxT();//invalidate BBox }// Geometry::readOBJ -void Geometry::readPDAL(const std::string &fileName) +// Works with multiple file formats, e.g. ply, obj, stl, hdf, matlab, numpy, pts, ptx, e57, las, laz +// Note, currently it only reads vertices and optionally colors +bool Geometry::readPDAL(const std::string &fileName) { #if VDB_TOOL_USE_PDAL - if (!pdal::FileUtils::fileExists(fileName)) throw std::invalid_argument("Error opening file \""+fileName+"\" - it doesn't exist!"); + if (!pdal::FileUtils::fileExists(fileName)) { + throw std::invalid_argument("Geometry: Error opening file \""+fileName+"\"!"); + } pdal::StageFactory factory; - std::string type = factory.inferReaderDriver(fileName); - std::string pipelineJson = R"({ + const std::string type = factory.inferReaderDriver(fileName); + if (type.empty()) return false;// PDAL cannot read this file + std::cerr << "Using PDAL to read \"" << fileName << "\"\n"; + const std::string pipelineJson = R"({ "pipeline" : [ { "type" : ")" + type + R"(", @@ -507,11 +507,10 @@ void Geometry::readPDAL(const std::string &fileName) std::stringstream s(pipelineJson); manager.readPipeline(s); manager.execute(pdal::ExecMode::Standard); - for (const std::shared_ptr& view : manager.views()) { - bool hasColor = false; - if (view->hasDim(pdal::Dimension::Id::Red) && view->hasDim(pdal::Dimension::Id::Green) && view->hasDim(pdal::Dimension::Id::Blue)) - hasColor = true; + const bool hasColor = view->hasDim(pdal::Dimension::Id::Red) && + view->hasDim(pdal::Dimension::Id::Green) && + view->hasDim(pdal::Dimension::Id::Blue); for (const pdal::PointRef& point : *view) { p[0] = point.getFieldAs(pdal::Dimension::Id::X); p[1] = point.getFieldAs(pdal::Dimension::Id::Y); @@ -524,8 +523,7 @@ void Geometry::readPDAL(const std::string &fileName) mRGB.push_back(rgb); } } - } - + }// loop over point views } catch (const pdal::pdal_error& e) { throw std::runtime_error("PDAL failed: " + std::string(e.what())); @@ -533,10 +531,11 @@ void Geometry::readPDAL(const std::string &fileName) catch (const std::exception& e) { throw std::runtime_error("Reading file failed: " + std::string(e.what())); } + mBBox = BBoxT(); //invalidate BBox #else throw std::runtime_error("Cannot read file \"" + fileName + "\". PDAL support is not enabled in this build, please recompile with PDAL support"); #endif - mBBox = BBoxT(); //invalidate BBox + return true; }// Geometry::readPDAL void Geometry::readOFF(const std::string &fileName) @@ -986,13 +985,12 @@ void Geometry::readNVDB(const std::string &fileName) mVtx.resize(n + count); for (size_t i=n; i Date: Mon, 1 Dec 2025 16:50:41 -0800 Subject: [PATCH 009/275] added read support to vdb_tool for xyz files Signed-off-by: Ken Museth --- openvdb_cmd/vdb_tool/README.md | 2 +- openvdb_cmd/vdb_tool/include/Geometry.h | 66 +++++++++++++++++++------ openvdb_cmd/vdb_tool/include/Tool.h | 20 ++++---- 3 files changed, 61 insertions(+), 27 deletions(-) diff --git a/openvdb_cmd/vdb_tool/README.md b/openvdb_cmd/vdb_tool/README.md index 23bb29066f..b741239611 100644 --- a/openvdb_cmd/vdb_tool/README.md +++ b/openvdb_cmd/vdb_tool/README.md @@ -10,7 +10,7 @@ This command-line tool, dubbed vdb_tool, can combine any number of the of high-l | **eval** | Evaluate an expression written in our Reverse Polish Notation (see below) | | **config** | Load a configuration file and add the actions for processing | | **default** | Set default values used by all subsequent actions | -| **read** | Read mesh, points and level sets as obj, ply, abc, stl, off, pts, vdb or nvdb files | +| **read** | Read mesh, points and level sets as obj, ply, abc, stl, off, pts, xyz, e57, vdb or nvdb files | | **write** | Write a polygon mesh, points or level set as a obj, ply, stl, off, abc or vdb file | | **vdb2points** | Extracts points from a VDB grid | | **mesh2ls** | Convert a polygon mesh to a narrow-band level set | diff --git a/openvdb_cmd/vdb_tool/include/Geometry.h b/openvdb_cmd/vdb_tool/include/Geometry.h index b67f184367..8b4c0b0381 100644 --- a/openvdb_cmd/vdb_tool/include/Geometry.h +++ b/openvdb_cmd/vdb_tool/include/Geometry.h @@ -126,12 +126,14 @@ class Geometry void readGEO(const std::string &fileName); void readABC(const std::string &fileName); bool readPDAL(const std::string &fileName); + void readXYZ(const std::string &fileName); void readVDB(const std::string &fileName); void readNVDB(const std::string &fileName); void readOBJ(std::istream &is); void readOFF(std::istream &is); void readPLY(std::istream &is); + void readXYZ(std::istream &is); size_t vtxCount() const { return mVtx.size(); } size_t triCount() const { return mTri.size(); } @@ -157,7 +159,7 @@ class Geometry std::vector mVtx; std::vector mTri; std::vector mQuad; - std::vector mRGB; + std::vector mRGB;// optional vertex colors mutable BBoxT mBBox; std::string mName; @@ -255,7 +257,7 @@ void Geometry::write(const std::string &fileName) const this->writeOFF(fileName); break; default: - throw std::invalid_argument("Geometry file \"" + fileName + "\" has an invalid extension"); + throw std::invalid_argument("Geometry::write: file \"" + fileName + "\" has an invalid extension"); } }// Geometry::write @@ -398,7 +400,7 @@ void Geometry::writeGEO(const std::string &fileName) const void Geometry::read(const std::string &fileName) { - switch (findFileExt(fileName, {"obj", "ply", "pts", "stl", "abc", "vdb", "nvdb", "geo", "off"})) { + switch (findFileExt(fileName, {"obj", "ply", "pts", "stl", "abc", "vdb", "nvdb", "geo", "off", "xyz"})) { case 1: this->readOBJ(fileName); break; @@ -426,6 +428,9 @@ void Geometry::read(const std::string &fileName) case 9: this->readOFF(fileName); break; + case 10: + this->readXYZ(fileName); + break; default: #if VDB_TOOL_USE_PDAL if (this->readPDAL(fileName)) break;// note, this only reads vertices @@ -448,15 +453,17 @@ void Geometry::readOBJ(const std::string &fileName) void Geometry::readOBJ(std::istream &is) { - Vec3f p; + Vec3f p;// coordinates + Vec3s c;// color std::string line; while (std::getline(is, line)) { std::istringstream iss(line); std::string str; - iss >> str; + iss >> str;// "v", "vn" or "f" if (str == "v") { iss >> p[0] >> p[1] >> p[2]; mVtx.push_back(p); + if (iss >> c[0] >> c[1] >> c[2]) mRGB.push_back(c); } else if (str == "f") { std::vector v; while (iss >> str) { @@ -490,7 +497,6 @@ bool Geometry::readPDAL(const std::string &fileName) pdal::StageFactory factory; const std::string type = factory.inferReaderDriver(fileName); if (type.empty()) return false;// PDAL cannot read this file - std::cerr << "Using PDAL to read \"" << fileName << "\"\n"; const std::string pipelineJson = R"({ "pipeline" : [ { @@ -595,6 +601,33 @@ void Geometry::readOFF(std::istream &is) mBBox = BBoxT();//invalidate BBox }// Geometry::readOFF +void Geometry::readXYZ(const std::string &fileName) +{ + if (fileName == "stdin.xyz") { + this->readXYZ(std::cin); + } else { + std::ifstream infile(fileName); + if (!infile.is_open()) throw std::invalid_argument("Error opening Geometry file \""+fileName+"\""); + this->readXYZ(infile); + } +}// Geometry::readXYZ + +void Geometry::readXYZ(std::istream &is) +{ + std::string line; + Vec3f p; + while (std::getline(is, line)) { + if (line.empty() || line[0] == '#') continue; + std::istringstream iss(line); + if (iss >> p[0] >> p[1] >> p[2]) { + mVtx.push_back(p); + } else { + throw std::invalid_argument("Error reading coordinates in xyz file from line \"" + line + "\""); + } + } + mBBox = BBoxT();//invalidate BBox +}// Geometry::readXYZ + void Geometry::readPLY(const std::string &fileName) { if (fileName == "stdin.ply") { @@ -871,7 +904,7 @@ void Geometry::readPTS(const std::string &fileName) if (!infile.is_open()) throw std::runtime_error("Error opening particle file \""+fileName+"\""); std::string line; std::istringstream iss; - bool readColor = false; + bool readColor = true; Vec3s rgb; while(std::getline(infile, line)) { const size_t n = mVtx.size(), m = std::stoi(line); @@ -885,15 +918,12 @@ void Geometry::readPTS(const std::string &fileName) throw std::invalid_argument("Geometry::readPTS: error parsing line: \""+line+"\""); } if (readColor) { - if (!(iss >> i) ) { // converting intensity to a multiplier on rgb might be appropriate, but i can't find a good spec for it + int dummy;// intensity which is currently ignored + if (iss >> dummy >> rgb[0] >> rgb[1] >> rgb[2]) { + mRGB.push_back(rgb/255.0); + } else { readColor = false; - continue; } - if (!(iss >> rgb[0] >> rgb[1] >> rgb[2])) { - readColor = false; - continue; - } - mRGB.push_back(rgb/255.0); } }// loop over points @@ -994,13 +1024,19 @@ void Geometry::readNVDB(const std::string&) void Geometry::print(size_t n, std::ostream& os) const { - os << "vtx = " << mVtx.size() << ", tri = " << mTri.size() << ", quad = " << mQuad.size()<< ", bbox=" << this->bbox(); + os << "vtx = " << mVtx.size() << ", rbg = " << mRGB.size() << ", tri = " << mTri.size() << ", quad = " << mQuad.size()<< ", bbox=" << this->bbox(); if (size_t m = std::min(n, mVtx.size())) { os << std::endl; for (size_t i=0; iread();}, 0);// anonymous options are treated as to the first option,i.e. "files" @@ -963,7 +963,7 @@ void Tool::read() { OPENVDB_ASSERT(mParser.getAction().name == "read"); for (auto &fileName : mParser.getVec("files")) { - switch (findFileExt(fileName, {"geo,obj,ply,abc,pts,off,stl", "vdb", "nvdb"})) { + switch (findFileExt(fileName, {"geo,obj,ply,abc,pts,off,stl,xyz", "vdb", "nvdb"})) { case 1: this->readGeo(fileName); break; @@ -975,17 +975,15 @@ void Tool::read() break; default: #if VDB_TOOL_USE_PDAL - pdal::StageFactory factory; - if (factory.inferReaderDriver(fileName) != "") - { - this->readGeo(fileName); - break; - } + pdal::StageFactory factory; + if (factory.inferReaderDriver(fileName) != "") { + this->readGeo(fileName); + break; + } #endif throw std::invalid_argument("File \""+fileName+"\" has an invalid extension"); - break; - } - } + }// end switch + }// end for loop over files } // ============================================================================================================== From 3582f756c506283d6cb08432fef72f293419d6b7 Mon Sep 17 00:00:00 2001 From: Ken Museth Date: Mon, 1 Dec 2025 16:51:23 -0800 Subject: [PATCH 010/275] added read support to vdb_tool for xyz files Signed-off-by: Ken Museth --- pendingchanges/openvdb_cmd.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/pendingchanges/openvdb_cmd.txt b/pendingchanges/openvdb_cmd.txt index bb556de098..17d8420915 100644 --- a/pendingchanges/openvdb_cmd.txt +++ b/pendingchanges/openvdb_cmd.txt @@ -1,6 +1,7 @@ vdb_tool: Improvements: - Improved error reporting when using unsupported actions and options + - Added read support for xyz files Fixes: - Fixed bug when reading ascii ply files - Fixed several typos in vdb_tool \ No newline at end of file From 9e46019b8f38a801f66d5970d031caab7e0a182e Mon Sep 17 00:00:00 2001 From: Ken Museth Date: Wed, 3 Dec 2025 16:12:42 -0800 Subject: [PATCH 011/275] added draft of soupToLevelSet Signed-off-by: Ken Museth --- openvdb_cmd/vdb_tool/include/Geometry.h | 4 + openvdb_cmd/vdb_tool/include/Tool.h | 162 +++++++++++++++++++----- 2 files changed, 134 insertions(+), 32 deletions(-) diff --git a/openvdb_cmd/vdb_tool/include/Geometry.h b/openvdb_cmd/vdb_tool/include/Geometry.h index 8b4c0b0381..7453dfd45d 100644 --- a/openvdb_cmd/vdb_tool/include/Geometry.h +++ b/openvdb_cmd/vdb_tool/include/Geometry.h @@ -612,6 +612,10 @@ void Geometry::readXYZ(const std::string &fileName) } }// Geometry::readXYZ +/* +xyz files are loosely defined as ascii files with x y z coordinates, possibly followed by rgb or normals +Empty lines and lines beginning with # ignored +*/ void Geometry::readXYZ(std::istream &is) { std::string line; diff --git a/openvdb_cmd/vdb_tool/include/Tool.h b/openvdb_cmd/vdb_tool/include/Tool.h index 26f98d8632..161d46cc75 100644 --- a/openvdb_cmd/vdb_tool/include/Tool.h +++ b/openvdb_cmd/vdb_tool/include/Tool.h @@ -184,9 +184,12 @@ class Tool /// @brief Converts a level set VDB into a VDB with a fog volume, i.e. normalized density void levelSetToFog(); - /// @brief Converts a polygon mesh into a narrow-band level set, i.e. a narrow-band signed distance to a polygon mesh + /// @brief Converts a polygon soup into a narrow-band level set, i.e. a narrow-band signed distance to a polygon mesh void meshToLevelSet(); + /// @brief Converts a polygon mesh into a narrow-band level set, i.e. a narrow-band signed distance to a polygon mesh + void soupToLevelSet(); + /// @brief construct a LoD sequences of VDB trees with powers of two refinements void multires(); @@ -249,6 +252,8 @@ class Tool inline auto getGrid(size_t age) const; inline auto getGeom(size_t age) const; + Geometry::Ptr mesherToGeometry(tools::VolumeToMesh&) const; + };// Tool class // ============================================================================================================== @@ -417,6 +422,17 @@ void Tool::init() {"name", "", "mesh2ls_input", "specify the name of the resulting vdb (by default it's derived from the input geometry)"}}, [&](){mParser.setDefaults();}, [&](){this->meshToLevelSet();}); + mParser.addAction( + "soup2ls", "s2ls", "Convert a polygon soup into a narrow-band level set, i.e. a narrow-band signed distance to a polygon mesh", + {{"dim", "", "256", "largest dimension in voxel units of the mesh bbox (defaults to 256). If \"vdb\" or \"voxel\" is defined then \"dim\" is ignored"}, + {"voxel", "", "0.01", "voxel size in world units (by defaults \"dim\" is used to derive \"voxel\"). If specified this option takes precedence over \"dim\""}, + {"width", "", "3.0", "half-width in voxel units of the output narrow-band level set (defaults to 3 units on either side of the zero-crossing)"}, + {"geo", "0", "0", "age (i.e. stack index) of the geometry to be processed. Defaults to 0, i.e. most recently inserted geometry."}, + {"vdb", "-1", "0", "age (i.e. stack index) of reference grid used to define the transform. Defaults to -1, i.e. disabled. If specified this option takes precedence over \"dim\" and \"voxel\"!"}, + {"keep", "", "1|0|true|false", "toggle wether the input geometry is preserved or deleted after the conversion"}, + {"name", "", "soup2ls_input", "specify the name of the resulting vdb (by default it's derived from the input geometry)"}}, + [&](){mParser.setDefaults();}, [&](){this->soupToLevelSet();}); + mParser.addAction( "ls2mesh", "l2m", "Convert a level set to an adaptive polygon mesh", {{"adapt", "0.0", "0.9", "normalized metric for the adaptive meshing. 0 is uniform and 1 is fully adaptive mesh. Defaults to 0."}, @@ -503,7 +519,7 @@ void Tool::init() [&](){mParser.setDefaults();}, [&](){this->enright();}); mParser.addAction( - "dilate", "", "erode level set surface by a fixed radius", + "dilate", "", "dilate level set surface by a fixed radius", {{"radius", "1.0", "1.0", "radius in voxel units by which the surface is dilated"}, {"space", "", "1|2|3|5", "order of the spatial discretization (defaults to 5, i.e. WENO)"}, {"time", "", "1|2|3", "order of the temporal discretization (defaults to 1, i.e. explicit Euler)"}, @@ -1671,6 +1687,81 @@ void Tool::meshToLevelSet() // ============================================================================================================== +void Tool::soupToLevelSet() +{ + const std::string &name = mParser.getAction().name; + OPENVDB_ASSERT(name == "soup2ls"); + try { + mParser.printAction(); + const int dim = mParser.get("dim");// initial dimension + float voxel = mParser.get("voxel");// initial voxel size + const float width = mParser.get("width"); + const int geo_age = mParser.get("geo"); + //const int vdb_age = mParser.get("vdb");// reference grid used to + const bool keep = mParser.get("keep"); + std::string grid_name = mParser.get("name"); + if (voxel == 0.0f) voxel = this->estimateVoxelSize(dim, width, geo_age); + auto it = this->getGeom(geo_age); + const Geometry &mesh = **it; + if (mesh.isPoints()) this->warning("Warning: -soup2ls was called on points, not a mesh! Hint: use -points2ls instead!"); + + auto myUpsample = [&](const GridT &grid)->GridT::Ptr{ + const float dx = grid.voxelSize()[0]; + auto xform = math::Transform::createLinearTransform(dx/2);// upsample + return tools::levelSetRebuild(grid, dx, width, xform.get()); + };// myUpsample + + //auto myOffsetUpsample = [&](const Geometry &geom, float dx)->GridT::Ptr{ + // auto xform = math::Transform::createLinearTransform(dx); + // auto udf = tools::meshToUnsignedDistanceField(*xform, geom.vtx(), geom.tri(), geom.quad(), width); + // return myUpsample(*udf); + //};// myOffsetUpsample + + auto myOffset = [&](float dx)->GridT::Ptr{ + auto xform = math::Transform::createLinearTransform(dx); + auto udf = tools::meshToUnsignedDistanceField(*xform, mesh.vtx(), mesh.tri(), mesh.quad(), width); + return tools::levelSetRebuild(*udf, /*iso-value=*/dx, width); + };// muOffset + + auto myErode = [&](GridT &grid, float voxelOffset)->void{ + const int space = 1, time = 1; + auto filter = this->createFilter(grid, space, time); + filter->offset(voxelOffset*grid.voxelSize()[0]); + };// myErode + + auto myUnion = [&](GridT &gridA, GridT &gridB)->void{ + tools::csgUnion(gridA, gridB, true);// overwrites A and cannibalizes B, and prune + tools::sdfToSdf(gridA);// re-normalize + };// myUnion + + if (mParser.verbose) mTimer.start("Soup -> SDF"); + + // Main algorithm + const int nLOD = 2, nErosion = 2; + float dx = voxel;// lets use the same notation for the voxel size as the paper + auto gridL = myOffset(dx); + // while loop + gridL = myUpsample(*gridL); + auto gridH = myOffset(dx/2); + // while loop + myErode(*gridL, dx/2); + myUnion(*gridL, *gridH); + // end while loop + dx *= 0.5f; + // end while loop + + if (grid_name.empty()) grid_name = "soup2ls_" + mesh.getName(); + gridL->setName(grid_name); + mGrid.push_back(gridL); + if (!keep) mGeom.erase(std::next(it).base()); + if (mParser.verbose) mTimer.stop(); + } catch (const std::exception& e) { + throw std::invalid_argument(name+": "+e.what()); + } +}// Tool::soupToLevelSet + +// ============================================================================================================== + void Tool::particlesToLevelSet() { const std::string &name = mParser.getAction().name; @@ -2093,36 +2184,7 @@ void Tool::levelSetToMesh() } } mesher(*grid); - - Geometry::Ptr geom(new Geometry()); - - {// allocate and copy vertices - auto &vtx = geom->vtx(); - vtx.resize(mesher.pointListSize()); - tools::volume_to_mesh_internal::PointListCopy ptnCpy(mesher.pointList(), vtx); - tbb::parallel_for(tbb::blocked_range(0, vtx.size()), ptnCpy); - mesher.pointList().reset(nullptr); - } - - {// allocate and copy polygons - auto& polygonPoolList = mesher.polygonPoolList(); - size_t numQuad = 0, numTri = 0; - for (size_t i = 0, N = mesher.polygonPoolListSize(); i < N; ++i) { - auto &polygons = polygonPoolList[i]; - numTri += polygons.numTriangles(); - numQuad += polygons.numQuads(); - } - auto &tri = geom->tri(); - auto &quad = geom->quad(); - tri.resize(numTri); - quad.resize(numQuad); - size_t qIdx = 0, tIdx = 0; - for (size_t n = 0, N = mesher.polygonPoolListSize(); n < N; ++n) { - auto &poly = polygonPoolList[n]; - for (size_t i = 0, I = poly.numQuads(); i < I; ++i) quad[qIdx++] = poly.quad(i); - for (size_t i = 0, I = poly.numTriangles(); i < I; ++i) tri[tIdx++] = poly.triangle(i); - } - } + Geometry::Ptr geom = this->mesherToGeometry(mesher); if (!keep) mGrid.erase(std::next(it).base()); if (grid_name.empty()) grid_name = "ls2mesh_"+grid->getName(); @@ -2891,6 +2953,42 @@ void Tool::print(std::ostream& os) const } }// Tool::print +// ============================================================================================================== + +Geometry::Ptr Tool::mesherToGeometry(tools::VolumeToMesh &mesher) const +{ + Geometry::Ptr geom(new Geometry()); + + {// allocate and copy vertices + auto &vtx = geom->vtx(); + vtx.resize(mesher.pointListSize()); + tools::volume_to_mesh_internal::PointListCopy ptnCpy(mesher.pointList(), vtx); + tbb::parallel_for(tbb::blocked_range(0, vtx.size()), ptnCpy); + mesher.pointList().reset(nullptr); + } + + {// allocate and copy polygons + auto& polygonPoolList = mesher.polygonPoolList(); + size_t numQuad = 0, numTri = 0; + for (size_t i = 0, N = mesher.polygonPoolListSize(); i < N; ++i) { + auto &polygons = polygonPoolList[i]; + numTri += polygons.numTriangles(); + numQuad += polygons.numQuads(); + } + auto &tri = geom->tri(); + auto &quad = geom->quad(); + tri.resize(numTri); + quad.resize(numQuad); + size_t qIdx = 0, tIdx = 0; + for (size_t n = 0, N = mesher.polygonPoolListSize(); n < N; ++n) { + auto &poly = polygonPoolList[n]; + for (size_t i = 0, I = poly.numQuads(); i < I; ++i) quad[qIdx++] = poly.quad(i); + for (size_t i = 0, I = poly.numTriangles(); i < I; ++i) tri[tIdx++] = poly.triangle(i); + } + } + return geom; +}// Tool::mesherToGeometry + } // namespace vdb_tool } // namespace OPENVDB_VERSION_NAME } // namespace openvdb From 752b0e5bc0c885f1581f839bfc5578df249f3a68 Mon Sep 17 00:00:00 2001 From: Ken Museth Date: Wed, 3 Dec 2025 17:18:14 -0800 Subject: [PATCH 012/275] snapshot Signed-off-by: Ken Museth --- openvdb_cmd/vdb_tool/include/Tool.h | 48 +++++++++++++++-------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/openvdb_cmd/vdb_tool/include/Tool.h b/openvdb_cmd/vdb_tool/include/Tool.h index 161d46cc75..56099aaa2e 100644 --- a/openvdb_cmd/vdb_tool/include/Tool.h +++ b/openvdb_cmd/vdb_tool/include/Tool.h @@ -1706,30 +1706,28 @@ void Tool::soupToLevelSet() if (mesh.isPoints()) this->warning("Warning: -soup2ls was called on points, not a mesh! Hint: use -points2ls instead!"); auto myUpsample = [&](const GridT &grid)->GridT::Ptr{ + if (mParser.verbose) mTimer.restart("upsample"); const float dx = grid.voxelSize()[0]; auto xform = math::Transform::createLinearTransform(dx/2);// upsample return tools::levelSetRebuild(grid, dx, width, xform.get()); };// myUpsample - //auto myOffsetUpsample = [&](const Geometry &geom, float dx)->GridT::Ptr{ - // auto xform = math::Transform::createLinearTransform(dx); - // auto udf = tools::meshToUnsignedDistanceField(*xform, geom.vtx(), geom.tri(), geom.quad(), width); - // return myUpsample(*udf); - //};// myOffsetUpsample - auto myOffset = [&](float dx)->GridT::Ptr{ + if (mParser.verbose) mTimer.restart("offset"); auto xform = math::Transform::createLinearTransform(dx); auto udf = tools::meshToUnsignedDistanceField(*xform, mesh.vtx(), mesh.tri(), mesh.quad(), width); return tools::levelSetRebuild(*udf, /*iso-value=*/dx, width); };// muOffset - auto myErode = [&](GridT &grid, float voxelOffset)->void{ + auto myErode = [&](GridT &grid)->void{ + if (mParser.verbose) mTimer.restart("erode"); const int space = 1, time = 1; auto filter = this->createFilter(grid, space, time); - filter->offset(voxelOffset*grid.voxelSize()[0]); + filter->offset(grid.voxelSize()[0]);// erode by dx };// myErode auto myUnion = [&](GridT &gridA, GridT &gridB)->void{ + if (mParser.verbose) mTimer.restart("union"); tools::csgUnion(gridA, gridB, true);// overwrites A and cannibalizes B, and prune tools::sdfToSdf(gridA);// re-normalize };// myUnion @@ -1737,22 +1735,26 @@ void Tool::soupToLevelSet() if (mParser.verbose) mTimer.start("Soup -> SDF"); // Main algorithm - const int nLOD = 2, nErosion = 2; + const int nLOD = 4, nErode = 4; float dx = voxel;// lets use the same notation for the voxel size as the paper - auto gridL = myOffset(dx); - // while loop - gridL = myUpsample(*gridL); - auto gridH = myOffset(dx/2); - // while loop - myErode(*gridL, dx/2); - myUnion(*gridL, *gridH); - // end while loop - dx *= 0.5f; - // end while loop - - if (grid_name.empty()) grid_name = "soup2ls_" + mesh.getName(); - gridL->setName(grid_name); - mGrid.push_back(gridL); + auto grid = myOffset(dx); + grid->setName("soup2ls_"+std::to_string(dx)); + mGrid.push_back(grid); + for (int i=0; isetName("soup2ls_"+std::to_string(dx)); + mGrid.push_back(grid); + } + + //if (grid_name.empty()) grid_name = "soup2ls_" + mesh.getName(); + //grid->setName(grid_name); + //mGrid.push_back(grid); if (!keep) mGeom.erase(std::next(it).base()); if (mParser.verbose) mTimer.stop(); } catch (const std::exception& e) { From 91a139f28c4ae2b34acdfbaebe50cf6fc594aac1 Mon Sep 17 00:00:00 2001 From: Ken Museth Date: Thu, 4 Dec 2025 09:55:47 -0800 Subject: [PATCH 013/275] improved action soup2ls Signed-off-by: Ken Museth --- openvdb_cmd/vdb_tool/include/Tool.h | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/openvdb_cmd/vdb_tool/include/Tool.h b/openvdb_cmd/vdb_tool/include/Tool.h index 56099aaa2e..c2ec574fa3 100644 --- a/openvdb_cmd/vdb_tool/include/Tool.h +++ b/openvdb_cmd/vdb_tool/include/Tool.h @@ -358,6 +358,9 @@ void Tool::init() { // note, the following actions were added when mParser was constructed: -quiet,-verbose,-debug,-default,-for,-each,-end + // mParser.addAction("name", "alias", "documentation of action", + // {{"option name", "default value", "expected values", "documentation of option"}}, + // {more options}...}); mParser.addAction( "config", "c", "Import and process one or more configuration files", {{"files", "", "config1.txt,config2.txt...", "list of configuration files to load and execute"}, @@ -428,7 +431,8 @@ void Tool::init() {"voxel", "", "0.01", "voxel size in world units (by defaults \"dim\" is used to derive \"voxel\"). If specified this option takes precedence over \"dim\""}, {"width", "", "3.0", "half-width in voxel units of the output narrow-band level set (defaults to 3 units on either side of the zero-crossing)"}, {"geo", "0", "0", "age (i.e. stack index) of the geometry to be processed. Defaults to 0, i.e. most recently inserted geometry."}, - {"vdb", "-1", "0", "age (i.e. stack index) of reference grid used to define the transform. Defaults to -1, i.e. disabled. If specified this option takes precedence over \"dim\" and \"voxel\"!"}, + {"lod", "2", "2", "number of LOD level. Defaults to 2."}, + {"erode", "2", "2", "number of iterations of constrained erosion. Defaults to 2."}, {"keep", "", "1|0|true|false", "toggle wether the input geometry is preserved or deleted after the conversion"}, {"name", "", "soup2ls_input", "specify the name of the resulting vdb (by default it's derived from the input geometry)"}}, [&](){mParser.setDefaults();}, [&](){this->soupToLevelSet();}); @@ -1687,6 +1691,7 @@ void Tool::meshToLevelSet() // ============================================================================================================== +// vdb_tool -read ~/dev/data/ply/cube.ply -soup2ls dim=64 lod=4 erode=5 -o vdb="*" stdout.vdb | vdb_view void Tool::soupToLevelSet() { const std::string &name = mParser.getAction().name; @@ -1697,7 +1702,8 @@ void Tool::soupToLevelSet() float voxel = mParser.get("voxel");// initial voxel size const float width = mParser.get("width"); const int geo_age = mParser.get("geo"); - //const int vdb_age = mParser.get("vdb");// reference grid used to + const int nErode = mParser.get("erode"); + const int nLOD = mParser.get("lod"); const bool keep = mParser.get("keep"); std::string grid_name = mParser.get("name"); if (voxel == 0.0f) voxel = this->estimateVoxelSize(dim, width, geo_age); @@ -1706,28 +1712,29 @@ void Tool::soupToLevelSet() if (mesh.isPoints()) this->warning("Warning: -soup2ls was called on points, not a mesh! Hint: use -points2ls instead!"); auto myUpsample = [&](const GridT &grid)->GridT::Ptr{ - if (mParser.verbose) mTimer.restart("upsample"); const float dx = grid.voxelSize()[0]; + if (mParser.verbose) mTimer.restart("upsample("+std::to_string(dx)+")"); auto xform = math::Transform::createLinearTransform(dx/2);// upsample return tools::levelSetRebuild(grid, dx, width, xform.get()); };// myUpsample auto myOffset = [&](float dx)->GridT::Ptr{ - if (mParser.verbose) mTimer.restart("offset"); + if (mParser.verbose) mTimer.restart("offset("+std::to_string(dx)+")"); auto xform = math::Transform::createLinearTransform(dx); auto udf = tools::meshToUnsignedDistanceField(*xform, mesh.vtx(), mesh.tri(), mesh.quad(), width); return tools::levelSetRebuild(*udf, /*iso-value=*/dx, width); };// muOffset auto myErode = [&](GridT &grid)->void{ - if (mParser.verbose) mTimer.restart("erode"); + const float dx = grid.voxelSize()[0]; + if (mParser.verbose) mTimer.restart("erode("+std::to_string(dx)+")"); const int space = 1, time = 1; auto filter = this->createFilter(grid, space, time); - filter->offset(grid.voxelSize()[0]);// erode by dx + filter->offset(dx);// erode by dx };// myErode auto myUnion = [&](GridT &gridA, GridT &gridB)->void{ - if (mParser.verbose) mTimer.restart("union"); + if (mParser.verbose) mTimer.restart("union("+std::to_string(gridA.voxelSize()[0])+")"); tools::csgUnion(gridA, gridB, true);// overwrites A and cannibalizes B, and prune tools::sdfToSdf(gridA);// re-normalize };// myUnion @@ -1735,7 +1742,6 @@ void Tool::soupToLevelSet() if (mParser.verbose) mTimer.start("Soup -> SDF"); // Main algorithm - const int nLOD = 4, nErode = 4; float dx = voxel;// lets use the same notation for the voxel size as the paper auto grid = myOffset(dx); grid->setName("soup2ls_"+std::to_string(dx)); @@ -1744,7 +1750,7 @@ void Tool::soupToLevelSet() dx *= 0.5f; grid = myUpsample(*grid); auto base = myOffset(dx); - for (int j = 0; j Date: Fri, 5 Dec 2025 17:50:40 -0800 Subject: [PATCH 014/275] snapshot Signed-off-by: Ken Museth --- openvdb_cmd/vdb_tool/include/Tool.h | 54 ++++++++++++++++++----------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/openvdb_cmd/vdb_tool/include/Tool.h b/openvdb_cmd/vdb_tool/include/Tool.h index c2ec574fa3..6d96ddb3da 100644 --- a/openvdb_cmd/vdb_tool/include/Tool.h +++ b/openvdb_cmd/vdb_tool/include/Tool.h @@ -1710,33 +1710,43 @@ void Tool::soupToLevelSet() auto it = this->getGeom(geo_age); const Geometry &mesh = **it; if (mesh.isPoints()) this->warning("Warning: -soup2ls was called on points, not a mesh! Hint: use -points2ls instead!"); + bool isSDF = false; auto myUpsample = [&](const GridT &grid)->GridT::Ptr{ const float dx = grid.voxelSize()[0]; - if (mParser.verbose) mTimer.restart("upsample("+std::to_string(dx)+")"); + //if (mParser.verbose) mTimer.restart("upsample("+std::to_string(dx)+")"); auto xform = math::Transform::createLinearTransform(dx/2);// upsample - return tools::levelSetRebuild(grid, dx, width, xform.get()); + isSDF = true; + return tools::levelSetRebuild(grid, dx, width, xform.get());// SDF -> mesh -> SDF };// myUpsample auto myOffset = [&](float dx)->GridT::Ptr{ - if (mParser.verbose) mTimer.restart("offset("+std::to_string(dx)+")"); + //if (mParser.verbose) mTimer.restart("offset("+std::to_string(dx)+")"); auto xform = math::Transform::createLinearTransform(dx); auto udf = tools::meshToUnsignedDistanceField(*xform, mesh.vtx(), mesh.tri(), mesh.quad(), width); - return tools::levelSetRebuild(*udf, /*iso-value=*/dx, width); + isSDF = true; + return tools::levelSetRebuild(*udf, /*iso-value=*/dx, width);// UDF -> mesh -> SDF };// muOffset auto myErode = [&](GridT &grid)->void{ const float dx = grid.voxelSize()[0]; - if (mParser.verbose) mTimer.restart("erode("+std::to_string(dx)+")"); + //if (mParser.verbose) mTimer.restart("erode("+std::to_string(dx)+")"); const int space = 1, time = 1; auto filter = this->createFilter(grid, space, time); + //if (isSDF == false) filter->normalize(); filter->offset(dx);// erode by dx + isSDF = true; };// myErode - auto myUnion = [&](GridT &gridA, GridT &gridB)->void{ - if (mParser.verbose) mTimer.restart("union("+std::to_string(gridA.voxelSize()[0])+")"); + auto myUnion = [&](GridT &gridA, GridT &gridB){ + //if (mParser.verbose) mTimer.restart("union("+std::to_string(gridA.voxelSize()[0])+")"); tools::csgUnion(gridA, gridB, true);// overwrites A and cannibalizes B, and prune - tools::sdfToSdf(gridA);// re-normalize + return tools::sdfToSdf(gridA);// re-normalize using fast sweeping + //return tools::levelSetRebuild(gridA, 0.0f, width);// SDF -> mesh -> SDF + //const int space = 1, time = 1; + //auto filter = this->createFilter(gridA, space, time); + //filter->normalize();// fast + isSDF = false; };// myUnion if (mParser.verbose) mTimer.start("Soup -> SDF"); @@ -1745,22 +1755,24 @@ void Tool::soupToLevelSet() float dx = voxel;// lets use the same notation for the voxel size as the paper auto grid = myOffset(dx); grid->setName("soup2ls_"+std::to_string(dx)); - mGrid.push_back(grid); + //mGrid.push_back(grid); for (int i=0; isetName("soup2ls_"+std::to_string(dx)); - mGrid.push_back(grid); + //grid->setName("soup2ls_"+std::to_string(dx)); + //mGrid.push_back(grid); } + //grid = tools::levelSetRebuild(*grid, /*iso-value=*/0.0f, width);// SDF -> mesh -> SDF - //if (grid_name.empty()) grid_name = "soup2ls_" + mesh.getName(); - //grid->setName(grid_name); - //mGrid.push_back(grid); + if (grid_name.empty()) grid_name = "soup2ls_" + mesh.getName(); + grid->setName(grid_name); + mGrid.push_back(grid); if (!keep) mGeom.erase(std::next(it).base()); if (mParser.verbose) mTimer.stop(); } catch (const std::exception& e) { @@ -2115,36 +2127,36 @@ void Tool::csg() if (mParser.verbose) mTimer.start("Union"); if (keep) { GridT::Ptr grid = tools::csgUnionCopy(*gridA, *gridB); - if (rebuild) tools::sdfToSdf(*grid); + if (rebuild) grid = tools::sdfToSdf(*grid); grid->setName("union_"+gridA->getName()); mGrid.push_back(grid);// A and B are unchanged! } else { tools::csgUnion(*gridA, *gridB, prune);// overwrites A and cannibalizes B - if (rebuild) tools::sdfToSdf(*gridA); + if (rebuild) gridA = tools::sdfToSdf(*gridA); gridA->setName("union_"+gridA->getName()); } } else if (name == "intersection") { if (mParser.verbose) mTimer.start("Intersection"); if (keep) { GridT::Ptr grid = tools::csgIntersectionCopy(*gridA, *gridB); - if (rebuild) tools::sdfToSdf(*grid); + if (rebuild) grid = tools::sdfToSdf(*grid); grid->setName("intersection_"+gridA->getName()); mGrid.push_back(grid);// A and B are unchanged! } else { tools::csgIntersection(*gridA, *gridB, prune);// overwrites A and cannibalizes B - if (rebuild) tools::sdfToSdf(*gridA); + if (rebuild) gridA = tools::sdfToSdf(*gridA); gridA->setName("intersection_"+gridA->getName()); } } else if (name == "difference") { if (mParser.verbose) mTimer.start("Difference"); if (keep) { GridT::Ptr grid = tools::csgDifferenceCopy(*gridA, *gridB); - if (rebuild) tools::sdfToSdf(*grid); + if (rebuild) grid = tools::sdfToSdf(*grid); grid->setName("difference_"+gridA->getName()); mGrid.push_back(grid);// A and B are unchanged! } else { tools::csgDifference(*gridA, *gridB, prune);// overwrites A and deletes B - if (rebuild) tools::sdfToSdf(*gridA); + if (rebuild) gridA = tools::sdfToSdf(*gridA); gridA->setName("difference_"+gridA->getName()); } } else { From f6e13183c6fdbda1ebaf4610fcbf17e73470fbea Mon Sep 17 00:00:00 2001 From: Ken Museth Date: Mon, 8 Dec 2025 12:24:19 -0800 Subject: [PATCH 015/275] fixed a bug in the union operator of soup2ls Signed-off-by: Ken Museth --- openvdb_cmd/vdb_tool/include/Tool.h | 46 +++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/openvdb_cmd/vdb_tool/include/Tool.h b/openvdb_cmd/vdb_tool/include/Tool.h index 6d96ddb3da..a82bf07a50 100644 --- a/openvdb_cmd/vdb_tool/include/Tool.h +++ b/openvdb_cmd/vdb_tool/include/Tool.h @@ -1714,40 +1714,64 @@ void Tool::soupToLevelSet() auto myUpsample = [&](const GridT &grid)->GridT::Ptr{ const float dx = grid.voxelSize()[0]; + isSDF = true; //if (mParser.verbose) mTimer.restart("upsample("+std::to_string(dx)+")"); +#if 0 auto xform = math::Transform::createLinearTransform(dx/2);// upsample - isSDF = true; - return tools::levelSetRebuild(grid, dx, width, xform.get());// SDF -> mesh -> SDF + return tools::levelSetRebuild(grid, 0.0f, width, xform.get());// SDF -> mesh -> SDF +#else + GridT::Ptr outGrid = createLevelSet(dx/2, width); + tools::resampleToMatch(grid, *outGrid); + return outGrid; +#endif };// myUpsample auto myOffset = [&](float dx)->GridT::Ptr{ //if (mParser.verbose) mTimer.restart("offset("+std::to_string(dx)+")"); auto xform = math::Transform::createLinearTransform(dx); auto udf = tools::meshToUnsignedDistanceField(*xform, mesh.vtx(), mesh.tri(), mesh.quad(), width); - isSDF = true; return tools::levelSetRebuild(*udf, /*iso-value=*/dx, width);// UDF -> mesh -> SDF };// muOffset + /* auto myErode = [&](GridT &grid)->void{ const float dx = grid.voxelSize()[0]; //if (mParser.verbose) mTimer.restart("erode("+std::to_string(dx)+")"); const int space = 1, time = 1; auto filter = this->createFilter(grid, space, time); - //if (isSDF == false) filter->normalize(); + if (isSDF == false) { + filter->normalize(); + isSDF = true; + } filter->offset(dx);// erode by dx - isSDF = true; };// myErode auto myUnion = [&](GridT &gridA, GridT &gridB){ //if (mParser.verbose) mTimer.restart("union("+std::to_string(gridA.voxelSize()[0])+")"); - tools::csgUnion(gridA, gridB, true);// overwrites A and cannibalizes B, and prune - return tools::sdfToSdf(gridA);// re-normalize using fast sweeping + //tools::csgUnion(gridA, gridB, true);// overwrites A and cannibalizes B, and prune + //return tools::sdfToSdf(gridA);// re-normalize using fast sweeping //return tools::levelSetRebuild(gridA, 0.0f, width);// SDF -> mesh -> SDF //const int space = 1, time = 1; //auto filter = this->createFilter(gridA, space, time); //filter->normalize();// fast isSDF = false; + return tools::csgUnionCopy(gridA, gridB); };// myUnion + */ + + auto myConstrainedErosion = [&](GridT &grid, const GridT &gridB)->GridT::Ptr{ + const float dx = grid.voxelSize()[0]; + //if (mParser.verbose) mTimer.restart("erode("+std::to_string(dx)+")"); + const int space = 1, time = 1; + auto filter = this->createFilter(grid, space, time); + if (isSDF == false) { + filter->normalize(); + isSDF = true; + } + filter->offset(dx);// erode by dx + isSDF = false;// the next CSG operation will mess up the SDF + return tools::csgUnionCopy(grid, gridB); + };// myConstrainedErosion if (mParser.verbose) mTimer.start("Soup -> SDF"); @@ -1757,14 +1781,10 @@ void Tool::soupToLevelSet() grid->setName("soup2ls_"+std::to_string(dx)); //mGrid.push_back(grid); for (int i=0; isetName("soup2ls_"+std::to_string(dx)); //mGrid.push_back(grid); } From 85aab0dd90b0d3ac4e83e079e9e7dde4bf1a3f94 Mon Sep 17 00:00:00 2001 From: Jonathan Swartz Date: Mon, 22 Dec 2025 18:56:36 +1300 Subject: [PATCH 016/275] Fix python nanoToOpenVDB bindings Signed-off-by: Jonathan Swartz --- nanovdb/nanovdb/python/PyNanoToOpenVDB.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nanovdb/nanovdb/python/PyNanoToOpenVDB.cc b/nanovdb/nanovdb/python/PyNanoToOpenVDB.cc index 49dc464a05..07fb5285ef 100644 --- a/nanovdb/nanovdb/python/PyNanoToOpenVDB.cc +++ b/nanovdb/nanovdb/python/PyNanoToOpenVDB.cc @@ -20,9 +20,9 @@ template void defineNanoToOpenVDB(nb::module_& m) #ifdef NANOVDB_USE_OPENVDB // Wrap nanoToOpenVDB into a lambda to workaround an MSVC compiler bug m.def( - "nanoToOpenVDB", [](GridHandle& handle, int verbose, uint32_t n){ - return tools::nanoToOpenVDB(handle, verbose, n); - }, "handle"_a, "verbose"_a = 0, "n"_a = 0); + "nanoToOpenVDB", [](GridHandle& handle, uint32_t n){ + return tools::nanoToOpenVDB(handle, n); + }, "handle"_a, "n"_a = 0); #endif } From 05fe3abf6073bab95d8696bd350135699984d7b3 Mon Sep 17 00:00:00 2001 From: Jonathan Swartz Date: Tue, 23 Dec 2025 16:57:25 +1300 Subject: [PATCH 017/275] Enable build of nanovdb python module and running python tests in nano CI action Signed-off-by: Jonathan Swartz --- .github/workflows/nanovdb.yml | 8 ++++---- ci/build.sh | 2 ++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/nanovdb.yml b/.github/workflows/nanovdb.yml index d66266735b..9054224258 100644 --- a/.github/workflows/nanovdb.yml +++ b/.github/workflows/nanovdb.yml @@ -78,14 +78,14 @@ jobs: run: > ./ci/build.sh -v --build-type=${{ matrix.config.build }} - --components=core,nano,nanotest,nanoexam,nanobench,nanotool + --components=core,nano,nanotest,nanoexam,nanobench,nanotool,nanopython,nanopytest --cargs=\' -DUSE_EXPLICIT_INSTANTIATION=OFF -DNANOVDB_USE_CUDA=ON -DCMAKE_CUDA_ARCHITECTURES="80" -DNANOVDB_USE_OPENVDB=ON -DCMAKE_INSTALL_PREFIX=`pwd` - -DUSE_BLOSC=OFF + -DUSE_BLOSC=ON -DCMAKE_POSITION_INDEPENDENT_CODE=ON \' - name: test @@ -122,7 +122,7 @@ jobs: run: > ./ci/build.sh -v --config=Release - --components=core,nano,nanotest,nanoexam,nanobench,nanotool + --components=core,nano,nanotest,nanoexam,nanobench,nanotool,nanopython,nanopytest --cargs=\' -A x64 -G \"Visual Studio 17 2022\" -DOPENVDB_CORE_STATIC=OFF -DMSVC_COMPRESS_PDB=ON @@ -162,7 +162,7 @@ jobs: run: > ./ci/build.sh -v --build-type=${{ matrix.config.build }} - --components=core,nano,nanotest,nanoexam,nanobench,nanotool + --components=core,nano,nanotest,nanoexam,nanobench,nanotool,nanopython,nanopytest --cargs=\'-DUSE_EXPLICIT_INSTANTIATION=OFF -DNANOVDB_USE_CUDA=OFF -DNANOVDB_USE_OPENVDB=ON -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/install \' - name: test run: cd build && ctest -V -E ".*cuda.*|.*mgpu.*" diff --git a/ci/build.sh b/ci/build.sh index 8e778c6255..465403ffe8 100755 --- a/ci/build.sh +++ b/ci/build.sh @@ -73,6 +73,8 @@ COMPONENTS['nanotest']='NANOVDB_BUILD_UNITTESTS' COMPONENTS['nanoexam']='NANOVDB_BUILD_EXAMPLES' COMPONENTS['nanobench']='NANOVDB_BUILD_BENCHMARK' COMPONENTS['nanotool']='NANOVDB_BUILD_TOOLS' +COMPONENTS['nanopython']='NANOVDB_BUILD_PYTHON_MODULE' +COMPONENTS['nanopytest']='NANOVDB_BUILD_PYTHON_UNITTESTS' ################################################ From a423053857a943787623438cf9ec832b9313eb77 Mon Sep 17 00:00:00 2001 From: Ken Museth Date: Tue, 30 Dec 2025 11:09:17 -0800 Subject: [PATCH 018/275] added detailed timing to soup2ls Signed-off-by: Ken Museth --- openvdb_cmd/vdb_tool/include/Tool.h | 37 ++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/openvdb_cmd/vdb_tool/include/Tool.h b/openvdb_cmd/vdb_tool/include/Tool.h index a82bf07a50..bcc53e72f9 100644 --- a/openvdb_cmd/vdb_tool/include/Tool.h +++ b/openvdb_cmd/vdb_tool/include/Tool.h @@ -1692,6 +1692,7 @@ void Tool::meshToLevelSet() // ============================================================================================================== // vdb_tool -read ~/dev/data/ply/cube.ply -soup2ls dim=64 lod=4 erode=5 -o vdb="*" stdout.vdb | vdb_view +// ./openvdb_cmd/vdb_tool/vdb_tool -i ~/dev/data/obj/Robot_arm.obj -soup2ls voxel=51.2 lod=8 erode=16 -print -o codec=blosc openvdb_10.vdb void Tool::soupToLevelSet() { const std::string &name = mParser.getAction().name; @@ -1711,8 +1712,11 @@ void Tool::soupToLevelSet() const Geometry &mesh = **it; if (mesh.isPoints()) this->warning("Warning: -soup2ls was called on points, not a mesh! Hint: use -points2ls instead!"); bool isSDF = false; + util::CpuTimer timer; + double t_offset = 0.0, t_erode = 0.0, t_upsample = 0.0;// all in milliseconds auto myUpsample = [&](const GridT &grid)->GridT::Ptr{ + timer.start(); const float dx = grid.voxelSize()[0]; isSDF = true; //if (mParser.verbose) mTimer.restart("upsample("+std::to_string(dx)+")"); @@ -1722,15 +1726,19 @@ void Tool::soupToLevelSet() #else GridT::Ptr outGrid = createLevelSet(dx/2, width); tools::resampleToMatch(grid, *outGrid); + t_upsample += timer.milliseconds(); return outGrid; #endif };// myUpsample auto myOffset = [&](float dx)->GridT::Ptr{ + timer.start(); //if (mParser.verbose) mTimer.restart("offset("+std::to_string(dx)+")"); auto xform = math::Transform::createLinearTransform(dx); - auto udf = tools::meshToUnsignedDistanceField(*xform, mesh.vtx(), mesh.tri(), mesh.quad(), width); - return tools::levelSetRebuild(*udf, /*iso-value=*/dx, width);// UDF -> mesh -> SDF + auto udf = tools::meshToUnsignedDistanceField(*xform, mesh.vtx(), mesh.tri(), mesh.quad(), width);// mesh -> UDF + auto tmp = tools::levelSetRebuild(*udf, /*iso-value=*/dx, width);// UDF -> mesh -> SDF + t_offset += timer.milliseconds(); + return tmp; };// muOffset /* @@ -1759,7 +1767,8 @@ void Tool::soupToLevelSet() };// myUnion */ - auto myConstrainedErosion = [&](GridT &grid, const GridT &gridB)->GridT::Ptr{ + auto myLevelSetDeform = [&](GridT &grid, const GridT &gridB)->GridT::Ptr{ + timer.start(); const float dx = grid.voxelSize()[0]; //if (mParser.verbose) mTimer.restart("erode("+std::to_string(dx)+")"); const int space = 1, time = 1; @@ -1770,25 +1779,35 @@ void Tool::soupToLevelSet() } filter->offset(dx);// erode by dx isSDF = false;// the next CSG operation will mess up the SDF - return tools::csgUnionCopy(grid, gridB); - };// myConstrainedErosion + auto tmp = tools::csgUnionCopy(grid, gridB); + t_erode += timer.milliseconds(); + return tmp; + };// myLevelSetDeform if (mParser.verbose) mTimer.start("Soup -> SDF"); // Main algorithm - float dx = voxel;// lets use the same notation for the voxel size as the paper + float dx = voxel, target_dx = dx / pow(2, nLOD) ;// lets use the same notation for the voxel size as the paper auto grid = myOffset(dx); - grid->setName("soup2ls_"+std::to_string(dx)); + //grid->setName("soup2ls_"+std::to_string(dx)); //mGrid.push_back(grid); - for (int i=0; i target_dx) { dx *= 0.5f;// refinement grid = myUpsample(*grid); auto base = myOffset(dx); - for (int j = 0; jsetName("soup2ls_"+std::to_string(dx)); //mGrid.push_back(grid); } //grid = tools::levelSetRebuild(*grid, /*iso-value=*/0.0f, width);// SDF -> mesh -> SDF + if (dx!=target_dx) std::cerr << "dx = " << dx << ", target_dx = " << target_dx << std::endl; + t_offset /= 1000.0; + t_erode /= 1000.0; + t_upsample /= 1000.0; + std::cerr << "offset = " << t_offset + << "s, erode = " << t_erode + << "s, upsample = " << t_upsample + << "s, total = " << (t_offset + t_erode + t_upsample) << "s\n"; if (grid_name.empty()) grid_name = "soup2ls_" + mesh.getName(); grid->setName(grid_name); From 22b12666f6940d11bd94f9a5bcfc2f0d2de8294f Mon Sep 17 00:00:00 2001 From: Ken Museth Date: Wed, 31 Dec 2025 10:55:15 -0800 Subject: [PATCH 019/275] -soup2ls voxel is now the final vs initial voxel size Signed-off-by: Ken Museth --- openvdb_cmd/vdb_tool/include/Tool.h | 39 ++++++++++++++++------------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/openvdb_cmd/vdb_tool/include/Tool.h b/openvdb_cmd/vdb_tool/include/Tool.h index bcc53e72f9..277a3ca46a 100644 --- a/openvdb_cmd/vdb_tool/include/Tool.h +++ b/openvdb_cmd/vdb_tool/include/Tool.h @@ -1647,7 +1647,7 @@ float Tool::estimateVoxelSize(int maxDim, float halfWidth, int geo_age) throw std::invalid_argument("estimateVoxelSize: invalid maxDim"); } const auto d = bbox.extents()[bbox.maxExtent()];// longest extent of bbox along any coordinate axis - return static_cast(static_cast(d)/static_cast(maxDim - static_cast(2.f * halfWidth))); + return static_cast(static_cast(d)/static_cast(maxDim - static_cast(2.f * halfWidth))); }// Tool::estimateVoxelSize // ============================================================================================================== @@ -1692,28 +1692,31 @@ void Tool::meshToLevelSet() // ============================================================================================================== // vdb_tool -read ~/dev/data/ply/cube.ply -soup2ls dim=64 lod=4 erode=5 -o vdb="*" stdout.vdb | vdb_view -// ./openvdb_cmd/vdb_tool/vdb_tool -i ~/dev/data/obj/Robot_arm.obj -soup2ls voxel=51.2 lod=8 erode=16 -print -o codec=blosc openvdb_10.vdb +// ./openvdb_cmd/vdb_tool/vdb_tool -i ~/dev/data/obj/Robot_arm.obj -soup2ls voxel=0.2 lod=8 erode=16 -print -o codec=blosc openvdb_10.vdb void Tool::soupToLevelSet() { const std::string &name = mParser.getAction().name; OPENVDB_ASSERT(name == "soup2ls"); try { mParser.printAction(); - const int dim = mParser.get("dim");// initial dimension - float voxel = mParser.get("voxel");// initial voxel size + const int dim = mParser.get("dim");// final dimension + float voxel = mParser.get("voxel");// final voxel size const float width = mParser.get("width"); const int geo_age = mParser.get("geo"); const int nErode = mParser.get("erode"); const int nLOD = mParser.get("lod"); const bool keep = mParser.get("keep"); std::string grid_name = mParser.get("name"); - if (voxel == 0.0f) voxel = this->estimateVoxelSize(dim, width, geo_age); + if (voxel == 0.0f) { + voxel = this->estimateVoxelSize(dim, width, geo_age); + std::cerr << "estimated voxel size = " << voxel << " from dim = " << dim << std::endl; + } auto it = this->getGeom(geo_age); const Geometry &mesh = **it; if (mesh.isPoints()) this->warning("Warning: -soup2ls was called on points, not a mesh! Hint: use -points2ls instead!"); bool isSDF = false; util::CpuTimer timer; - double t_offset = 0.0, t_erode = 0.0, t_upsample = 0.0;// all in milliseconds + double t_offset = 0.0, t_deform = 0.0, t_upscale = 0.0;// all in milliseconds auto myUpsample = [&](const GridT &grid)->GridT::Ptr{ timer.start(); @@ -1726,7 +1729,7 @@ void Tool::soupToLevelSet() #else GridT::Ptr outGrid = createLevelSet(dx/2, width); tools::resampleToMatch(grid, *outGrid); - t_upsample += timer.milliseconds(); + t_upscale += timer.milliseconds(); return outGrid; #endif };// myUpsample @@ -1780,18 +1783,18 @@ void Tool::soupToLevelSet() filter->offset(dx);// erode by dx isSDF = false;// the next CSG operation will mess up the SDF auto tmp = tools::csgUnionCopy(grid, gridB); - t_erode += timer.milliseconds(); + t_deform += timer.milliseconds(); return tmp; };// myLevelSetDeform if (mParser.verbose) mTimer.start("Soup -> SDF"); // Main algorithm - float dx = voxel, target_dx = dx / pow(2, nLOD) ;// lets use the same notation for the voxel size as the paper + float dx = voxel * pow(2, nLOD); auto grid = myOffset(dx); //grid->setName("soup2ls_"+std::to_string(dx)); //mGrid.push_back(grid); - while(dx > target_dx) { + while(dx > voxel) { dx *= 0.5f;// refinement grid = myUpsample(*grid); auto base = myOffset(dx); @@ -1800,14 +1803,14 @@ void Tool::soupToLevelSet() //mGrid.push_back(grid); } //grid = tools::levelSetRebuild(*grid, /*iso-value=*/0.0f, width);// SDF -> mesh -> SDF - if (dx!=target_dx) std::cerr << "dx = " << dx << ", target_dx = " << target_dx << std::endl; - t_offset /= 1000.0; - t_erode /= 1000.0; - t_upsample /= 1000.0; - std::cerr << "offset = " << t_offset - << "s, erode = " << t_erode - << "s, upsample = " << t_upsample - << "s, total = " << (t_offset + t_erode + t_upsample) << "s\n"; + if (dx!=voxel) std::cerr << "dx = " << dx << ", expected dx = " << voxel << std::endl; + t_offset /= 1000.0; + t_deform /= 1000.0; + t_upscale /= 1000.0; + std::cerr << "upsample = " << t_upscale + << "s, offset = " << t_offset + << "s, deform = " << t_deform + << "s, total = " << (t_offset + t_deform + t_upscale) << "s\n"; if (grid_name.empty()) grid_name = "soup2ls_" + mesh.getName(); grid->setName(grid_name); From 919e559b7bff456fe8fca2e6611e7a99d053f577 Mon Sep 17 00:00:00 2001 From: Ken Museth Date: Tue, 6 Jan 2026 09:40:07 -0800 Subject: [PATCH 020/275] improved documentation Signed-off-by: Ken Museth --- openvdb_cmd/vdb_tool/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openvdb_cmd/vdb_tool/README.md b/openvdb_cmd/vdb_tool/README.md index b741239611..839ef0d475 100644 --- a/openvdb_cmd/vdb_tool/README.md +++ b/openvdb_cmd/vdb_tool/README.md @@ -13,7 +13,8 @@ This command-line tool, dubbed vdb_tool, can combine any number of the of high-l | **read** | Read mesh, points and level sets as obj, ply, abc, stl, off, pts, xyz, e57, vdb or nvdb files | | **write** | Write a polygon mesh, points or level set as a obj, ply, stl, off, abc or vdb file | | **vdb2points** | Extracts points from a VDB grid | -| **mesh2ls** | Convert a polygon mesh to a narrow-band level set | +| **mesh2ls** | Convert a (water-tight) polygon mesh to a narrow-band level set | +| **soup2ls** | Convert an arbitrary polygon soup to a narrow-band level set | | **points2ls** | Convert points into a narrow-band level set | | **points2vdb** | Converts points into a VDB PointDataGrid | | **iso2ls** | Convert an iso-surface of a scalar field into a level set | From 465c4d2f0c907b0b98b807797cc5be19e525b8e0 Mon Sep 17 00:00:00 2001 From: Ken Museth Date: Tue, 6 Jan 2026 09:42:25 -0800 Subject: [PATCH 021/275] snapshot Signed-off-by: Ken Museth --- openvdb_cmd/vdb_tool/include/Tool.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openvdb_cmd/vdb_tool/include/Tool.h b/openvdb_cmd/vdb_tool/include/Tool.h index 277a3ca46a..8182e4fbea 100644 --- a/openvdb_cmd/vdb_tool/include/Tool.h +++ b/openvdb_cmd/vdb_tool/include/Tool.h @@ -1807,7 +1807,7 @@ void Tool::soupToLevelSet() t_offset /= 1000.0; t_deform /= 1000.0; t_upscale /= 1000.0; - std::cerr << "upsample = " << t_upscale + std::cerr << "\nupsample = " << t_upscale << "s, offset = " << t_offset << "s, deform = " << t_deform << "s, total = " << (t_offset + t_deform + t_upscale) << "s\n"; From a8525b978a7fbe9b438f95504c80ac27035712a3 Mon Sep 17 00:00:00 2001 From: Ken Museth Date: Wed, 7 Jan 2026 09:50:29 -0800 Subject: [PATCH 022/275] improved Geometry::print Signed-off-by: Ken Museth --- openvdb_cmd/vdb_tool/include/Geometry.h | 6 ++- openvdb_cmd/vdb_tool/include/Tool.h | 53 +++++++------------------ 2 files changed, 19 insertions(+), 40 deletions(-) diff --git a/openvdb_cmd/vdb_tool/include/Geometry.h b/openvdb_cmd/vdb_tool/include/Geometry.h index 7453dfd45d..1098760cce 100644 --- a/openvdb_cmd/vdb_tool/include/Geometry.h +++ b/openvdb_cmd/vdb_tool/include/Geometry.h @@ -1028,7 +1028,11 @@ void Geometry::readNVDB(const std::string&) void Geometry::print(size_t n, std::ostream& os) const { - os << "vtx = " << mVtx.size() << ", rbg = " << mRGB.size() << ", tri = " << mTri.size() << ", quad = " << mQuad.size()<< ", bbox=" << this->bbox(); + os << "vtx = " << mVtx.size(); + if (auto n = mRGB.size()) os << ", rbg = " << n; + if (auto n = mTri.size()) os << ", tri = " << n; + if (auto n = mQuad.size()) os << ", quad = " << n; + os << ", bbox=" << this->bbox(); if (size_t m = std::min(n, mVtx.size())) { os << std::endl; for (size_t i=0; iGridT::Ptr{ timer.start(); - //if (mParser.verbose) mTimer.restart("offset("+std::to_string(dx)+")"); auto xform = math::Transform::createLinearTransform(dx); auto udf = tools::meshToUnsignedDistanceField(*xform, mesh.vtx(), mesh.tri(), mesh.quad(), width);// mesh -> UDF auto tmp = tools::levelSetRebuild(*udf, /*iso-value=*/dx, width);// UDF -> mesh -> SDF @@ -1744,79 +1743,55 @@ void Tool::soupToLevelSet() return tmp; };// muOffset - /* - auto myErode = [&](GridT &grid)->void{ - const float dx = grid.voxelSize()[0]; - //if (mParser.verbose) mTimer.restart("erode("+std::to_string(dx)+")"); - const int space = 1, time = 1; - auto filter = this->createFilter(grid, space, time); - if (isSDF == false) { - filter->normalize(); - isSDF = true; - } - filter->offset(dx);// erode by dx - };// myErode - - auto myUnion = [&](GridT &gridA, GridT &gridB){ - //if (mParser.verbose) mTimer.restart("union("+std::to_string(gridA.voxelSize()[0])+")"); - //tools::csgUnion(gridA, gridB, true);// overwrites A and cannibalizes B, and prune - //return tools::sdfToSdf(gridA);// re-normalize using fast sweeping - //return tools::levelSetRebuild(gridA, 0.0f, width);// SDF -> mesh -> SDF - //const int space = 1, time = 1; - //auto filter = this->createFilter(gridA, space, time); - //filter->normalize();// fast - isSDF = false; - return tools::csgUnionCopy(gridA, gridB); - };// myUnion - */ - auto myLevelSetDeform = [&](GridT &grid, const GridT &gridB)->GridT::Ptr{ timer.start(); const float dx = grid.voxelSize()[0]; - //if (mParser.verbose) mTimer.restart("erode("+std::to_string(dx)+")"); const int space = 1, time = 1; auto filter = this->createFilter(grid, space, time); +#if 1 if (isSDF == false) { filter->normalize(); isSDF = true; } filter->offset(dx);// erode by dx - isSDF = false;// the next CSG operation will mess up the SDF auto tmp = tools::csgUnionCopy(grid, gridB); + isSDF = false;// the CSG operation messed up the SDF +#else + filter->offset(dx);// erode by dx + auto tmp = tools::csgUnionCopy(grid, gridB); + filter = this->createFilter(*tmp, space, time); + filter->normalize(); +#endif t_deform += timer.milliseconds(); return tmp; };// myLevelSetDeform - if (mParser.verbose) mTimer.start("Soup -> SDF"); + //if (mParser.verbose) mTimer.start("Soup -> SDF"); // Main algorithm float dx = voxel * pow(2, nLOD); auto grid = myOffset(dx); - //grid->setName("soup2ls_"+std::to_string(dx)); - //mGrid.push_back(grid); while(dx > voxel) { dx *= 0.5f;// refinement grid = myUpsample(*grid); auto base = myOffset(dx); for (int j = 0; jsetName("soup2ls_"+std::to_string(dx)); - //mGrid.push_back(grid); } //grid = tools::levelSetRebuild(*grid, /*iso-value=*/0.0f, width);// SDF -> mesh -> SDF if (dx!=voxel) std::cerr << "dx = " << dx << ", expected dx = " << voxel << std::endl; t_offset /= 1000.0; t_deform /= 1000.0; t_upscale /= 1000.0; - std::cerr << "\nupsample = " << t_upscale - << "s, offset = " << t_offset - << "s, deform = " << t_deform - << "s, total = " << (t_offset + t_deform + t_upscale) << "s\n"; + std::cerr << "upscale:\t" << t_upscale + << "s\noffset: \t" << t_offset + << "s\ndeform: \t" << t_deform + << "s\ntotal: \t" << (t_offset + t_deform + t_upscale) << "s\n"; if (grid_name.empty()) grid_name = "soup2ls_" + mesh.getName(); grid->setName(grid_name); mGrid.push_back(grid); if (!keep) mGeom.erase(std::next(it).base()); - if (mParser.verbose) mTimer.stop(); + //if (mParser.verbose) mTimer.stop(); } catch (const std::exception& e) { throw std::invalid_argument(name+": "+e.what()); } From 29b5dff21417c473f7142b0e88bb1855c600b352 Mon Sep 17 00:00:00 2001 From: Dan Bailey Date: Mon, 24 Nov 2025 12:30:39 -0800 Subject: [PATCH 023/275] Fix a bug in PointIndexGrid when reading without a CoordBBox Signed-off-by: Dan Bailey --- openvdb/openvdb/tools/PointIndexGrid.h | 4 ++ .../openvdb/unittest/TestPointIndexGrid.cc | 57 +++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/openvdb/openvdb/tools/PointIndexGrid.h b/openvdb/openvdb/tools/PointIndexGrid.h index 463a07039f..7eddcd71c3 100644 --- a/openvdb/openvdb/tools/PointIndexGrid.h +++ b/openvdb/openvdb/tools/PointIndexGrid.h @@ -1728,6 +1728,10 @@ PointIndexLeafNode::readBuffers(std::istream& is, bool fromHalf) mIndices.resize(size_t(numIndices)); is.read(reinterpret_cast(mIndices.data()), numIndices * sizeof(T)); + + // Reserved for future use + Index64 auxDataBytes = Index64(0); + is.read(reinterpret_cast(&auxDataBytes), sizeof(Index64)); } diff --git a/openvdb/openvdb/unittest/TestPointIndexGrid.cc b/openvdb/openvdb/unittest/TestPointIndexGrid.cc index ff37f35c68..1229e05c0b 100644 --- a/openvdb/openvdb/unittest/TestPointIndexGrid.cc +++ b/openvdb/openvdb/unittest/TestPointIndexGrid.cc @@ -216,6 +216,63 @@ TEST_F(TestPointIndexGrid, testPointIndexGrid) } +TEST_F(TestPointIndexGrid, testPointIndexGridIO) +{ + // Register grid and transform. + openvdb::initialize(); + + const float voxelSize = 1.0f; + const openvdb::math::Transform::Ptr transform = + openvdb::math::Transform::createLinearTransform(voxelSize); + + // generate points + + std::vector points; + unittest_util::genPoints(10, points); + + PointList pointList(points); + + + // construct data structure + typedef openvdb::tools::PointIndexGrid PointIndexGrid; + + PointIndexGrid::Ptr pointGridPtr = + openvdb::tools::createPointIndexGrid(pointList, *transform); + + // test I/O + + pointGridPtr->setName("points"); + + openvdb::GridPtrVec grids; + grids.push_back(pointGridPtr); + + // Write the vdb out to a file. + openvdb::io::File vdbfile("pointidx.vdb2"); + vdbfile.write(grids); + + openvdb::io::File vdbfile2("pointidx.vdb2"); + + vdbfile2.open(); + + EXPECT_TRUE(vdbfile2.isOpen()); + + openvdb::tools::PointIndexGrid::Ptr pointGridPtr2 = openvdb::gridPtrCast(vdbfile2.readGrid("points")); + + EXPECT_TRUE(pointGridPtr2.get() != nullptr); + EXPECT_EQ(pointGridPtr->tree().activeVoxelCount(), pointGridPtr2->tree().activeVoxelCount()); + EXPECT_TRUE(openvdb::tools::isValidPartition(pointList, *pointGridPtr2)); + + // Clear registries. + openvdb::GridBase::clearRegistry(); + openvdb::Metadata::clearRegistry(); + openvdb::math::MapRegistry::clear(); + + vdbfile2.close(); + + remove("pointidx.vdb2"); +} + + TEST_F(TestPointIndexGrid, testPointIndexFilter) { // generate points From f476145a89fa9c48a65c01c41b5b5a68909c60b7 Mon Sep 17 00:00:00 2001 From: Dan Bailey Date: Wed, 7 Jan 2026 12:02:16 -0800 Subject: [PATCH 024/275] Add changes Signed-off-by: Dan Bailey --- pendingchanges/fixpointindexio.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 pendingchanges/fixpointindexio.txt diff --git a/pendingchanges/fixpointindexio.txt b/pendingchanges/fixpointindexio.txt new file mode 100644 index 0000000000..f22ed21d70 --- /dev/null +++ b/pendingchanges/fixpointindexio.txt @@ -0,0 +1,2 @@ +Bug Fixes: + - Fixed a bug in the I/O for the Point Index Grid when reading without providing a CoordBBox From 06a38ad3fb48c48d8d2041d4914bc0d0822bc022 Mon Sep 17 00:00:00 2001 From: Ken Museth Date: Wed, 7 Jan 2026 12:35:26 -0800 Subject: [PATCH 025/275] introduced level dependent erosion count Signed-off-by: Ken Museth --- openvdb_cmd/vdb_tool/include/Tool.h | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/openvdb_cmd/vdb_tool/include/Tool.h b/openvdb_cmd/vdb_tool/include/Tool.h index c888239c87..33769a44f4 100644 --- a/openvdb_cmd/vdb_tool/include/Tool.h +++ b/openvdb_cmd/vdb_tool/include/Tool.h @@ -1714,15 +1714,13 @@ void Tool::soupToLevelSet() auto it = this->getGeom(geo_age); const Geometry &mesh = **it; if (mesh.isPoints()) this->warning("Warning: -soup2ls was called on points, not a mesh! Hint: use -points2ls instead!"); - bool isSDF = false; util::CpuTimer timer; double t_offset = 0.0, t_deform = 0.0, t_upscale = 0.0;// all in milliseconds + bool isGridSDF = true; auto myUpsample = [&](const GridT &grid)->GridT::Ptr{ timer.start(); const float dx = grid.voxelSize()[0]; - isSDF = true; - //if (mParser.verbose) mTimer.restart("upsample("+std::to_string(dx)+")"); #if 0 auto xform = math::Transform::createLinearTransform(dx/2);// upsample return tools::levelSetRebuild(grid, 0.0f, width, xform.get());// SDF -> mesh -> SDF @@ -1730,6 +1728,7 @@ void Tool::soupToLevelSet() GridT::Ptr outGrid = createLevelSet(dx/2, width); tools::resampleToMatch(grid, *outGrid); t_upscale += timer.milliseconds(); + //isGridSDF = true; return outGrid; #endif };// myUpsample @@ -1749,13 +1748,9 @@ void Tool::soupToLevelSet() const int space = 1, time = 1; auto filter = this->createFilter(grid, space, time); #if 1 - if (isSDF == false) { - filter->normalize(); - isSDF = true; - } + if (isGridSDF == false) filter->normalize(); filter->offset(dx);// erode by dx auto tmp = tools::csgUnionCopy(grid, gridB); - isSDF = false;// the CSG operation messed up the SDF #else filter->offset(dx);// erode by dx auto tmp = tools::csgUnionCopy(grid, gridB); @@ -1763,6 +1758,7 @@ void Tool::soupToLevelSet() filter->normalize(); #endif t_deform += timer.milliseconds(); + isGridSDF = false;// the CSG operation messed up the SDF return tmp; };// myLevelSetDeform @@ -1770,12 +1766,16 @@ void Tool::soupToLevelSet() // Main algorithm float dx = voxel * pow(2, nLOD); + const float factor = float(nErode-1)/(nLOD-1); auto grid = myOffset(dx); - while(dx > voxel) { + for (int i=0; i voxel) { dx *= 0.5f;// refinement grid = myUpsample(*grid); auto base = myOffset(dx); - for (int j = 0; j mesh -> SDF if (dx!=voxel) std::cerr << "dx = " << dx << ", expected dx = " << voxel << std::endl; From af49e45b58850766ad5ea8b3bca525ff67416d8e Mon Sep 17 00:00:00 2001 From: Ken Museth Date: Thu, 8 Jan 2026 21:39:52 -0800 Subject: [PATCH 026/275] improved algorith my MultiResGrid Signed-off-by: Ken Museth --- openvdb/openvdb/tools/MultiResGrid.h | 8 ++------ openvdb_cmd/vdb_tool/include/Tool.h | 24 +++++++++++++++++++++--- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/openvdb/openvdb/tools/MultiResGrid.h b/openvdb/openvdb/tools/MultiResGrid.h index 10eee0e1b4..fd1cae8a25 100644 --- a/openvdb/openvdb/tools/MultiResGrid.h +++ b/openvdb/openvdb/tools/MultiResGrid.h @@ -5,10 +5,6 @@ /// /// @author Ken Museth /// -/// @warning This class is fairly new and as such has not seen a lot of -/// use in production. Please report any issues or request for new -/// features directly to ken.museth@dreamworks.com. -/// /// @brief Multi-resolution grid that contains LoD sequences of trees /// with powers of two refinements. /// @@ -19,7 +15,7 @@ /// @note Prolongation means interpolation from coarse -> fine /// @note Restriction means interpolation (or remapping) from fine -> coarse /// -/// @todo Add option to define the level of the input grid (currenlty +/// @todo Add option to define the level of the input grid (currently /// 0) so as to allow for super-sampling. #ifndef OPENVDB_TOOLS_MULTIRESGRID_HAS_BEEN_INCLUDED @@ -692,7 +688,7 @@ struct MultiResGrid::MaskOp { OPENVDB_ASSERT( coarseTree.empty() ); - // Create Mask of restruction performed on fineTree + // Create Mask of restriction performed on fineTree MaskT mask(fineTree, false, true, TopologyCopy() ); // Multi-threaded dilation which also linearizes the tree to leaf nodes diff --git a/openvdb_cmd/vdb_tool/include/Tool.h b/openvdb_cmd/vdb_tool/include/Tool.h index 33769a44f4..5302e08213 100644 --- a/openvdb_cmd/vdb_tool/include/Tool.h +++ b/openvdb_cmd/vdb_tool/include/Tool.h @@ -1715,7 +1715,7 @@ void Tool::soupToLevelSet() const Geometry &mesh = **it; if (mesh.isPoints()) this->warning("Warning: -soup2ls was called on points, not a mesh! Hint: use -points2ls instead!"); util::CpuTimer timer; - double t_offset = 0.0, t_deform = 0.0, t_upscale = 0.0;// all in milliseconds + double t_offset = 0.0, t_deform = 0.0, t_upscale = 0.0, t_multigrid = 0.0;// all in milliseconds bool isGridSDF = true; auto myUpsample = [&](const GridT &grid)->GridT::Ptr{ @@ -1728,7 +1728,7 @@ void Tool::soupToLevelSet() GridT::Ptr outGrid = createLevelSet(dx/2, width); tools::resampleToMatch(grid, *outGrid); t_upscale += timer.milliseconds(); - //isGridSDF = true; + isGridSDF = true; return outGrid; #endif };// myUpsample @@ -1767,16 +1767,34 @@ void Tool::soupToLevelSet() // Main algorithm float dx = voxel * pow(2, nLOD); const float factor = float(nErode-1)/(nLOD-1); + +#if 0// old method + auto grid = myOffset(dx); for (int i=0; i voxel) { dx *= 0.5f;// refinement grid = myUpsample(*grid); auto base = myOffset(dx); - const int end = nErode - int(i*factor); + const int end = nErode;// - int(i*factor); + std::cerr << "Level: " << i << ", erosions: " << end << std::endl; + for (int j = 0; j ms(nLOD, myOffset(voxel)); + t_offset = timer.milliseconds(); + auto grid = ms.grid(nLOD-1); + for (int i=0; i mesh -> SDF if (dx!=voxel) std::cerr << "dx = " << dx << ", expected dx = " << voxel << std::endl; t_offset /= 1000.0; From 7806c8ecc6bd0d8ba298d1c0f95a4028b03b9221 Mon Sep 17 00:00:00 2001 From: Dan Bailey Date: Fri, 21 Nov 2025 11:57:34 -0800 Subject: [PATCH 027/275] Extend visitNodesDepthFirst() to be able to visit nodes bottom-up during traversal Signed-off-by: Dan Bailey --- openvdb/openvdb/tools/NodeVisitor.h | 18 +++++---- openvdb/openvdb/unittest/TestNodeVisitor.cc | 43 +++++++++++++++++++++ 2 files changed, 54 insertions(+), 7 deletions(-) diff --git a/openvdb/openvdb/tools/NodeVisitor.h b/openvdb/openvdb/tools/NodeVisitor.h index 5bf436f9c8..526cea97b1 100644 --- a/openvdb/openvdb/tools/NodeVisitor.h +++ b/openvdb/openvdb/tools/NodeVisitor.h @@ -30,6 +30,9 @@ namespace tools { /// @param op user-supplied functor, see examples for interface details. /// @param idx optional offset to start sequential node indexing from a /// non-zero index. +/// @param topDown if true, visit parent nodes before their children +/// (pre-order traversal). If false, visit children before +/// their parent nodes (post-order traversal). Defaults to true. /// /// @warning This method is single-threaded. Use the NodeManager or /// DynamicNodeManager for much greater threaded performance. @@ -173,7 +176,7 @@ namespace tools { /// /// @endcode template -size_t visitNodesDepthFirst(TreeT& tree, OpT& op, size_t idx = 0); +size_t visitNodesDepthFirst(TreeT& tree, OpT& op, size_t idx = 0, bool topDown = true); /// @brief Visit all nodes that are downstream of a specific node in @@ -199,14 +202,15 @@ struct DepthFirstNodeVisitor using ChildNodeType = typename CopyConstness::Type; template - static size_t visit(NodeT& node, OpT& op, size_t idx = 0) + static size_t visit(NodeT& node, OpT& op, size_t idx = 0, bool topDown = true) { size_t offset = 0; - op(node, idx + offset++); + if (topDown) op(node, idx + offset++); for (auto iter = node.beginChildOn(); iter; ++iter) { offset += DepthFirstNodeVisitor::visit( - *iter, op, idx + offset); + *iter, op, idx + offset, topDown); } + if (!topDown) op(node, idx + offset++); return offset; } }; @@ -217,7 +221,7 @@ template struct DepthFirstNodeVisitor { template - static size_t visit(NodeT& node, OpT& op, size_t idx = 0) + static size_t visit(NodeT& node, OpT& op, size_t idx = 0, bool /*topDown*/ = true) { op(node, idx); return size_t(1); @@ -226,12 +230,12 @@ struct DepthFirstNodeVisitor template -size_t visitNodesDepthFirst(TreeT& tree, OpT& op, size_t idx) +size_t visitNodesDepthFirst(TreeT& tree, OpT& op, size_t idx, bool topDown) { using NonConstRootNodeType = typename TreeT::RootNodeType; using RootNodeType = typename CopyConstness::Type; - return DepthFirstNodeVisitor::visit(tree.root(), op, idx); + return DepthFirstNodeVisitor::visit(tree.root(), op, idx, topDown); } diff --git a/openvdb/openvdb/unittest/TestNodeVisitor.cc b/openvdb/openvdb/unittest/TestNodeVisitor.cc index e24bf72bb2..28e548e436 100644 --- a/openvdb/openvdb/unittest/TestNodeVisitor.cc +++ b/openvdb/openvdb/unittest/TestNodeVisitor.cc @@ -286,3 +286,46 @@ TEST_F(TestNodeVisitor, testOffset) OffsetByLevelOp byLevelOp(3.0f); tools::visitNodesDepthFirst(grid->tree(), byLevelOp); } + + +struct RecordLevelOrderOp +{ + template + void operator()(const NodeT&, size_t) + { + const openvdb::Index level = NodeT::LEVEL; + levels.push_back(level); + } + + std::vector levels; +}; // struct RecordLevelOrderOp + + +TEST_F(TestNodeVisitor, testTopDownBottomUp) +{ + using namespace openvdb; + + FloatGrid::Ptr grid = tools::createLevelSetCube(/*scale=*/10.0f); + + // Visit with topDown=true (pre-order: parent before children) + RecordLevelOrderOp topDownOp; + tools::visitNodesDepthFirst(grid->tree(), topDownOp, 0, true); + + // Visit with topDown=false (post-order: children before parent) + RecordLevelOrderOp bottomUpOp; + tools::visitNodesDepthFirst(grid->tree(), bottomUpOp, 0, false); + + // Both should visit the same number of nodes + EXPECT_EQ(topDownOp.levels.size(), bottomUpOp.levels.size()); + EXPECT_GT(topDownOp.levels.size(), size_t(0)); + + // Top-down: first element should be the root (highest level) + const Index rootLevel = FloatTree::RootNodeType::LEVEL; + EXPECT_EQ(topDownOp.levels.front(), rootLevel); + + // Bottom-up: last element should be the root (highest level) + EXPECT_EQ(bottomUpOp.levels.back(), rootLevel); + + // The two sequences should not be identical (different traversal order) + EXPECT_NE(topDownOp.levels, bottomUpOp.levels); +} From 4f5a90224e5de09d4e285a151dfb4cfe070793f5 Mon Sep 17 00:00:00 2001 From: Dan Bailey Date: Fri, 9 Jan 2026 16:56:02 -0800 Subject: [PATCH 028/275] Add changes Signed-off-by: Dan Bailey --- pendingchanges/nodevisitorbottomup.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 pendingchanges/nodevisitorbottomup.txt diff --git a/pendingchanges/nodevisitorbottomup.txt b/pendingchanges/nodevisitorbottomup.txt new file mode 100644 index 0000000000..aa0e6c265c --- /dev/null +++ b/pendingchanges/nodevisitorbottomup.txt @@ -0,0 +1,3 @@ +OpenVDB: + Improvements: + - Extend tools::visitNodesDepthFirst() to be able to perform bottom-up traversal in addition to top-down traversal. From 8f308369364aeaa604c7d6a73a23c2c11c441067 Mon Sep 17 00:00:00 2001 From: Dan Bailey Date: Fri, 9 Jan 2026 20:30:11 -0800 Subject: [PATCH 029/275] Disable CI daily scheduled builds Signed-off-by: Dan Bailey --- .github/workflows/ax.yml | 3 --- .github/workflows/build.yml | 7 ++----- .github/workflows/houdini.yml | 3 --- .github/workflows/nanovdb.yml | 3 --- 4 files changed, 2 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ax.yml b/.github/workflows/ax.yml index ce4d496d3f..9217d97bc2 100644 --- a/.github/workflows/ax.yml +++ b/.github/workflows/ax.yml @@ -30,9 +30,6 @@ on: - 'fvdb/**' - 'pendingchanges/**' - '**.md' - schedule: - # run this workflow every day 7am UTC - - cron: '0 7 * * *' workflow_dispatch: inputs: type: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 299aaba7be..1f74904158 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,9 +32,6 @@ on: - 'fvdb/**' - 'pendingchanges/**' - '**.md' - schedule: - # run this workflow every day 7am UTC - - cron: '0 7 * * *' workflow_dispatch: inputs: type: @@ -70,7 +67,7 @@ jobs: strategy: matrix: config: - - { cxx: clang++, image: '2026', abi: '13', build: 'Release', cmake: '-DOPENVDB_USE_FUTURE_ABI_13=ON' } + - { cxx: clang++, image: '2026', abi: '13', build: 'Release', cmake: '-DOPENVDB_USE_FUTURE_ABI_13=ON -DOPENVDB_ENABLE_ASSERTS=ON' } - { cxx: g++, image: '2026', abi: '13', build: 'Release', cmake: '-DOPENVDB_USE_FUTURE_ABI_13=ON' } - { cxx: clang++, image: '2026', abi: '13', build: 'Debug', cmake: '-DOPENVDB_USE_FUTURE_ABI_13=ON' } - { cxx: clang++, image: '2025-clang19.1', abi: '12', build: 'Release', cmake: '' } @@ -110,7 +107,7 @@ jobs: \" - name: test # Skip Debug on commits as they take a while. - if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || matrix.config.build == 'Release' + if: github.event_name == 'workflow_dispatch' || matrix.config.build == 'Release' run: | cd build && ctest -V cd - && ./ci/test_install.sh diff --git a/.github/workflows/houdini.yml b/.github/workflows/houdini.yml index c5f275e9b1..00330e7fa1 100644 --- a/.github/workflows/houdini.yml +++ b/.github/workflows/houdini.yml @@ -28,9 +28,6 @@ on: - 'fvdb/**' - 'pendingchanges/**' - '**.md' - schedule: - # run this workflow every day 7am UTC - - cron: '0 7 * * *' workflow_dispatch: # Allow subsequent pushes to the same PR or REF to cancel any previous jobs. diff --git a/.github/workflows/nanovdb.yml b/.github/workflows/nanovdb.yml index d66266735b..a4366f8ce3 100644 --- a/.github/workflows/nanovdb.yml +++ b/.github/workflows/nanovdb.yml @@ -30,9 +30,6 @@ on: - 'fvdb/**' - 'pendingchanges/**' - '**.md' - schedule: - # run this workflow every day 7am UTC - - cron: '0 7 * * *' workflow_dispatch: inputs: type: From 798ef005ea493118ec800c42c23dfb067c38782c Mon Sep 17 00:00:00 2001 From: Ken Museth Date: Sun, 11 Jan 2026 13:04:20 -0800 Subject: [PATCH 030/275] alternative algorithm that avoids LOD offsets from the original mesh Signed-off-by: Ken Museth --- openvdb_cmd/vdb_tool/include/Tool.h | 72 +++++++++++++++++++++++------ 1 file changed, 58 insertions(+), 14 deletions(-) diff --git a/openvdb_cmd/vdb_tool/include/Tool.h b/openvdb_cmd/vdb_tool/include/Tool.h index 5302e08213..12cbf85860 100644 --- a/openvdb_cmd/vdb_tool/include/Tool.h +++ b/openvdb_cmd/vdb_tool/include/Tool.h @@ -253,6 +253,7 @@ class Tool inline auto getGeom(size_t age) const; Geometry::Ptr mesherToGeometry(tools::VolumeToMesh&) const; + Geometry::Ptr volumeToGeometry(const GridT &grid, float isoValue=0.0f, float adaptivity=0.0f) const; };// Tool class @@ -1712,8 +1713,9 @@ void Tool::soupToLevelSet() std::cerr << "estimated voxel size = " << voxel << " from dim = " << dim << std::endl; } auto it = this->getGeom(geo_age); - const Geometry &mesh = **it; - if (mesh.isPoints()) this->warning("Warning: -soup2ls was called on points, not a mesh! Hint: use -points2ls instead!"); + //const Geometry &mesh = **it; + Geometry::Ptr mesh = *it; + if (mesh->isPoints()) this->warning("Warning: -soup2ls was called on points, not a mesh! Hint: use -points2ls instead!"); util::CpuTimer timer; double t_offset = 0.0, t_deform = 0.0, t_upscale = 0.0, t_multigrid = 0.0;// all in milliseconds bool isGridSDF = true; @@ -1733,14 +1735,14 @@ void Tool::soupToLevelSet() #endif };// myUpsample - auto myOffset = [&](float dx)->GridT::Ptr{ + auto myOffset = [&](const Geometry &mesh, float dx)->GridT::Ptr{ timer.start(); auto xform = math::Transform::createLinearTransform(dx); auto udf = tools::meshToUnsignedDistanceField(*xform, mesh.vtx(), mesh.tri(), mesh.quad(), width);// mesh -> UDF auto tmp = tools::levelSetRebuild(*udf, /*iso-value=*/dx, width);// UDF -> mesh -> SDF t_offset += timer.milliseconds(); return tmp; - };// muOffset + };// myOffset auto myLevelSetDeform = [&](GridT &grid, const GridT &gridB)->GridT::Ptr{ timer.start(); @@ -1762,7 +1764,7 @@ void Tool::soupToLevelSet() return tmp; };// myLevelSetDeform - //if (mParser.verbose) mTimer.start("Soup -> SDF"); + if (mParser.verbose) mTimer.start("Soup -> SDF"); // Main algorithm float dx = voxel * pow(2, nLOD); @@ -1781,17 +1783,50 @@ void Tool::soupToLevelSet() for (int j = 0; j ms(nLOD, myOffset(voxel)); - t_offset = timer.milliseconds(); auto grid = ms.grid(nLOD-1); - for (int i=0; ivoxelSize()[0]; + std::cerr << "dx = " << dx << std::endl; + for (int i=0; i offsets = {grid};// finest grid + mGrid.push_back(grid); + //std::cerr << "final voxel size: " << voxel << " at level 0" << std::endl; + //dx = offsets.back()->voxelSize()[0]; + for (int level = 1; level <= nLOD; ++level) { + mesh = this->volumeToGeometry(*offsets.back()); + grid = myOffset(*mesh, 2*offsets.back()->voxelSize()[0]); + grid->setName("offset_level_" + std::to_string(level)); + //dx *= 2; + //std::cerr << "generating offset grid with voxel size: " << dx << " at level: " << level << std::endl; + offsets.push_back(grid); + mGrid.push_back(grid); + } + grid = offsets.back();// coarse grid + dx = grid->voxelSize()[0]; + std::cerr << "dx = " << dx << std::endl; + for (int i=0; i<=nLOD; ++i) std::cerr << "Level: " << i << ", dx = " << offsets[i]->voxelSize()[0] << std::endl; + //ms.print(); + t_offset = timer.milliseconds(); + for (int level = nLOD - 1; level >= 0; --level) { + //dx *= 0.5f;// refinement + grid = myUpsample(*grid); + std::cerr << "Level: " << level << " offset = " << offsets[level]->voxelSize()[0] << " grid = " << grid->voxelSize()[0] << std::endl; + for (int iter = 0; iter < nErode; ++iter) grid = myLevelSetDeform(*grid, *offsets[level]); } #endif @@ -1805,11 +1840,11 @@ void Tool::soupToLevelSet() << "s\ndeform: \t" << t_deform << "s\ntotal: \t" << (t_offset + t_deform + t_upscale) << "s\n"; - if (grid_name.empty()) grid_name = "soup2ls_" + mesh.getName(); + if (grid_name.empty()) grid_name = "soup2ls_" + mesh->getName(); grid->setName(grid_name); mGrid.push_back(grid); if (!keep) mGeom.erase(std::next(it).base()); - //if (mParser.verbose) mTimer.stop(); + if (mParser.verbose) mTimer.stop(); } catch (const std::exception& e) { throw std::invalid_argument(name+": "+e.what()); } @@ -3044,6 +3079,15 @@ Geometry::Ptr Tool::mesherToGeometry(tools::VolumeToMesh &mesher) const return geom; }// Tool::mesherToGeometry +// ============================================================================================================== + +Geometry::Ptr Tool::volumeToGeometry(const GridT &grid, float isoValue, float adaptivity) const +{ + tools::VolumeToMesh mesher(isoValue, adaptivity, /*relaxDisorientedTriangles*/true); + mesher(grid); + return this->mesherToGeometry(mesher); +} + } // namespace vdb_tool } // namespace OPENVDB_VERSION_NAME } // namespace openvdb From b0d291eb761d3ebedc29ad783da217d8abb3ead0 Mon Sep 17 00:00:00 2001 From: Ken Museth Date: Sun, 11 Jan 2026 14:19:16 -0800 Subject: [PATCH 031/275] snapshot Signed-off-by: Ken Museth --- openvdb_cmd/vdb_tool/include/Tool.h | 49 +++++++++++++++-------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/openvdb_cmd/vdb_tool/include/Tool.h b/openvdb_cmd/vdb_tool/include/Tool.h index 12cbf85860..8ac3a69f3b 100644 --- a/openvdb_cmd/vdb_tool/include/Tool.h +++ b/openvdb_cmd/vdb_tool/include/Tool.h @@ -1801,37 +1801,40 @@ void Tool::soupToLevelSet() } */ timer.start(); - auto grid = myOffset(*mesh, voxel); - grid->setName("offset_level_0"); - std::vector offsets = {grid};// finest grid - mGrid.push_back(grid); - //std::cerr << "final voxel size: " << voxel << " at level 0" << std::endl; - //dx = offsets.back()->voxelSize()[0]; - for (int level = 1; level <= nLOD; ++level) { - mesh = this->volumeToGeometry(*offsets.back()); - grid = myOffset(*mesh, 2*offsets.back()->voxelSize()[0]); + + std::vector offsets;// = {grid};// finest grid + std::cerr << std::endl; + dx = voxel;// final desired voxel size + for (int level = 0; level <= nLOD; ++level) {// both inclusive + std::cerr << "Generating offset at level " << level << " with dx = " << dx << std::endl; + auto grid = myOffset(*mesh, dx); grid->setName("offset_level_" + std::to_string(level)); - //dx *= 2; - //std::cerr << "generating offset grid with voxel size: " << dx << " at level: " << level << std::endl; offsets.push_back(grid); - mGrid.push_back(grid); - } - grid = offsets.back();// coarse grid - dx = grid->voxelSize()[0]; - std::cerr << "dx = " << dx << std::endl; - for (int i=0; i<=nLOD; ++i) std::cerr << "Level: " << i << ", dx = " << offsets[i]->voxelSize()[0] << std::endl; - //ms.print(); + dx *= 2.0f; + }// loop from fine to coarse voxel sizes + + offsets.clear(); + std::cerr << std::endl; + dx = voxel;// final desired voxel size + for (int level = 0; level <= nLOD; ++level) {// both inclusive + std::cerr << "Generating offset at level " << level << " with dx = " << dx << std::endl; + if (level) mesh = this->volumeToGeometry(*offsets.back()); + auto grid = myOffset(*mesh, dx); + grid->setName("offset_level_" + std::to_string(level)); + offsets.push_back(grid); + dx *= 2.0f; + }// loop from fine to coarse voxel sizes + //for (auto p : offsets) mGrid.push_back(p);// cache offset grid for debugging + auto grid = offsets.back();// coarse grid t_offset = timer.milliseconds(); - for (int level = nLOD - 1; level >= 0; --level) { - //dx *= 0.5f;// refinement + for (int level = nLOD-1; level >= 0; --level) { grid = myUpsample(*grid); - std::cerr << "Level: " << level << " offset = " << offsets[level]->voxelSize()[0] << " grid = " << grid->voxelSize()[0] << std::endl; + std::cerr << "Level: " << level << " dx of offset = " << offsets[level]->voxelSize()[0] << " dx of grid = " << grid->voxelSize()[0] << std::endl; for (int iter = 0; iter < nErode; ++iter) grid = myLevelSetDeform(*grid, *offsets[level]); - } + }// loop from coarse to fine voxel sizes #endif //grid = tools::levelSetRebuild(*grid, /*iso-value=*/0.0f, width);// SDF -> mesh -> SDF - if (dx!=voxel) std::cerr << "dx = " << dx << ", expected dx = " << voxel << std::endl; t_offset /= 1000.0; t_deform /= 1000.0; t_upscale /= 1000.0; From 09fbad609445d7e82693d59e10806c21d80b4a2f Mon Sep 17 00:00:00 2001 From: Ken Museth Date: Sun, 11 Jan 2026 14:20:27 -0800 Subject: [PATCH 032/275] snapshot Signed-off-by: Ken Museth --- openvdb_cmd/vdb_tool/include/Tool.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openvdb_cmd/vdb_tool/include/Tool.h b/openvdb_cmd/vdb_tool/include/Tool.h index 8ac3a69f3b..aa25663ad4 100644 --- a/openvdb_cmd/vdb_tool/include/Tool.h +++ b/openvdb_cmd/vdb_tool/include/Tool.h @@ -1808,7 +1808,7 @@ void Tool::soupToLevelSet() for (int level = 0; level <= nLOD; ++level) {// both inclusive std::cerr << "Generating offset at level " << level << " with dx = " << dx << std::endl; auto grid = myOffset(*mesh, dx); - grid->setName("offset_level_" + std::to_string(level)); + grid->setName("old_offset_level_" + std::to_string(level)); offsets.push_back(grid); dx *= 2.0f; }// loop from fine to coarse voxel sizes @@ -1820,7 +1820,7 @@ void Tool::soupToLevelSet() std::cerr << "Generating offset at level " << level << " with dx = " << dx << std::endl; if (level) mesh = this->volumeToGeometry(*offsets.back()); auto grid = myOffset(*mesh, dx); - grid->setName("offset_level_" + std::to_string(level)); + grid->setName("new_offset_level_" + std::to_string(level)); offsets.push_back(grid); dx *= 2.0f; }// loop from fine to coarse voxel sizes From 12a79ee400a499f8d6c9df9100c1d5fb2629b958 Mon Sep 17 00:00:00 2001 From: Ken Museth Date: Sun, 11 Jan 2026 18:23:05 -0800 Subject: [PATCH 033/275] snapshot Signed-off-by: Ken Museth --- openvdb_cmd/vdb_tool/include/Tool.h | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/openvdb_cmd/vdb_tool/include/Tool.h b/openvdb_cmd/vdb_tool/include/Tool.h index aa25663ad4..624ad5ec19 100644 --- a/openvdb_cmd/vdb_tool/include/Tool.h +++ b/openvdb_cmd/vdb_tool/include/Tool.h @@ -1802,9 +1802,11 @@ void Tool::soupToLevelSet() */ timer.start(); - std::vector offsets;// = {grid};// finest grid + std::vector offsets;// = {grid};// finest grid + /* std::cerr << std::endl; dx = voxel;// final desired voxel size + mTimer.start("old offset"); for (int level = 0; level <= nLOD; ++level) {// both inclusive std::cerr << "Generating offset at level " << level << " with dx = " << dx << std::endl; auto grid = myOffset(*mesh, dx); @@ -1812,9 +1814,10 @@ void Tool::soupToLevelSet() offsets.push_back(grid); dx *= 2.0f; }// loop from fine to coarse voxel sizes - offsets.clear(); + */ std::cerr << std::endl; + mTimer.restart("new offset"); dx = voxel;// final desired voxel size for (int level = 0; level <= nLOD; ++level) {// both inclusive std::cerr << "Generating offset at level " << level << " with dx = " << dx << std::endl; @@ -1824,7 +1827,9 @@ void Tool::soupToLevelSet() offsets.push_back(grid); dx *= 2.0f; }// loop from fine to coarse voxel sizes + mTimer.stop(); //for (auto p : offsets) mGrid.push_back(p);// cache offset grid for debugging + //return; auto grid = offsets.back();// coarse grid t_offset = timer.milliseconds(); for (int level = nLOD-1; level >= 0; --level) { From 8171958437f857bedb0bb375d25b0254b80931d8 Mon Sep 17 00:00:00 2001 From: Ken Museth Date: Mon, 12 Jan 2026 14:33:57 -0800 Subject: [PATCH 034/275] snapshot Signed-off-by: Ken Museth --- openvdb_cmd/vdb_tool/include/Tool.h | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/openvdb_cmd/vdb_tool/include/Tool.h b/openvdb_cmd/vdb_tool/include/Tool.h index 624ad5ec19..21604e6050 100644 --- a/openvdb_cmd/vdb_tool/include/Tool.h +++ b/openvdb_cmd/vdb_tool/include/Tool.h @@ -1735,11 +1735,11 @@ void Tool::soupToLevelSet() #endif };// myUpsample - auto myOffset = [&](const Geometry &mesh, float dx)->GridT::Ptr{ + auto myOffset = [&](const Geometry &mesh, float dx, float isoValue)->GridT::Ptr{ timer.start(); auto xform = math::Transform::createLinearTransform(dx); auto udf = tools::meshToUnsignedDistanceField(*xform, mesh.vtx(), mesh.tri(), mesh.quad(), width);// mesh -> UDF - auto tmp = tools::levelSetRebuild(*udf, /*iso-value=*/dx, width);// UDF -> mesh -> SDF + auto tmp = tools::levelSetRebuild(*udf, isoValue, width);// UDF -> mesh -> SDF t_offset += timer.milliseconds(); return tmp; };// myOffset @@ -1803,33 +1803,36 @@ void Tool::soupToLevelSet() timer.start(); std::vector offsets;// = {grid};// finest grid - /* + std::cerr << std::endl; dx = voxel;// final desired voxel size mTimer.start("old offset"); for (int level = 0; level <= nLOD; ++level) {// both inclusive std::cerr << "Generating offset at level " << level << " with dx = " << dx << std::endl; - auto grid = myOffset(*mesh, dx); + auto grid = myOffset(*mesh, dx, dx); grid->setName("old_offset_level_" + std::to_string(level)); offsets.push_back(grid); dx *= 2.0f; }// loop from fine to coarse voxel sizes - offsets.clear(); - */ + //offsets.clear(); + /* std::cerr << std::endl; mTimer.restart("new offset"); dx = voxel;// final desired voxel size + float prev = 0.0f; for (int level = 0; level <= nLOD; ++level) {// both inclusive std::cerr << "Generating offset at level " << level << " with dx = " << dx << std::endl; - if (level) mesh = this->volumeToGeometry(*offsets.back()); - auto grid = myOffset(*mesh, dx); + if (level) mesh = this->volumeToGeometry(*offsets.back(), 0.0f); + auto grid = myOffset(*mesh, dx, dx - prev); grid->setName("new_offset_level_" + std::to_string(level)); offsets.push_back(grid); + prev = dx; dx *= 2.0f; }// loop from fine to coarse voxel sizes + for (auto p : offsets) mGrid.push_back(p);// cache offset grid for debugging + return; + */ mTimer.stop(); - //for (auto p : offsets) mGrid.push_back(p);// cache offset grid for debugging - //return; auto grid = offsets.back();// coarse grid t_offset = timer.milliseconds(); for (int level = nLOD-1; level >= 0; --level) { From 9e725442468ba01579b91fb17d54c97170553517 Mon Sep 17 00:00:00 2001 From: Ken Museth Date: Tue, 13 Jan 2026 08:14:21 -0800 Subject: [PATCH 035/275] added early termination of erosion Signed-off-by: Ken Museth --- openvdb_cmd/vdb_tool/include/Tool.h | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/openvdb_cmd/vdb_tool/include/Tool.h b/openvdb_cmd/vdb_tool/include/Tool.h index 21604e6050..47096a0a27 100644 --- a/openvdb_cmd/vdb_tool/include/Tool.h +++ b/openvdb_cmd/vdb_tool/include/Tool.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -1739,9 +1740,9 @@ void Tool::soupToLevelSet() timer.start(); auto xform = math::Transform::createLinearTransform(dx); auto udf = tools::meshToUnsignedDistanceField(*xform, mesh.vtx(), mesh.tri(), mesh.quad(), width);// mesh -> UDF - auto tmp = tools::levelSetRebuild(*udf, isoValue, width);// UDF -> mesh -> SDF + auto sdf = tools::levelSetRebuild(*udf, isoValue, width);// UDF -> mesh -> SDF t_offset += timer.milliseconds(); - return tmp; + return sdf; };// myOffset auto myLevelSetDeform = [&](GridT &grid, const GridT &gridB)->GridT::Ptr{ @@ -1812,8 +1813,11 @@ void Tool::soupToLevelSet() auto grid = myOffset(*mesh, dx, dx); grid->setName("old_offset_level_" + std::to_string(level)); offsets.push_back(grid); + //mGrid.push_back(grid);return; dx *= 2.0f; }// loop from fine to coarse voxel sizes + for (auto p : offsets) mGrid.push_back(p);// cache offset grid for debugging + //return; //offsets.clear(); /* std::cerr << std::endl; @@ -1835,10 +1839,19 @@ void Tool::soupToLevelSet() mTimer.stop(); auto grid = offsets.back();// coarse grid t_offset = timer.milliseconds(); + float vol[2]; for (int level = nLOD-1; level >= 0; --level) { grid = myUpsample(*grid); std::cerr << "Level: " << level << " dx of offset = " << offsets[level]->voxelSize()[0] << " dx of grid = " << grid->voxelSize()[0] << std::endl; - for (int iter = 0; iter < nErode; ++iter) grid = myLevelSetDeform(*grid, *offsets[level]); + for (int iter = 0; iter < nErode; ++iter) { + grid = myLevelSetDeform(*grid, *offsets[level]); + vol[1] = tools::levelSetVolume(*grid); + if (iter && math::Abs(vol[0]-vol[1]) == 0.0f ) { + std::cerr << "iter = " << iter << " old = " << vol[0] << ", new = " << vol[1] << std::endl; + break; + } + vol[0] = vol[1]; + } }// loop from coarse to fine voxel sizes #endif From c9bd4ca548e38e0f46d31f2c05961335f3bd5876 Mon Sep 17 00:00:00 2001 From: Alex Fuller Date: Wed, 14 Jan 2026 16:36:39 -0500 Subject: [PATCH 036/275] NanoVDB : Rename padding to blindDataCount (#2138) Signed-off-by: Alex Fuller --- nanovdb/nanovdb/python/PyIO.cc | 2 +- nanovdb/nanovdb/python/test/TestNanoVDB.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/nanovdb/nanovdb/python/PyIO.cc b/nanovdb/nanovdb/python/PyIO.cc index b46a52ba74..83f5089483 100644 --- a/nanovdb/nanovdb/python/PyIO.cc +++ b/nanovdb/nanovdb/python/PyIO.cc @@ -39,7 +39,7 @@ void defineFileGridMetaData(nb::module_& m) .def_prop_ro("tileCount", [](io::FileMetaData& metaData) { return std::make_tuple(metaData.tileCount[0], metaData.tileCount[1], metaData.tileCount[2]); }) .def_ro("codec", &io::FileMetaData::codec) - .def_ro("padding", &io::FileMetaData::padding) + .def_ro("blindDataCount", &io::FileMetaData::blindDataCount) .def_ro("version", &io::FileMetaData::version); nb::bind_vector>(m, "FileMetaDataVector"); diff --git a/nanovdb/nanovdb/python/test/TestNanoVDB.py b/nanovdb/nanovdb/python/test/TestNanoVDB.py index 03660c6342..5cfac6e742 100644 --- a/nanovdb/nanovdb/python/test/TestNanoVDB.py +++ b/nanovdb/nanovdb/python/test/TestNanoVDB.py @@ -358,7 +358,7 @@ def test_metadata(self): self.assertIsInstance(metadata.nodeCount, tuple) self.assertIsInstance(metadata.tileCount, tuple) self.assertEqual(metadata.codec, nanovdb.io.Codec.NONE) - self.assertEqual(metadata.padding, 0) + self.assertEqual(metadata.blindDataCount, 0) self.assertEqual(metadata.version, nanovdb.Version()) def test_read_write_grid(self): @@ -428,7 +428,7 @@ def test_metadata(self): self.assertIsInstance(metadata.nodeCount, tuple) self.assertIsInstance(metadata.tileCount, tuple) self.assertEqual(metadata.codec, nanovdb.io.Codec.NONE) - self.assertEqual(metadata.padding, 0) + self.assertEqual(metadata.blindDataCount, 0) self.assertEqual(metadata.version, nanovdb.Version()) def test_read_write_grid(self): From 270cd6711c3e22becbc45112583067ce4641c614 Mon Sep 17 00:00:00 2001 From: Ken Museth Date: Thu, 15 Jan 2026 10:23:33 -0800 Subject: [PATCH 037/275] tried offset with a mask but reverted due to numerical dissipation Signed-off-by: Ken Museth --- openvdb_cmd/vdb_tool/include/Tool.h | 41 ++++++++++++++++++----------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/openvdb_cmd/vdb_tool/include/Tool.h b/openvdb_cmd/vdb_tool/include/Tool.h index 47096a0a27..a5d608ee7e 100644 --- a/openvdb_cmd/vdb_tool/include/Tool.h +++ b/openvdb_cmd/vdb_tool/include/Tool.h @@ -1745,7 +1745,7 @@ void Tool::soupToLevelSet() return sdf; };// myOffset - auto myLevelSetDeform = [&](GridT &grid, const GridT &gridB)->GridT::Ptr{ + auto myShrinkWrap = [&](GridT &grid, const GridT &gridB)->GridT::Ptr{ timer.start(); const float dx = grid.voxelSize()[0]; const int space = 1, time = 1; @@ -1763,7 +1763,17 @@ void Tool::soupToLevelSet() t_deform += timer.milliseconds(); isGridSDF = false;// the CSG operation messed up the SDF return tmp; - };// myLevelSetDeform + };// myShrinkWrap + + auto myLevelSetErode = [&](GridT &grid, const GridT &gridB){ + timer.start(); + const float dx = grid.voxelSize()[0]; + const int space = 1, time = 1; + auto filter = this->createFilter(grid, space, time); + filter->setMaskRange(0.0f, gridB.background()); + filter->offset(nErode*dx, &gridB);// erode by dx + t_deform += timer.milliseconds(); + };// myLevelSetErode if (mParser.verbose) mTimer.start("Soup -> SDF"); @@ -1781,7 +1791,7 @@ void Tool::soupToLevelSet() auto base = myOffset(dx); const int end = nErode;// - int(i*factor); std::cerr << "Level: " << i << ", erosions: " << end << std::endl; - for (int j = 0; jsetName("old_offset_level_" + std::to_string(level)); offsets.push_back(grid); //mGrid.push_back(grid);return; dx *= 2.0f; }// loop from fine to coarse voxel sizes - for (auto p : offsets) mGrid.push_back(p);// cache offset grid for debugging + */ + //for (auto p : offsets) mGrid.push_back(p);// cache offset grid for debugging //return; //offsets.clear(); - /* + std::cerr << std::endl; mTimer.restart("new offset"); dx = voxel;// final desired voxel size - float prev = 0.0f; + //float prev = 0.0f; for (int level = 0; level <= nLOD; ++level) {// both inclusive std::cerr << "Generating offset at level " << level << " with dx = " << dx << std::endl; if (level) mesh = this->volumeToGeometry(*offsets.back(), 0.0f); - auto grid = myOffset(*mesh, dx, dx - prev); + auto grid = myOffset(*mesh, dx, dx );//- prev); grid->setName("new_offset_level_" + std::to_string(level)); offsets.push_back(grid); - prev = dx; + //prev = dx; dx *= 2.0f; }// loop from fine to coarse voxel sizes - for (auto p : offsets) mGrid.push_back(p);// cache offset grid for debugging - return; - */ + //for (auto p : offsets) mGrid.push_back(p);// cache offset grid for debugging mTimer.stop(); + auto grid = offsets.back();// coarse grid t_offset = timer.milliseconds(); float vol[2]; for (int level = nLOD-1; level >= 0; --level) { grid = myUpsample(*grid); std::cerr << "Level: " << level << " dx of offset = " << offsets[level]->voxelSize()[0] << " dx of grid = " << grid->voxelSize()[0] << std::endl; + //myLevelSetErode(*grid, *offsets[level]); for (int iter = 0; iter < nErode; ++iter) { - grid = myLevelSetDeform(*grid, *offsets[level]); + grid = myShrinkWrap(*grid, *offsets[level]); vol[1] = tools::levelSetVolume(*grid); if (iter && math::Abs(vol[0]-vol[1]) == 0.0f ) { std::cerr << "iter = " << iter << " old = " << vol[0] << ", new = " << vol[1] << std::endl; From a183a9219e71b719c9c5d4ecface43bf6b81a313 Mon Sep 17 00:00:00 2001 From: Ken Museth Date: Thu, 15 Jan 2026 10:48:40 -0800 Subject: [PATCH 038/275] snapshot Signed-off-by: Ken Museth --- openvdb/openvdb/tools/LevelSetFilter.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openvdb/openvdb/tools/LevelSetFilter.h b/openvdb/openvdb/tools/LevelSetFilter.h index bc1a425815..3746e7ce0d 100644 --- a/openvdb/openvdb/tools/LevelSetFilter.h +++ b/openvdb/openvdb/tools/LevelSetFilter.h @@ -16,7 +16,7 @@ #define OPENVDB_TOOLS_LEVELSETFILTER_HAS_BEEN_INCLUDED #include "LevelSetTracker.h" -#include "Interpolation.h" +#include "Interpolation.h"// for tools::AlphaMask #include // for std::max() #include #include @@ -173,7 +173,7 @@ class LevelSetFilter : public LevelSetTracker using BufferT = typename tree::LeafManager::BufferType; using LeafRange = typename tree::LeafManager::LeafRange; using LeafIterT = typename LeafRange::Iterator; - using AlphaMaskT = tools::AlphaMask; + using AlphaMaskT = tools::AlphaMask;// defined in tools/Interpolation.h Filter(LevelSetFilter* parent, const MaskType* mask) : mParent(parent), mMask(mask) {} Filter(const Filter&) = default; From b43b383f435c241ac9f4c49c0cb974790090c6d9 Mon Sep 17 00:00:00 2001 From: Ken Museth Date: Thu, 15 Jan 2026 12:04:19 -0800 Subject: [PATCH 039/275] revert to old Signed-off-by: Ken Museth --- openvdb_cmd/vdb_tool/include/Tool.h | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/openvdb_cmd/vdb_tool/include/Tool.h b/openvdb_cmd/vdb_tool/include/Tool.h index a5d608ee7e..3b592acca5 100644 --- a/openvdb_cmd/vdb_tool/include/Tool.h +++ b/openvdb_cmd/vdb_tool/include/Tool.h @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -1775,6 +1776,16 @@ void Tool::soupToLevelSet() t_deform += timer.milliseconds(); };// myLevelSetErode + auto myLevelSetMorph = [&](GridT &srcGrid, const GridT &targetGrid){ + timer.start(); + //tools::LevelSetMorphing + tools::LevelSetMorphing morph(srcGrid, targetGrid); + morph.setSpatialScheme(math::FIRST_BIAS); + morph.setTemporalScheme(math::TVD_RK1); + morph.advect(0, nErode*srcGrid.voxelSize()[0]); + t_deform += timer.milliseconds(); + };// myLevelSetMorph + if (mParser.verbose) mTimer.start("Soup -> SDF"); // Main algorithm @@ -1832,7 +1843,7 @@ void Tool::soupToLevelSet() //offsets.clear(); std::cerr << std::endl; - mTimer.restart("new offset"); + //mTimer.restart("new offset"); dx = voxel;// final desired voxel size //float prev = 0.0f; for (int level = 0; level <= nLOD; ++level) {// both inclusive From e775b5ed35e8bd2b024e17a4cc1af23c70bff2c3 Mon Sep 17 00:00:00 2001 From: Ken Museth Date: Thu, 15 Jan 2026 15:47:15 -0800 Subject: [PATCH 040/275] 6*dx Signed-off-by: Ken Museth --- openvdb_cmd/vdb_tool/include/Tool.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openvdb_cmd/vdb_tool/include/Tool.h b/openvdb_cmd/vdb_tool/include/Tool.h index 3b592acca5..f57270f2ab 100644 --- a/openvdb_cmd/vdb_tool/include/Tool.h +++ b/openvdb_cmd/vdb_tool/include/Tool.h @@ -1753,7 +1753,7 @@ void Tool::soupToLevelSet() auto filter = this->createFilter(grid, space, time); #if 1 if (isGridSDF == false) filter->normalize(); - filter->offset(dx);// erode by dx + filter->offset(6*dx);// erode by dx auto tmp = tools::csgUnionCopy(grid, gridB); #else filter->offset(dx);// erode by dx From 4bf0b53d464940834c048447dca2580b9ed7b923 Mon Sep 17 00:00:00 2001 From: Ken Museth Date: Fri, 16 Jan 2026 10:49:42 -0800 Subject: [PATCH 041/275] cleanup Signed-off-by: Ken Museth --- openvdb_cmd/vdb_tool/include/Tool.h | 62 +++++++++-------------------- 1 file changed, 18 insertions(+), 44 deletions(-) diff --git a/openvdb_cmd/vdb_tool/include/Tool.h b/openvdb_cmd/vdb_tool/include/Tool.h index f57270f2ab..4f9231294d 100644 --- a/openvdb_cmd/vdb_tool/include/Tool.h +++ b/openvdb_cmd/vdb_tool/include/Tool.h @@ -1715,11 +1715,10 @@ void Tool::soupToLevelSet() std::cerr << "estimated voxel size = " << voxel << " from dim = " << dim << std::endl; } auto it = this->getGeom(geo_age); - //const Geometry &mesh = **it; Geometry::Ptr mesh = *it; if (mesh->isPoints()) this->warning("Warning: -soup2ls was called on points, not a mesh! Hint: use -points2ls instead!"); util::CpuTimer timer; - double t_offset = 0.0, t_deform = 0.0, t_upscale = 0.0, t_multigrid = 0.0;// all in milliseconds + double t_offset = 0.0, t_deform = 0.0, t_upscale = 0.0;// all in milliseconds bool isGridSDF = true; auto myUpsample = [&](const GridT &grid)->GridT::Ptr{ @@ -1753,7 +1752,7 @@ void Tool::soupToLevelSet() auto filter = this->createFilter(grid, space, time); #if 1 if (isGridSDF == false) filter->normalize(); - filter->offset(6*dx);// erode by dx + filter->offset(2.0f*dx);// erode by dx auto tmp = tools::csgUnionCopy(grid, gridB); #else filter->offset(dx);// erode by dx @@ -1778,7 +1777,6 @@ void Tool::soupToLevelSet() auto myLevelSetMorph = [&](GridT &srcGrid, const GridT &targetGrid){ timer.start(); - //tools::LevelSetMorphing tools::LevelSetMorphing morph(srcGrid, targetGrid); morph.setSpatialScheme(math::FIRST_BIAS); morph.setTemporalScheme(math::TVD_RK1); @@ -1822,44 +1820,22 @@ void Tool::soupToLevelSet() for (int iter = 0; iter < nErode; ++iter) grid = myShrinkWrap(*grid, *base); } */ - timer.start(); + //timer.start(); std::vector offsets;// = {grid};// finest grid - /* std::cerr << std::endl; dx = voxel;// final desired voxel size - mTimer.start("old offset"); - for (int level = 0; level <= nLOD; ++level) {// both inclusive - std::cerr << "Generating offset at level " << level << " with dx = " << dx << std::endl; - auto grid = myOffset(*mesh, dx, dx); - //grid->setName("old_offset_level_" + std::to_string(level)); - offsets.push_back(grid); - //mGrid.push_back(grid);return; - dx *= 2.0f; - }// loop from fine to coarse voxel sizes - */ - //for (auto p : offsets) mGrid.push_back(p);// cache offset grid for debugging - //return; - //offsets.clear(); - - std::cerr << std::endl; - //mTimer.restart("new offset"); - dx = voxel;// final desired voxel size - //float prev = 0.0f; + for (int level = 0; level <= nLOD; ++level) {// both inclusive std::cerr << "Generating offset at level " << level << " with dx = " << dx << std::endl; - if (level) mesh = this->volumeToGeometry(*offsets.back(), 0.0f); - auto grid = myOffset(*mesh, dx, dx );//- prev); - grid->setName("new_offset_level_" + std::to_string(level)); + if (level) mesh = this->volumeToGeometry(*offsets.back(), 0.0f);// uncomment for optimization + auto grid = myOffset(*mesh, dx, dx ); + //grid->setName("offset_level_" + std::to_string(level)); offsets.push_back(grid); - //prev = dx; dx *= 2.0f; }// loop from fine to coarse voxel sizes - //for (auto p : offsets) mGrid.push_back(p);// cache offset grid for debugging - mTimer.stop(); auto grid = offsets.back();// coarse grid - t_offset = timer.milliseconds(); float vol[2]; for (int level = nLOD-1; level >= 0; --level) { grid = myUpsample(*grid); @@ -1867,17 +1843,21 @@ void Tool::soupToLevelSet() //myLevelSetErode(*grid, *offsets[level]); for (int iter = 0; iter < nErode; ++iter) { grid = myShrinkWrap(*grid, *offsets[level]); - vol[1] = tools::levelSetVolume(*grid); - if (iter && math::Abs(vol[0]-vol[1]) == 0.0f ) { - std::cerr << "iter = " << iter << " old = " << vol[0] << ", new = " << vol[1] << std::endl; - break; - } - vol[0] = vol[1]; + //vol[1] = tools::levelSetVolume(*grid); + //if (iter && math::Abs(vol[0]-vol[1]) == 0.0f ) { + // std::cerr << "iter = " << iter << " old = " << vol[0] << ", new = " << vol[1] << std::endl; + // break; + //} + //vol[0] = vol[1]; } }// loop from coarse to fine voxel sizes #endif - //grid = tools::levelSetRebuild(*grid, /*iso-value=*/0.0f, width);// SDF -> mesh -> SDF + if (grid_name.empty()) grid_name = "soup2ls_" + mesh->getName(); + grid->setName(grid_name); + mGrid.push_back(grid); + if (!keep) mGeom.erase(std::next(it).base()); + if (mParser.verbose) mTimer.stop(); t_offset /= 1000.0; t_deform /= 1000.0; t_upscale /= 1000.0; @@ -1885,12 +1865,6 @@ void Tool::soupToLevelSet() << "s\noffset: \t" << t_offset << "s\ndeform: \t" << t_deform << "s\ntotal: \t" << (t_offset + t_deform + t_upscale) << "s\n"; - - if (grid_name.empty()) grid_name = "soup2ls_" + mesh->getName(); - grid->setName(grid_name); - mGrid.push_back(grid); - if (!keep) mGeom.erase(std::next(it).base()); - if (mParser.verbose) mTimer.stop(); } catch (const std::exception& e) { throw std::invalid_argument(name+": "+e.what()); } From a43008dca8b41ba99afc8e9d4f9e496abb5c4f0c Mon Sep 17 00:00:00 2001 From: Ken Museth Date: Fri, 16 Jan 2026 14:17:02 -0800 Subject: [PATCH 042/275] cleanup Signed-off-by: Ken Museth --- openvdb_cmd/vdb_tool/include/Tool.h | 91 ++++++++++------------------- 1 file changed, 32 insertions(+), 59 deletions(-) diff --git a/openvdb_cmd/vdb_tool/include/Tool.h b/openvdb_cmd/vdb_tool/include/Tool.h index 4f9231294d..df6fe266c7 100644 --- a/openvdb_cmd/vdb_tool/include/Tool.h +++ b/openvdb_cmd/vdb_tool/include/Tool.h @@ -1717,12 +1717,14 @@ void Tool::soupToLevelSet() auto it = this->getGeom(geo_age); Geometry::Ptr mesh = *it; if (mesh->isPoints()) this->warning("Warning: -soup2ls was called on points, not a mesh! Hint: use -points2ls instead!"); - util::CpuTimer timer; - double t_offset = 0.0, t_deform = 0.0, t_upscale = 0.0;// all in milliseconds + //util::CpuTimer timer; + //double t_offset = 0.0, t_deform = 0.0, t_upscale = 0.0;// all in milliseconds bool isGridSDF = true; + if (mParser.verbose) mTimer.start("Soup -> SDF"); + auto myUpsample = [&](const GridT &grid)->GridT::Ptr{ - timer.start(); + //timer.start(); const float dx = grid.voxelSize()[0]; #if 0 auto xform = math::Transform::createLinearTransform(dx/2);// upsample @@ -1730,62 +1732,50 @@ void Tool::soupToLevelSet() #else GridT::Ptr outGrid = createLevelSet(dx/2, width); tools::resampleToMatch(grid, *outGrid); - t_upscale += timer.milliseconds(); + //t_upscale += timer.milliseconds(); isGridSDF = true; return outGrid; #endif };// myUpsample auto myOffset = [&](const Geometry &mesh, float dx, float isoValue)->GridT::Ptr{ - timer.start(); + //timer.start(); auto xform = math::Transform::createLinearTransform(dx); auto udf = tools::meshToUnsignedDistanceField(*xform, mesh.vtx(), mesh.tri(), mesh.quad(), width);// mesh -> UDF auto sdf = tools::levelSetRebuild(*udf, isoValue, width);// UDF -> mesh -> SDF - t_offset += timer.milliseconds(); + //t_offset += timer.milliseconds(); return sdf; };// myOffset - auto myShrinkWrap = [&](GridT &grid, const GridT &gridB)->GridT::Ptr{ - timer.start(); - const float dx = grid.voxelSize()[0]; - const int space = 1, time = 1; + auto myShrinkWrap = [&](GridT &grid, const GridT &gridB, int &iter)->GridT::Ptr{ + const int space = 1, time = 1, steps = 2; auto filter = this->createFilter(grid, space, time); -#if 1 if (isGridSDF == false) filter->normalize(); - filter->offset(2.0f*dx);// erode by dx - auto tmp = tools::csgUnionCopy(grid, gridB); -#else - filter->offset(dx);// erode by dx - auto tmp = tools::csgUnionCopy(grid, gridB); - filter = this->createFilter(*tmp, space, time); - filter->normalize(); -#endif - t_deform += timer.milliseconds(); + filter->offset(steps*grid.voxelSize()[0]);// erode by steps * dx isGridSDF = false;// the CSG operation messed up the SDF - return tmp; + iter += steps; + return tools::csgUnionCopy(grid, gridB); };// myShrinkWrap auto myLevelSetErode = [&](GridT &grid, const GridT &gridB){ - timer.start(); + //timer.start(); const float dx = grid.voxelSize()[0]; const int space = 1, time = 1; auto filter = this->createFilter(grid, space, time); filter->setMaskRange(0.0f, gridB.background()); filter->offset(nErode*dx, &gridB);// erode by dx - t_deform += timer.milliseconds(); + //t_deform += timer.milliseconds(); };// myLevelSetErode auto myLevelSetMorph = [&](GridT &srcGrid, const GridT &targetGrid){ - timer.start(); + //timer.start(); tools::LevelSetMorphing morph(srcGrid, targetGrid); morph.setSpatialScheme(math::FIRST_BIAS); morph.setTemporalScheme(math::TVD_RK1); morph.advect(0, nErode*srcGrid.voxelSize()[0]); - t_deform += timer.milliseconds(); + //t_deform += timer.milliseconds(); };// myLevelSetMorph - if (mParser.verbose) mTimer.start("Soup -> SDF"); - // Main algorithm float dx = voxel * pow(2, nLOD); const float factor = float(nErode-1)/(nLOD-1); @@ -1794,7 +1784,7 @@ void Tool::soupToLevelSet() auto grid = myOffset(dx); for (int i=0; i voxel) { + while(dx > voxel) { dx *= 0.5f;// refinement grid = myUpsample(*grid); auto base = myOffset(dx); @@ -1803,31 +1793,12 @@ void Tool::soupToLevelSet() for (int j = 0; j ms(nLOD, myOffset(voxel)); - auto grid = ms.grid(nLOD-1); - dx = grid->voxelSize()[0]; - std::cerr << "dx = " << dx << std::endl; - for (int i=0; igetName(); grid->setName(grid_name); mGrid.push_back(grid); if (!keep) mGeom.erase(std::next(it).base()); - if (mParser.verbose) mTimer.stop(); - t_offset /= 1000.0; - t_deform /= 1000.0; - t_upscale /= 1000.0; - std::cerr << "upscale:\t" << t_upscale - << "s\noffset: \t" << t_offset - << "s\ndeform: \t" << t_deform - << "s\ntotal: \t" << (t_offset + t_deform + t_upscale) << "s\n"; + + //t_offset /= 1000.0; + //t_deform /= 1000.0; + //t_upscale /= 1000.0; + //std::cerr << "upscale:\t" << t_upscale + // << "s\noffset: \t" << t_offset + // << "s\ndeform: \t" << t_deform + // << "s\ntotal: \t" << (t_offset + t_deform + t_upscale) << "s\n"; } catch (const std::exception& e) { throw std::invalid_argument(name+": "+e.what()); } From 09e58af9b032b5cc622c116835c8eeb1ee8c6a98 Mon Sep 17 00:00:00 2001 From: Dan Bailey Date: Fri, 16 Jan 2026 17:38:10 -0800 Subject: [PATCH 043/275] Discard any auxiliary data in non-CoordBBox variant of readBuffers() Signed-off-by: Dan Bailey --- openvdb/openvdb/tools/PointIndexGrid.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openvdb/openvdb/tools/PointIndexGrid.h b/openvdb/openvdb/tools/PointIndexGrid.h index 7eddcd71c3..3db213ea38 100644 --- a/openvdb/openvdb/tools/PointIndexGrid.h +++ b/openvdb/openvdb/tools/PointIndexGrid.h @@ -1732,6 +1732,11 @@ PointIndexLeafNode::readBuffers(std::istream& is, bool fromHalf) // Reserved for future use Index64 auxDataBytes = Index64(0); is.read(reinterpret_cast(&auxDataBytes), sizeof(Index64)); + if (auxDataBytes > 0) { + // For now, read and discard any auxiliary data. + std::unique_ptr auxData{new char[auxDataBytes]}; + is.read(auxData.get(), auxDataBytes); + } } From c7e40ea81318e8cfe75378f356cc2f1069410a46 Mon Sep 17 00:00:00 2001 From: Ken Museth Date: Sat, 17 Jan 2026 15:14:02 -0800 Subject: [PATCH 044/275] nLOD is now derived from BBOX Signed-off-by: Ken Museth --- openvdb_cmd/vdb_tool/include/Tool.h | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/openvdb_cmd/vdb_tool/include/Tool.h b/openvdb_cmd/vdb_tool/include/Tool.h index df6fe266c7..90abc029b2 100644 --- a/openvdb_cmd/vdb_tool/include/Tool.h +++ b/openvdb_cmd/vdb_tool/include/Tool.h @@ -434,7 +434,7 @@ void Tool::init() {"voxel", "", "0.01", "voxel size in world units (by defaults \"dim\" is used to derive \"voxel\"). If specified this option takes precedence over \"dim\""}, {"width", "", "3.0", "half-width in voxel units of the output narrow-band level set (defaults to 3 units on either side of the zero-crossing)"}, {"geo", "0", "0", "age (i.e. stack index) of the geometry to be processed. Defaults to 0, i.e. most recently inserted geometry."}, - {"lod", "2", "2", "number of LOD level. Defaults to 2."}, + {"lod", "0", "2", "number of LOD level. Defaults to 0, i.e. will be derived form the mesh size."}, {"erode", "2", "2", "number of iterations of constrained erosion. Defaults to 2."}, {"keep", "", "1|0|true|false", "toggle wether the input geometry is preserved or deleted after the conversion"}, {"name", "", "soup2ls_input", "specify the name of the resulting vdb (by default it's derived from the input geometry)"}}, @@ -1707,19 +1707,25 @@ void Tool::soupToLevelSet() const float width = mParser.get("width"); const int geo_age = mParser.get("geo"); const int nErode = mParser.get("erode"); - const int nLOD = mParser.get("lod"); + int nLOD = mParser.get("lod"); const bool keep = mParser.get("keep"); + + auto it = this->getGeom(geo_age); + Geometry::Ptr mesh = *it; + if (mesh->isPoints()) this->warning("Warning: -soup2ls was called on points, not a mesh! Hint: use -points2ls instead!"); + bool isGridSDF = true; + std::string grid_name = mParser.get("name"); if (voxel == 0.0f) { voxel = this->estimateVoxelSize(dim, width, geo_age); std::cerr << "estimated voxel size = " << voxel << " from dim = " << dim << std::endl; } - auto it = this->getGeom(geo_age); - Geometry::Ptr mesh = *it; - if (mesh->isPoints()) this->warning("Warning: -soup2ls was called on points, not a mesh! Hint: use -points2ls instead!"); - //util::CpuTimer timer; - //double t_offset = 0.0, t_deform = 0.0, t_upscale = 0.0;// all in milliseconds - bool isGridSDF = true; + if (nLOD == 0) { + const auto &bbox = mesh->bbox();// bbox size with max extend + const auto maxLength = bbox.extents()[bbox.maxExtent()];// max length of bbox + nLOD = int(std::log2(maxLength/(2.0*voxel))); + std::cerr << "Max size = " << maxLength << ", nLOD = " << nLOD << std::endl; + } if (mParser.verbose) mTimer.start("Soup -> SDF"); From bc47e5cfc255511cbbcade65e4913720a21f9bb6 Mon Sep 17 00:00:00 2001 From: Ken Museth Date: Sat, 17 Jan 2026 15:20:17 -0800 Subject: [PATCH 045/275] nLOD is now derived from BBOX Signed-off-by: Ken Museth --- openvdb_cmd/vdb_tool/include/Tool.h | 1 + 1 file changed, 1 insertion(+) diff --git a/openvdb_cmd/vdb_tool/include/Tool.h b/openvdb_cmd/vdb_tool/include/Tool.h index 90abc029b2..3f5feaff3d 100644 --- a/openvdb_cmd/vdb_tool/include/Tool.h +++ b/openvdb_cmd/vdb_tool/include/Tool.h @@ -1725,6 +1725,7 @@ void Tool::soupToLevelSet() const auto maxLength = bbox.extents()[bbox.maxExtent()];// max length of bbox nLOD = int(std::log2(maxLength/(2.0*voxel))); std::cerr << "Max size = " << maxLength << ", nLOD = " << nLOD << std::endl; + if (nLOD <= 1) throw std::invalid_argument(name+": nLOD="+std::to_string(nLOD)+" is too small!"); } if (mParser.verbose) mTimer.start("Soup -> SDF"); From 7558cbe8d3e4a8cc1db74ec569a6406d7812b453 Mon Sep 17 00:00:00 2001 From: Ken Museth Date: Sat, 17 Jan 2026 16:53:09 -0800 Subject: [PATCH 046/275] snapshot Signed-off-by: Ken Museth --- openvdb_cmd/vdb_tool/include/Tool.h | 37 +++++++++-------------------- 1 file changed, 11 insertions(+), 26 deletions(-) diff --git a/openvdb_cmd/vdb_tool/include/Tool.h b/openvdb_cmd/vdb_tool/include/Tool.h index 3f5feaff3d..01d053d995 100644 --- a/openvdb_cmd/vdb_tool/include/Tool.h +++ b/openvdb_cmd/vdb_tool/include/Tool.h @@ -435,7 +435,7 @@ void Tool::init() {"width", "", "3.0", "half-width in voxel units of the output narrow-band level set (defaults to 3 units on either side of the zero-crossing)"}, {"geo", "0", "0", "age (i.e. stack index) of the geometry to be processed. Defaults to 0, i.e. most recently inserted geometry."}, {"lod", "0", "2", "number of LOD level. Defaults to 0, i.e. will be derived form the mesh size."}, - {"erode", "2", "2", "number of iterations of constrained erosion. Defaults to 2."}, + {"erode", "8", "2", "number of iterations of constrained erosion. Defaults to 8."}, {"keep", "", "1|0|true|false", "toggle wether the input geometry is preserved or deleted after the conversion"}, {"name", "", "soup2ls_input", "specify the name of the resulting vdb (by default it's derived from the input geometry)"}}, [&](){mParser.setDefaults();}, [&](){this->soupToLevelSet();}); @@ -1695,7 +1695,7 @@ void Tool::meshToLevelSet() // ============================================================================================================== // vdb_tool -read ~/dev/data/ply/cube.ply -soup2ls dim=64 lod=4 erode=5 -o vdb="*" stdout.vdb | vdb_view -// ./openvdb_cmd/vdb_tool/vdb_tool -i ~/dev/data/obj/Robot_arm.obj -soup2ls voxel=0.2 lod=8 erode=16 -print -o codec=blosc openvdb_10.vdb +// ./openvdb_cmd/vdb_tool/vdb_tool -i ~/dev/data/obj/Robot_arm.obj -soup2ls voxel=0.2 erode=16 -print -o codec=blosc openvdb_10.vdb void Tool::soupToLevelSet() { const std::string &name = mParser.getAction().name; @@ -1724,14 +1724,13 @@ void Tool::soupToLevelSet() const auto &bbox = mesh->bbox();// bbox size with max extend const auto maxLength = bbox.extents()[bbox.maxExtent()];// max length of bbox nLOD = int(std::log2(maxLength/(2.0*voxel))); - std::cerr << "Max size = " << maxLength << ", nLOD = " << nLOD << std::endl; + std::cerr << "Max size of bbox = " << maxLength << ", LOD levels = " << nLOD << std::endl; if (nLOD <= 1) throw std::invalid_argument(name+": nLOD="+std::to_string(nLOD)+" is too small!"); } if (mParser.verbose) mTimer.start("Soup -> SDF"); auto myUpsample = [&](const GridT &grid)->GridT::Ptr{ - //timer.start(); const float dx = grid.voxelSize()[0]; #if 0 auto xform = math::Transform::createLinearTransform(dx/2);// upsample @@ -1739,18 +1738,15 @@ void Tool::soupToLevelSet() #else GridT::Ptr outGrid = createLevelSet(dx/2, width); tools::resampleToMatch(grid, *outGrid); - //t_upscale += timer.milliseconds(); isGridSDF = true; return outGrid; #endif };// myUpsample auto myOffset = [&](const Geometry &mesh, float dx, float isoValue)->GridT::Ptr{ - //timer.start(); auto xform = math::Transform::createLinearTransform(dx); auto udf = tools::meshToUnsignedDistanceField(*xform, mesh.vtx(), mesh.tri(), mesh.quad(), width);// mesh -> UDF auto sdf = tools::levelSetRebuild(*udf, isoValue, width);// UDF -> mesh -> SDF - //t_offset += timer.milliseconds(); return sdf; };// myOffset @@ -1765,13 +1761,11 @@ void Tool::soupToLevelSet() };// myShrinkWrap auto myLevelSetErode = [&](GridT &grid, const GridT &gridB){ - //timer.start(); const float dx = grid.voxelSize()[0]; const int space = 1, time = 1; auto filter = this->createFilter(grid, space, time); filter->setMaskRange(0.0f, gridB.background()); filter->offset(nErode*dx, &gridB);// erode by dx - //t_deform += timer.milliseconds(); };// myLevelSetErode auto myLevelSetMorph = [&](GridT &srcGrid, const GridT &targetGrid){ @@ -1800,25 +1794,23 @@ void Tool::soupToLevelSet() for (int j = 0; j offsets;// = {grid};// finest grid - //std::cerr << std::endl; + std::vector offsets(nLOD+1);// = {grid};// finest grid dx = voxel;// final desired voxel size - for (int level = 0; level <= nLOD; ++level) {// both inclusive //std::cerr << "Generating offset at level " << level << " with dx = " << dx << std::endl; - if (level) mesh = this->volumeToGeometry(*offsets.back(), 0.0f);// uncomment for optimization + if (level) mesh = this->volumeToGeometry(*offsets[level-1], 0.0f);// uncomment for optimization auto grid = myOffset(*mesh, dx, dx ); //grid->setName("offset_level_" + std::to_string(level)); - offsets.push_back(grid); + offsets[level] = grid; dx *= 2.0f; }// loop from fine to coarse voxel sizes - auto grid = offsets.back();// coarse grid - float vol[2]; + auto grid = offsets[nLOD];// coarse grid + //float vol[2]; + std::cerr << std::endl; for (int level = nLOD-1; level >= 0; --level) { grid = myUpsample(*grid); - std::cerr << "Level: " << level << " dx of offset = " << offsets[level]->voxelSize()[0] << " dx of grid = " << grid->voxelSize()[0] << std::endl; - //myLevelSetErode(*grid, *offsets[level]); + std::cerr << "Level: " << level << ", dx of offset = " << offsets[level]->voxelSize()[0] << ", dx of grid = " << grid->voxelSize()[0] << std::endl; for (int iter = 0; iter < nErode;) { grid = myShrinkWrap(*grid, *offsets[level], iter); //vol[1] = tools::levelSetVolume(*grid); @@ -1837,14 +1829,7 @@ void Tool::soupToLevelSet() grid->setName(grid_name); mGrid.push_back(grid); if (!keep) mGeom.erase(std::next(it).base()); - - //t_offset /= 1000.0; - //t_deform /= 1000.0; - //t_upscale /= 1000.0; - //std::cerr << "upscale:\t" << t_upscale - // << "s\noffset: \t" << t_offset - // << "s\ndeform: \t" << t_deform - // << "s\ntotal: \t" << (t_offset + t_deform + t_upscale) << "s\n"; + } catch (const std::exception& e) { throw std::invalid_argument(name+": "+e.what()); } From 7cf419813858e1e95761bc298f26bd52df226918 Mon Sep 17 00:00:00 2001 From: Ken Museth Date: Sat, 17 Jan 2026 21:54:17 -0800 Subject: [PATCH 047/275] added engineering threshold and early out Signed-off-by: Ken Museth --- openvdb_cmd/vdb_tool/include/Tool.h | 52 +++++++++++++---------------- 1 file changed, 24 insertions(+), 28 deletions(-) diff --git a/openvdb_cmd/vdb_tool/include/Tool.h b/openvdb_cmd/vdb_tool/include/Tool.h index 01d053d995..4ad5528588 100644 --- a/openvdb_cmd/vdb_tool/include/Tool.h +++ b/openvdb_cmd/vdb_tool/include/Tool.h @@ -436,6 +436,7 @@ void Tool::init() {"geo", "0", "0", "age (i.e. stack index) of the geometry to be processed. Defaults to 0, i.e. most recently inserted geometry."}, {"lod", "0", "2", "number of LOD level. Defaults to 0, i.e. will be derived form the mesh size."}, {"erode", "8", "2", "number of iterations of constrained erosion. Defaults to 8."}, + {"thres", "0", "0.01", "engineering threshold. Defaults to 0."}, {"keep", "", "1|0|true|false", "toggle wether the input geometry is preserved or deleted after the conversion"}, {"name", "", "soup2ls_input", "specify the name of the resulting vdb (by default it's derived from the input geometry)"}}, [&](){mParser.setDefaults();}, [&](){this->soupToLevelSet();}); @@ -1708,6 +1709,7 @@ void Tool::soupToLevelSet() const int geo_age = mParser.get("geo"); const int nErode = mParser.get("erode"); int nLOD = mParser.get("lod"); + float thres = mParser.get("thres"); const bool keep = mParser.get("keep"); auto it = this->getGeom(geo_age); @@ -1727,8 +1729,16 @@ void Tool::soupToLevelSet() std::cerr << "Max size of bbox = " << maxLength << ", LOD levels = " << nLOD << std::endl; if (nLOD <= 1) throw std::invalid_argument(name+": nLOD="+std::to_string(nLOD)+" is too small!"); } + if (thres == 0.0f) std::cerr << "Disabled engineering threshold\n"; + + auto myErode = [&](float dx)->int{ + if (dx >= 2*thres) return nErode;// if thres == 0 this is aways true + if (dx <= thres) return 1; + return 1 + int((nErode-1)*(dx-thres)/thres); + }; if (mParser.verbose) mTimer.start("Soup -> SDF"); + std::cerr << std::endl; auto myUpsample = [&](const GridT &grid)->GridT::Ptr{ const float dx = grid.voxelSize()[0]; @@ -1769,59 +1779,45 @@ void Tool::soupToLevelSet() };// myLevelSetErode auto myLevelSetMorph = [&](GridT &srcGrid, const GridT &targetGrid){ - //timer.start(); tools::LevelSetMorphing morph(srcGrid, targetGrid); morph.setSpatialScheme(math::FIRST_BIAS); morph.setTemporalScheme(math::TVD_RK1); morph.advect(0, nErode*srcGrid.voxelSize()[0]); - //t_deform += timer.milliseconds(); };// myLevelSetMorph // Main algorithm float dx = voxel * pow(2, nLOD); const float factor = float(nErode-1)/(nLOD-1); -#if 0// old method - - auto grid = myOffset(dx); - for (int i=0; i voxel) { - dx *= 0.5f;// refinement - grid = myUpsample(*grid); - auto base = myOffset(dx); - const int end = nErode;// - int(i*factor); - std::cerr << "Level: " << i << ", erosions: " << end << std::endl; - for (int j = 0; j offsets(nLOD+1);// = {grid};// finest grid dx = voxel;// final desired voxel size for (int level = 0; level <= nLOD; ++level) {// both inclusive - //std::cerr << "Generating offset at level " << level << " with dx = " << dx << std::endl; + std::cerr << "Generating offset at level " << level << " with dx = " << dx << std::endl; if (level) mesh = this->volumeToGeometry(*offsets[level-1], 0.0f);// uncomment for optimization - auto grid = myOffset(*mesh, dx, dx ); + auto grid = myOffset(*mesh, dx, dx); //grid->setName("offset_level_" + std::to_string(level)); offsets[level] = grid; dx *= 2.0f; }// loop from fine to coarse voxel sizes auto grid = offsets[nLOD];// coarse grid - //float vol[2]; - std::cerr << std::endl; + float vol[2]; for (int level = nLOD-1; level >= 0; --level) { grid = myUpsample(*grid); - std::cerr << "Level: " << level << ", dx of offset = " << offsets[level]->voxelSize()[0] << ", dx of grid = " << grid->voxelSize()[0] << std::endl; - for (int iter = 0; iter < nErode;) { + dx = grid->voxelSize()[0]; + OPENVDB_ASSERT(dx == offsets[level]->voxelSize()[0]); + int iter = 0, end = myErode(dx); + std::cerr << "Level: " << level << ", dx = " << dx << ", D(dx) = " << end << std::endl; + while (iter < end) { grid = myShrinkWrap(*grid, *offsets[level], iter); - //vol[1] = tools::levelSetVolume(*grid); - //if (iter && math::Abs(vol[0]-vol[1]) == 0.0f ) { - // std::cerr << "iter = " << iter << " old = " << vol[0] << ", new = " << vol[1] << std::endl; - // break; - //} - //vol[0] = vol[1]; + vol[1] = tools::levelSetVolume(*grid); + if (iter && math::Abs(vol[0]-vol[1]) == 0.0f ) { + std::cerr << "\tTermination after " << iter << " steps, vol = " << vol[0] << std::endl; + break; + } + vol[0] = vol[1]; } }// loop from coarse to fine voxel sizes -#endif if (mParser.verbose) mTimer.stop(); From c3b9e5af427da1a10f8d0de8236270c078a11909 Mon Sep 17 00:00:00 2001 From: Ken Museth Date: Sun, 18 Jan 2026 14:10:34 -0800 Subject: [PATCH 048/275] improved documentation Signed-off-by: Ken Museth --- openvdb_cmd/vdb_tool/include/Tool.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openvdb_cmd/vdb_tool/include/Tool.h b/openvdb_cmd/vdb_tool/include/Tool.h index 4ad5528588..017c3eadde 100644 --- a/openvdb_cmd/vdb_tool/include/Tool.h +++ b/openvdb_cmd/vdb_tool/include/Tool.h @@ -443,7 +443,7 @@ void Tool::init() mParser.addAction( "ls2mesh", "l2m", "Convert a level set to an adaptive polygon mesh", - {{"adapt", "0.0", "0.9", "normalized metric for the adaptive meshing. 0 is uniform and 1 is fully adaptive mesh. Defaults to 0."}, + {{"adapt", "0.0", "0.005", "normalized metric for the adaptive meshing. 0 is uniform and 1 is extreme adaptivity. Defaults to 0."}, {"iso", "0.0", "0.1", "iso-value used to define the implicit surface. Defaults to zero."}, {"vdb", "0", "0", "age (i.e. stack index) of the level set VDB grid to be meshed. Defaults to 0, i.e. most recently inserted VDB."}, {"mask","-1", "1", "age (i.e. stack index) of the level set VDB grid used as a surface mask during meshing. Defaults to -1, i.e. it's disabled."}, From 706e2fd4e1c05d8ab9696c800784ab51b100295b Mon Sep 17 00:00:00 2001 From: Ken Museth Date: Sun, 18 Jan 2026 18:50:07 -0800 Subject: [PATCH 049/275] temp fix to read of binary STL files Signed-off-by: Ken Museth --- openvdb_cmd/vdb_tool/include/Geometry.h | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/openvdb_cmd/vdb_tool/include/Geometry.h b/openvdb_cmd/vdb_tool/include/Geometry.h index 1098760cce..a10b7e0b9f 100644 --- a/openvdb_cmd/vdb_tool/include/Geometry.h +++ b/openvdb_cmd/vdb_tool/include/Geometry.h @@ -938,12 +938,27 @@ void Geometry::readPTS(const std::string &fileName) // Reading ASCII or binary STL file void Geometry::readSTL(const std::string &fileName) { + //std::cerr << "entering readSTL\n"; std::ifstream infile(fileName, std::ios::in | std::ios::binary); if (!infile.is_open()) throw std::runtime_error("Geometry::readSTL: Error opening STL file \""+fileName+"\""); PosT xyz; char buffer[80] = "";// small fixed stack allocated buffer if (!infile.read(buffer, 5)) throw std::invalid_argument("Geometry::readSTL: Failed to head header"); - if (strcmp(buffer, "solid") == 0) {//ASCII file + + auto isAscii = [&]()->bool{ + if (strcmp(buffer, "solid") != 0) return false;// binary + std::string line; + while (std::getline(infile, line)) { + std::string tmp = trim(line, " ");// remove leading (and trailing) white spaces + if (tmp.compare(0, 5, "facet")==0) return true;// ascii + } + return false; + }; + const bool test = isAscii(); + infile.clear(); + infile.seekg(5); + if (test) {//ASCII file + //std::cerr << "ASCII: buffer: " << buffer << std::endl; std::string line; std::getline(infile, line);// read rest of the first line, which completes the header std::istringstream iss; @@ -979,6 +994,7 @@ void Geometry::readSTL(const std::string &fileName) } }// loop over lines in file } else {// binary file + //std::cerr << "binary STL\n"; if (!isLittleEndian()) throw std::invalid_argument("Geometry::readSTL binary: STL file only supports little endian, but this system is big endian"); if (!infile.read(buffer, 80 - 5)) throw std::invalid_argument("Geometry::readSTL binary: Failed to head header"); uint32_t numTri; From 49ca51a0e0dbeb4e63188a587d66b8e10ac4c2a6 Mon Sep 17 00:00:00 2001 From: Ken Museth Date: Sun, 18 Jan 2026 20:25:28 -0800 Subject: [PATCH 050/275] improved fix to read of binary STL files Signed-off-by: Ken Museth --- openvdb_cmd/vdb_tool/include/Geometry.h | 33 +++++++++---------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/openvdb_cmd/vdb_tool/include/Geometry.h b/openvdb_cmd/vdb_tool/include/Geometry.h index a10b7e0b9f..282e2de2ec 100644 --- a/openvdb_cmd/vdb_tool/include/Geometry.h +++ b/openvdb_cmd/vdb_tool/include/Geometry.h @@ -938,29 +938,21 @@ void Geometry::readPTS(const std::string &fileName) // Reading ASCII or binary STL file void Geometry::readSTL(const std::string &fileName) { - //std::cerr << "entering readSTL\n"; std::ifstream infile(fileName, std::ios::in | std::ios::binary); if (!infile.is_open()) throw std::runtime_error("Geometry::readSTL: Error opening STL file \""+fileName+"\""); PosT xyz; - char buffer[80] = "";// small fixed stack allocated buffer - if (!infile.read(buffer, 5)) throw std::invalid_argument("Geometry::readSTL: Failed to head header"); - + std::array buffer{}; + if (!infile.read(buffer.data(), buffer.size())) throw std::invalid_argument("Geometry::readSTL: Failed to head header"); + infile.clear(); + infile.seekg(0, std::ios_base::beg);// rewind auto isAscii = [&]()->bool{ - if (strcmp(buffer, "solid") != 0) return false;// binary - std::string line; - while (std::getline(infile, line)) { - std::string tmp = trim(line, " ");// remove leading (and trailing) white spaces - if (tmp.compare(0, 5, "facet")==0) return true;// ascii - } - return false; + std::string str(buffer.data(), infile.gcount()); + toLowerCase(str); + return contains(str, "solid") && contains(str, '\n') && contains(str, "facet") && contains(str, "normal"); }; - const bool test = isAscii(); - infile.clear(); - infile.seekg(5); - if (test) {//ASCII file - //std::cerr << "ASCII: buffer: " << buffer << std::endl; + if (isAscii()) {//ASCII file std::string line; - std::getline(infile, line);// read rest of the first line, which completes the header + std::getline(infile, line);// read the first line, which completes the header std::istringstream iss; while(std::getline(infile, line)) { std::string tmp = trim(line, " ");// remove leading (and trailing) white spaces @@ -994,9 +986,8 @@ void Geometry::readSTL(const std::string &fileName) } }// loop over lines in file } else {// binary file - //std::cerr << "binary STL\n"; if (!isLittleEndian()) throw std::invalid_argument("Geometry::readSTL binary: STL file only supports little endian, but this system is big endian"); - if (!infile.read(buffer, 80 - 5)) throw std::invalid_argument("Geometry::readSTL binary: Failed to head header"); + if (!infile.read(buffer.data(), 80)) throw std::invalid_argument("Geometry::readSTL binary: Failed to head header"); uint32_t numTri; if (!infile.read((char*)&numTri, sizeof(numTri))) throw std::invalid_argument("Geometry::readSTL binary: Failed to read triangle count"); infile.seekg (0, infile.end); @@ -1008,8 +999,8 @@ void Geometry::readSTL(const std::string &fileName) Vec3f *pV = mVtx.data() + vtxBegin; Vec3I *pT = mTri.data() + triBegin; for (uint32_t i = 0; i < numTri; ++i) {// loop over triangles - if (!infile.read(buffer, 50)) throw std::invalid_argument("Geometry::readSTL binary: error reading triangle #"+std::to_string(i)); - const float *p = 3 + reinterpret_cast(buffer);// ignore 3 vector components of normal + if (!infile.read(buffer.data(), 50)) throw std::invalid_argument("Geometry::readSTL binary: error reading triangle #"+std::to_string(i)); + const float *p = 3 + reinterpret_cast(buffer.data());// ignore 3 vector components of normal for (int j=0; j<3; ++j) {// loop over vertices of triangle for (int k=0; k<3; ++k) xyz[k] = *p++;//loop over coordinates of vertex *pV++ = xyz; From 5eb19641f6fbf6f2de3c7d6806f0965389d2d893 Mon Sep 17 00:00:00 2001 From: Ken Museth Date: Wed, 21 Jan 2026 23:07:30 -0800 Subject: [PATCH 051/275] added triangulation to Geometry.h Signed-off-by: Ken Museth --- openvdb_cmd/vdb_tool/include/Geometry.h | 82 ++++++++++++++++++++----- openvdb_cmd/vdb_tool/include/Tool.h | 2 + 2 files changed, 70 insertions(+), 14 deletions(-) diff --git a/openvdb_cmd/vdb_tool/include/Geometry.h b/openvdb_cmd/vdb_tool/include/Geometry.h index 282e2de2ec..71be52f117 100644 --- a/openvdb_cmd/vdb_tool/include/Geometry.h +++ b/openvdb_cmd/vdb_tool/include/Geometry.h @@ -142,6 +142,11 @@ class Geometry inline void transform(const math::Transform &xform); + /// @brief Triangulates each quad into two triangles, using the shortest diagonal + /// @return number of new triangles appended + /// @note The quads are removed while the vertex list is unchanged + size_t triangulateQuads(); + bool isEmpty() const { return mVtx.empty() && mTri.empty() && mQuad.empty(); } bool isPoints() const { return !mVtx.empty() && mTri.empty() && mQuad.empty(); } bool isMesh() const { return !mVtx.empty() && (!mTri.empty() || !mQuad.empty()); } @@ -154,6 +159,8 @@ class Geometry size_t write(std::ostream &os) const; size_t read(std::istream &is); + static std::vector triangulate(const std::vector &nGon); + private: std::vector mVtx; @@ -368,7 +375,7 @@ void Geometry::writeSTL(const std::string &fileName) const void Geometry::writeSTL(std::ostream &os) const { if (!isLittleEndian()) throw std::invalid_argument("STL file only supports little endian, but this system is big endian"); - if (!mQuad.empty()) throw std::invalid_argument("STL file only supports triangles"); + if (!mQuad.empty()) throw std::invalid_argument("Binary STL files only supports triangles, but the mesh contains quads"); uint8_t buffer[80] = {0};// fixed-sized buffer initiated with zeros! os.write((const char*)buffer, 80);// write header as zeros const uint32_t nTri = static_cast(mTri.size()); @@ -477,7 +484,9 @@ void Geometry::readOBJ(std::istream &is) mQuad.emplace_back(v[0] - 1, v[1] - 1, v[2] - 1, v[3] - 1);// obj is 1-based break; default: - throw std::invalid_argument("Geometry::readOBJ: " + std::to_string(v.size()) + "-gons are not supported"); + std::cerr << "Geometry::readOBJ: triangulating " << v.size() << "-gon\n"; + for (size_t i = 0; i < v.size()-2; ++i) mTri.emplace_back(v[0] - 1, v[i+1] - 1, v[i+2] - 1);// obj is 1-based + //throw std::invalid_argument("Geometry::readOBJ: " + std::to_string(v.size()) + "-gons are not supported"); break; } } @@ -801,7 +810,8 @@ void Geometry::readPLY(std::istream &is) } // read polygon vertex lists - uint32_t vtx[4]; + static const int nGon = 10;// maximum allowed nGon + uint32_t vtx[nGon]; if (format) {// binary char *buffer = static_cast(std::malloc(faceSkip[0].bytes + 1));// uninitialized if (buffer==nullptr) throw std::invalid_argument("Geometry::readPLY: failed to allocate buffer"); @@ -820,7 +830,11 @@ void Geometry::readPLY(std::istream &is) mQuad.emplace_back(vtx); break; default: - throw std::invalid_argument("Geometry::readPLY: binary " + std::to_string(n) + "-gons are not supported"); + if (n > nGon) throw std::invalid_argument("Geometry::readPLY: binary " + std::to_string(n) + "-gons are not supported"); + std::cerr << "Geometry::readPLY: binary triangulating " << n << "-gon\n"; + is.read((char*)vtx, n*sizeof(uint32_t)); + if (reverseBytes) swapBytes(vtx, n); + for (int i = 0; i < n-2; ++i) mTri.emplace_back(vtx[0], vtx[i+1], vtx[i+2]); break; } is.ignore(faceSkip[1].bytes); @@ -831,12 +845,15 @@ void Geometry::readPLY(std::istream &is) tokens = tokenize_line(); const std::string polySize = tokens[faceSkip[0].count]; const int n = std::stoi(polySize); - if (n!=3 && n!=4) throw std::invalid_argument("Geometry::readPLY: ascii " + polySize + "-gons are not supported"); + if ( n < 3 || n > nGon) throw std::invalid_argument("Geometry::readPLY: ascii " + polySize + "-gons are not supported"); for (int i = 0, j=1+faceSkip[0].count; i(std::stoll(tokens[j])); if (n==3) { mTri.emplace_back(vtx); - } else { + } else if (n==4) { mQuad.emplace_back(vtx); + } else { + std::cerr << "Geometry::readPLY: ascii triangulating " << n << "-gon\n"; + for (int i = 0; i < n - 2; ++i) mTri.emplace_back(vtx[0], vtx[i+1], vtx[i+2]); } }// loop over polygons } @@ -942,7 +959,7 @@ void Geometry::readSTL(const std::string &fileName) if (!infile.is_open()) throw std::runtime_error("Geometry::readSTL: Error opening STL file \""+fileName+"\""); PosT xyz; std::array buffer{}; - if (!infile.read(buffer.data(), buffer.size())) throw std::invalid_argument("Geometry::readSTL: Failed to head header"); + if (!infile.read(buffer.data(), buffer.size())) throw std::invalid_argument("Geometry::readSTL: Failed to read header in \""+fileName+"\""); infile.clear(); infile.seekg(0, std::ios_base::beg);// rewind auto isAscii = [&]()->bool{ @@ -965,11 +982,12 @@ void Geometry::readSTL(const std::string &fileName) OPENVDB_ASSERT(tmp.compare(0, 6, "vertex")==0); iss.clear(); iss.str(tmp.substr(6)); - if (iss >> xyz[0] >> xyz[1] >> xyz[2]) { - mVtx.push_back(xyz); + double p[3];// more robust to read ascii coordinates as double than float + if (iss >> p[0] >> p[1] >> p[2]) { + mVtx.emplace_back(float(p[0]), float(p[1]), float(p[2])); ++nGone; } else { - throw std::invalid_argument("Geometry::readSTL ASCII: error parsing line: \""+line+"\""); + throw std::invalid_argument("Geometry::readSTL ASCII: error parsing line: \""+line+"\" in \""+fileName+"\""); } }// endloop const int vtx = static_cast(mVtx.size()) - 1; @@ -981,17 +999,18 @@ void Geometry::readSTL(const std::string &fileName) mQuad.emplace_back(vtx - 3, vtx - 2, vtx - 1, vtx); break; default: + // could be fixed as in readOBJ! throw std::invalid_argument("Geometry::readSTL ASCII: " + std::to_string(nGone)+"-gons are not supported"); } } }// loop over lines in file } else {// binary file if (!isLittleEndian()) throw std::invalid_argument("Geometry::readSTL binary: STL file only supports little endian, but this system is big endian"); - if (!infile.read(buffer.data(), 80)) throw std::invalid_argument("Geometry::readSTL binary: Failed to head header"); + if (!infile.read(buffer.data(), 80)) throw std::invalid_argument("Geometry::readSTL binary: Failed to read header in \""+fileName+"\""); uint32_t numTri; - if (!infile.read((char*)&numTri, sizeof(numTri))) throw std::invalid_argument("Geometry::readSTL binary: Failed to read triangle count"); + if (!infile.read((char*)&numTri, sizeof(numTri))) throw std::invalid_argument("Geometry::readSTL binary: Failed to read triangle count in \""+fileName+"\""); infile.seekg (0, infile.end); - if (infile.tellg() != 80 + 4 + 50*numTri) throw std::invalid_argument("Geometry::readSTL binary: Unexpected file size"); + if (infile.tellg() != 80 + 4 + 50*numTri) throw std::invalid_argument("Geometry::readSTL binary: Unexpected file size in \""+fileName+"\""); infile.seekg(80 + 4, infile.beg); uint32_t vtxBegin = static_cast(mVtx.size()), triBegin = static_cast(mTri.size()); mVtx.resize(vtxBegin + 3*numTri); @@ -999,7 +1018,7 @@ void Geometry::readSTL(const std::string &fileName) Vec3f *pV = mVtx.data() + vtxBegin; Vec3I *pT = mTri.data() + triBegin; for (uint32_t i = 0; i < numTri; ++i) {// loop over triangles - if (!infile.read(buffer.data(), 50)) throw std::invalid_argument("Geometry::readSTL binary: error reading triangle #"+std::to_string(i)); + if (!infile.read(buffer.data(), 50)) throw std::invalid_argument("Geometry::readSTL binary: error reading triangle #"+std::to_string(i)+" in \""+fileName+"\""); const float *p = 3 + reinterpret_cast(buffer.data());// ignore 3 vector components of normal for (int j=0; j<3; ++j) {// loop over vertices of triangle for (int k=0; k<3; ++k) xyz[k] = *p++;//loop over coordinates of vertex @@ -1306,6 +1325,41 @@ void Geometry::transform(const math::Transform &xform) mBBox = BBoxT();//invalidate BBox }// Geometry::transform +size_t Geometry::triangulateQuads() +{ + const size_t quadCount = mQuad.size(); + if (quadCount == 0) return 0; + const size_t triCount = mTri.size(); + mTri.resize(triCount + 2*quadCount); + using RangeT = tbb::blocked_range; + tbb::parallel_for(RangeT(0, quadCount), [&](RangeT r){ + for (size_t i=r.begin(); i Geometry::triangulate(const std::vector &nGon) +{ + OPENVDB_ASSERT(nGon.size()>=3); + std::vector tri(nGon.size() - 2); + for (size_t i = 0; i < tri.size(); ++i) tri[i] = Vec3I(nGon[0], nGon[i + 1], nGon[i + 2]); + return tri; +}; + } // namespace vdb_tool } // namespace OPENVDB_VERSION_NAME } // namespace openvdb diff --git a/openvdb_cmd/vdb_tool/include/Tool.h b/openvdb_cmd/vdb_tool/include/Tool.h index 017c3eadde..7658c69fa5 100644 --- a/openvdb_cmd/vdb_tool/include/Tool.h +++ b/openvdb_cmd/vdb_tool/include/Tool.h @@ -13,6 +13,8 @@ /// generate adaptive polygon meshes from level sets, render images and write particles, /// meshes or VDBs them to disk. /// +/// @todo use LevelSetMeasure, write binary/ascii +/// //////////////////////////////////////////////////////////////////////////////// #ifndef VDB_TOOL_HAS_BEEN_INCLUDED From 01c3c6bcc1606d860d3f260121235ebeb592a83f Mon Sep 17 00:00:00 2001 From: Ken Museth Date: Thu, 22 Jan 2026 09:29:22 -0800 Subject: [PATCH 052/275] added vdb_tool -quad2tri Signed-off-by: Ken Museth --- openvdb_cmd/vdb_tool/include/Geometry.h | 5 +++- openvdb_cmd/vdb_tool/include/Tool.h | 31 ++++++++++++++++++++++++- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/openvdb_cmd/vdb_tool/include/Geometry.h b/openvdb_cmd/vdb_tool/include/Geometry.h index 71be52f117..34e7d31a6c 100644 --- a/openvdb_cmd/vdb_tool/include/Geometry.h +++ b/openvdb_cmd/vdb_tool/include/Geometry.h @@ -959,7 +959,10 @@ void Geometry::readSTL(const std::string &fileName) if (!infile.is_open()) throw std::runtime_error("Geometry::readSTL: Error opening STL file \""+fileName+"\""); PosT xyz; std::array buffer{}; - if (!infile.read(buffer.data(), buffer.size())) throw std::invalid_argument("Geometry::readSTL: Failed to read header in \""+fileName+"\""); + if (!infile.read(buffer.data(), buffer.size())) { + std::cerr << "Geometry::readSTL: Failed to read 256B in \""+fileName+"\" so this must be an empty STL file\n"; + return; + } infile.clear(); infile.seekg(0, std::ios_base::beg);// rewind auto isAscii = [&]()->bool{ diff --git a/openvdb_cmd/vdb_tool/include/Tool.h b/openvdb_cmd/vdb_tool/include/Tool.h index 7658c69fa5..c75057a7d5 100644 --- a/openvdb_cmd/vdb_tool/include/Tool.h +++ b/openvdb_cmd/vdb_tool/include/Tool.h @@ -13,7 +13,7 @@ /// generate adaptive polygon meshes from level sets, render images and write particles, /// meshes or VDBs them to disk. /// -/// @todo use LevelSetMeasure, write binary/ascii +/// @todo expose LevelSetMeasure, write binary/ascii /// //////////////////////////////////////////////////////////////////////////////// @@ -194,6 +194,9 @@ class Tool /// @brief Converts a polygon mesh into a narrow-band level set, i.e. a narrow-band signed distance to a polygon mesh void soupToLevelSet(); + /// @brief Converts all quads into triangles + void quadsToTriangles(); + /// @brief construct a LoD sequences of VDB trees with powers of two refinements void multires(); @@ -419,6 +422,11 @@ void Tool::init() {"name", "sphere", "sphere", "name assigned to the level set sphere"}}, [&](){mParser.setDefaults();}, [&](){this->levelSetSphere();}); + mParser.addAction( + "quad2tri", "q2t", "Convert all quads in mesh to triangles, assuming they are both planar and convex", + {{"geo", "0", "0", "age (i.e. stack index) of the geometry to be processed. Defaults to 0, i.e. most recently inserted geometry."}}, + [&](){mParser.setDefaults();}, [&](){this->quadsToTriangles();}); + mParser.addAction( "mesh2ls", "m2ls", "Convert a polygon mesh into a narrow-band level set, i.e. a narrow-band signed distance to a polygon mesh", {{"dim", "", "256", "largest dimension in voxel units of the mesh bbox (defaults to 256). If \"vdb\" or \"voxel\" is defined then \"dim\" is ignored"}, @@ -1658,6 +1666,27 @@ float Tool::estimateVoxelSize(int maxDim, float halfWidth, int geo_age) // ============================================================================================================== +void Tool::quadsToTriangles() +{ + const std::string &name = mParser.getAction().name; + OPENVDB_ASSERT(name == "quad2tri"); + try { + mParser.printAction(); + const int geo_age = mParser.get("geo"); + auto it = this->getGeom(geo_age); + Geometry &mesh = **it; + + if (mesh.isPoints()) this->warning("Warning: -quad2tri was called on points, not a mesh!"); + if (mParser.verbose) mTimer.start("Quads -> Triangles"); + mesh.triangulateQuads(); + if (mParser.verbose) mTimer.stop(); + } catch (const std::exception& e) { + throw std::invalid_argument(name+": "+e.what()); + } +}// Tool::quadsToTriangles + +// ============================================================================================================== + void Tool::meshToLevelSet() { const std::string &name = mParser.getAction().name; From 32bfd45cf987a360fcb19de850eaeeccccc3b02d Mon Sep 17 00:00:00 2001 From: Ken Museth Date: Fri, 23 Jan 2026 13:37:07 -0800 Subject: [PATCH 053/275] snapshot Signed-off-by: Ken Museth --- openvdb_cmd/vdb_tool/include/Tool.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openvdb_cmd/vdb_tool/include/Tool.h b/openvdb_cmd/vdb_tool/include/Tool.h index c75057a7d5..f73fb7a92e 100644 --- a/openvdb_cmd/vdb_tool/include/Tool.h +++ b/openvdb_cmd/vdb_tool/include/Tool.h @@ -1838,7 +1838,7 @@ void Tool::soupToLevelSet() dx = grid->voxelSize()[0]; OPENVDB_ASSERT(dx == offsets[level]->voxelSize()[0]); int iter = 0, end = myErode(dx); - std::cerr << "Level: " << level << ", dx = " << dx << ", D(dx) = " << end << std::endl; + std::cerr << "Level: " << level << ", D(" << dx << ") = " << end << std::endl; while (iter < end) { grid = myShrinkWrap(*grid, *offsets[level], iter); vol[1] = tools::levelSetVolume(*grid); From 968ce7132227835320532923b55064276431d9e5 Mon Sep 17 00:00:00 2001 From: Ken Museth Date: Fri, 23 Jan 2026 14:47:56 -0800 Subject: [PATCH 054/275] add keep to quad2tri and fixed a bug in readOBJ Signed-off-by: Ken Museth --- openvdb_cmd/vdb_tool/include/Geometry.h | 32 ++++++++++++------------- openvdb_cmd/vdb_tool/include/Tool.h | 20 +++++++++++----- 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/openvdb_cmd/vdb_tool/include/Geometry.h b/openvdb_cmd/vdb_tool/include/Geometry.h index 34e7d31a6c..c3ebc2cf16 100644 --- a/openvdb_cmd/vdb_tool/include/Geometry.h +++ b/openvdb_cmd/vdb_tool/include/Geometry.h @@ -473,21 +473,19 @@ void Geometry::readOBJ(std::istream &is) if (iss >> c[0] >> c[1] >> c[2]) mRGB.push_back(c); } else if (str == "f") { std::vector v; - while (iss >> str) { - v.push_back(std::stoi(str.substr(0, str.find_first_of("/")))); - } - switch (v.size()) { - case 3: + while (iss >> str) v.push_back(std::stoi(str.substr(0, str.find_first_of("/")))); + const size_t nGon = v.size(); + if (nGon == 1) { + std::cerr << "Geometry::readOBJ: ignoring point, i.e. a face with with a single vertex\n"; + } else if (nGon == 2) { + std::cerr << "Geometry::readOBJ: ignoring line, i.e. a face with two vertices\n"; + } else if (nGon == 3) { mTri.emplace_back(v[0] - 1, v[1] - 1, v[2] - 1);// obj is 1-based - break; - case 4: + } else if (nGon == 4) { mQuad.emplace_back(v[0] - 1, v[1] - 1, v[2] - 1, v[3] - 1);// obj is 1-based - break; - default: - std::cerr << "Geometry::readOBJ: triangulating " << v.size() << "-gon\n"; - for (size_t i = 0; i < v.size()-2; ++i) mTri.emplace_back(v[0] - 1, v[i+1] - 1, v[i+2] - 1);// obj is 1-based - //throw std::invalid_argument("Geometry::readOBJ: " + std::to_string(v.size()) + "-gons are not supported"); - break; + } else { + std::cerr << "Geometry::readOBJ: triangulating " << nGon << "-gon\n"; + for (size_t i = 0; i < nGon - 2; ++i) mTri.emplace_back(v[0] - 1, v[i+1] - 1, v[i+2] - 1);// obj is 1-based } } } @@ -978,7 +976,7 @@ void Geometry::readSTL(const std::string &fileName) std::string tmp = trim(line, " ");// remove leading (and trailing) white spaces if (tmp.compare(0, 5, "facet")==0) { while (std::getline(infile, line) && trim(line, " ").compare(0, 10, "outer loop")); - int nGone = 0; + int nGon = 0; while(std::getline(infile, line)) {// loop over vertices of the facet tmp = trim(line, " "); if (tmp.compare(0, 7, "endloop")==0) break; @@ -988,13 +986,13 @@ void Geometry::readSTL(const std::string &fileName) double p[3];// more robust to read ascii coordinates as double than float if (iss >> p[0] >> p[1] >> p[2]) { mVtx.emplace_back(float(p[0]), float(p[1]), float(p[2])); - ++nGone; + ++nGon; } else { throw std::invalid_argument("Geometry::readSTL ASCII: error parsing line: \""+line+"\" in \""+fileName+"\""); } }// endloop const int vtx = static_cast(mVtx.size()) - 1; - switch (nGone){ + switch (nGon){ case 3: mTri.emplace_back(vtx - 2, vtx - 1, vtx); break; @@ -1003,7 +1001,7 @@ void Geometry::readSTL(const std::string &fileName) break; default: // could be fixed as in readOBJ! - throw std::invalid_argument("Geometry::readSTL ASCII: " + std::to_string(nGone)+"-gons are not supported"); + throw std::invalid_argument("Geometry::readSTL ASCII: " + std::to_string(nGon)+"-gons are not supported"); } } }// loop over lines in file diff --git a/openvdb_cmd/vdb_tool/include/Tool.h b/openvdb_cmd/vdb_tool/include/Tool.h index f73fb7a92e..0356262a40 100644 --- a/openvdb_cmd/vdb_tool/include/Tool.h +++ b/openvdb_cmd/vdb_tool/include/Tool.h @@ -424,7 +424,8 @@ void Tool::init() mParser.addAction( "quad2tri", "q2t", "Convert all quads in mesh to triangles, assuming they are both planar and convex", - {{"geo", "0", "0", "age (i.e. stack index) of the geometry to be processed. Defaults to 0, i.e. most recently inserted geometry."}}, + {{"geo", "0", "0", "age (i.e. stack index) of the geometry to be processed. Defaults to 0, i.e. most recently inserted geometry."}, + {"keep", "", "1|0|true|false", "toggle wether the input geometry is preserved or deleted after the conversion"}}, [&](){mParser.setDefaults();}, [&](){this->quadsToTriangles();}); mParser.addAction( @@ -1673,12 +1674,19 @@ void Tool::quadsToTriangles() try { mParser.printAction(); const int geo_age = mParser.get("geo"); - auto it = this->getGeom(geo_age); - Geometry &mesh = **it; - - if (mesh.isPoints()) this->warning("Warning: -quad2tri was called on points, not a mesh!"); + const bool keep = mParser.get("keep"); + Geometry::Ptr mesh = *this->getGeom(geo_age); + if (mesh->isPoints()) { + this->warning("Warning: -quad2tri was called on points, i.e. no quads!"); + return; + } + if (keep) { + Geometry::Ptr meshCopy = mesh->copyGeom(); + mGeom.push_back(meshCopy); + mesh = meshCopy; + } if (mParser.verbose) mTimer.start("Quads -> Triangles"); - mesh.triangulateQuads(); + mesh->triangulateQuads(); if (mParser.verbose) mTimer.stop(); } catch (const std::exception& e) { throw std::invalid_argument(name+": "+e.what()); From 05ca232c804f974b42303a309e86baad33904992 Mon Sep 17 00:00:00 2001 From: Ken Museth Date: Fri, 23 Jan 2026 18:23:12 -0800 Subject: [PATCH 055/275] removed CHANGES.md and improved documentation Signed-off-by: Ken Museth --- openvdb_cmd/vdb_tool/CHANGES.md | 107 -------------------------------- openvdb_cmd/vdb_tool/README.md | 7 +++ 2 files changed, 7 insertions(+), 107 deletions(-) delete mode 100644 openvdb_cmd/vdb_tool/CHANGES.md diff --git a/openvdb_cmd/vdb_tool/CHANGES.md b/openvdb_cmd/vdb_tool/CHANGES.md deleted file mode 100644 index 8c589c74ec..0000000000 --- a/openvdb_cmd/vdb_tool/CHANGES.md +++ /dev/null @@ -1,107 +0,0 @@ - -# Changes and to-dos: - -- [X] vdb_tool::readGeo -- [X] vdb_tool::readVDB -- [X] vdb_tool::particlesToLevelSet -- [X] vdb_tool::processLevelSet -- [X] vdb_tool::offsetLevelSet -- [X] vdb_tool::filterLevelSet -- [X] vdb_tool::levelSetToMesh -- [X] vdb_tool::writeGeo -- [X] vdb_tool::writeVDB -- [X] read ASCI obj particle files -- [X] read ASCI ply particle files -- [X] read binary ply particle files -- [X] write binary ply mesh files -- [X] write ascii obj mesh files -- [X] Geometry::readVdb -- [X] Geometry::readPts -- [X] define time and space order -- [X] Mesh::readPly -- [X] vdb_tool::readMesh -- [X] vdb_tool::meshToLevelSet -- [X] Geometry::readObj -- [X] Geometry::readPly -- [X] Geometry::readNvdb -- [X] vdb_tool::writeVDB -- [X] allow actions to have multiple "-" -- [X] add "-sphere" -- [X] add volume/geometry ages to all actions -- [X] add CSG operations -- [X] "-read" supports multiple files -- [X] "-write" supports multiple files -- [X] added "-print" -- [X] works with tcsh, sh, ksh, and zsh shells -- [X] added "-default" -- [X] cache a list of base grids instead of FloatGrids -- [X] -points2vdb : points -> PointDataGrid -- [X] -vdb2points : PointDataGrid -> points -- [X] -write geo=1 vdb=1,3 file.ply file.vdb -- [X] -iso2ls, convert scalar field to level set -- [X] -ls2fog, convert level set to fog volume -- [X] -scatter, scatter points -- [X] -prune, prune level set -- [X] -flood, signed flood fill of level set -- [X] -multires, generate multi-resolution grids -- [X] -expand, expand narrow band of level set -- [X] -cpt, generate closest-point transfer -- [X] -grad, generate gradient field -- [X] -div, generate divergence from vector field -- [X] -curl, generate curl from vector field -- [X] -curvature, generate mean curvature from scalar field -- [X] -length, generate length of vector field -- [X] -render, render level set and fog volumes -- [X] -enright, performs advection test on level set -- [X] -for i=0,10,1 -end -- [X] -each s=str1,str2 -end -- [X] -read grids=sphere file_%4i.vdb -- [X] Geometry::readSTL -- [X] Geometry::writeSTL -- [X] -clip against either a mask grid, bbox or frustum -- [X] Added local counter "%I" to for-loops -- [X] Added global counter "%G" -- [X] add Tool::savePNG -- [X] add Tool::saveEXR -- [X] -platonic faces=4 -- [X] -segment vdb=0 keep=0 -- [X] -resample vdb=0[,1] scale=0 translate=0,0,0 order=1[0|2] keep=0 -- [X] add Geometry::readABC -- [X] add support for unix pipelining -- [X] add Tool::saveJPG -- [X] add Geometry::read/write to support streaming -- [X] -read stdin.[ply,obj,stl,geo,vdb] -- [X] -write stdout.[ply,obj,stl,geo,vdb] -- [X] actions can now have an optional alias, e.g. -read, -i -- [X] -write file.nvdb stdout.nvdb -- [X] -write bits=32|16|8|N codec=blosc|zip|active -- [X] -help read,write,ls2mesh brief=true -- [X] use openvdb namespace -- [X] Major revision with Parser.h -- [X] -read delayed=false file.vdb -- [X] -clear vdb=1,2,3 geo=* -- [X] -config update=false execute=true configure.txt -- [X] -each f=*.vdb -- [X] add stack-based translator and storage -- [X] -eval '{1:@G}' -- [X] add if-statement: {$x:0:==:if(0.5)} equals if (x==0) 0.5 and {\$x:1:>:if(0.5:sin?0.3:cos)} equals if (x>1) sin(0.5) else cos(0.3) -- [X] add switch-statement: {\$i:switch(1:A?2:B?3:C)} equals switch(x) case 1: A; break; case 2: B; break; case 3: C -- [X] Added numerous methods to scripting language -- [X] -mesh2ls vdb=0 (use another vdb to defined the transform) -- [X] -iso2ls vdb=0,1 (use another vdb to defined the transform) -- [X] loops will now skip, instead of throw, if its initial condition is invalid -- [X] -for i=1,9 (third argument defaults to 1, i.e. i=1,9,1) -- [X] -if 0|1|false|true ... -end (if statement) -- [X] -eval help="*" or -eval help=if,switch -- [X] {date}, {uuid}, {1:a:set}, {a:get}, {a:is_set}, {sphere:sp:match} -- [X] composite: -min, -max, -sum -- [X] -transform vdb=0,3 geo=5 (scale -> rotate -> translate of VDB grids and geometry) -- [X] -print mem=1 prints variables saved to memory, e.g. loop variables -- [X] use cmake (thanks to Greg Klar!) -- [X] read NanoVDB voxel volumes (thanks to Greg Klar) -- [X] -ls2mesh iso=0.1 mask=1 invert=true -- [X] -write binary abc mesh files (thanks to Alexandre Sirois-Vigneux) -- [x] -write keep=false (by default grids and geometries written are also removed) -- [ ] -merge -- [ ] -points2mask -- [ ] -erodeTopology diff --git a/openvdb_cmd/vdb_tool/README.md b/openvdb_cmd/vdb_tool/README.md index 839ef0d475..d96df98361 100644 --- a/openvdb_cmd/vdb_tool/README.md +++ b/openvdb_cmd/vdb_tool/README.md @@ -19,6 +19,7 @@ This command-line tool, dubbed vdb_tool, can combine any number of the of high-l | **points2vdb** | Converts points into a VDB PointDataGrid | | **iso2ls** | Convert an iso-surface of a scalar field into a level set | | **ls2fog** | Convert a level set into a fog volume | +| **quad2tri** | Convert all quads in a mesh to triangles | | **segment** | Segment level set and float grids into its disconnected parts | | **sphere** | Create a narrow-band level set of a sphere | | **platonic** | Create a narrow-band level set of a tetrahedron(4), cube(6), octahedron(8), dodecahedron(12) or icosahedron(2) | @@ -193,6 +194,12 @@ Convert a polygon mesh file into a narrow-band level and save it to a file vdb_tool -read mesh.obj -mesh2ls -write level_set.vdb ``` +## Converting all quads in a mesh into triangles +Convert an obj file with n-gons into a ply file with only triangles +``` +vdb_tool -read mesh.obj -quad2tri -write mesh.ply +``` + ## Read multiple files Convert a polygon mesh file into a narrow-band level with a transform that matches a reference vdb ``` From d8a0d4cf128cd4dd2e229f7e6740ed2649ab113a Mon Sep 17 00:00:00 2001 From: Ken Museth Date: Fri, 23 Jan 2026 18:28:09 -0800 Subject: [PATCH 056/275] improved Geometry::triangulate Signed-off-by: Ken Museth --- openvdb_cmd/vdb_tool/include/Geometry.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/openvdb_cmd/vdb_tool/include/Geometry.h b/openvdb_cmd/vdb_tool/include/Geometry.h index c3ebc2cf16..4bd6e2ea2f 100644 --- a/openvdb_cmd/vdb_tool/include/Geometry.h +++ b/openvdb_cmd/vdb_tool/include/Geometry.h @@ -1355,9 +1355,11 @@ size_t Geometry::triangulateQuads() std::vector Geometry::triangulate(const std::vector &nGon) { - OPENVDB_ASSERT(nGon.size()>=3); - std::vector tri(nGon.size() - 2); - for (size_t i = 0; i < tri.size(); ++i) tri[i] = Vec3I(nGon[0], nGon[i + 1], nGon[i + 2]); + std::vector tri; + if (mGon.size()>=3) { + tri.resize(nGon.size() - 2); + for (size_t i = 0; i < tri.size(); ++i) tri[i] = Vec3I(nGon[0], nGon[i + 1], nGon[i + 2]); + } return tri; }; From b8c9a004618cb40eb610b57a725bb7a00ef61d22 Mon Sep 17 00:00:00 2001 From: Ken Museth Date: Fri, 23 Jan 2026 18:30:32 -0800 Subject: [PATCH 057/275] fixed type Signed-off-by: Ken Museth --- openvdb_cmd/vdb_tool/include/Geometry.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openvdb_cmd/vdb_tool/include/Geometry.h b/openvdb_cmd/vdb_tool/include/Geometry.h index 4bd6e2ea2f..833a67ba97 100644 --- a/openvdb_cmd/vdb_tool/include/Geometry.h +++ b/openvdb_cmd/vdb_tool/include/Geometry.h @@ -1356,7 +1356,7 @@ size_t Geometry::triangulateQuads() std::vector Geometry::triangulate(const std::vector &nGon) { std::vector tri; - if (mGon.size()>=3) { + if (nGon.size()>=3) { tri.resize(nGon.size() - 2); for (size_t i = 0; i < tri.size(); ++i) tri[i] = Vec3I(nGon[0], nGon[i + 1], nGon[i + 2]); } From 9a1119b04c2b04e94263d71e23ff76baa6b0a23f Mon Sep 17 00:00:00 2001 From: Ken Museth Date: Mon, 26 Jan 2026 09:55:59 -0800 Subject: [PATCH 058/275] snapshot - possibly broken Signed-off-by: Ken Museth --- openvdb_cmd/vdb_tool/include/Parser.h | 93 ++++++++++++++++++--------- openvdb_cmd/vdb_tool/include/Tool.h | 50 +++++++------- pendingchanges/openvdb_cmd.txt | 5 ++ 3 files changed, 91 insertions(+), 57 deletions(-) diff --git a/openvdb_cmd/vdb_tool/include/Parser.h b/openvdb_cmd/vdb_tool/include/Parser.h index 6da21469a8..94eaf81561 100644 --- a/openvdb_cmd/vdb_tool/include/Parser.h +++ b/openvdb_cmd/vdb_tool/include/Parser.h @@ -53,6 +53,21 @@ struct Option { // ============================================================================================================== struct Action { + /// @brief c-tor + Action(std::vector _names, + std::string _doc, + std::vector