Vtk-js: STL Writer

Created on 12 May 2020  路  10Comments  路  Source: Kitware/vtk-js

Hello everyone,

I would like to be able to generate STL files from a vtkPolyData object in vtk.js. I see that for now, it is only possible to write this object to the .vtp format. My questions are thus:

  • Is this a feature that you are planning to implement soon?
  • To implement it myself, should I just transcribe the vtkSTLWriter from VTK to javascript? Are there some particular considerations/ changes between VTK and vtk.js to take into account?
  • Any hints/tips on how to implement this are welcome!

Thanks!

released

Most helpful comment

import {vec3} from 'gl-matrix';
import vtkTriangle from 'vtk.js/Sources/Common/DataModel/Triangle';

export class STLWriter {

    static getPolyDataAsAsciiSTL(polyData, transform = null) {
        const polys = polyData.getPolys().getData();
        const points = polyData.getPoints().getData();
        const strips = polyData.getStrips() ? polyData.getStrips().getData() : null;
        let file = '';

        file += 'solid ascii\n';

        let n = [];
        let v1 = [];
        let v2 = [];
        let v3 = [];

        // Strips

        if(strips && (strips.length > 0))
        {
            throw new Error ('Unsupported strips');
        }

        // Polys
        for(let i = 0; i < polys.length;)
        {
            const pointNumber = polys[i++];

            if(pointNumber)
            {
                v1 = [points[polys[i] * 3], points[(polys[i] * 3) + 1], points[(polys[i++] * 3) + 2]];
                v2 = [points[polys[i] * 3], points[(polys[i] * 3) + 1], points[(polys[i++] * 3) + 2]];
                v3 = [points[polys[i] * 3], points[(polys[i] * 3) + 1], points[(polys[i++] * 3) + 2]];
                if (transform) {
                    vec3.transformMat4(v1, v1, transform);
                    vec3.transformMat4(v2, v2, transform);
                    vec3.transformMat4(v3, v3, transform);
                }

                vtkTriangle.computeNormal(v1, v2, v3, n);

                file += ' facet normal ' + n[0].toPrecision(6) + ' ' + n[1].toPrecision(6) + ' ' + n[2].toPrecision(6) + '\n';
                file += '  outer loop\n';
                file += '   vertex ' + v1[0].toPrecision(6) + ' ' + v1[1].toPrecision(6) + ' ' + v1[2].toPrecision(6) + '\n';
                file += '   vertex ' + v2[0].toPrecision(6) + ' ' + v2[1].toPrecision(6) + ' ' + v2[2].toPrecision(6) + '\n';
                file += '   vertex ' + v3[0].toPrecision(6) + ' ' + v3[1].toPrecision(6) + ' ' + v3[2].toPrecision(6) + '\n';
                file += '  endloop\n';
                file += ' endfacet\n';
            }
            else
            {
                throw new Error('Unsupported polygon');
            }
        }

        file += 'endsolid\n';

        return file;
    }
}

All 10 comments

I don't think this feature is going to be implemented anytime soon. You might be the first who needs that feature for vtk.js.

You are more than welcome to contribute such writer and you can use the VTK/C++ code as reference on how to do so. You might also face a couple missing functions that you can also implement along the way.

Things to consider:

  • Try to keep the same file hierarchy (Kit/Module/ClassName) as the C++
  • Methods should start with a lower case
  • If you just want to deal with ASCII format and skip binary, that could be fine as well.
  • Then because you are on the Web, you need to come up with a way to generate the data in memory and provide a way to access it in a meaningful manner. Maybe with methods like getBlob() and getMemoryBuffer(): Uint8Array/ArrayBuffer.

HTH

I actually have a function that converts a polydata into an STL ascii string. I have no time to clean it up. Would you be willing to wrap it into a proper writer and push it to vtk.js ?

@finetjul , yes it could indeed be interesting! I am actually more interested in the binary STL, but it could already serve as an example to get started, as I don't have much experience with vtk.js.

import {vec3} from 'gl-matrix';
import vtkTriangle from 'vtk.js/Sources/Common/DataModel/Triangle';

export class STLWriter {

    static getPolyDataAsAsciiSTL(polyData, transform = null) {
        const polys = polyData.getPolys().getData();
        const points = polyData.getPoints().getData();
        const strips = polyData.getStrips() ? polyData.getStrips().getData() : null;
        let file = '';

        file += 'solid ascii\n';

        let n = [];
        let v1 = [];
        let v2 = [];
        let v3 = [];

        // Strips

        if(strips && (strips.length > 0))
        {
            throw new Error ('Unsupported strips');
        }

        // Polys
        for(let i = 0; i < polys.length;)
        {
            const pointNumber = polys[i++];

            if(pointNumber)
            {
                v1 = [points[polys[i] * 3], points[(polys[i] * 3) + 1], points[(polys[i++] * 3) + 2]];
                v2 = [points[polys[i] * 3], points[(polys[i] * 3) + 1], points[(polys[i++] * 3) + 2]];
                v3 = [points[polys[i] * 3], points[(polys[i] * 3) + 1], points[(polys[i++] * 3) + 2]];
                if (transform) {
                    vec3.transformMat4(v1, v1, transform);
                    vec3.transformMat4(v2, v2, transform);
                    vec3.transformMat4(v3, v3, transform);
                }

                vtkTriangle.computeNormal(v1, v2, v3, n);

                file += ' facet normal ' + n[0].toPrecision(6) + ' ' + n[1].toPrecision(6) + ' ' + n[2].toPrecision(6) + '\n';
                file += '  outer loop\n';
                file += '   vertex ' + v1[0].toPrecision(6) + ' ' + v1[1].toPrecision(6) + ' ' + v1[2].toPrecision(6) + '\n';
                file += '   vertex ' + v2[0].toPrecision(6) + ' ' + v2[1].toPrecision(6) + ' ' + v2[2].toPrecision(6) + '\n';
                file += '   vertex ' + v3[0].toPrecision(6) + ' ' + v3[1].toPrecision(6) + ' ' + v3[2].toPrecision(6) + '\n';
                file += '  endloop\n';
                file += ' endfacet\n';
            }
            else
            {
                throw new Error('Unsupported polygon');
            }
        }

        file += 'endsolid\n';

        return file;
    }
}

I have adapted the code of @finetjul for the Binary STL:

import { vec3 } from 'gl-matrix';
import vtkTriangle from 'vtk.js/Sources/Common/DataModel/Triangle';

function writeFloatBinary(dataview, offset, float) {
  dataview.setFloat32(offset, float.toPrecision(6), true);
  return offset + 4;
}

function writeVectorBinary(dataview, offset, vector) {
  let off = writeFloatBinary(dataview, offset, vector[0]);
  off = writeFloatBinary(dataview, off, vector[1]);
  return writeFloatBinary(dataview, off, vector[2]);
}

// From finetjul: https://github.com/Kitware/vtk-js/issues/1442
// Inspired from https://gist.github.com/paulkaplan/6d5f0ab2c7e8fdc68a61
class STLWriter {
  static getPolyDataAsBinarySTL(polyData, transform = null) {
    const polys = polyData.getPolys().getData();
    const points = polyData.getPoints().getData();
    const strips = polyData.getStrips() ? polyData.getStrips().getData() : null;
    const buffer = new ArrayBuffer(80 + 4 + (50 * polys.length) / 4); // buffer for the full file; size = header (80) + num cells (4) +  50 bytes per poly
    const dataview = new DataView(buffer);
    let offset = 0;

    offset += 80; // Header is empty // TODO: could add date, version, package

    let v1 = [];
    let v2 = [];
    let v3 = [];
    const dn = [];

    // First need to write the number of cells
    dataview.setUint32(offset, polyData.getNumberOfCells(), true);
    offset += 4;

    // Strips
    if (strips && strips.length > 0) {
      throw new Error('Unsupported strips');
    }

    // Polys
    for (let i = 0; i < polys.length; ) {
      const pointNumber = polys[i++];

      if (pointNumber) {
        v1 = [
          points[polys[i] * 3],
          points[polys[i] * 3 + 1],
          points[polys[i++] * 3 + 2],
        ];
        v2 = [
          points[polys[i] * 3],
          points[polys[i] * 3 + 1],
          points[polys[i++] * 3 + 2],
        ];
        v3 = [
          points[polys[i] * 3],
          points[polys[i] * 3 + 1],
          points[polys[i++] * 3 + 2],
        ];
        if (transform) {
          vec3.transformMat4(v1, v1, transform);
          vec3.transformMat4(v2, v2, transform);
          vec3.transformMat4(v3, v3, transform);
        }

        vtkTriangle.computeNormal(v1, v2, v3, dn);

        offset = writeVectorBinary(dataview, offset, dn);
        offset = writeVectorBinary(dataview, offset, v1);
        offset = writeVectorBinary(dataview, offset, v2);
        offset = writeVectorBinary(dataview, offset, v3);
        offset += 2; // unused 'attribute byte count' is a Uint16
      }
    }
    return dataview;
  }
}

But I struggle with the vtk.js architecture to integrate it; I am not sure to understand the overall architecture and the concept of "publicAPI", "model", etc. Would it be possible to explain it or to give some resources to understand it? A skeleton of how the vtkSTLWriter "class" should look like would be also a great help. Thanks!

vtk.js create objects via closures and the publicAPI is similar to your instance object on which you find the public methods. While the model is where you store any internal fields that are protected.
Then we use the macro to decorate the publicAPI with methods and compose them.

Typically here your class will be both a macro.obj and macro.algo where you will implement the processing method like any filter. Then you may provide getters using macro.get.

You can look this class as an example: https://github.com/Kitware/vtk-js/blob/master/Sources/Filters/General/ImageOutlineFilter/index.js

If need be I can try to take the time to write it for you since you've done the hard part. That way you could see how to do it for the next time.

I will try to write a first version based on the example you gave and will post it here for corrections!

Thanks. Feel free to create a merge request. That will be easier to comment.

:tada: This issue has been resolved in version 14.12.0 :tada:

The release is available on:

Your semantic-release bot :package::rocket:

Was this page helpful?
0 / 5 - 0 ratings