Trouble exporting Scene to .vtkjs

Dear All,

Using the xml plugin from Point labels available with PV 5.7? - #7 by jourdain, we were able to add text labels to a point cloud in ParaView (Nightly on Mac).

Now, we would like to export the scene to a .vtkjs file to finally allow our colleagues a quick inspection in the vtk Scene Explorer.

Unfortunately, as soon as the Label Representation is selected for our data, the following error message appears upon .vtkjs-export:

Generic Warning: In vtkPVWebExporter.cxx, line 71
Failed to rename datasets using ParaView proxy name

Traceback (most recent call last):
  File "/Applications/ParaView-master-5.10.1-1356-g8c4be40eab.app/Contents/Python/paraview/web/vtkjs_helper.py", line 77, in applyParaViewNaming
    renameMap = getRenameMap()
  File "/Applications/ParaView-master-5.10.1-1356-g8c4be40eab.app/Contents/Python/paraview/web/vtkjs_helper.py", line 36, in getRenameMap
    names = getAllNames()
  File "/Applications/ParaView-master-5.10.1-1356-g8c4be40eab.app/Contents/Python/paraview/web/vtkjs_helper.py", line 26, in getAllNames
    actorRep = vtkRepInstance.GetActiveRepresentation().GetActor()
AttributeError: 'paraview.modules.vtkRemotingViews.vtkDataLabelRepr' object has no attribute 'GetActor'

Do you have any idea what we can do about this? Thank you :slight_smile:

Unfortunately I don’t think we have yet an implementation of those 3D labels in vtk.js.
But once we do, we will need to extend some code in VTK/ParaView to properly handle those object in the scene so they can be exported and displayed in vtk.js.

Maybe @martink or @Forrest_Li may know better the status of vtk.js on those text labels.

HTH

We do not have a point label class yet on the JS side.

But …you can sort of do what you want with a bit of code like this example

but it isn’t a direct in/out from paraview. But I think it is possible with some JS side coding.

Actually @martink suggestion is really good. You could export from ParaView just the pointset with the string array attached to it and have a custom vtk.js scene viewer that will do the same labels technique as that example.

The only caveat I see is that the PV exporter may currently skip non numerical array but that should be easy to fix/extend. And the JS code required to do such viewer will be pretty minimal and quick.

Hi @martink , @jourdain ,
thank you for your suggestions and sorry for not coming back to the topic earlier - I needed a while to learn at least some basics of javascript, npm, webpack, etc. to get things running :slight_smile:

Slightly inspired by SceneExplorerWidget.js, I put together the following LabelWidget.js:


import vtkPixelSpaceCallbackMapper from '@kitware/vtk.js/Rendering/Core/PixelSpaceCallbackMapper';
import vtkActor from '@kitware/vtk.js/Rendering/Core/Actor';


export default function addWidget(renderer, container, sceneItems, render) {

    let dims = null;

    const textCanvas = document.createElement('canvas');
    textCanvas.classList.add('labelCanvas');
    textCanvas.style.position = 'absolute';
    textCanvas.style.top = '0px';
    textCanvas.style.left = '0px';
    textCanvas.style.width = '100%';
    textCanvas.style.height = '100%';
    textCanvas.style.overflow = 'hidden';
    renderer.getContainer().appendChild(textCanvas);

    const textCtx = textCanvas.getContext('2d');

    window.addEventListener('resize', () => {
        dims = container.getBoundingClientRect();
        textCanvas.setAttribute('width', dims.width);
        textCanvas.setAttribute('height', dims.height);
        render();
      });

    window.dispatchEvent(new Event("resize"));

    function getArrayNames(sceneItem) {
        return `<option value="0">---</option>`
            + sceneItem.source.getArrays()
                // introduced "+1" in array index, so that "0" means "no labels"
                .map((array, idx) => `<option value="${idx+1}">${array.name}</option>`)
                .join('');
    }

    const listStr = sceneItems
        .map(
        (item, idx) =>
            `<li><select name="${idx}">${getArrayNames(item)}</select>&nbsp;&nbsp;${item.name}</li>`
        )
        .join('');

    const listContainer = document.createElement('ul');
    listContainer.innerHTML = listStr;

    listContainer.style.position = 'absolute';
    listContainer.style.left = '25px';
    listContainer.style.top = '100px';
    listContainer.style.backgroundColor = 'white';
    listContainer.style.borderRadius = '5px';
    listContainer.style.listStyle = 'none';
    listContainer.style.padding = '5px 10px';
    listContainer.style.margin = '0';
    listContainer.style.display = 'block';
    listContainer.style.border = 'solid 1px black';

    container.appendChild(listContainer);

    document.querySelector('body').addEventListener('keypress', (e) => {
      if (String.fromCharCode(e.charCode) === 'l') {
        if (listContainer.style.display === 'none') {
          listContainer.style.display = 'block';
        } else {
          listContainer.style.display = 'none';
        }
      }
    });

    const selectList = listContainer.querySelectorAll('select');
    for (let i = 0; i < selectList.length; i++) {
      const selectElem = selectList[i];
      selectElem.addEventListener('change', handleChange);
    }

    function handleChange(e) {
        const itemIdx = Number(e.target.name);
        const value = Number(e.target.value);
        // ugly hack: we just append an attribute to the sceneItem
        sceneItems[itemIdx].labelArray = value;
        if (render) {
            render();
        }
    }

    for (const sceneItem of sceneItems) {
       console.log(`Adding labels to ${sceneItem.name}`)

        const psMapper = vtkPixelSpaceCallbackMapper.newInstance();
        psMapper.setInputConnection(sceneItem.source.getOutputPort());
        psMapper.setCallback((coordsList) => {
        if (textCtx && dims) {
            textCtx.clearRect(0, 0, dims.width, dims.height);

            const labelArray = sceneItem.labelArray;

            if (labelArray) {
                coordsList.forEach((xy, idx) => {
                    textCtx.font = '12px serif';
                    textCtx.textAlign = 'center';
                    textCtx.textBaseline = 'middle';
                    textCtx.fillText(
                        // "-1" to compensate for offset introduced in getArrays()
                        `${sceneItem.source.getArrays()[labelArray-1].array.values[idx]}`,
                        // pixel ratio scaling from https://github.com/Kitware/vtk-js/issues/1179#issuecomment-544709725
                        xy[0] / window.devicePixelRatio,
                        dims.height - xy[1] / window.devicePixelRatio);
                    });            }
            }
        });

        const textActor = vtkActor.newInstance();
        textActor.setMapper(psMapper);
        renderer.getRenderer().addActor(textActor);
    }
}

which can be instantiated in the onReady() function of the SceneExplorer via

import labelWidget from './LabelWidget';
...

        // Add UI to enable labels for different scene items
        labelWidget(
          fullScreenRenderer,
          document.querySelector('body'),
          sceneImporter.getScene(),
          renderWindow.render
        );

Now, I can conveniently select, for which sceneItems I want to see labels at the respective vtkPoints and which data array entries are to be printed (very messy here, this is not the original data):

Missing point is now, to also export the non-numeric point data arrays from Paraview into the *.vtkjs file.

Do you possibly have suggestions where I could start with this?

You will have to fix that class and the Array one to support the vtkAbstractArray/vtkStringArray.

Ouch, seeing C++ here does that mean I‘d have to build Paraview myself? - Did that years ago on Linux, but today I‘m on a corporate Windows machine with virtually no permissions…

Would there be an option with Python scripting in Paraview?