Hi ParaView developers,
I’m developing a Python plugin for ParaView that produces time-evolving datasets. The goal is to generate a structured 3D grid with full control of the coordinates (x, y, z), where:
-
x and y can have non-uniform spacing (non-orthogonal curvilinear grid),
-
z can have non-constant vertical spacing.
I currently have a working plugin that produces a rectilinear grid with temporal scalar fields, inspired by the TemporalTestDataSource.py
example from ParaView:
https://gitlab.kitware.com/paraview/paraview/-/blob/master/Examples/Plugins/TemporalTestDataSource/TemporalTestDataSource.py
Here’s a summary of what I have implemented:
-
Time management:
-
***_get_timesteps()***
returns an array of time values. -
***RequestInformation()***
sets the time steps and time range in ParaView.
-
-
Grid generation in
RequestData()
:-
Currently using
***vtkRectilinearGrid***
. -
Scalars are generated per cell, one array per
***_numOfArrays***
. -
FieldData stores the current time.
-
-
Controls exposed as sliders in the ParaView GUI:
-
TimeSteps: number of timesteps.
-
z Scale: scaling factor applied to the vertical coordinate.
-
ArrayCount: number of scalar arrays per timestep.
-
Value Offset: arbitrary offset added to the generated scalar values.
-
-
Observed problem:
-
When trying to switch to
***vtkStructuredGrid***
and fully control x, y, z coordinates, the grid appears empty in ParaView — no points, no cells, no outline. -
I have tried various approaches:
-
Assigning coordinates as
***vtkPoints***
and using***SetDimensions()***
. -
Using
***numpy.meshgrid***
with different flattening orders (C
vsF
). -
Attempting
***vtkStructuredGrid***
with cell-centered scalars. -
Trying
***vtkUnstructuredGrid***
with manually defined hexahedral cells. -
Attempting
***vtkPolyData***
with explicit quads for each voxel.
-
-
None of these approaches produces a visible grid in ParaView when embedded in the plugin pipeline. Standalone scripts work fine (e.g., generating a structured grid in pure Python and writing to .vts
), but embedding it in ***RequestData()***
fails.
Goal: I want a fully working structured grid plugin that:
-
Has time-dependent scalar fields.
-
Allows arbitrary x, y, z coordinates (non-uniform / curvilinear).
-
Works immediately in ParaView without external files.
Plugin sliders explanation:
-
TimeSteps: controls how many time steps the source produces.
-
z Scale: scales the vertical dimension (useful to exaggerate vertical spacing).
-
ArrayCount: number of independent scalar arrays generated per timestep.
-
Value Offset: shifts the scalar values globally.
Question to the community:
-
How can I properly generate a vtkStructuredGrid with arbitrary curvilinear coordinates inside
RequestData()
, so that it is visible and renders correctly in ParaView? -
Are there special ordering rules for points or dimensions when using structured grids in a plugin?
-
Any working minimal example of time-dependent structured grid with non-uniform x/y/z in a Python plugin would be greatly appreciated.
Thanks!
Plugin code skeleton (rectilinear working version):
from paraview.util.vtkAlgorithm import *
import vtk
import numpy as np
from vtkmodules.util import numpy_support@smproxy.source(name=“TimeSourceStructured”,
label=“Time Source Structured Grid with Scalars”)
class TimeSourceStructured(VTKPythonAlgorithmBase):
def init(self):
VTKPythonAlgorithmBase.init(self,
nInputPorts=0, nOutputPorts=1,
outputType=‘vtkRectilinearGrid’)self.nx, self.ny, self.nz = 115, 85, 32 self._numOfValues = 10 self._numOfArrays = 2 self._valueOffset = 0.0 self._zscale = 1.0 def _get_timesteps(self): return np.arange(self._numOfValues, dtype=np.float64) def RequestInformation(self, request, inInfoVec, outInfoVec): outInfo = outInfoVec.GetInformationObject(0) timesteps = self._get_timesteps() outInfo.Set(vtk.vtkStreamingDemandDrivenPipeline.TIME_STEPS(), timesteps, len(timesteps)) outInfo.Set(vtk.vtkStreamingDemandDrivenPipeline.TIME_RANGE(), [timesteps[0], timesteps[-1]], 2) return 1 def _get_current_time(self, outInfo): timesteps = self._get_timesteps() if outInfo.Has(vtk.vtkStreamingDemandDrivenPipeline.UPDATE_TIME_STEP()): return outInfo.Get(vtk.vtkStreamingDemandDrivenPipeline.UPDATE_TIME_STEP()) else: return timesteps[0] def RequestData(self, request, inInfoVec, outInfoVec): outInfo = outInfoVec.GetInformationObject(0) nx, ny, nz = self.nx, self.ny, self.nz x_coords = np.linspace(0, nx-1, nx, dtype=np.float32) y_coords = np.linspace(0, ny-1, ny, dtype=np.float32) z_coords = self._zscale * np.linspace(0, nz-1, nz, dtype=np.float32) vtk_x = numpy_support.numpy_to_vtk(x_coords, deep=True) vtk_y = numpy_support.numpy_to_vtk(y_coords, deep=True) vtk_z = numpy_support.numpy_to_vtk(z_coords, deep=True) grid = vtk.vtkRectilinearGrid() grid.SetDimensions(nx, ny, nz) grid.SetXCoordinates(vtk_x) grid.SetYCoordinates(vtk_y) grid.SetZCoordinates(vtk_z) t = self._get_current_time(outInfo) gx, gy, gz = np.meshgrid(x_coords[:-1]+0.5, y_coords[:-1]+0.5, z_coords[:-1]+0.5, indexing='ij') gx, gy, gz = gx.ravel(), gy.ravel(), gz.ravel() for k in range(self._numOfArrays): arr = np.sin(0.2*t + 0.1*k + 0.05*gx + 0.05*gy + 0.05*gz) vtkarr = numpy_support.numpy_to_vtk(arr, deep=True) vtkarr.SetName(f"Array_{k}") grid.GetCellData().AddArray(vtkarr) if k == 0: grid.GetCellData().SetScalars(vtkarr) time_array = vtk.vtkDoubleArray() time_array.SetName("TIME") time_array.SetNumberOfTuples(1) time_array.SetValue(0, t) grid.GetFieldData().AddArray(time_array) output = self.GetOutputData(outInfoVec, 0) output.ShallowCopy(grid) output.GetInformation().Set(output.DATA_TIME_STEP(), t) return 1 @smproperty.intvector(name="TimeSteps", default_values=10) @smdomain.intrange(min=1, max=50) def SetSteps(self, x): self._numOfValues = x self.Modified() @smproperty.doublevector(name="z Scale", default_values=1) @smdomain.doublerange(min=1.e-6, max=2) def SetScale(self, x): self._zscale = x self.Modified() @smproperty.intvector(name="ArrayCount", default_values=2) @smdomain.intrange(min=1, max=10) def SetArrayCount(self, x): self._numOfArrays = x self.Modified() @smproperty.doublevector(name="Value Offset", default_values=0.0) def SetOffset(self, x): self._valueOffset = x self.Modified() @smproperty.doublevector(name="TimestepValues", information_only="1", si_class="vtkSITimeStepsProperty") def GetTimestepValues(self): return self._get_timesteps()