# pvd , vtu , vti files with vtk wasm

**URL:** https://discourse.vtk.org/t/pvd-vtu-vti-files-with-vtk-wasm/16060
**Category:** vtk.wasm
**Created:** [September 24, 2025, 8:17pm UTC](https://discourse.vtk.org/t/pvd-vtu-vti-files-with-vtk-wasm/16060 "2025-09-24T20:17:56Z")
**Posts on this page:** 11
**Page:** 1

<div class="post-metadata">

### Author: ![jtristanod3d](https://discourse.vtk.org/user_avatar/discourse.vtk.org/jtristanod3d/32/10299_2.png) [@jtristanod3d](https://discourse.vtk.org/u/jtristanod3d)
#### Post date: [September 24, 2025, 8:17pm UTC](https://discourse.vtk.org/t/pvd-vtu-vti-files-with-vtk-wasm/16060/1 "2025-09-24T20:17:57Z")

</div>

Hi, I am a paraview and pyvista user. I’ve stayed away from trame and vtk.js due to server side rendering and slow js rendering. Now that you have vtk wasm I want a simple page to just load a pvd file from disk and plot it along with a drop down of it’s data arrays. GH Copilot couldnt do it for me so I’m asking here. any ideas how to do this?

Thanks

Joe

---

<div class="post-metadata">

### Author: ![Sebastien\_Jourdain](https://discourse.vtk.org/user_avatar/discourse.vtk.org/sebastien_jourdain/32/100_2.png) [@Sebastien\_Jourdain](https://discourse.vtk.org/u/Sebastien_Jourdain)
#### Post date: [September 25, 2025, 2:54am UTC](https://discourse.vtk.org/t/pvd-vtu-vti-files-with-vtk-wasm/16060/2 "2025-09-25T02:54:11Z")

</div>

They are a couple of trickiness to the setup but the following code, works for me

```python
from pathlib import Path

from trame.app import TrameApp
from trame.ui.vuetify3 import VAppLayout
from trame.widgets import vuetify3 as v3, vtklocal
from trame.decorators import change

from paraview.modules.vtkPVVTKExtensionsIOCore import vtkPVDReader
import vtk

OVERLAY_TOP_LEFT = "position: absolute; top: 1rem; left: 1rem;z-index: 1;width: 10rem;"

class PVDViewer(TrameApp):
    def __init__ (self, server=None):
        super(). __init__ (server)

        # CLI handling
        self.server.cli.add_argument("--data", required=True)
        args, _ = self.server.cli.parse_known_args()

        # Init app
        self._setup_vtk(args.data)
        self._build_ui()

    def _setup_vtk(self, file_to_load):
        reader = vtkPVDReader(
           file_name=str(Path(file_to_load).resolve())
        )
        geometry = vtk.vtkGeometryFilter(
           fast_mode=False,
           remove_ghost_interfaces=False,
        )
        mapper = vtk.vtkPolyDataMapper()
        mapper.GetLookupTable().SetHueRange(0.6, 0)
        actor = vtk.vtkActor(mapper=mapper)
        reader >> geometry >> mapper

        renderer = vtk.vtkRenderer(background=(0.5, 0.5, 0.5))
        render_window = vtk.vtkRenderWindow()
        render_window.AddRenderer(renderer)
        render_window.OffScreenRenderingOn()

        interactor = vtk.vtkRenderWindowInteractor()
        interactor.SetRenderWindow(render_window)
        interactor.GetInteractorStyle().SetCurrentStyleToTrackballCamera()

        renderer.AddActor(actor)
        renderer.ResetCamera()

        self.reader = reader
        self.mapper = mapper
        self.render_window = render_window
        self.fields = [
            reader.GetPointArrayName(i)
            for i in range(reader.GetNumberOfPointArrays())
        ]

    @change("color_by")
    def _on_color_by(self, color_by, **_):
        if not color_by:
            self.mapper.SetScalarVisibility(0)
            return

        self.mapper.SetScalarVisibility(1)
        self.mapper.SetColorModeToMapScalars()
        self.mapper.SelectColorArray(color_by)
        self.mapper.SetScalarModeToUsePointFieldData()

        # Update LUT (not great)
        color_range = (
           self.reader
               .GetOutputAsDataSet()
               .point_data[color_by]
               .GetRange()
        )
        self.mapper.SetScalarRange(color_range)
        print(f"{color_by}={color_range}")

        # Update view
        self.ctx.view.update()

    def _build_ui(self):
        with VAppLayout(self.server, full_height=True) as self.ui:
            vtklocal.LocalView(
                self.render_window,
                ctx_name="view",
            )
            v3.VSelect(
                v_model=("color_by", None),
                items=("fields", self.fields),
                variant="outlined",
                hide_details=True,
                density="compact",
                style=OVERLAY_TOP_LEFT,
            )

def main():
    app = PVDViewer()
    app.server.start()

if __name__ == " __main__":
    main()

```

So for running the following code, you need ParaView 6.0 and do the following:

1. Create a venv using Python 3.12 (MUST match ParaView Python version)
2. Install in that venv `trame`, `trame-vtklocal`, `trame-vuetify` and `vtk==9.5.0`
3. Execute `python -m trame.tools.www --output www` from your venv to trigger the download of the wasm bundle matching the VTK of your bundle. (In this case `vtk==9.5.0` which is the one used in ParaView).
4. Remove vtk from venv (`pip uninstall vtk`) to prevent conflict between venv and ParaView’s VTK.
5. Run ParaView with the venv `/Applications/ParaView-6.0.0.app/Contents/bin/pvpython --venv .venv main.py --data ./path/to/file.pvd`

Hopefully this sample code show you the basics of what can be done with trame, ParaView and WASM. 😉

Seb

PS: You have a full VTK example with WASM available [here](https://kitware.github.io/trame/examples/vtk/wasm.html).

---

<div class="post-metadata">

### Author: ![jtristanod3d](https://discourse.vtk.org/user_avatar/discourse.vtk.org/jtristanod3d/32/10299_2.png) [@jtristanod3d](https://discourse.vtk.org/u/jtristanod3d)
#### Post date: [September 25, 2025, 1:24pm UTC](https://discourse.vtk.org/t/pvd-vtu-vti-files-with-vtk-wasm/16060/3 "2025-09-25T13:24:56Z")

</div>

Thank you so much for the help!

Joe

---

<div class="post-metadata">

### Author: ![jtristanod3d](https://discourse.vtk.org/user_avatar/discourse.vtk.org/jtristanod3d/32/10299_2.png) [@jtristanod3d](https://discourse.vtk.org/u/jtristanod3d)
#### Post date: [September 25, 2025, 7:23pm UTC](https://discourse.vtk.org/t/pvd-vtu-vti-files-with-vtk-wasm/16060/4 "2025-09-25T19:23:55Z")

</div>

So, it’s all working fairly well except for these 2 issues:

1. The tree view is not in leaf selection mode, that is I cannot select deselect the root nodes of the tree no matter which options I choose
2. Using the card, the select and tree control are rendered twice. Once in the card, and again at the bottom of the screen. Here’s the code

My dataset is a pvd file that consists of multiblock data

```auto
from pathlib import Path

from trame.app import TrameApp
from trame.ui.vuetify3 import VAppLayout
from trame.widgets import vuetify3 as v3, vtklocal, html
from trame.decorators import change

from paraview.modules.vtkPVVTKExtensionsIOCore import vtkPVDReader
import vtk

OVERLAY_TOP_LEFT = "position: absolute; top: 1rem; left: 1rem;z-index: 1; width: 20rem; max-width: 22rem; background: rgba(255,255,255,0.1);"
v3.enable_lab()

class PVDViewer(TrameApp):
    def _build_multiblock_tree(self, dataset, id_counter, renderer, lut, actors, block_id_to_actor, block_ids, fields):
        import vtk
        tree = []
        if hasattr(dataset, "GetNumberOfBlocks") and dataset.GetNumberOfBlocks() > 0:
            for i in range(dataset.GetNumberOfBlocks()):
                child = dataset.GetBlock(i)
                name = dataset.GetMetaData(i).Get(vtk.vtkCompositeDataSet.NAME()) if dataset.GetMetaData(i) and dataset.GetMetaData(i).Has(vtk.vtkCompositeDataSet.NAME()) else f"Block {i}"
                node_id = next(id_counter)
                if hasattr(child, "GetNumberOfBlocks") and child.GetNumberOfBlocks() > 0:
                    children = self._build_multiblock_tree(child, id_counter, renderer, lut, actors, block_id_to_actor, block_ids, fields)
                    tree.append({"id": node_id, "name": name, "children": children})
                elif child and child.IsA('vtkDataSet'):
                    # Only create actor for leaf
                    mapper = vtk.vtkDataSetMapper()
                    mapper.SetInputData(child)
                    mapper.SetLookupTable(lut)
                    actor = vtk.vtkActor(mapper=mapper)
                    renderer.AddActor(actor)
                    actors.append(actor)
                    block_id_to_actor[node_id] = actor
                    block_ids.append(node_id)
                    # Collect point data arrays
                    pd = child.GetPointData()
                    arr_names = [pd.GetArrayName(j) for j in range(pd.GetNumberOfArrays())]
                    for name_field in arr_names:
                        if name_field:
                            fields.add(name_field)
                    tree.append({"id": node_id, "name": name})
                else:
                    tree.append({"id": node_id, "name": name})
        return tree
    def __init__ (self, server=None):
        super(). __init__ (server)

        # CLI handling
        self.server.cli.add_argument("--data", required=True)
        args, _ = self.server.cli.parse_known_args()

        # Init app
        self._setup_vtk(args.data)
        self._build_ui()

    def _setup_vtk(self, file_to_load):
        reader = vtkPVDReader(
            file_name=str(Path(file_to_load).resolve())
        )
        reader.Update()

        renderer = vtk.vtkRenderer(background=(0.5, 0.5, 0.5))
        render_window = vtk.vtkRenderWindow()
        render_window.AddRenderer(renderer)
        render_window.OffScreenRenderingOn()

        interactor = vtk.vtkRenderWindowInteractor()
        interactor.SetRenderWindow(render_window)
        interactor.GetInteractorStyle().SetCurrentStyleToTrackballCamera()

        # Create a shared 12-band rainbow lookup table
        lut = vtk.vtkLookupTable()
        lut.SetNumberOfTableValues(12)
        lut.SetHueRange(0.66667, 0.0) # VTK rainbow: blue to red
        lut.SetTableRange(0, 1) # Will be set to data range later
        lut.SetRampToLinear()
        lut.SetNumberOfColors(12)
        lut.Build()
        self.lut = lut

        # Add all blocks as actors and collect all unique point data arrays
        output = reader.GetOutputDataObject(0)
        fields = set()
        actors = []
        self._block_id_to_actor = {}
        self._tree_data = []
        self._block_ids = []
        def id_counter_gen():
            n = 1
            while True:
                yield n
                n += 1
        id_counter = id_counter_gen()
        if output and output.IsA('vtkMultiBlockDataSet'):
            print("MultiBlockDataSet with blocks")
            self._tree_data = self._build_multiblock_tree(output, id_counter, renderer, lut, actors, self._block_id_to_actor, self._block_ids, fields)
        elif output and output.IsA('vtkDataSet'):
            print("Single vtkDataSet")
            node_id = 1
            mapper = vtk.vtkDataSetMapper()
            mapper.SetInputData(output)
            mapper.SetLookupTable(lut)
            actor = vtk.vtkActor(mapper=mapper)
            renderer.AddActor(actor)
            actors.append(actor)
            self._block_id_to_actor[node_id] = actor
            self._block_ids = [node_id]
            self._tree_data = [{"id": node_id, "name": "Block 0"}]
            pd = output.GetPointData()
            arr_names = [pd.GetArrayName(i) for i in range(pd.GetNumberOfArrays())]
            print(f"Single block arrays: {arr_names}")
            for name in arr_names:
                if name:
                    fields.add(name)

        renderer.ResetCamera()

        self.reader = reader
        self.actors = actors
        self.render_window = render_window
        self.fields = sorted(fields)
        self.server.state.tree_data = self._tree_data
        self.server.state.visible_blocks = list(self._block_ids)

    @change("visible_blocks")
    def _on_visible_blocks(self, visible_blocks, **_):
        # Set actor visibility based on selected block ids
        for block_id, actor in self._block_id_to_actor.items():
            actor.SetVisibility(block_id in visible_blocks)
        self.ctx.view.update()

    @change("color_by")
    def _on_color_by(self, color_by, **_):
        if not color_by:
            for actor in getattr(self, 'actors', []):
                mapper = actor.GetMapper()
                mapper.SetScalarVisibility(0)
            return

        # First, find the global min/max for the selected array across all blocks
        global_min = None
        global_max = None
        for idx, actor in enumerate(getattr(self, 'actors', [])):
            mapper = actor.GetMapper()
            ds = mapper.GetInput()
            pd = ds.GetPointData() if ds else None
            arr = pd.GetArray(color_by) if pd else None
            if arr is not None:
                arr_range = arr.GetRange()
                if global_min is None or arr_range[0] < global_min:
                    global_min = arr_range[0]
                if global_max is None or arr_range[1] > global_max:
                    global_max = arr_range[1]
        print(f"Global range for '{color_by}': {global_min}, {global_max}")

        # Set the LUT range globally
        if hasattr(self, 'lut') and global_min is not None and global_max is not None:
            self.lut.SetTableRange(global_min, global_max)
            self.lut.Build()

        # Now, apply this range to all actors that have the array
        for idx, actor in enumerate(getattr(self, 'actors', [])):
            mapper = actor.GetMapper()
            ds = mapper.GetInput()
            pd = ds.GetPointData() if ds else None
            arr_names = [pd.GetArrayName(i) for i in range(pd.GetNumberOfArrays())] if pd else []
            print(f"Block {idx} arrays: {arr_names}")
            arr = pd.GetArray(color_by) if pd else None
            print(f"Trying to color by: '{color_by}'")
            if arr is not None and global_min is not None and global_max is not None:
                mapper.SetScalarVisibility(1)
                mapper.SetColorModeToMapScalars()
                mapper.SelectColorArray(color_by)
                mapper.SetScalarModeToUsePointFieldData()
                mapper.SetScalarRange(global_min, global_max)
                print(f"{color_by} set to global range: {global_min}, {global_max}")
            else:
                mapper.SetScalarVisibility(0)
                print(f"Block missing array '{color_by}', not colored.")

        # Update view
        self.ctx.view.update()

    def _build_ui(self):
        print(self._block_ids)
        print(self._tree_data)
        with VAppLayout(self.server, full_height=True) as self.ui:
            with v3.VMain():
                # 3D view
                vtklocal.LocalView(
                    self.render_window,
                    ctx_name="view",
                )
                # Floating card with treeview and select
                v3.VCard(
                    style=OVERLAY_TOP_LEFT,
                    children=[
                        v3.VSelect(
                            v_model=("color_by", None),
                            items=("fields", self.fields),
                            variant="outlined",
                            hide_details=True,
                            density="compact",
                            style="background: rgba(255,255,255,0.1);",
                        ),
                        v3.VTreeview(
                            v_model_selected="visible_blocks",
                            items=("tree_data", self._tree_data),
                            selectable=True,
                            active_strategy ="leaf",
                            select_strategy="leaf",
                            open_all=True,
                            item_title="name",
                            item_value="id",
                            variant="outlined",
                            hide_details=True,
                            density="compact",
                            style="margin-bottom: 1rem; background: rgba(255,255,255,0.1);",
                        ),
                    ],
                )

def main():
    app = PVDViewer()
    app.server.start()

if __name__ == " __main__":
    main()

```

---

<div class="post-metadata">

### Author: ![Sebastien\_Jourdain](https://discourse.vtk.org/user_avatar/discourse.vtk.org/sebastien_jourdain/32/100_2.png) [@Sebastien\_Jourdain](https://discourse.vtk.org/u/Sebastien_Jourdain)
#### Post date: [September 25, 2025, 8:16pm UTC](https://discourse.vtk.org/t/pvd-vtu-vti-files-with-vtk-wasm/16060/5 "2025-09-25T20:16:41Z")

</div>

I haven’t look at the code in detail, but to fix the duplicate, you need to write your card like so

```python
with v3.VCard(style=OVERLAY_TOP_LEFT):
   v3.VSelect(...)
   v3.VTreeview(...)

```

Otherwise the VSelect and VTree get linked to the parent container (with self.ui) and then added as children in the VCard. So by tagging the VCard as the parent, they only get added once.

---

<div class="post-metadata">

### Author: ![jtristanod3d](https://discourse.vtk.org/user_avatar/discourse.vtk.org/jtristanod3d/32/10299_2.png) [@jtristanod3d](https://discourse.vtk.org/u/jtristanod3d)
#### Post date: [September 25, 2025, 8:23pm UTC](https://discourse.vtk.org/t/pvd-vtu-vti-files-with-vtk-wasm/16060/6 "2025-09-25T20:23:45Z")

</div>

Thanks! That did it, also vuetify doc is a bit conflicting, the fix is “clasic”

---

<div class="post-metadata">

### Author: ![Sebastien\_Jourdain](https://discourse.vtk.org/user_avatar/discourse.vtk.org/sebastien_jourdain/32/100_2.png) [@Sebastien\_Jourdain](https://discourse.vtk.org/u/Sebastien_Jourdain)
#### Post date: [September 25, 2025, 8:39pm UTC](https://discourse.vtk.org/t/pvd-vtu-vti-files-with-vtk-wasm/16060/7 "2025-09-25T20:39:14Z")

</div>

Are you talking about that [doc](https://vuetifyjs.com/en/) or something else?

Ideally for testing and getting a nice UI, I use their [playground](https://play.vuetifyjs.com/) that I then convert into Python for trame…

---

<div class="post-metadata">

### Author: ![jtristanod3d](https://discourse.vtk.org/user_avatar/discourse.vtk.org/jtristanod3d/32/10299_2.png) [@jtristanod3d](https://discourse.vtk.org/u/jtristanod3d)
#### Post date: [September 28, 2025, 3:42pm UTC](https://discourse.vtk.org/t/pvd-vtu-vti-files-with-vtk-wasm/16060/8 "2025-09-28T15:42:02Z")

</div>

@Sebastien_Jourdain Yes, those docs. Good tips. On another note, I am trying to decide between using pyvsta and raw vtk for the app. My team and I are very comfortable with pyvista but vtk is more nuanced (less help than pyvista). The big blocker for me now is that I had a multiblock tree working with the vtk version. When I ported it to pyvista, the parent tree nodes don’t get selected in classic view. Is there other stuff going on behind the scenes with pyvista that vtk doesnt get involved with that would causes this to occur?

Trying to find the best way forward and most supported way forward in creating this post processing application. Your guidance on pyvista vs vtk would be appreciated.

Another thing w/pyvista is that I got trame working without having to install paraview. When containerizing this is a plus to not have to download 840+ MB.

Thanks in advance!

Joe

---

<div class="post-metadata">

### Author: ![jtristanod3d](https://discourse.vtk.org/user_avatar/discourse.vtk.org/jtristanod3d/32/10299_2.png) [@jtristanod3d](https://discourse.vtk.org/u/jtristanod3d)
#### Post date: [October 3, 2025, 6:55pm UTC](https://discourse.vtk.org/t/pvd-vtu-vti-files-with-vtk-wasm/16060/9 "2025-10-03T18:55:58Z")

</div>

@Sebastien_Jourdain when I reload geometry in my pvd from a different timestep the app blows up with all sorts of mapper errors on the wasm side. I’ve tried multiple things like removing the actors, resetting just the mappers, updating the vtkLocalView but I cannot get a new geometry to render. I would also like this for a file →new, file→open operation where the user deliberatley changes the geometry and updates the entire app. However, my case here is much simpler, the number of blocks and everything stays the same except the geometry and it’s arrays.

Do you have examples anywhere that does this with Trame and VTK Wasm?

Thanks

Joe

---

<div class="post-metadata">

### Author: ![Sebastien\_Jourdain](https://discourse.vtk.org/user_avatar/discourse.vtk.org/sebastien_jourdain/32/100_2.png) [@Sebastien\_Jourdain](https://discourse.vtk.org/u/Sebastien_Jourdain)
#### Post date: [October 7, 2025, 6:05pm UTC](https://discourse.vtk.org/t/pvd-vtu-vti-files-with-vtk-wasm/16060/10 "2025-10-07T18:05:22Z")

</div>

For the sync/reload issue, did you try with nightly version of vtk? We are still working heavily on improving the serialization. If not, it would be good to provide a simple example showcasing the issue on the [trame-vtklocal repo](https://github.com/Kitware/trame-vtklocal).

If you want a file browser within your window, you can use the one within [paraview-trame-components](https://github.com/Kitware/paraview-trame-components/blob/main/ptc/file.py) and just adapt it for your use case.

Either way, it should be fairly easy/quick to create what you are describing.

HTH

---

<div class="post-metadata">

### Author: ![Joe\_Tristano](https://discourse.vtk.org/user_avatar/discourse.vtk.org/joe_tristano/32/10300_2.png) [@Joe\_Tristano](https://discourse.vtk.org/u/Joe_Tristano)
#### Post date: [October 8, 2025, 10:08am UTC](https://discourse.vtk.org/t/pvd-vtu-vti-files-with-vtk-wasm/16060/11 "2025-10-08T10:08:44Z")

</div>

I will look into those. Part of then issue was a poorly constructed vtk pipeline on my part.

Speaking of nightly builds, sonthose address issues with axis interactors? I wanted to hook up clicking on those like a cad system. Adding it to my scene greatly reduced performance though
