STLWriter

Introduction

vtkSTLWriter writes stereo lithography (.stl) files in either ASCII or binary
form. Stereo lithography files contain only triangles. Since VTK 8.1, this
writer converts non-triangle polygons into triangles, so there is no longer a
need to use vtkTriangleFilter prior to using this writer if the input
contains polygons with more than three vertices.

Methods

extend

Method used to decorate a given object (publicAPI+model) with vtkSTLWriter characteristics.

Argument Type Required Description
publicAPI Yes object on which methods will be bounds (public)
model Yes object on which data structure will be bounds (protected)
initialValues ISTLWriterInitialValues No (default: {})

getFormat

getTransform

newInstance

Method used to create a new instance of vtkSTLWriter

Argument Type Required Description
initialValues ISTLWriterInitialValues No for pre-setting some of its content

requestData

Argument Type Required Description
inData Yes
outData Yes

setFormat

Argument Type Required Description
format FormatTypes Yes

setTransform

Argument Type Required Description
transform mat4 Yes Tranformation matrix.

writeSTL

Argument Type Required Description
polyData vktPolyData Yes
format FormatTypes No
transform mat4 No

Source

Constants.js
export const FormatTypes = {
ASCII: 'ascii',
BINARY: 'binary',
};

export default {
FormatTypes,
};
index.d.ts
import { mat4 } from 'gl-matrix';
import vtkPolyData from '../../../Common/DataModel/PolyData';
import { vtkAlgorithm, vtkObject } from '../../../interfaces';

export enum FormatTypes {
ASCII,
BINARY,
}

/**
*
*/
export interface ISTLWriterInitialValues {}

type vtkSTLWriterBase = vtkObject & vtkAlgorithm;

export interface vtkSTLWriter extends vtkSTLWriterBase {
/**
*
*/
getFormat(): FormatTypes;

/**
*
*/
getTransform(): mat4;

/**
*
* @param inData
* @param outData
*/
requestData(inData: any, outData: any): void;

/**
*
* @param {FormatTypes} format
*/
setFormat(format: FormatTypes): boolean;

/**
*
* @param {mat4} transform Tranformation matrix.
*/
setTransform(transform: mat4): boolean;
}

/**
* Method used to decorate a given object (publicAPI+model) with vtkSTLWriter characteristics.
*
* @param publicAPI object on which methods will be bounds (public)
* @param model object on which data structure will be bounds (protected)
* @param {ISTLWriterInitialValues} [initialValues] (default: {})
*/
export function extend(
publicAPI: object,
model: object,
initialValues?: ISTLWriterInitialValues
): void;

/**
* Method used to create a new instance of vtkSTLWriter
* @param {ISTLWriterInitialValues} [initialValues] for pre-setting some of its content
*/
export function newInstance(
initialValues?: ISTLWriterInitialValues
): vtkSTLWriter;

/**
*
* @param {vktPolyData} polyData
* @param {FormatTypes} [format]
* @param {mat4} [transform]
*/
export function writeSTL(
polyData: vtkPolyData,
format?: FormatTypes,
transform?: mat4
): vtkPolyData;

/**
* vtkSTLWriter writes stereo lithography (.stl) files in either ASCII or binary
* form. Stereo lithography files contain only triangles. Since VTK 8.1, this
* writer converts non-triangle polygons into triangles, so there is no longer a
* need to use vtkTriangleFilter prior to using this writer if the input
* contains polygons with more than three vertices.
*/
export declare const vtkSTLWriter: {
newInstance: typeof newInstance;
extend: typeof extend;
writeSTL: typeof writeSTL;
};
export default vtkSTLWriter;
index.js
// @author: Thomas Beznik <thomas.beznik@relu.eu>, with the help of Julien Finet <julien.finet@kitware.com> (https://github.com/Kitware/vtk-js/issues/1442)
// and inspired from Paul Kaplan (https://gist.github.com/paulkaplan/6d5f0ab2c7e8fdc68a61).

import { vec3 } from 'gl-matrix';
import macro from 'vtk.js/Sources/macros';
import vtkTriangle from 'vtk.js/Sources/Common/DataModel/Triangle';
import { FormatTypes } from 'vtk.js/Sources/IO/Geometry/STLWriter/Constants';

const { vtkErrorMacro } = macro;

// ----------------------------------------------------------------------------
// Global methods
// ----------------------------------------------------------------------------

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]);
}

// ----------------------------------------------------------------------------
// vtkSTLWriter methods
// ----------------------------------------------------------------------------

const binaryWriter = () => {
let offset = 0;
let dataView = null;
return {
init: (polyData) => {
const polys = polyData.getPolys().getData();
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
dataView = new DataView(buffer);
},
writeHeader: (polyData) => {
offset += 80; // Header is empty // TODO: could add date, version, package

// First need to write the number of cells
dataView.setUint32(offset, polyData.getNumberOfCells(), true);
offset += 4;
},
writeTriangle: (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
},
writeFooter: (polyData) => {},
getOutputData: () => dataView,
};
};

const asciiWriter = () => {
let file = '';
return {
init: (polyData) => {},
writeHeader: (polyData) => {
file += 'solid ascii\n';
},
writeTriangle: (v1, v2, v3, dn) => {
file += ` facet normal ${dn[0].toPrecision(6)} ${dn[1].toPrecision(
6
)} ${dn[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';
},
writeFooter: (polyData) => {
file += 'endsolid\n';
},
getOutputData: () => file,
};
};

function writeSTL(polyData, format = FormatTypes.BINARY, transform = null) {
let writer = null;
if (format === FormatTypes.BINARY) {
writer = binaryWriter();
} else if (format === FormatTypes.ASCII) {
writer = asciiWriter();
} else {
vtkErrorMacro('Invalid format type');
}

writer.init(polyData);
writer.writeHeader(polyData);

const polys = polyData.getPolys().getData();
const points = polyData.getPoints().getData();
const strips = polyData.getStrips() ? polyData.getStrips().getData() : null;

const 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);

writer.writeTriangle(v1, v2, v3, n);
}
}
writer.writeFooter(polyData);
return writer.getOutputData();
}

// ----------------------------------------------------------------------------
// Static API
// ----------------------------------------------------------------------------

export const STATIC = {
writeSTL,
};

// ----------------------------------------------------------------------------
// vtkSTLWriter methods
// ----------------------------------------------------------------------------

function vtkSTLWriter(publicAPI, model) {
// Set our className
model.classHierarchy.push('vtkSTLWriter');

publicAPI.requestData = (inData, outData) => {
const input = inData[0];
if (!input || input.getClassName() !== 'vtkPolyData') {
vtkErrorMacro('Invalid or missing input');
return;
}
outData[0] = writeSTL(input, model.format, model.transform);
};
}

// ----------------------------------------------------------------------------
// Object factory
// ----------------------------------------------------------------------------

const DEFAULT_VALUES = {
format: FormatTypes.BINARY,
transform: null,
};

// ----------------------------------------------------------------------------

export function extend(publicAPI, model, initialValues = {}) {
Object.assign(model, DEFAULT_VALUES, initialValues);

// Make this a VTK object
macro.obj(publicAPI, model);

// Also make it an algorithm with one input and one output
macro.algo(publicAPI, model, 1, 1);

macro.setGet(publicAPI, model, ['format', 'transform']);

// Object specific methods
vtkSTLWriter(publicAPI, model);
}

// ----------------------------------------------------------------------------

export const newInstance = macro.newInstance(extend, 'vtkSTLWriter');

// ----------------------------------------------------------------------------

export default { newInstance, extend, ...STATIC };