Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions openvdb/openvdb/tools/PolySoupToLevelSet.h
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,9 @@ PolySoupToLevelSet<GridType>::PolySoupToLevelSet(PolySoup &&poly, int dim, float
OPENVDB_THROW(ArithmeticError, "polySoupToLevelSet: computed voxel size is not "
"finite and positive (is dim too small for the given halfWidth?)");
}
// NB: the 2*mMinVoxelSize <= mMaxVoxelSize relationship is a precondition of
// the coarse-to-fine hierarchy in process() only (not of offset()), so it is
// enforced there rather than in this shared constructor -- see process().
OPENVDB_ASSERT(2*mMinVoxelSize <= mMaxVoxelSize);
}// tools::PolySoupToLevelSet::PolySoupToLevelSet()

Expand Down Expand Up @@ -314,6 +317,9 @@ PolySoupToLevelSet<GridType>::PolySoupToLevelSet(PolySoup &&poly, float voxelSiz
if (!math::isFinite(mMaxVoxelSize) || !(mMaxVoxelSize > 0.0f)) {
OPENVDB_THROW(ArithmeticError, "polySoupToLevelSet: computed voxel size is not finite and positive");
}
// NB: the 2*mMinVoxelSize <= mMaxVoxelSize relationship is a precondition of
// the coarse-to-fine hierarchy in process() only (not of offset()), so it is
// enforced there rather than in this shared constructor -- see process().
OPENVDB_ASSERT(2*mMinVoxelSize <= mMaxVoxelSize);
}// tools::PolySoupToLevelSet::PolySoupToLevelSet()

Expand All @@ -331,6 +337,17 @@ void PolySoupToLevelSet<GridType>::process(const ShrinkWrapT &D, ProgressT *prog
mGrids.push_back(this->offset(dx, offset_mode));
}

// The loop above produces no grids when mMinVoxelSize > mMaxVoxelSize (i.e. the

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

☑️ todo: ‏We should have a test for this case now that it's fixed.

// requested voxel size exceeds maxLength/2, half the largest bounding-box
// dimension). Guard against that here: mGrids.back() below is otherwise
// undefined behaviour on an empty vector and crashes in optimized builds.
if (mGrids.empty()) {
OPENVDB_THROW(ValueError, "PolySoupToLevelSet::process: voxel size (" +
std::to_string(mMinVoxelSize) + ") is too large for this mesh; it must not "
"exceed maxLength/2 = " + std::to_string(mMaxVoxelSize) +
" (half the largest bounding-box dimension)");
}

// Coarse to fine shrink wrap algorithm
double vol[2] = {0.0, 0.0};// levelSetVolume returns Real (double); keep full precision.
// Zero-init silences a GCC -Wmaybe-uninitialized false positive:
Expand Down
45 changes: 42 additions & 3 deletions openvdb_cmd/vdb_tool/include/Tool.h
Original file line number Diff line number Diff line change
Expand Up @@ -756,6 +756,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."},
{"erode", "8", "2", "number of iterations of constrained erosion. Defaults to 8."},
{"thres", "0", "0.01", "closing (or engineering) threshold. Defaults to 0, i.e. it\'s diabled."},
{"vdb", "0", "0|0,1,2|*", "selects which of the level-set grids generated by the hierarchical shrink-wrap algorithm to output, by resolution level: 0 is the finest (highest-resolution) grid, 1 the next-finest, and so on. Accepts a single index (default 0, i.e. only the finest grid), a comma-separated list (e.g. \"0,1,2\" outputs the three finest grids), or \"*\" to output every generated grid. A runtime error is thrown if a requested level does not exist. Note: unlike other actions, here \"vdb\" selects outputs (not inputs)."},
{"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();});
Expand Down Expand Up @@ -2180,6 +2181,9 @@ void Tool::soupToLevelSet()
const float thres = mParser.get<float>("thres");
const bool keep = mParser.get<bool>("keep");
std::string grid_name = mParser.get<std::string>("name");
// Output selector (NB: selects outputs, not inputs): "*" for all generated
// grids, otherwise a list of resolution levels (0 = finest). Defaults to "0".
const std::string vdb_sel = mParser.get<std::string>("vdb");

auto it = this->getGeom(geo_age);
Geometry::Ptr mesh = *it;
Expand All @@ -2193,13 +2197,48 @@ void Tool::soupToLevelSet()
Spinner spin, *progress = mParser.verbose ? &spin : nullptr;
const tools::ShrinkWrapLimit D(nErode, thres);
tools::PolySoup poly{std::move(mesh->vtx()), std::move(mesh->tri()), std::move(mesh->quad()), mesh->bbox()};
auto grid = tools::polySoupToLevelSet<GridT>(std::move(poly), dim, voxel, D, width, progress, offset_mode);

// Build the LOD hierarchy directly (rather than the single-grid free function)
// so we can output more than just the finest level. grids() is ordered
// finest(0) -> coarsest(size-1), matching the "vdb" level indices below.
using SW = tools::PolySoupToLevelSet<GridT>;
auto sw = voxel > 0.0f ? std::make_unique<SW>(std::move(poly), voxel, width)
: std::make_unique<SW>(std::move(poly), dim, width);
sw->process(D, progress, offset_mode);

if (mParser.verbose) mTimer.stop();

const std::vector<GridT::Ptr> grids = sw->grids();// finest(0) -> coarsest
const int count = static_cast<int>(grids.size());

// Resolve the selector into a list of level indices. "*" -> every level.
std::vector<int> levels;
if (vdb_sel == "*") {
for (int i = 0; i < count; ++i) levels.push_back(i);
} else {
levels = mParser.getVec<int>("vdb");
if (levels.empty()) levels.push_back(0);
}

// Validate every requested level BEFORE pushing any grid, so an out-of-range
// index produces a clean error with no partial output on the stack.
for (const int lvl : levels) {
if (lvl < 0 || lvl >= count) {
throw std::invalid_argument("soup2ls: requested output grid vdb=" + std::to_string(lvl) +

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 suggestion: ‏Use OPENVDB_THROW to be consistent with other throws

" does not exist; the shrink-wrap hierarchy generated " + std::to_string(count) +
" grid(s), so valid levels are 0 (finest) .. " + std::to_string(count - 1) + " (coarsest)");
}
}

if (grid_name.empty()) grid_name = "soup2ls_" + mesh->getName();
grid->setName(grid_name);
mGrid.push_back(grid);
const bool multi = levels.size() > 1;
for (const int lvl : levels) {
GridT::Ptr g = grids[lvl];
// Disambiguate names only when several grids are output; a single output
// keeps the plain name (preserving the historical vdb=0 behavior).
g->setName(multi ? grid_name + "_" + std::to_string(lvl) : grid_name);
mGrid.push_back(g);
}
if (!keep) mGeom.erase(std::next(it).base());
}// Tool::soupToLevelSet
#endif// VDB_TOOL_USE_SHRINKWRAP
Expand Down
1 change: 1 addition & 0 deletions pendingchanges/openvdb_cmd.txt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ vdb_tool:
- The expression-based actions "-calc", "-forAllValues/forOnValues/forOffValues" and "-ax" gained a "file=" option that reads the kernel/expression/code from a file instead of the inline option (useful for longer programs). If both the inline option and "file" are given, "file" takes precedence.
- Actions "-for", "-each" and "-files" gained a "quiet" option (default false) that suppresses the per-iteration "Processing: ..." line for that loop without silencing the rest of the pipeline (unlike the global -quiet).
- Action "-log" now redirects std::cerr and std::cout in addition to std::clog (so warnings/errors and -print output also land in the log file), uses unit-buffered output so "watch"/"tail -f" see updates in real time, writes a self-describing header (date, version, full command line), and gained "append" (append rather than truncate) and "tee" (also echo to the terminal, default true) options.
- Action "-soup2ls" gained a "vdb" option that selects which of the level-set grids generated by the hierarchical shrink-wrap algorithm to output, by resolution level (0 is the finest / highest-resolution grid, 1 the next-finest, and so on). It accepts a single index (default 0, i.e. only the finest grid, matching the previous behavior), a comma-separated list (e.g. "vdb=0,1,2" outputs the three finest grids, named with a "_<level>" suffix), or "vdb=*" to output every generated grid. All requested levels are validated up front, so an out-of-range index throws a descriptive error before any grid is added to the stack. Note that here "vdb" selects outputs rather than inputs.

Fixes:
- Fixed issues in Geometry::readSTL so it works on all files in Thingi10K.
Expand Down
11 changes: 10 additions & 1 deletion pendingchanges/openvdb_tools.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,13 @@ tools:

New Features:
- Added a new tool PolySoupToLevelSet, which generates a LOD family of watertight shrink wrap
surfaces from a soup of polygons.
surfaces from a soup of polygons.

Fixes:
- Fixed a crash in PolySoupToLevelSet::process() when the requested voxel size is too coarse
relative to the input mesh (larger than maxLength/2, where maxLength is the largest bounding-box
dimension). Such input left the internal offset-grid list empty, and process() then dereferenced
an empty vector, segfaulting in optimized builds where the guarding assertion is compiled out.
process() now checks for this and throws a descriptive ValueError (naming the voxel size and the
maximum allowed) instead of crashing. The check is confined to process() so that offset() -- used
by the offset-only path -- continues to accept coarser voxel sizes unaffected.
Loading