From 9249e96864c2e7cc2f5ae6277a6faf82854f875b Mon Sep 17 00:00:00 2001 From: Dan Lipsa Date: Mon, 30 Mar 2026 08:47:32 -0400 Subject: [PATCH 1/8] fix: check self MTime (projection was modified) as well --- src/e3sm_quickview/plugins/eam_projection.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/e3sm_quickview/plugins/eam_projection.py b/src/e3sm_quickview/plugins/eam_projection.py index 3e6fcc3..a7bdc57 100644 --- a/src/e3sm_quickview/plugins/eam_projection.py +++ b/src/e3sm_quickview/plugins/eam_projection.py @@ -279,10 +279,8 @@ def RequestData(self, request, inInfo, outInfo): outData.SetPoints(self.cached_points) return 1 - - if ( - self.cached_points - and self.cached_points.GetMTime() >= inData.GetPoints().GetMTime() + if self.cached_points and self.cached_points.GetMTime() >= max( + inData.GetPoints().GetMTime(), self.GetMTime() ): outData.SetPoints(self.cached_points) else: From b7a17c9e5389fd128e121b14158811ba12608230 Mon Sep 17 00:00:00 2001 From: Dan Lipsa Date: Thu, 2 Apr 2026 10:41:14 -0400 Subject: [PATCH 2/8] fix: cache output for EAMExtract Caching only the ghost cells won't work as RemoveGhostCells modifies the output. Only new arrays are modified using PedigreeIds --- src/e3sm_quickview/plugins/eam_projection.py | 103 ++++++++++++------- 1 file changed, 64 insertions(+), 39 deletions(-) diff --git a/src/e3sm_quickview/plugins/eam_projection.py b/src/e3sm_quickview/plugins/eam_projection.py index a7bdc57..92c4e15 100644 --- a/src/e3sm_quickview/plugins/eam_projection.py +++ b/src/e3sm_quickview/plugins/eam_projection.py @@ -2,6 +2,7 @@ from paraview.util.vtkAlgorithm import * from vtkmodules.vtkCommonCore import ( vtkPoints, + vtkUnsignedCharArray, ) from vtkmodules.vtkCommonDataModel import ( vtkCellArray, @@ -61,6 +62,44 @@ def ProcessPoint(point, radius): return [x, y, z] +def add_cell_arrays(inData, outData, cached_output): + """ + Adds arrays not modified in inData to outData. + New arrays (or arrays modified) values are + set using the PedigreeIds because the number of values + in the new array (just read from the file) is different + than the number of values in the arrays already processed through he + pipeline. + """ + pedigreeIds = cached_output.cell_data["PedigreeIds"] + if pedigreeIds is None: + print_error("Error: no PedigreeIds array") + return + cached_cell_data = cached_output.GetCellData() + in_cell_data = inData.GetCellData() + outData.ShallowCopy(cached_output) + out_cell_data = outData.GetCellData() + + out_cell_data.Initialize() + for i in range(in_cell_data.GetNumberOfArrays()): + in_array = in_cell_data.GetArray(i) + cached_array = cached_cell_data.GetArray(in_array.GetName()) + if cached_array and cached_array.GetMTime() >= in_array.GetMTime(): + # this scalar has been seen before + # simply add a reference in the outData + out_cell_data.AddArray(cached_array) + else: + # this scalar is new + # we have to fill in the additional cells resulted from the clip + out_array = in_array.NewInstance() + array0 = cached_cell_data.GetArray(0) + out_array.SetNumberOfComponents(array0.GetNumberOfComponents()) + out_array.SetNumberOfTuples(array0.GetNumberOfTuples()) + out_array.SetName(in_array.GetName()) + out_cell_data.AddArray(out_array) + outData.cell_data[out_array.GetName()] = inData.cell_data[i][pedigreeIds] + + @smproxy.filter() @smproperty.input(name="Input") @smdomain.datatype( @@ -440,7 +479,7 @@ def __init__(self): self.trim_lon = [0, 0] self.trim_lat = [0, 0] self.cached_cell_centers = None - self.cached_ghosts = None + self._cached_output = None def SetTrimLongitude(self, left, right): if left < 0 or left > 180 or right < 0 or right > 180: @@ -469,7 +508,6 @@ def RequestData(self, request, inInfo, outInfo): outData.ShallowCopy(inData) return 1 - outData.ShallowCopy(inData) if self.cached_cell_centers and self.cached_cell_centers.GetMTime() >= max( inData.GetPoints().GetMTime(), inData.GetCells().GetMTime() ): @@ -492,12 +530,27 @@ def RequestData(self, request, inInfo, outInfo): # get the numpy array for cell centers cc = numpy_support.vtk_to_numpy(cell_centers) - if self.cached_ghosts and self.cached_ghosts.GetMTime() >= max( + if self._cached_output and self._cached_output.GetMTime() >= max( self.GetMTime(), inData.GetPoints().GetMTime(), cell_centers.GetMTime() ): - ghost = self.cached_ghosts + outData.ShallowCopy(self._cached_output) + add_cell_arrays(inData, outData, self._cached_output) else: - # import pdb;pdb.set_trace() + # add PedigreeIds + generate_ids = vtkGenerateIds() + generate_ids.SetInputData(inData) + generate_ids.PointIdsOff() + generate_ids.SetCellIdsArrayName("PedigreeIds") + generate_ids.Update() + outData.ShallowCopy(generate_ids.GetOutput()) + # we have to deep copy the cell array because we modify it + # with RemoveGhostCells + cells = vtkCellArray() + cell_types = vtkUnsignedCharArray() + cells.DeepCopy(outData.GetCells()) + cell_types.DeepCopy(outData.GetCellTypesArray()) + outData.SetCells(cell_types, cells) + # compute the new bounds by trimming the inData bounds bounds = list(inData.GetBounds()) bounds[0] = bounds[0] + self.trim_lon[0] @@ -505,14 +558,13 @@ def RequestData(self, request, inInfo, outInfo): bounds[2] = bounds[2] + self.trim_lat[0] bounds[3] = bounds[3] - self.trim_lat[1] - # add hidden cells based on bounds + # add HIDDENCELL based on bounds outside_mask = ( (cc[:, 0] < bounds[0]) | (cc[:, 0] > bounds[1]) | (cc[:, 1] < bounds[2]) | (cc[:, 1] > bounds[3]) ) - # Create ghost array (0 = visible, HIDDENCELL = invisible) ghost_np = np.where( outside_mask, vtkDataSetAttributes.HIDDENCELL, 0 @@ -521,11 +573,11 @@ def RequestData(self, request, inInfo, outInfo): # Convert to VTK and add to output ghost = numpy_support.numpy_to_vtk(ghost_np) ghost.SetName(vtkDataSetAttributes.GhostArrayName()) - # the previous cached_ghosts, if any, - # is available for garbage collection after this assignment - self.cached_ghosts = ghost - outData.GetCellData().AddArray(ghost) + outData.GetCellData().AddArray(ghost) + outData.RemoveGhostCells() + self._cached_output = outData.NewInstance() + self._cached_output.ShallowCopy(outData) return 1 @@ -600,34 +652,7 @@ def RequestData(self, request, inInfo, outInfo): and self._cached_output.GetCells().GetMTime() >= inData.GetCells().GetMTime() ): - # only scalars have been added or removed - cached_cell_data = self._cached_output.GetCellData() - - in_cell_data = inData.GetCellData() - - outData.ShallowCopy(self._cached_output) - out_cell_data = outData.GetCellData() - - out_cell_data.Initialize() - for i in range(in_cell_data.GetNumberOfArrays()): - in_array = in_cell_data.GetArray(i) - cached_array = cached_cell_data.GetArray(in_array.GetName()) - if cached_array and cached_array.GetMTime() >= in_array.GetMTime(): - # this scalar has been seen before - # simply add a reference in the outData - out_cell_data.AddArray(cached_array) - else: - # this scalar is new - # we have to fill in the additional cells resulted from the clip - out_array = in_array.NewInstance() - array0 = cached_cell_data.GetArray(0) - out_array.SetNumberOfComponents(array0.GetNumberOfComponents()) - out_array.SetNumberOfTuples(array0.GetNumberOfTuples()) - out_array.SetName(in_array.GetName()) - out_cell_data.AddArray(out_array) - outData.cell_data[out_array.GetName()] = inData.cell_data[i][ - self._cached_output.cell_data["PedigreeIds"] - ] + add_cell_arrays(inData, outData, self._cached_output) else: generate_ids = vtkGenerateIds() generate_ids.SetInputData(inData) From dd6ec62763566580be36a03f151494ad98188037 Mon Sep 17 00:00:00 2001 From: Dan Lipsa Date: Mon, 6 Apr 2026 16:08:28 -0400 Subject: [PATCH 3/8] fix: allow trimming beyond mid point --- src/e3sm_quickview/plugins/eam_projection.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/e3sm_quickview/plugins/eam_projection.py b/src/e3sm_quickview/plugins/eam_projection.py index 92c4e15..222a5ef 100644 --- a/src/e3sm_quickview/plugins/eam_projection.py +++ b/src/e3sm_quickview/plugins/eam_projection.py @@ -482,9 +482,9 @@ def __init__(self): self._cached_output = None def SetTrimLongitude(self, left, right): - if left < 0 or left > 180 or right < 0 or right > 180: + if left < 0 or left > 360 or right < 0 or right > 360 or left > (360 - right): print_error( - f"SetTrimLongitude called with parameters outside [0, 180]: {left=}, {right=}" + f"SetTrimLongitude called with invalid parameters: {left=}, {right=}" ) return if self.trim_lon[0] != left or self.trim_lon[1] != right: @@ -492,9 +492,9 @@ def SetTrimLongitude(self, left, right): self.Modified() def SetTrimLatitude(self, left, right): - if left < 0 or left > 90 or right < 0 or right > 90: + if left < 0 or left > 180 or right < 0 or right > 180 or left > (180 - right): print_error( - f"SetTrimLatitude called with parameters outside [0, 180]: {left=}, {right=}" + f"SetTrimLatitude called with invalid parameters: {left=}, {right=}" ) return if self.trim_lat[0] != left or self.trim_lat[1] != right: From dadc5ae38e57d402e63892edfbb603aa614e35fe Mon Sep 17 00:00:00 2001 From: Dan Lipsa Date: Tue, 7 Apr 2026 11:44:20 -0400 Subject: [PATCH 4/8] fix: fix crash when: trim_lon = 200 then trim_lon = 0 --- src/e3sm_quickview/plugins/eam_projection.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/e3sm_quickview/plugins/eam_projection.py b/src/e3sm_quickview/plugins/eam_projection.py index 222a5ef..65fadcc 100644 --- a/src/e3sm_quickview/plugins/eam_projection.py +++ b/src/e3sm_quickview/plugins/eam_projection.py @@ -505,7 +505,8 @@ def RequestData(self, request, inInfo, outInfo): inData = self.GetInputData(inInfo, 0, 0) outData = self.GetOutputData(outInfo, 0) if self.trim_lon == [0, 0] and self.trim_lat == [0, 0]: - outData.ShallowCopy(inData) + # ShallowCopy results in a crash + outData.DeepCopy(inData) return 1 if self.cached_cell_centers and self.cached_cell_centers.GetMTime() >= max( From cf1ad82c0b9bf2a7f3959b0106b163c734d730b2 Mon Sep 17 00:00:00 2001 From: Dan Lipsa Date: Wed, 8 Apr 2026 09:42:33 -0400 Subject: [PATCH 5/8] fix: remove vtkPVGeometryFilter as it is applied in EAMExtract --- src/e3sm_quickview/pipeline.py | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/src/e3sm_quickview/pipeline.py b/src/e3sm_quickview/pipeline.py index d203c0c..e62833d 100644 --- a/src/e3sm_quickview/pipeline.py +++ b/src/e3sm_quickview/pipeline.py @@ -3,7 +3,6 @@ from pathlib import Path from paraview import simple -from paraview.modules.vtkPVVTKExtensionsFiltersRendering import vtkPVGeometryFilter from vtkmodules.vtkCommonCore import vtkLogger from vtkmodules.vtkRenderingCore import vtkActor, vtkPolyDataMapper @@ -153,17 +152,8 @@ def __init__(self, projection="Mollweide"): Projection=projection, Translate=0, ) - vtk_geometry = self.proj.GetClientSideObject() - self.vtk_geometry = vtkPVGeometryFilter( - use_outline=0, - block_colors_distinct_values=0, - generate_cell_normals=0, - generate_point_normals=0, - generate_feature_edges=0, - splitting=False, - triangulate=0, - input_connection=vtk_geometry.output_port, - ) + self.geometry = simple.ExtractSurface(Input=self.proj) + self.vtk_geometry = self.geometry.GetClientSideObject() # Add observer to vtk_obj = self.reader.GetClientSideObject() @@ -250,7 +240,7 @@ def update(self, time=0.0): if not self.valid: return - self.proj.UpdatePipeline(time) + self.geometry.UpdatePipeline(time) def crop(self, longitude_min_max, latitude_min_max): self._crop.TrimLongitude = range_to_trim(longitude_min_max, 180) From cf7558adbb8d29ecca1ae07a1d64ecba002974f6 Mon Sep 17 00:00:00 2001 From: Dan Lipsa Date: Wed, 8 Apr 2026 10:52:09 -0400 Subject: [PATCH 6/8] fix: no need to do something different for latlon projection This also has the problem that it does not check the point modification time --- src/e3sm_quickview/plugins/eam_projection.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/e3sm_quickview/plugins/eam_projection.py b/src/e3sm_quickview/plugins/eam_projection.py index 65fadcc..93c609b 100644 --- a/src/e3sm_quickview/plugins/eam_projection.py +++ b/src/e3sm_quickview/plugins/eam_projection.py @@ -310,14 +310,6 @@ def RequestData(self, request, inInfo, outInfo): else: outData.ShallowCopy(inData) - if self.project == 0: - # Use cache to move mtime forward when needed - if self.cached_points is None: - self.cached_points = vtkPoints() - self.cached_points.ShallowCopy(inData.GetPoints()) - - outData.SetPoints(self.cached_points) - return 1 if self.cached_points and self.cached_points.GetMTime() >= max( inData.GetPoints().GetMTime(), self.GetMTime() ): From 669576af9796dc5434490271b2305fda4601be55 Mon Sep 17 00:00:00 2001 From: Dan Lipsa Date: Wed, 8 Apr 2026 14:33:58 -0400 Subject: [PATCH 7/8] fix: fix the filter for polydata input --- src/e3sm_quickview/plugins/eam_projection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/e3sm_quickview/plugins/eam_projection.py b/src/e3sm_quickview/plugins/eam_projection.py index 93c609b..59fe3b0 100644 --- a/src/e3sm_quickview/plugins/eam_projection.py +++ b/src/e3sm_quickview/plugins/eam_projection.py @@ -317,7 +317,7 @@ def RequestData(self, request, inInfo, outInfo): else: # we modify the points, so copy them out_points_vtk = vtkPoints() - out_points_vtk.DeepCopy(inData.GetPoints()) + out_points_vtk.DeepCopy(outData.GetPoints()) outData.SetPoints(out_points_vtk) out_points_np = outData.points From 214461156def797ba03abc72216b4ecf565da9ad Mon Sep 17 00:00:00 2001 From: Dan Lipsa Date: Wed, 8 Apr 2026 14:34:42 -0400 Subject: [PATCH 8/8] fix: better fix for MTime() issue --- src/e3sm_quickview/plugins/eam_projection.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/e3sm_quickview/plugins/eam_projection.py b/src/e3sm_quickview/plugins/eam_projection.py index 59fe3b0..3e73823 100644 --- a/src/e3sm_quickview/plugins/eam_projection.py +++ b/src/e3sm_quickview/plugins/eam_projection.py @@ -497,8 +497,11 @@ def RequestData(self, request, inInfo, outInfo): inData = self.GetInputData(inInfo, 0, 0) outData = self.GetOutputData(outInfo, 0) if self.trim_lon == [0, 0] and self.trim_lat == [0, 0]: - # ShallowCopy results in a crash - outData.DeepCopy(inData) + outData.ShallowCopy(inData) + # if the filter execution follows an another execution that trims the + # number of points, the downstream filter could think that + # the trimmed points are still valid which results in a crash + outData.GetPoints().Modified() return 1 if self.cached_cell_centers and self.cached_cell_centers.GetMTime() >= max(