A new gsParaview class to sanitize paraview file I/O - #876
Conversation
- New gsParaview<T> class with gsOptionList-based configuration - Options: numPoints, precision, plotElements, plotControlNet, show, bezier, boundary, interfaces, etc. - Default filenames for all write() methods based on object type - write() methods for: gsGeometry, gsMultiPatch, gsField, gsBasis, gsMesh, gsTrimSurface, gsSolid, etc. - writePoints(), writeBasisFunction(), writeTrimmedCurve() helper methods - gsParaview_.cpp instantiation file with pybind11 bindings - gsView2.cpp example demonstrating the new class usage
```
gsParaviewUtils.h (format only; NO evaluator include)
▲
gsParaviewDataSet.{h,cpp} (real_t; .cpp includes evaluator, does expr sampling)
▲
gsParaviewCollection.{h,cpp} (real_t; the time-stepping API)
▲
gsParaview.{h,hpp} (gsParaview<T>; one-shot write only)
▲
gsExprEvaluator.h (includes gsParaviewCollection.h — no cycle)
```
No back-edge from Utils to the evaluator, so the include cycle cannot re-form in
any build mode (library build or header-only).
| Layer | File(s) | Role | Templated? |
|---|---|---|---|
| Format utils | `gsParaviewUtils.h/.hpp` | `toDataArray(matrix)`, `toParaview(field/funcset)` | free fns |
| Per-timestep bag | `gsParaviewDataSet.h/.cpp` | one multipatch's `.vts` files; `addField(expr\|field)` | **`real_t`** (internal) |
| Collection | `gsParaviewCollection.h/.cpp` | writes `.pvd`; time-stepping loop (`newTimeStep`/`addField`/`saveTimeStep`/`save`) | **`real_t`** |
| OO writer | `gsParaview.h/.hpp/_.cpp` | one-shot `write(...)` overloads only | `gsParaview<T>` |
| Evaluator | `gsExprEvaluator.h` | expr eval; deprecated `writeParaview(expr,G,fn)` convenience wrapper | `gsExprEvaluator<T>` |
- `gsParaview<T>` is a **one-shot writer only**. It has no filename/evaluator
constructor and no time-stepping members; every write* method is const and
self-contained.
- `gsParaviewCollection` is the **canonical, endorsed time-stepping API**
(`newTimeStep`/`addField`/`addFields`/`saveTimeStep`/`save` are no longer
deprecated). `gsParaviewDataSet` remains an internal implementation detail
used by the collection (and directly, if needed) — not user-facing.
- `gsExprEvaluator<T>::writeParaview(expr, G, fn)` stays as a thin,
`GISMO_DEPRECATED` convenience wrapper that builds one `gsParaviewCollection`
timestep. It only works for `T = real_t`, matching `gsParaviewCollection`'s
`real_t`-only evaluator pointer (this is a pre-existing constraint of
keeping the collection untemplated, not a regression).
…, remove lifetime hacks Make Paraview time-stepping generic over T, replace the evaluator/geometry borrowing hacks with explicit non-owning pointers, and unify legacy option keys behind canonical aliases while keeping tests green
There was a problem hiding this comment.
Pull request overview
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
This PR introduces an object-oriented gsParaview writer to replace scattered free functions, adds support for single-file unstructured-grid (.vtu) exports (optionally base64), and refactors Paraview collection/dataset utilities (including template-ization and legacy option aliases).
Changes:
- Add
gsParaviewclass (C++ + optional pybind11) and update many examples to use it. - Extend Paraview I/O APIs with
skipPvdand addgsWriteParaviewUnstructuredGridfor one-file.vtuoutput. - Refactor
gsParaviewCollection/gsParaviewDataSetinto templated headers with new instantiation units and legacy option aliases.
Reviewed changes
Copilot reviewed 56 out of 56 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| unittests/gsParaview_test.cpp | Adds unit tests covering defaults, single-file VTU output, and collection workflows. |
| src/gsIO/gsWriteParaview_.cpp | Updates explicit instantiations + pybind bindings for modified function signatures. |
| src/gsIO/gsWriteParaview.hpp | Adds base64 include, skipPvd plumbing, and unstructured-grid VTU writers. |
| src/gsIO/gsWriteParaview.h | Updates public signatures (skipPvd) and declares unstructured-grid writers. |
| src/gsIO/gsParaview_.cpp | Adds template instantiation + pybind11 bindings for gsParaview. |
| src/gsIO/gsParaviewUtils_.cpp | Renames exported helper API from toVTK to toParaview. |
| src/gsIO/gsParaviewUtils.hpp | Removes some heavy includes and renames helpers to toParaview. |
| src/gsIO/gsParaviewUtils.h | Renames helpers to toParaview and removes expression-based toVTK overloads. |
| src/gsIO/gsParaviewDataSet_.cpp | Adds explicit instantiation unit for templated gsParaviewDataSet. |
| src/gsIO/gsParaviewDataSet.hpp | Implements templated dataset writer logic (structured-grid .vts). |
| src/gsIO/gsParaviewDataSet.h | Templates dataset class, adds legacy option aliases, and evaluator bridge decl. |
| src/gsIO/gsParaviewDataSet.cpp | Removes old non-templated implementation (migrated to header). |
| src/gsIO/gsParaviewCollection_.cpp | Adds explicit instantiation unit for templated gsParaviewCollection. |
| src/gsIO/gsParaviewCollection.hpp | Adds templated collection implementation (timestep/dataset management). |
| src/gsIO/gsParaviewCollection.h | Templates collection API, updates addField forwarding, keeps makeCollection helper. |
| src/gsIO/gsParaviewCollection.cpp | Removes old non-templated implementation (migrated to header). |
| src/gsIO/gsParaview.hpp | Implements gsParaview writer that forwards to existing I/O primitives. |
| src/gsIO/gsParaview.h | Declares gsParaview public API and options interface. |
| src/gsCore/gsForwardDeclarations.h | Adds forward declarations for templated dataset/collection. |
| src/gsAssembler/gsExprEvaluator.h | Rewires deprecated writeParaview to use new collection workflow and adds expr sampling bridge. |
| src/gismo.h | Adjusts umbrella include set (drops gsParaviewDataSet.h). |
| examples/triangulatedMeshToSolid_example.cpp | Switches Paraview output to gsParaview. |
| examples/thbSplineBasis_example.cpp | Switches Paraview output to gsParaview. |
| examples/thbRefinement_example.cpp | Switches Paraview output to gsParaview (with options). |
| examples/surfaceReparametrization_example.cpp | Switches Paraview output to gsParaview. |
| examples/subdivisionSurfaces_example.cpp | Switches Paraview output to gsParaview. |
| examples/stokes_ieti_example.cpp | Switches Paraview output to gsParaview (numPoints set once). |
| examples/refitting_example.cpp | Switches Paraview output to gsParaview and uses options for mesh/cnet. |
| examples/quadrature_example.cpp | Switches Paraview points/basis output to gsParaview. |
| examples/precision_example.cpp | Switches Paraview output to gsParaview. |
| examples/poissonTHB_example.cpp | Switches Paraview output to gsParaview (changes numPoints between outputs). |
| examples/patches_from_mesh.cpp | Switches Paraview output to gsParaview. |
| examples/paraview_example.cpp | Switches Paraview output to gsParaview including Bezier via option. |
| examples/parametrization_example.cpp | Switches Paraview output to gsParaview. |
| examples/multiGrid_example.cpp | Switches Paraview output to gsParaview using show option. |
| examples/kirchhoff-Love_example.cpp | Switches Paraview output to gsParaview. |
| examples/inputOutput_example.cpp | Switches Paraview output to gsParaview including Bezier via option. |
| examples/ieti_example.cpp | Switches Paraview output to gsParaview. |
| examples/ieti2_example.cpp | Switches Paraview output to gsParaview. |
| examples/gsView2.cpp | Adds a new CLI example using gsParaview. |
| examples/gsView.cpp | Refactors existing CLI example to use gsParaview. |
| examples/gsMappedSpline_example.cpp | Switches Paraview output to gsParaview for mapped spline/basis. |
| examples/gsHElement_marking_example.cpp | Switches Paraview output to gsParaview for meshes and boxes. |
| examples/gsHBox_example.cpp | Switches Paraview output to gsParaview for hbox exports. |
| examples/gsAdaptiveMeshing_example.cpp | Switches Paraview output to gsParaview for marked boxes and fields. |
| examples/geometry_example.cpp | Switches Paraview output to gsParaview. |
| examples/fitting_mspline.cpp | Switches Paraview output to gsParaview with show. |
| examples/compositions_example.cpp | Switches Paraview output to gsParaview and reuses object. |
| examples/biharmonic_example.cpp | Switches Paraview output to gsParaview with show. |
| examples/basis_example.cpp | Switches Paraview output to gsParaview. |
| examples/bSplineSurface_example.cpp | Switches Paraview output to gsParaview (numPoints set for geometry). |
| examples/bSplineCurve_example.cpp | Switches Paraview output to gsParaview (show/mesh/cnet options). |
| examples/bSplineBasis_example.cpp | Switches Paraview output to gsParaview. |
| examples/assembly_example.cpp | Switches Paraview output to gsParaview with show. |
| examples/adaptiveConvectionDiffusionReaction_example.cpp | Switches Paraview output to gsParaview including show. |
| examples/adaptRefinementThb_example.cpp | Switches Paraview output to gsParaview. |
Suppressed comments (9)
src/gsIO/gsParaview.hpp:1
openIfRequestedalways opensfn + ".pvd", but several write paths can legitimately produce only.vtu(singleFile + writePvd=false / skipPvd) or.vtp(curve/mesh). This makes theshowoption unreliable and can open a non-existent file. Consider tracking the actual produced “entry file” per write call (e.g.,.pvdwhen written, otherwise the concrete output like.vtu/.vts/.vtp) and open that instead.
src/gsIO/gsParaview.hpp:1- The
gsParaviewoption"writePvd"is explicitly not honored for the non-singleFile, non-bezier multipatch export path, which is surprising for API consumers and makes behavior inconsistent withwrite(geometry)/write(field). A tangible fix is to add askipPvd(orwritePvd) parameter to the relevantgsWriteParaview(const gsMultiPatch&, ...)/vector overloads (or route this path through thegsParaviewCollectionobject conditionally) so"writePvd"is consistently applied.
src/gsIO/gsParaviewCollection.hpp:1 - Explicit timesteps are truncated to an integer (
cast<T,int>(time)), which loses information for non-integer times (e.g., 0.1, 0.5) and will write incorrecttimestep="..."attributes in the.pvd. Since the public API acceptsT time,m_timeshould store the fullTvalue (ordouble) without integer truncation.
src/gsIO/gsParaviewDataSet.hpp:1 isEmpty()always returningfalsemakes the method misleading and undermines the defensive checks ingsParaviewCollection(!dataSet.isEmpty()). Even if an “empty dataset” is now structurally impossible (because constructors require a geometry reference), it’s better to reflect actual state (e.g.,m_geometry == nullptrorm_filenames.empty()) or remove/replaceisEmpty()from the contract to avoid future misuse.
src/gsIO/gsWriteParaview.hpp:1- After detecting
!file.is_open(), the function continues writing tofileunconditionally, which will silently drop output and can mask failures. This should return early (or throw/assert) once opening fails, especially since the function later callsmakeCollection, potentially creating a.pvdthat references a missing.vtu.
src/gsIO/gsWriteParaview.hpp:1 - VTK inline binary (base64) expects the 64-bit byte-count header to match the declared
byte_order(and typically assumes little-endian whenbyte_order="LittleEndian"). Here the header is emitted in host endianness with no conversion, so output can be invalid on big-endian systems. Consider explicitly serializing the header (and potentially numeric payload) to the chosen byte order before base64 encoding, or enforce/validate little-endian whenexport_base64=true.
src/gsIO/gsWriteParaview_.cpp:1 - The Python binding renames the keyword argument from
controlNettoctrlNet, which is a breaking change for users relying on keyword args. To maintain compatibility, keep the existing keyword (controlNet) (even if the C++ parameter isctrlNet) or provide both names (e.g., keepcontrolNetinpy::arg).
src/gsAssembler/gsExprEvaluator.h:1 - This “backward compatibility” logic will prefer
numPoints/plotElementswhenever those keys exist, even if users set only the legacy keys (plot.npts/plot.elements). SincedefaultOptions()now adds the canonical keys,exists("numPoints")will likely always be true, effectively ignoring legacy values. Align this with the more robust alias logic used ingsParaviewDataSet::getNumPoints/getPlotElements(e.g., prefer canonical only when it differs from its default, otherwise fall back to legacy if it differs from default).
unittests/gsParaview_test.cpp:1 - The test likely doesn’t exercise the precision behavior because it guesses output filenames as
fn + "0.vts"(no delimiter), while other tests already acknowledge multiple naming schemes (e.g.,_0,_patch0, etc.). As written, the check can silently skip on most outputs, weakening coverage. Consider resolving the actual produced.vtsfilename(s) (e.g., using the configured patch delimiter, or searching expected variants and asserting one is found) and then asserting on size/content.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Introduces a new class gsParaview to avoid having all the free functions..