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:
Thanks!
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:
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:
Most helpful comment