-
Notifications
You must be signed in to change notification settings - Fork 765
Expand file tree
/
Copy pathTool.h
More file actions
4240 lines (3824 loc) · 210 KB
/
Copy pathTool.h
File metadata and controls
4240 lines (3824 loc) · 210 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright Contributors to the OpenVDB Project
// SPDX-License-Identifier: Apache-2.0
////////////////////////////////////////////////////////////////////////////////
///
/// @author Ken Museth
///
/// @file Tool.h
///
/// @brief Defines the Tool class, which chains together any sequence of high-level
/// OpenVDB operations exposed by the vdb_tool command-line utility.
///
/// @details Tool ties Parser (command-line action registry) and Geometry (polygon
/// mesh and point storage) together with the internal stacks of VDB grids
/// and Geometry instances. For example, it can convert a sequence of polygon
/// meshes and particles to level sets, perform a large number of operations
/// on these level set surfaces, generate adaptive polygon meshes from level
/// sets, render images, and write particles, meshes, or VDBs to disk.
///
/// @warning Human-readable output is written to the standard-error stream
/// (primarily std::clog, with std::cerr for errors), never to
/// std::cout, because std::cout is reserved for piping VDB / NanoVDB
/// grids to stdout (e.g. "-write stdout.vdb").
///
////////////////////////////////////////////////////////////////////////////////
#ifndef VDB_TOOL_HAS_BEEN_INCLUDED
#define VDB_TOOL_HAS_BEEN_INCLUDED
#include <openvdb/openvdb.h>
#include <openvdb/io/Stream.h>
#include <openvdb/util/CpuTimer.h>
#include <openvdb/util/Formats.h>
#include <openvdb/util/Assert.h>
#include <openvdb/tools/Composite.h>
#include <openvdb/tools/Count.h>// for tools::minMax (used by -print level=2)
#include <openvdb/tools/FastSweeping.h>
#include <openvdb/tools/LevelSetAdvect.h>
#include <openvdb/tools/LevelSetDilatedMesh.h>
#include <openvdb/tools/LevelSetSphere.h>
#include <openvdb/tools/LevelSetFilter.h>
#include <openvdb/tools/LevelSetMeasure.h>
#include <openvdb/tools/LevelSetMorph.h>
#include <openvdb/tools/LevelSetPlatonic.h>
#include <openvdb/tools/LevelSetRebuild.h>
#include <openvdb/tools/LevelSetUtil.h>
#include <openvdb/tools/RayIntersector.h>
#include <openvdb/tools/RayTracer.h>
#include <openvdb/tools/MeshToVolume.h>
#include <openvdb/tools/ParticlesToLevelSet.h>
#include <openvdb/tools/PolySoupToLevelSet.h>
#include <openvdb/tools/PointScatter.h>
#include <openvdb/tools/PointsToMask.h>
#include <openvdb/tools/Composite.h>
#include <openvdb/tools/VolumeToMesh.h>
#include <openvdb/tools/GridOperators.h>
#include <openvdb/tools/GridTransformer.h>
#include <openvdb/tools/FastSweeping.h>
#include <openvdb/tools/Prune.h>
#include <openvdb/tools/Clip.h>
#include <openvdb/tools/Mask.h> // for tools::interiorMask()
#include <openvdb/tools/MultiResGrid.h>
#include <openvdb/tools/SignedFloodFill.h>
#include <openvdb/tools/PointIndexGrid.h>
#include <openvdb/points/PointConversion.h>
#include <openvdb/points/PointCount.h>
#ifdef VDB_TOOL_USE_NANO
#include <nanovdb/NanoVDB.h>
#include <nanovdb/io/IO.h>
#include <nanovdb/tools/CreateNanoGrid.h>
#include <nanovdb/tools/NanoToOpenVDB.h>
#endif
#ifdef VDB_TOOL_USE_EXR
#include <OpenEXR/ImfChannelList.h>
#include <OpenEXR/ImfFrameBuffer.h>
#include <OpenEXR/ImfHeader.h>
#include <OpenEXR/ImfOutputFile.h>
#include <OpenEXR/ImfPixelType.h>
#endif
#ifdef VDB_TOOL_USE_PNG
#include <png.h>
#endif
#ifdef VDB_TOOL_USE_PDAL
#include <pdal/pdal.hpp>
#endif
#ifdef VDB_TOOL_USE_JPG
#include <jpeglib.h>
#endif
#ifdef VDB_TOOL_USE_AX
#include <openvdb_ax/ax.h>// for openvdb::ax::run (the -ax action)
#endif
#include <tbb/blocked_range2d.h>
#include <tbb/enumerable_thread_specific.h>
#include "Calculator.h"
#include "Parser.h"
#include "Geometry.h"
#if defined(_WIN32)
#include <io.h>
#else
#include <unistd.h>
#endif
#ifdef VDB_TOOL_USE_MPEG
#include <cstdlib>// for std::system
#endif
#ifndef VDB_TOOL_FFMPEG_PATH
#define VDB_TOOL_FFMPEG_PATH "ffmpeg"
#endif
namespace openvdb {
OPENVDB_USE_VERSION_NAMESPACE
namespace OPENVDB_VERSION_NAME {
namespace vdb_tool {
/// @brief Top-level command-line tool that chains OpenVDB high-level operations.
/// @details Owns the action parser, the stacks of in-flight Geometry and VDB grids,
/// and the log-redirection state. A Tool instance is constructed from argv,
/// which registers and parses all command-line actions; run() then executes
/// them in order. The class is non-copyable and non-movable.
class Tool
{
public:
/// @brief Construct a Tool from command-line arguments.
/// @param argc Argument count, as received by main().
/// @param argv Argument vector, as received by main(). argv[0] is taken as the command name.
/// @throw std::invalid_argument if parsing fails (unknown action, malformed option, etc.).
Tool(int argc, char *argv[]);
/// @brief Destructor; restores std::clog if a log file was opened.
~Tool() {this->endLog();}
Tool(const Tool&) = delete; ///< Copy construction is disabled.
Tool(Tool&&) = delete; ///< Move construction is disabled.
Tool& operator=(const Tool&) = delete; ///< Copy assignment is disabled.
Tool& operator=(Tool&&) = delete; ///< Move assignment is disabled.
/// @brief Execute every action that was registered during construction, in order.
/// @note On a fatal exception inside an action this method writes the message to
/// std::cerr and calls std::exit(EXIT_FAILURE) rather than propagating.
void run();
/// @brief Redirect std::clog/std::cerr/std::cout to a log file for the remainder of this Tool's lifetime.
/// @param logFile Path of the log file. If empty, a timestamped name is generated.
/// @param append If true, append to the existing file; otherwise truncate it (default).
/// @param tee If true (default), output is also written to the original terminal stream so the
/// user keeps interactive feedback while the file accumulates the same data. If false,
/// output is routed exclusively to the log file (the pre-#6 behaviour).
/// @throw std::invalid_argument if the file cannot be opened or the standard stream buffers cannot be captured.
/// @note Subsequent calls are no-ops while a redirection is already active.
void startLog(std::string logFile, bool append = false, bool tee = true);
/// @brief Restore std::clog/std::cerr/std::cout to their original buffers and close the log file (if any).
void endLog() {
if (mOldClogBuffer) std::clog.rdbuf(mOldClogBuffer);
if (mOldCerrBuffer) std::cerr.rdbuf(mOldCerrBuffer);
if (mOldCoutBuffer) std::cout.rdbuf(mOldCoutBuffer);
if (mLogFile.is_open()) mLogFile.close();
mOldClogBuffer = nullptr;
mOldCerrBuffer = nullptr;
mOldCoutBuffer = nullptr;
mClogTee.reset();
mCerrTee.reset();
mCoutTee.reset();
}
/// @brief Print a summary of the current VDB-grid and Geometry stacks to @a os.
void print(std::ostream& os = std::clog) const;
/// @brief Print the canonical "-action option=value ..." form of every queued action to @a os.
void print_args(std::ostream& os = std::clog) const;
/// @brief Return the current version of this tool as "major.minor.patch".
static std::string version() {return std::to_string(sMajor)+"."+std::to_string(sMinor)+"."+std::to_string(sPatch);}
/// @brief Return the major version (incremented on incompatible option/file changes).
static int major() {return sMajor;}
/// @brief Return the minor version (incremented on backwards-compatible new features).
static int minor() {return sMinor;}
/// @brief Return the patch version (incremented on backwards-compatible bug fixes).
static int patch() {return sPatch;}
private:
static const int sMajor = 10; ///< Major version: incremented on incompatible option/file changes.
static const int sMinor = 8; ///< Minor version: incremented on backwards-compatible new features.
static const int sPatch = 0; ///< Patch version: incremented on backwards-compatible bug fixes.
using GridT = FloatGrid; ///< Scalar grid type used by most level-set operations.
using FilterT = std::unique_ptr<tools::LevelSetFilter<GridT>>; ///< Owned pointer to a LevelSetFilter over GridT.
struct Points; ///< Forward declaration of the helper points wrapper used by particlesToSdf.
struct Header; ///< Forward declaration of the config-file header record.
mutable util::CpuTimer mTimer; ///< Reusable timer for verbose timing reports.
std::string mCmdName; ///< Base name of this command-line tool (argv[0]).
std::string mRawCmdLine; ///< Verbatim argv joined by spaces — used in the log header.
std::list<Geometry::Ptr> mGeom; ///< Stack of Geometry instances owned by this tool (back = top).
std::list<GridBase::Ptr> mGrid; ///< Stack of VDB grids owned by this tool (back = top).
Parser mParser; ///< Command-line action parser and processor.
bool mErrorOnWarning; ///< If true, warning() escalates to a fatal error.
std::ofstream mLogFile; ///< Backing file used by startLog/endLog when active.
std::streambuf *mOldClogBuffer; ///< Cached std::clog buffer for restoring after logging.
std::streambuf *mOldCerrBuffer; ///< Cached std::cerr buffer for restoring after logging.
std::streambuf *mOldCoutBuffer; ///< Cached std::cout buffer for restoring after logging.
std::unique_ptr<TeeBuf> mClogTee; ///< Tee streambuf for std::clog (terminal + log file) when -log tee=true.
std::unique_ptr<TeeBuf> mCerrTee; ///< Tee streambuf for std::cerr.
std::unique_ptr<TeeBuf> mCoutTee; ///< Tee streambuf for std::cout.
/// @brief Delete all queued Geometry, VDB grids, and local variables.
void clear();
/// @brief Clip an input VDB grid against another grid, a bbox, or a frustum.
/// @tparam GridType Type of the input grid being clipped.
/// @param v Numeric parameters defining the clipping region (interpretation depends on mode).
/// @param age Stack age of the secondary clipping grid, or sentinel meaning "use bbox/frustum".
/// @param input The grid being clipped (left unchanged).
/// @return Shared pointer to the clipped grid.
template <typename GridType>
GridBase::Ptr clip(const VecF &v, int age, const GridType &input);
/// @brief Action callback for "-clip"; dispatches to the templated clip() above.
void clip();
/// @brief Composite two grids using a binary op (min, max, or sum).
void composite();
/// @brief Generate a derived grid (e.g. gradient, curl, divergence) from another grid.
void compute();
/// @brief Import and process one or more configuration files.
void config();
/// @brief Perform CSG operations (union/intersection/difference) between two level-set surfaces.
void csg();
/// @brief Run the Enright advection benchmark on a level set.
void enright();
/// @brief Expand the narrow band of a level set.
void expandLevelSet();
/// @brief Perform filtering (convolution) of a level-set surface.
void filterLevelSet();
/// @brief Signed flood-fill of a level-set VDB.
void floodLevelSet();
/// @brief Print documentation for one, multiple, or all available actions.
void help();
/// @brief Convert an iso-surface of a scalar field into a level set (i.e. SDF).
void isoToLevelSet();
/// @brief Convert a volume into an adaptive polygon mesh.
void volumeToMesh();
/// @brief Create a level-set sphere, i.e. a narrow-band signed distance to a sphere.
void levelSetSphere();
/// @brief Convert signed distance field into a unsigned distance field
void sdf2udf();
/// @brief Apply a simple function to each voxel in a grid
void forValues();
/// @brief Create a level-set platonic solid with the specified number of polygon faces.
void levelSetPlatonic();
/// @brief Convert a level-set VDB into a fog volume (normalized density).
void levelSetToFog();
/// @brief Convert a polygon mesh into a symmetric or asymmetric narrow-band level set.
void meshToLevelSet();
/// @brief Convert a polygon mesh into a symmetric narrow-band unsigned distance field.
void meshToUnsignedDistanceField();
#ifdef VDB_TOOL_USE_SHRINKWRAP
/// @brief Convert an arbitrary (possibly non-watertight) polygon soup into a narrow-band level set.
/// @note Gated behind VDB_TOOL_USE_SHRINKWRAP (temporarily disabled; not exposed via CMake).
/// Enable a local build with: cmake -DCMAKE_CXX_FLAGS="-DVDB_TOOL_USE_SHRINKWRAP" ..
void soupToLevelSet();
#endif
/// @brief Generate a dx-offset surface from a polygon soup.
void soupToOffset();
#ifdef VDB_TOOL_USE_AX
/// @brief Run an OpenVDB AX code snippet over one or more selected grids.
/// @note Gated behind VDB_TOOL_USE_AX (requires the openvdb_ax library + LLVM).
void ax();
#endif
/// @brief Convert every quad in the current mesh into two triangles.
void quadsToTriangles();
/// @brief Convert a sequence of image files into a single MPEG movie file.
void movie();
/// @brief Construct a level-of-detail sequence of VDB trees with powers-of-two refinements.
void multires();
/// @brief Perform morphological dilation/erosion on a level-set surface.
void offsetLevelSet();
/// @brief Convert geometry points into a narrow-band level set.
void particlesToLevelSet();
/// @brief Encode geometry points into a VDB PointDataGrid.
void pointsToVdb();
/// @brief Prune away inactive values in a VDB grid.
void pruneLevelSet();
/// @brief Read one or more geometry or VDB files from disk or STDIN.
void read();
/// @brief Read a geometry file (mesh or point cloud) and push it onto the geometry stack.
void readGeo( const std::string &fileName);
/// @brief Read an OpenVDB file and push every selected grid onto the grid stack.
void readVDB( const std::string &fileName);
/// @brief Read a NanoVDB file (.nvdb) and push every selected grid onto the grid stack.
void readNVDB( const std::string &fileName);
/// @brief Ray-trace level-set surfaces or volume-render fog volumes.
void render();
/// @brief Resample one VDB grid into another VDB grid or onto a transformed copy of itself.
void resample();
/// @brief Segment an input VDB into a list of topologically disconnected VDB grids.
void segment();
/// @brief Scatter points into the active values of an input VDB grid.
void scatter();
/// @brief Generate images of axis-aligned volume slices.
void slice();
/// @brief Apply affine transformations (uniform scale -> rotation -> translation) to VDB grids and geometry.
void transform();
/// @brief Extract points encoded in a VDB into geometry-format point lists.
void vdbToPoints();
/// @brief Write the list of geometries, VDB grids, or config files to disk or STDOUT.
void write();
/// @brief Write a single geometry to disk in the format implied by the file extension.
void writeGeo( const std::string &fileName);
/// @brief Write a single VDB grid to disk.
void writeVDB( const std::string &fileName);
/// @brief Write a single VDB grid as a NanoVDB (.nvdb) file.
void writeNVDB(const std::string &fileName);
/// @brief Write the currently parsed action list as a config file.
void writeConf(const std::string &fileName);
/// @brief Estimate the voxel size of a level set from a desired grid dimension.
/// @param maxDimension Maximum voxel resolution along the longest axis of the bbox.
/// @param exWidth Exterior half-width of the narrow band, in voxel units.
/// @param inWidth Interior half-width of the narrow band, in voxel units.
/// @param geo_age Stack age of the geometry whose bbox drives the estimate.
/// @return Voxel size in world units.
float estimateVoxelSize(int maxDimension, float exWidth, float inWidth, int geo_age);
/// @brief Convenience overload: symmetric narrow band (exWidth == inWidth == halfWidth).
float estimateVoxelSize(int maxDim, float halfWidth, int geo_age) {return this->estimateVoxelSize(maxDim, halfWidth, halfWidth, geo_age);}
/// @brief Build a LevelSetFilter configured with the given spatial and temporal schemes.
/// @param grid Grid the filter will operate on.
/// @param space Spatial discretization order.
/// @param time Temporal discretization order.
FilterT createFilter(GridT &grid, int space, int time);
/// @brief Return a formatted string of usage examples (for the -examples action).
std::string examples() const;
/// @brief Emit a banner-framed warning to @a os. Escalates to error if mErrorOnWarning is true.
void warning(const std::string &msg, std::ostream& os = std::clog) const;
/// @brief Register every available action with the parser. Called from the constructor.
void init();
/// @brief Return an iterator to the VDB grid at stack age @a age (0 = most recent).
/// @throw std::invalid_argument if @a age exceeds the current stack depth.
inline auto getGrid(size_t age) const;
/// @brief Return an iterator to the Geometry at stack age @a age (0 = most recent).
/// @throw std::invalid_argument if @a age exceeds the current stack depth.
inline auto getGeom(size_t age) const;
/// @brief Convert the output of a VolumeToMesh pass into a Geometry instance.
Geometry::Ptr mesherToGeometry(tools::VolumeToMesh&) const;
/// @brief Adaptively mesh a scalar grid at @a isoValue and return the result as a Geometry.
/// @param grid Input scalar grid.
/// @param isoValue Iso-value at which to extract the surface (default 0 for SDFs).
/// @param adaptivity Adaptivity parameter passed to VolumeToMesh (0 = uniform quads).
Geometry::Ptr volumeToGeometry(const GridT &grid, float isoValue=0.0f, float adaptivity=0.0f) const;
};// Tool class
// ==============================================================================================================
Tool::Tool(int argc, char *argv[])
: mTimer(std::clog)
, mCmdName(getBase(argv[0]))// name of executable
, mRawCmdLine([&]{
std::string s;
for (int i = 0; i < argc; ++i) {
if (i > 0) s += ' ';
s += argv[i];
}
return s;
}())
, mParser({{"dim", "256", "256", "default grid resolution along the longest axis"},
{"voxel", "0.0", "0.01", "default voxel size in world units. A value of zero indicates that dim is used to derive the voxel size."},
{"width", "3.0", "3.0", "default narrow-band width of level sets in voxel units"},
{"time", "1", "1|2|3", "default temporal discretization order"},
{"space", "5", "1|2|3|5", "default spatial discretization order"},
{"keep", "false", "1|0|true|false", "by default delete the input"}})
, mErrorOnWarning(false)
, mLogFile()
, mOldClogBuffer(nullptr)
, mOldCerrBuffer(nullptr)
, mOldCoutBuffer(nullptr)
{
openvdb::initialize();
this->init();// fast: less than 1 ms
try {
mParser.finalize();
// Set up centralized error handler for action execution
mParser.onActionError = [this](const std::string&, const std::string&) {
return mErrorOnWarning; // true = fatal (re-throw), false = skip
};
mParser.parse(argc, argv);// extremely fast, but might throw
} catch (const std::exception& e) {
this->endLog();
throw std::invalid_argument(e.what());
}
}// Tool::Tool
// ==============================================================================================================
void Tool::startLog(std::string logFile, bool append, bool tee)
{
if (mOldClogBuffer != nullptr) return;// handles repeated calls
if (logFile.empty()) logFile = "vdb_tool_" + dateStamp() + ".log";
const auto mode = std::ios::out | (append ? std::ios::app : std::ios::trunc);
mLogFile.open(logFile, mode);
if (!mLogFile.is_open()) {
throw std::invalid_argument("startLog: failed to open log file \"" + logFile + "\"");
}
// Unit-buffered output so `watch -n 0.5 vdb_tool.log` (and tail -f) see
// each line as soon as it's written, instead of waiting for the 4 KB
// block buffer to flush. The help text recommends this workflow.
mLogFile.setf(std::ios::unitbuf);
// Redirect all three text streams so warnings/errors (cerr) and any
// stdout-bound output also land in the log — not just clog.
mOldClogBuffer = std::clog.rdbuf();
mOldCerrBuffer = std::cerr.rdbuf();
mOldCoutBuffer = std::cout.rdbuf();
if (mOldClogBuffer == nullptr || mOldCerrBuffer == nullptr || mOldCoutBuffer == nullptr) {
throw std::invalid_argument("startLog: failed to cache standard stream buffers");
}
if (tee) {
// Each TeeBuf fans output to (original terminal stream, log file) so
// the user keeps live console feedback while the log accumulates.
mClogTee = std::make_unique<TeeBuf>(mOldClogBuffer, mLogFile.rdbuf());
mCerrTee = std::make_unique<TeeBuf>(mOldCerrBuffer, mLogFile.rdbuf());
mCoutTee = std::make_unique<TeeBuf>(mOldCoutBuffer, mLogFile.rdbuf());
std::clog.rdbuf(mClogTee.get());
std::cerr.rdbuf(mCerrTee.get());
std::cout.rdbuf(mCoutTee.get());
} else {
// Exclusive log mode (tee=false): nothing goes to the terminal.
std::clog.rdbuf(mLogFile.rdbuf());
std::cerr.rdbuf(mLogFile.rdbuf());
std::cout.rdbuf(mLogFile.rdbuf());
}
// Self-describing log header — timestamp, vdb_tool version, and the full
// command line. Makes a stored log readable days later without needing
// to remember what was invoked. In append mode a blank line separates
// this run's header from the previous run's output.
if (append) mLogFile << "\n";
mLogFile << "# vdb_tool log\n"
<< "# date : " << dateStamp() << "\n"
<< "# version : " << Tool::version() << "\n"
<< "# command : " << mRawCmdLine << "\n"
<< std::flush;
}
// ==============================================================================================================
auto Tool::getGrid(size_t age) const
{
if (age>=mGrid.size()) {
throw std::invalid_argument("-"+mParser.getAction().names[0]+" called getGrid("+std::to_string(age)+"), but grid count = "+std::to_string(mGrid.size()));
}
auto it = mGrid.crbegin();
std::advance(it, age);
return it;
}// Tool::getGrid
// ==============================================================================================================
auto Tool::getGeom(size_t age) const
{
if (age>=mGeom.size()) {
throw std::invalid_argument("-"+mParser.getAction().names[0]+" called getGeom("+std::to_string(age)+"), but geometry count = "+std::to_string(mGeom.size()));
}
auto it = mGeom.crbegin();
std::advance(it, age);
return it;
}// Tool::getGeom
// ==============================================================================================================
void Tool::run()
{
if (mParser.verbose>1) this->print_args();
try {
mParser.run();
} catch (const std::exception& e) {
std::cerr << "Fatal error in Tool::run: " << e.what() << std::endl;
std::exit(EXIT_FAILURE);
}
}// Tool::run
// ==============================================================================================================
void Tool::warning(const std::string &msg, std::ostream& os) const
{
if (mParser.verbose) {
os << "\n" << std::setw(static_cast<int>(msg.size())) << std::setfill('*') << "\n" << msg
<< "\n" << std::setw(static_cast<int>(msg.size())) << std::setfill('*') << "\n";
}
}// Tool::warning
// ==============================================================================================================
/// @brief Header record prepended to every vdb_tool config (.txt) file.
/// @details Identifies a config file with the magic string "vdb_tool" followed by
/// the major.minor.patch version that produced it. Used to gate loading
/// configs from incompatible tool versions.
struct Tool::Header {
/// @brief Construct a header for the current tool version.
Header() : mMagic("vdb_tool"), mMajor(sMajor), mMinor(sMinor), mPatch(sPatch) {}
/// @brief Parse a header from the first line of a config file.
/// @param line First line of the config file, e.g. "vdb_tool 10.8.0".
/// @throw std::invalid_argument if @a line does not match "vdb_tool MAJOR.MINOR.PATCH".
Header(const std::string &line) : mMagic("vdb_tool") {
const VecS header = tokenize(line, " .");
if (header.size()!=4 || header[0]!=mMagic ||
!isInt(header[1], mMajor) ||
!isInt(header[2], mMinor) ||
!isInt(header[3], mPatch)) throw std::invalid_argument("Header: incompatible: \""+line+"\"");
}
/// @brief Format the header as the string written at the top of a config file.
std::string str() const {
return mMagic+" "+std::to_string(mMajor)+"."+std::to_string(mMinor)+"."+std::to_string(mPatch);
}
/// @brief Returns true if this header's major version matches the running tool.
bool isCompatible() const {return mMajor == sMajor;}
std::string mMagic; ///< Magic identifier; always "vdb_tool" for a valid header.
int mMajor; ///< Major version recorded in (or expected by) the config file.
int mMinor; ///< Minor version recorded in (or expected by) the config file.
int mPatch; ///< Patch version recorded in (or expected by) the config file.
};// Header struct
// ==============================================================================================================
/// @brief Lightweight adapter exposing a std::vector<Vec3s> as the point-source interface
/// expected by tools::ParticlesToLevelSet.
/// @details ParticlesToLevelSet requires the source to define a PosType alias and to provide
/// size() and getPos() member functions. This wrapper supplies them over a borrowed
/// vector of vertices without copying the data.
struct Tool::Points {
using PosType = Vec3R; ///< Position type required by ParticlesToLevelSet (double precision).
/// @brief Construct a Points adapter over an existing vertex array (stored by reference).
Points(const std::vector<Vec3s> &vtx) : mPoints(vtx) {}
/// @brief Number of points exposed by this adapter.
size_t size() const { return mPoints.size(); }
/// @brief Write the n'th point into @a p, converting from Vec3s to PosType (Vec3R).
void getPos(size_t n, PosType &p) const { p = mPoints[n]; }
const std::vector<Vec3s> &mPoints; ///< Borrowed reference to the underlying vertex array.
};// Points struct
// ==============================================================================================================
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"},
{"execute", "true", "1|0|true|false", "toggle wether to execute the actions in the config file"},
{"update", "false", "1|0|true|false", "toggle wether to update the version number of the config file"}},
[&](){this->config();}, [](){}, 0); // anonymous options are appended to "files"
mParser.addAction(
{"help", "h"}, "Print documentation for one, multiple or all available actions",
{{"actions", "", "read,write,...", "list of actions to document. If the list is empty documentation is printed for all available actions and if other actions proceed this action, documentation is printed for those actions only"},
{"exit", "true", "1|0|true|false", "toggle wether to terminate after this action or not"},
{"brief", "false", "1|0|true|false", "toggle brief or detailed documentation"},
{"search", "", "mesh", "case-insensitive keyword: list every action whose name or documentation contains it (e.g. search=mesh). Useful for discovering the right action without scrolling the full help."},
{"format", "text", "text|md", "output format. 'text' (default) is the usual human-readable help; 'md' emits a single Markdown table of action names + descriptions, used to regenerate the action list in README.md so it can't drift from the registered actions."}},
[](){}, [&](){this->help();}, 0); // anonymous options are appended to "actions"
mParser.addAction(
{"read", "import", "load", "i"}, "Read one or more geometry or VDB files from disk or STDIN.",
{{"files", "", "{file|stdin}.{obj|ply|abc|stl|off|pts|xyz|e57|vdb|nvdb|gltf|glb|geo|usd|usda|usdc|usdz}", "list of files or the input stream, e.g. file.vdb,stdin.vdb. Note that \"files=\" is optional since any argument without \"=\" is intrepreted as a file and appended to \"files\""},
{"grids", "*", "*|grid_name,...", "list of VDB grids name to be imported (defaults to \"*\", i.e. import all available grids)"},
{"delayed", "true", "1|0|true|false", "toggle delayed loading of VDB grids (enabled by default). This option is ignored by other file types"}},
[](){}, [&](){this->read();}, 0);// anonymous options are treated as to the first option,i.e. "files"
mParser.addAction(
{"write", "export", "save", "o"}, "Write list of geometry, VDB or config files to disk or STDOUT",
{{"files", "", "{file|stdout}.{obj|ply|stl|off|geo|abc|vdb|nvdb|txt}", "list of files or the output stream, e.g. file.vdb or stdin.vdb. Note that \"files=\" is optional since any argument without the \"=\" character is intrepreted as a file and appended to \"files\"."},
{"geo", "0", "0|1...", "geometry to write (defaults to \"0\" which is the latest)."},
{"vdb", "*", "0,1,...", "list of VDB grids to write (defaults to \"*\", i.e. all available grids)."},
{"keep", "", "1|0|true|false", "toggle wether to preserved or deleted geometry and grids after they have been written."},
{"codec", "", "none|zip|blosc|active", "compression codec for the file or stream"},
{"bits", "32", "32|16|8|4|N", "bit-width of floating point numbers during quantization of VDB and NanoVDB grids, i.e. 32 is full, 16, is half (defaults to 32). NanoVDB also supports 8, 4 and N which is adaptive bit-width"},// VDB: 32, 16 + for NVDB 8, 4 or N
{"dither", "false", "1|0|true|false", "toggle dithering of quantized NanoVDB grids (disabled by default)"},
{"absolute", "true", "1|0|true|false", "toggle absolute or relative error tolerance during quantization of NanoVDBs. Only used if bits=N. Defaults to absolute"},// absolute or relative error for N bits in NVDB
{"tolerance", "-1", "1.0", "absolute or relative error tolerance used during quantization of NanoVDBs. Only used if bits=N."},// error tolerance for N bits in NVDB
{"stats", "", "none|bbox|extrema|all", "specify the statistics to compute for NanoVDBs."},
{"ascii", "false", "1|0|true|false", "for ascii vs binary output format when available (e.g. for ply files). Defaults to false, i.e. binary is preferred over ascii when available"},
{"checksum", "", "none|partial|full", "specify the type of checksum to compute for NanoVDBs"}},
[&](){mParser.setDefaults();}, [&](){this->write();}, 0);// anonymous options are treated as to the first option,i.e. "files"
mParser.addAction(
{"clear"}, "Deletes geometry, VDB grids and local variables",
{{"geo", "*", "*|0,1,...", "list of geometries to delete (defaults to all)"},
{"vdb", "*", "*|0,1,...", "list of VDB grids to delete (defaults to all)"},
{"variables", "0", "1|0|true|false", "clear all the local variables (defaults to off)"}},
[](){}, [&](){this->clear();});
mParser.addAction(
{"sphere"}, "Create a level set sphere, i.e. a narrow-band signed distance to a sphere",
{{"dim", "", "256", "largest dimension in voxel units of the sphere (defaults to 256). If \"voxel\" is defined \"dim\" is ignored"},
{"voxel", "", "0.0", "voxel size in world units (by defaults \"dim\" is used to derive \"voxel\"). If specified this option takes precedence over \"dim\". Defaults to 0.0, i.e. this option is disabled"},
{"radius", "1.0", "1.0", "radius of sphere in world units"},
{"center", "(0,0,0)", "(0.0,0.0,0.0)", "center of sphere in world units"},
{"signed", "true", "1|0|true|false", "toggle wether the output volume should be a signed vs unsigned distance field"},
{"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)"},
{"name", "sphere", "sphere", "name assigned to the level set sphere"}},
[&](){mParser.setDefaults();}, [&](){this->levelSetSphere();});
// Register forAllValues/forOnValues/forOffValues in a loop to prevent drift
// (they share identical options and implementation)
const std::vector<std::pair<std::vector<std::string>, std::string>> forValuesVariants = {
{{"forAllValues"}, "Applied a simple computational kernel to ALL values in a grid."},
{{"forOnValues"}, "Applied a simple computational kernel to ON values in a grid."},
{{"forOffValues"}, "Applied a simple computational kernel to OFF values in a grid."}
};
const std::vector<Option> forValuesOptions = {
{"keep", "", "1|0|true|false", "toggle wether the input volume is preserved or deleted after the conversion"},
{"vdb", "0", "0|0,1", "age(s) (i.e. stack index) of grid(s) to be processed. Defaults to 0, i.e. most recently inserted VDB. Accepts a comma-separated list to use multiple grids in the kernel: the FIRST grid is written (iterated), the rest are read-only inputs. Length must match use=."},
{"kernel", "", "sin(v)+2*v*v", "user-defined math expression to apply to each value. The \"kernel=\" prefix is OPTIONAL; the kernel may also be supplied as a bare positional argument, e.g. \"-forOnValues 'sin(v)+1'\" or \"-forOnValues 'sin(v)+1' keep=true\" — other named options of the same action still parse normally. Supports infix (e.g. \"sin(v)+2*v*v\"), RPN (e.g. \"$v:sin:$v:pow2:2:*:+\"), and infix multi-statement programs with assignment (e.g. \"t = v*v; t + sin(t)\"). The variable that holds the current voxel value is configurable via the \"use\" option (defaults to \"v\"). Stencil kernels: write \"v(dx,dy,dz)\" with integer-literal offsets to read a relative neighbor voxel through a per-thread ConstAccessor (e.g. \"v(1,0,0)-v(-1,0,0)\" computes a finite-difference x-derivative). The grid is internally deep-copied so reads come from a stable snapshot. Any other identifier in the kernel is looked up once in the Processor's string memory (the same namespace used by -eval / -calc) and used as a per-voxel constant — kernels like \"a*v + b\" therefore require -eval / -calc to have set \"a\" and \"b\" beforehand, else an error is thrown. An empty kernel is a no-op."},
{"use", "v", "v|x|x,y", "name(s) of the kernel variable(s) bound to the voxel value(s) of the input grid(s). Defaults to \"v\". Accepts a comma-separated list matching vdb=: use=x,y vdb=0,1 makes \"x\" the output grid and \"y\" a read-only input. Each name is excluded from the Processor-memory lookup and may be called as a function (e.g. \"x(1,0,0)\") to read a relative neighbor through a per-thread ConstAccessor."},
{"class", "", "ls", "class label of the output volume."},
{"background", "", "1.5,2.0", "background value(s) of the output volume. If two values are provided they are assumed to be outside, inside"},
{"name", "", "foo-bar", "name assigned to the output volume"},
{"file", "", "prog.txt", "read the kernel from a file instead of the \"kernel\" option (useful for longer kernels). If both are given, \"file\" takes precedence."}
};
for (const auto& variant : forValuesVariants) {
mParser.addAction(
std::vector<std::string>(variant.first), std::string(variant.second),
std::vector<Option>(forValuesOptions),
[&](){mParser.setDefaults();}, [&](){this->forValues();},
/*anonymous=*/2, /*greedy=*/true);
}
#ifdef VDB_TOOL_USE_AX
mParser.addAction(
{"ax"}, "run an OpenVDB AX expression over selected grids (requires the openvdb_ax library and LLVM)",
{{"vdb", "*", "*|0|0,1", "age(s) (i.e. stack index) of grid(s) to process, or \"*\" for all (default). AX volume code references grids by name via @gridname, so all selected grids are passed to the executable together and may be read or written."},
{"keep", "", "1|0|true|false", "toggle whether the input grid(s) are preserved. By default (keep=false) the selected grids are edited in place; keep=true instead deep-copies each selected grid, runs AX on the copies (which are pushed onto the stack), and leaves the originals untouched."},
{"code", "", "@v += 1;", "AX code snippet to parse, compile and execute. The \"code=\" prefix is OPTIONAL; the snippet may be given as a bare positional argument, e.g. -ax '@density += 1;'. See the OpenVDB AX documentation for the language."},
{"file", "", "prog.ax", "read the AX code from a file instead of the \"code\" option (useful for longer programs). If both are given, \"file\" takes precedence."},
{"bindings", "", "x:density,y:temp", "comma-separated axname:gridname pairs that remap AX attribute/grid names to actual grid names, e.g. bindings=x:density makes @x in the code operate on the grid named \"density\". Lets a kernel be written against generic names and retargeted without editing the code."}},
[&](){mParser.setDefaults();}, [&](){this->ax();},
/*anonymous=*/2, /*greedy=*/true);// code (index 2) contains spaces/';'/'='; accept bare "@v+=1;" alongside code='...'
#endif
mParser.addAction(
{"sdf2udf"}, "Converts a signed distance field into an unsigned distance field, i.e. performs the Abs of all values and changes GridClass to UNKNOWN.",
{{"keep", "", "1|0|true|false", "toggle wether the input volume is preserved or deleted after the conversion"},
{"vdb", "0", "0|0,1", "age(s) (i.e. stack index) of grid(s) to be processed. Defaults to 0, i.e. most recently inserted VDB. Accepts a comma-separated list to use multiple grids in the kernel: the FIRST grid is written (iterated), the rest are read-only inputs. Length must match use=."},
{"name", "sphere", "sphere", "name assigned to the output volume"}},
[&](){mParser.setDefaults();}, [&](){this->sdf2udf();});
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."},
{"keep", "", "1|0|true|false", "toggle wether the input geometry is preserved or deleted after the conversion"}},
[&](){mParser.setDefaults();}, [&](){this->quadsToTriangles();});
mParser.addAction(
{"movie", "img2mpeg", "mov2mpeg", "mov2gif", "img2gif"}, "Convert image and movie files to mpeg or animated gif files",
{{"fps", "24", "24", "desired frame rate of mpeg movie"},
{"input", "slice_*.ppm", "slice_*.ppm|input.avi", "input image files or movie file to get converted"},
{"output", "slices.mp4", "output.mp4|output.gif", "name of output mpeg or gif file"},
{"scale", "", "1280x720|640", "scale of the output movie or gif."},
// {"keep", "true", "1|0|true|false", "toggle wether the input images are preserved or deleted after the conversion"},
{"flip", "", "vertical|horizontal|180", "flip output video vertical or horizontal or rotate it by 180"}},
[&](){mParser.setDefaults();}, [&](){this->movie();});
mParser.addAction(
{"mesh2ls", "mesh2sdf"}, "Convert a watertight polygon surface 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)"},
{"exWidth", "0.0", "3.0", "half-width in voxel units of the output narrow-band level set (disabled by default)"},
{"inWidth", "0.0", "3.0", "half-width in voxel units of the input narrow-band level set (disabled by default)"},
{"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", "", "mesh2ls_input", "specify the name of the resulting vdb (by default it's derived from the input geometry)"}},
[&](){mParser.setDefaults();}, [&](){this->meshToLevelSet();});
mParser.addAction(
{"soup2udf", "mesh2udf"}, "Convert a polygon soup into a to a unsigned distance field with an symmetrical narrow band",
{{"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", "", "mesh2udf_input", "specify the name of the resulting vdb (by default it's derived from the input geometry)"}},
[&](){mParser.setDefaults();}, [&](){this->meshToUnsignedDistanceField();});
#ifdef VDB_TOOL_USE_SHRINKWRAP
// Temporarily gated out of the PR. Not exposed via CMake by design; enable a
// local build with: cmake -DCMAKE_CXX_FLAGS="-DVDB_TOOL_USE_SHRINKWRAP" ..
mParser.addAction(
{"soup2ls", "soup2sdf", "shrinkwrap"}, "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)"},
{"mode", "0", "0", "mode of offset operator: 0) old method (using mesh -> UDF -> mesh -> SDF), 1) Mihai's signed-flood-fill and 2) Greg's createLevelSetDilatedMesh. Defaults to 0, i.e. paper."},
{"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();});
#endif
mParser.addAction(
{"soup2offset"}, "Convert a polygon soup into an offset 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\""},
//{"offset", "1.0", "1.0", "Offset in voxel units. Defaults to one, i.e. offset surface corresponds to one voxel dilation from mesh."},
{"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)"},
{"mode", "0", "0", "mode of offset operator: 0) old method (using mesh -> UDF -> mesh -> SDF), 1) Mihai's signed-flood-fill and 2) Greg's createLevelSetDilatedMesh. Defaults to 0, i.e. paper."},
{"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"},
{"name", "", "soup2ls_input", "specify the name of the resulting vdb (by default it's derived from the input geometry)"}},
[&](){mParser.setDefaults();}, [&](){this->soupToOffset();});
mParser.addAction(
{"vol2mesh", "vdb2mesh"}, "Convert a scalar volume to an adaptive polygon mesh",
{{"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."},
{"invert", "false", "1|0|true|false", "boolean toggle to mesh the complement of the mask. Defaults to false and ignored if no mask is specified."},
{"keep", "", "1|0|true|false", "toggle wether the input VDB is preserved or deleted after the processing. The mask is never removed!"},
{"name", "", "vol2mesh_input", "specify the name of the resulting vdb (by default it's derived from the input VDB)"}},
[&](){mParser.setDefaults();}, [&](){this->volumeToMesh();});
mParser.addAction(
{"ls2mesh", "sdf2mesh"}, "Convert a level set to an adaptive polygon mesh",
{{"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."},
{"invert", "false", "1|0|true|false", "boolean toggle to mesh the complement of the mask. Defaults to false and ignored if no mask is specified."},
{"keep", "", "1|0|true|false", "toggle wether the input VDB is preserved or deleted after the processing. The mask is never removed!"},
{"name", "", "ls2mesh_input", "specify the name of the resulting vdb (by default it's derived from the input VDB)"}},
[&](){mParser.setDefaults();}, [&](){this->volumeToMesh();});
mParser.addAction(
{"fog2mesh"}, "Convert a fog volume to an adaptive polygon mesh",
{{"adapt", "0.0", "0.005", "normalized metric for the adaptive meshing. 0 is uniform and 1 is extreme adaptivity. Defaults to 0."},
{"iso", "0.5", "0.5", "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."},
{"invert", "false", "1|0|true|false", "boolean toggle to mesh the complement of the mask. Defaults to false and ignored if no mask is specified."},
{"keep", "", "1|0|true|false", "toggle wether the input VDB is preserved or deleted after the processing. The mask is never removed!"},
{"name", "", "fog2mesh_input", "specify the name of the resulting vdb (by default it's derived from the input VDB)"}},
[&](){mParser.setDefaults();}, [&](){this->volumeToMesh();});
mParser.addAction(
{"ls2fog", "l2f", "sdf2fog"}, "Convert a level set VDB into a VDB with a fog volume, i.e. normalized density.",
{{"vdb", "0", "0", "age (i.e. stack index) of the VDB grid to be processed. Defaults to 0, i.e. most recently inserted VDB."},
{"cutoff", "0.0", "3.0", "cut-off in voxel units so fog = sdf >=0 ? 0 : -sdf/|cutoff|*dx (defaults to 0, i.e. cutoff = max for smoothest ramp"},
{"keep", "", "1|0|true|false", "toggle wether the input VDB is preserved or deleted after the processing"},
{"name", "", "ls2fog_input", "specify the name of the resulting VDB (by default it's derived from the input VDB)"}},
[&](){mParser.setDefaults();}, [&](){this->levelSetToFog();});
mParser.addAction(
{"points2ls", "points2sdf", "p2l", "pts2sdf"}, "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). 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"},
{"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 points are preserved or deleted after the processing"},
{"name", "", "points2ls_input", "specify the name of the resulting VDB (by default it's derived from the input points)"}},
[&](){mParser.setDefaults();}, [&](){this->particlesToLevelSet();});
mParser.addAction(
{"iso2ls", "lsRebuild", "i2l"}, "Convert an iso-surface of a scalar field into a level set (i.e. SDF)",
{{"vdb", "0", "0,1", "age (i.e. stack index) of the VDB grid to be processed and an optional reference grid. Defaults to 0, i.e. most recently inserted VDB."},
{"iso", "0.0", "0.0", "value of the iso-surface from which to compute the level set"},
{"voxel", "", "0.0", "voxel size in world units (defaults to zero, i.e the transform out the output matches the input)"},
{"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)"},
{"keep", "", "1|0|true|false", "toggle wether the input VDB is preserved or deleted after the processing"},
{"name", "", "iso2ls_input", "specify the name of the resulting VDB (by default it's derived from the input VDB)"}},
[&](){mParser.setDefaults();}, [&](){this->isoToLevelSet();});
mParser.addAction(
{"points2vdb", "p2v"}, "Encode geometry points into a VDB grid",
{{"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 points are preserved or deleted after the processing"},
{"ppv", "8", "8", "the number of points per voxel in the output VDB grid (defaults to 8)"},
{"bits", "16", "16|8|32", "the number of bits used to represent a single point in the VDB grid (defaults to 16, i.e. half precision)"},
{"name", "", "points_2vdb_input", "specify the name of the resulting VDB (by default it's derived from the input geometry)"}},
[&](){mParser.setDefaults();}, [&](){this->pointsToVdb();});
mParser.addAction(
{"vdb2points", "v2p"}, "Extract points encoded in a VDB to points in a geometry format",
{{"vdb", "0", "0", "age (i.e. stack index) of the VDB grid to be processed. Defaults to 0, i.e. most recently inserted VDB."},
{"keep", "", "1|0|true|false", "toggle wether the input VDB is preserved or deleted after the processing"},
{"name", "", "vdb2points_input", "specify the name of the resulting points (by default it's derived from the input VDB)"}},
[&](){mParser.setDefaults();}, [&](){this->vdbToPoints();});
mParser.addAction(
{"scatter"}, "Scatter point into the active values of an input VDB grid",
{{"count", "0", "0", "fixed number of points to randomly scatter (disabled by default)"},
{"density", "0.0", "0.0", "uniform density of points per active voxel (disabled by default)"},
{"ppv", "8", "8", "number of points per active voxel (defaults to 8)"},
{"vdb", "0", "0", "age (i.e. stack index) of the VDB grid to be scatter points into. Defaults to 0, i.e. most recently inserted VDB"},
{"keep", "", "1|0|true|false", "toggle wether the input VDB is preserved or deleted after the processing"},
{"name", "", "scatter_input", "specify the name of the resulting points (by default it's derived from the input VDB)"}},
[&](){mParser.setDefaults();}, [&](){this->scatter();});
mParser.addAction(
{"platonic"}, "Create a level set shape with the specified number of polygon faces",
{{"dim", "", "256", "largest dimension in voxel units of the bbox of all the shape (defaults to 256). In \"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\""},
{"faces", "4", "{4|6|8|12|20}", "number of polygon faces of the shape to generate the level set VDB from"},
{"scale", "1.0", "1.0", "scale of the shape in world units. E.g. if faces=6 and scale=1.0 the result is a unit cube"},
{"center", "(0,0,0)", "(0.0,0.0,0.0)", "center of the shape in world units. defaults to the origin"},
{"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)"},
{"name", "", "Tetrahedron", "specify the name of the resulting VDB (by default it's derived from face count)"}},
[&](){mParser.setDefaults();}, [&](){this->levelSetPlatonic();});
mParser.addAction(
{"enright"}, "Performs Enright advection benchmark test on a level set",
{{"translate", "(0,0,0)", "(0.0,0.0,0.0)", "defines the origin of the Enright velocity field"},
{"scale", "1.0", "1.0", "defined the scale of the Enright velocity field"},
{"dt", "0.05", "0.05", "time-step the input level set is advected"},
{"vdb", "0", "0", "age (i.e. stack index) of the VDB grid to be processed. Defaults to 0, i.e. most recently inserted VDB."},
{"keep", "", "1|0|true|false", "toggle wether the input VDB is preserved or deleted after the processing"}},
[&](){mParser.setDefaults();}, [&](){this->enright();});
mParser.addAction(
{"dilate", "dilateLS"}, "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)"},
{"vdb", "0", "0", "age (i.e. stack index) of the VDB grid to be processed. Defaults to 0, i.e. most recently inserted VDB."}},
[&](){mParser.setDefaults();}, [&](){this->offsetLevelSet();});
mParser.addAction(
{"erode", "erodeLS"}, "erode level set surface by a fixed radius",
{{"radius", "1.0", "1.0", "radius in voxel units by which the surface is eroded"},
{"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)"},
{"vdb", "0", "0", "age (i.e. stack index) of the VDB grid to be processed. Defaults to 0, i.e. most recently inserted VDB."}},
[&](){mParser.setDefaults();}, [&](){this->offsetLevelSet();});
mParser.addAction(
{"open", "openLS"}, "morphological opening, i.e. erosion followed by dilation, of a level set surface by a fixed radius",
{{"radius", "1.0", "1.0", "radius in voxel units by which the surface is opened"},
{"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)"},
{"vdb", "0", "0", "age (i.e. stack index) of the VDB grid to be processed. Defaults to 0, i.e. most recently inserted VDB."}},
[&](){mParser.setDefaults();}, [&](){this->offsetLevelSet();});
mParser.addAction(
{"close", "closeLS"}, "morphological closing, i.e. dilation followed by erosion, of level set surface by a fixed radius",
{{"radius", "1.0", "1.0", "radius in voxel units by which the surface is closed"},
{"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)"},
{"vdb", "0", "0", "age (i.e. stack index) of the VDB grid to be processed. Defaults to 0, i.e. most recently inserted VDB."}},
[&](){mParser.setDefaults();}, [&](){this->offsetLevelSet();});
mParser.addAction(
{"gauss", "gaussLS"}, "gaussian convolution of a level set surface",
{{"iter", "1", "1", "number of iterations are that the filter is applied"},
{"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)"},
{"vdb", "0", "0", "age (i.e. stack index) of the VDB grid to be processed. Defaults to 0, i.e. most recently inserted VDB."},
{"size", "1", "1", "size of filter in voxel units"}},
[&](){mParser.setDefaults();}, [&](){this->filterLevelSet();});
mParser.addAction(
{"mean", "meanLS"}, "mean value filtering of a level set surface",
{{"iter", "1", "1", "number of iterations are that the filter is applied"},
{"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)"},
{"vdb", "0", "0", "age (i.e. stack index) of the VDB grid to be processed. Defaults to 0, i.e. most recently inserted VDB."},
{"size", "1", "1", "size of filter in voxel units"}},
[&](){mParser.setDefaults();}, [&](){this->filterLevelSet();});
mParser.addAction(
{"median", "medianLS"}, "median value filtering of a level set surface",
{{"iter", "1", "1", "number of iterations are that the filter is applied"},
{"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)"},
{"vdb", "0", "0", "age (i.e. stack index) of the VDB grid to be processed. Defaults to 0, i.e. most recently inserted VDB."},
{"size", "1", "1", "size of filter in voxel units"}},
[&](){mParser.setDefaults();}, [&](){this->filterLevelSet();});
mParser.addAction(
{"cpt"}, "generate a vector grid with the closest-point-transform to a level set surface",
{{"vdb", "0", "0", "age (i.e. stack index) of the VDB grid to be processed. Defaults to 0, i.e. most recently inserted VDB."},
{"keep", "", "1|0|true|false", "toggle wether the input VDB is preserved or deleted after the processing"}},
[&](){mParser.setDefaults();}, [&](){this->compute();});
mParser.addAction(
{"div"}, "generate a scalar grid with the divergence of a vector grid",
{{"vdb", "0", "0", "age (i.e. stack index) of the VDB grid to be processed. Defaults to 0, i.e. most recently inserted VDB."},
{"keep", "", "1|0|true|false", "toggle wether the input VDB is preserved or deleted after the processing"}},
[&](){mParser.setDefaults();}, [&](){this->compute();});
mParser.addAction(
{"curl"}, "generate a vector grid with the curl of another vector grid",
{{"vdb", "0", "0", "age (i.e. stack index) of the VDB grid to be processed. Defaults to 0, i.e. most recently inserted VDB."},
{"keep", "", "1|0|true|false", "toggle wether the input VDB is preserved or deleted after the processing"}},
[&](){mParser.setDefaults();}, [&](){this->compute();});
mParser.addAction(
{"grad"}, "generate a vector grid with the gradient of a scalar grid",
{{"vdb", "0", "0", "age (i.e. stack index) of the VDB grid to be processed. Defaults to 0, i.e. most recently inserted VDB."},
{"keep", "", "1|0|true|false", "toggle wether the input VDB is preserved or deleted after the processing"}},
[&](){mParser.setDefaults();}, [&](){this->compute();});
mParser.addAction(
{"curvature"}, "generate scalar grid with the mean curvature of a level set surface",
{{"vdb", "0", "0", "age (i.e. stack index) of the VDB grid to be processed. Defaults to 0, i.e. most recently inserted VDB."},
{"keep", "", "1|0|true|false", "toggle wether the input VDB is preserved or deleted after the processing"}},
[&](){mParser.setDefaults();}, [&](){this->compute();});
mParser.addAction(
{"length"}, "generate a scalar grid with the magnitude of a vector grid",
{{"vdb", "0", "0", "age (i.e. stack index) of the VDB grid to be processed. Defaults to 0, i.e. most recently inserted VDB."},
{"keep", "", "1|0|true|false", "toggle wether the input VDB is preserved or deleted after the processing"}},
[&](){mParser.setDefaults();}, [&](){this->compute();});
mParser.addAction(
{"union"}, "CSG union of two level sets surfaces",
{{"vdb", "0,1", "0,1", "ages (i.e. stack indices) of the two VDB grids to union. Defaults to 0,1, i.e. two most recently inserted VDBs."},
{"keep", "", "1|0|true|false", "toggle wether the input VDB is preserved or deleted after the processing"},
{"prune", "true", "true", "toggle wether to prune the tree after the boolean operation (enabled by default)"},
{"rebuild", "true", "true", "toggle wether to re-build the level set after the boolean operation (enabled by default)"}},
[&](){mParser.setDefaults();}, [&](){this->csg();});
mParser.addAction(
{"intersection"}, "CSG intersection of two level sets surfaces",
{{"vdb", "0,1", "0,1", "ages (i.e. stack indices) of the two VDB grids to intersect. Defaults to 0,1, i.e. two most recently inserted VDBs."},
{"keep", "", "1|0|true|false", "toggle wether the input VDB is preserved or deleted after the processing"},
{"prune", "true", "true", "toggle wether to prune the tree after the boolean operation (enabled by default)"},
{"rebuild", "true", "true", "toggle wether to re-build the level set after the boolean operation (enabled by default)"}},
[&](){mParser.setDefaults();}, [&](){this->csg();});
mParser.addAction(
{"difference"}, "CSG difference of two level sets surfaces",
{{"vdb", "0,1", "0,1", "ages (i.e. stack indices) of the two VDB grids to difference. Defaults to 0,1, i.e. two most recently inserted VDBs."},
{"keep", "", "1|0|true|false", "toggle wether the input VDB is preserved or deleted after the processing"},
{"prune", "true", "true", "toggle wether to prune the tree after the boolean operation (enabled by default)"},