PLYWriter

Introduction

vtkPLYWriter writes polygonal data in Stanford University PLY format (see
http://graphics.stanford.edu/data/3Dscanrep/). The data can be written in
either binary (little or big endian) or ASCII representation. As for
PointData and CellData, vtkPLYWriter cannot handle normals or vectors. It
only handles RGB PointData and CellData. You need to set the name of the
array (using SetName for the array and SetArrayName for the writer). If the
array is not a vtkUnsignedCharArray with 3 or 4 components, you need to
specify a vtkLookupTable to map the scalars to RGB.

Methods

extend

Method used to decorate a given object (publicAPI+model) with vtkPLYWriter 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 IPLYWriterInitialValues No (default: {})

getDataByteOrder

Get byte order value.

getFormat

Get file format value.

getHeaderComments

Get header comments.

getTextureCoordinatesName

Get textures mapping coordinates format.

getTextureFileName

Get texture filename.

getTransform

Get transformation matrix.

getWithColors

Get whether colors values are included.

getWithIndices

Get whether indices are included.

getWithNormals

Get whether normals are included.

getWithUVs

Get textures mapping coordinates.

newInstance

Method used to create a new instance of vtkPLYWriter

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

requestData

Argument Type Required Description
inData Yes
outData Yes

setDataByteOrder

Set byte order.

Argument Type Required Description
byteOrder Number Yes Byte order.

setFormat

Set file format.

Argument Type Required Description
format FormatTypes Yes File format.

setHeaderComments

Set header comments.

Argument Type Required Description
headerComments Array. Yes Header comments.

setTextureCoordinatesName

Set textures coordinates format.

Argument Type Required Description
textureCoordinatesName TextureCoordinatesName Yes Textures mapping coordinates format.

setTextureFileName

Set texture filename.

Argument Type Required Description
textureFileName String Yes Texture filename.

setTransform

Set tranformation matrix.

Argument Type Required Description
transform mat4 Yes Tranformation matrix.

setWithColors

Set colors values.

Argument Type Required Description
withColors Boolean Yes Include colors.

setWithIndices

Set indices values.

Argument Type Required Description
withIndices Boolean Yes Include indices.

setWithNormals

Set normals values.

Argument Type Required Description
withNormals Boolean Yes Include normals.

setWithUVs

Set UVs values.

Argument Type Required Description
withUVs Boolean Yes Include textures mapping coordinates.

writePLY

Argument Type Required Description
polyData vktPolyData Yes
format FormatTypes No
dataByteOrder Number No
comments Array. No Header comments.
textureFileName String No Texture file n coordinates name.
textureCoordinatesName TextureCoordinatesName No Textures mapping coordinates format.
transform mat4 No Tranformation matrix.
withNormals Boolean No Include normals.
withUVs Boolean No Include textures mapping coordinates.
withColors Boolean No Include colors.
withIndice Boolean No Include indice.

Source

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

/**
* Choose the name used for the texture coordinates.
* (u, v) or (texture_u, texture_v)
*/
export const TextureCoordinatesName = {
UV: ['u', 'v'],
TEXTURE_UV: ['texture_u', 'texture_v'],
};

export default {
FormatTypes,
TextureCoordinatesName,
};
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 enum TextureCoordinatesName {
UV,
TEXTURE_UV,
}

/**
*
*/
export interface IPLYWriterInitialValues {
format?: FormatTypes;
dataByteOrder?: number;
comments?: string[];
textureFileName?: string;
textureCoordinatesName?: TextureCoordinatesName;
transform?: mat4;
withNormals?: boolean;
withUVs?: boolean;
withColors?: boolean;
withIndice?: boolean;
}

type vtkPLYWriterBase = vtkObject & vtkAlgorithm;

export interface vtkPLYWriter extends vtkPLYWriterBase {
/**
* Get byte order value.
*/
getDataByteOrder(): number;

/**
* Get file format value.
*/
getFormat(): FormatTypes;

/**
* Get header comments.
*/
getHeaderComments(): string[];

/**
* Get textures mapping coordinates format.
*/
getTextureCoordinatesName(): TextureCoordinatesName;

/**
* Get texture filename.
*/
getTextureFileName(): string;

/**
* Get transformation matrix.
*/
getTransform(): mat4;

/**
* Get whether colors values are included.
*/
getWithColors(): boolean;

/**
* Get whether indices are included.
*/
getWithIndices(): boolean;

/**
* Get whether normals are included.
*/
getWithNormals(): boolean;

/**
* Get textures mapping coordinates.
*/
getWithUVs(): boolean;

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

/**
* Set byte order.
* @param {Number} byteOrder Byte order.
*/
setDataByteOrder(byteOrder: number): boolean;

/**
* Set file format.
* @param {FormatTypes} format File format.
*/
setFormat(format: FormatTypes): boolean;

/**
* Set header comments.
* @param {String[]} headerComments Header comments.
*/
setHeaderComments(headerComments: string[]): boolean;

/**
* Set textures coordinates format.
* @param {TextureCoordinatesName} textureCoordinatesName Textures mapping coordinates format.
*/
setTextureCoordinatesName(
textureCoordinatesName: TextureCoordinatesName
): boolean;

/**
* Set texture filename.
* @param {String} textureFileName Texture filename.
*/
setTextureFileName(textureFileName: string): boolean;

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

/**
* Set colors values.
* @param {Boolean} withColors Include colors.
*/
setWithColors(withColors: boolean): boolean;

/**
* Set indices values.
* @param {Boolean} withIndices Include indices.
*/
setWithIndices(withIndices: boolean): boolean;

/**
* Set normals values.
* @param {Boolean} withNormals Include normals.
*/
setWithNormals(withNormals: boolean): boolean;

/**
* Set UVs values.
* @param {Boolean} withUVs Include textures mapping coordinates.
*/
setWithUVs(withUVs: boolean): boolean;
}

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

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

/**
*
* @param {vktPolyData} polyData
* @param {FormatTypes} [format]
* @param {Number} [dataByteOrder]
* @param {String[]} [comments] Header comments.
* @param {String} [textureFileName] Texture file n coordinates name.
* @param {TextureCoordinatesName} [textureCoordinatesName] Textures mapping coordinates format.
* @param {mat4} [transform] Tranformation matrix.
* @param {Boolean} [withNormals] Include normals.
* @param {Boolean} [withUVs] Include textures mapping coordinates.
* @param {Boolean} [withColors] Include colors.
* @param {Boolean} [withIndice] Include indice.
*/
export function writePLY(
polyData: vtkPolyData,
format?: FormatTypes,
dataByteOrder?: number,
comments?: string[],
textureFileName?: string,
textureCoordinatesName?: TextureCoordinatesName,
transform?: mat4,
withNormals?: boolean,
withUVs?: boolean,
withColors?: boolean,
withIndice?: boolean
): vtkPolyData;

/**
* vtkPLYWriter writes polygonal data in Stanford University PLY format (see
* http://graphics.stanford.edu/data/3Dscanrep/). The data can be written in
* either binary (little or big endian) or ASCII representation. As for
* PointData and CellData, vtkPLYWriter cannot handle normals or vectors. It
* only handles RGB PointData and CellData. You need to set the name of the
* array (using SetName for the array and SetArrayName for the writer). If the
* array is not a vtkUnsignedCharArray with 3 or 4 components, you need to
* specify a vtkLookupTable to map the scalars to RGB.
*/
export declare const vtkPLYWriter: {
newInstance: typeof newInstance;
extend: typeof extend;
writePLY: typeof writePLY;
};
export default vtkPLYWriter;
index.js
import macro from 'vtk.js/Sources/macros';

import {
FormatTypes,
TextureCoordinatesName,
} from 'vtk.js/Sources/IO/Geometry/PLYWriter/Constants';

const { vtkErrorMacro, vtkWarningMacro } = macro;

// ----------------------------------------------------------------------------
// vtkPLYWriter methods
// ----------------------------------------------------------------------------
const writeHeader = (
polyData,
fileFormat,
fileType,
headerComments,
textureFileName,
textureCoordinatesName,
vertexCount,
faceListLength,
withNormals,
withUVs,
withColors,
withIndices
) => {
const isBinary = fileFormat !== FormatTypes.ASCII;
let format;
if (isBinary) {
format = fileType ? 'binary_little_endian' : 'binary_big_endian';
} else format = 'ascii';

headerComments.unshift('VTK.js generated PLY File');
if (textureFileName) {
headerComments.push(`TextureFile ${textureFileName}`);
}
const commentElements = headerComments
.map((comment) => `comment ${comment}`)
.join('\n');

const header = [
'ply',
`format ${format} 1.0`,
`${commentElements}`,
`element vertex ${vertexCount}`,
'property float x',
'property float y',
'property float z',
];

// normals
if (withNormals) {
header.push('property float nx', 'property float ny', 'property float nz');
}

// uvs
if (withUVs) {
header.push(
`property float ${textureCoordinatesName[0]}`,
`property float ${textureCoordinatesName[1]}`
);
}

// colors
if (withColors) {
header.push(
'property uchar red',
'property uchar green',
'property uchar blue'
);
}

// faces
if (withIndices) {
header.push(
`element face ${faceListLength}`,
'property list uchar int vertex_indices'
);
}

header.push('end_header\n');
return header.join('\n');
};

const binaryWriter = () => {
let output;
let vOffset;
let fOffset;
const indexByteCount = 4;
let ft;
return {
init: (polyData) => {},
writeHeader: (
polyData,
fileFormat,
fileType,
headerComments,
textureFileName,
textureCoordinatesName,
numPts,
numPolys,
withNormals,
withUVs,
withColors,
withIndices
) => {
const vertexCount = polyData.getPoints().getNumberOfPoints();
ft = fileType;
// 1 byte shape descriptor
// 3 vertex indices at ${indexByteCount} bytes
const faceListLength = withIndices
? numPolys * (indexByteCount * 3 + 1)
: 0;

// 3 position values at 4 bytes
// 3 normal values at 4 bytes
// 3 color channels with 1 byte
// 2 uv values at 4 bytes
const vertexListLength =
vertexCount *
(4 * 3 +
(withNormals ? 4 * 3 : 0) +
(withUVs ? 4 * 2 : 0) +
(withColors ? 3 : 0));

const header = writeHeader(
polyData,
fileFormat,
fileType,
headerComments,
textureFileName,
textureCoordinatesName,
numPts,
numPolys,
withNormals,
withUVs,
withColors,
withIndices
);
const headerBin = new TextEncoder().encode(header);
output = new DataView(
new ArrayBuffer(headerBin.length + vertexListLength + faceListLength)
);
new Uint8Array(output.buffer).set(headerBin, 0);
vOffset = headerBin.length;
fOffset = vOffset + vertexListLength;
},
writeVertice: (x, y, z, nx, ny, nz, u, v, r, g, b) => {
// xyz
output.setFloat32(vOffset, x, ft);
vOffset += 4;
output.setFloat32(vOffset, y, ft);
vOffset += 4;
output.setFloat32(vOffset, z, ft);
vOffset += 4;
// nxnynz
if (nx !== null && ny !== null && nz !== null) {
output.setFloat32(vOffset, nx, ft);
vOffset += 4;
output.setFloat32(vOffset, ny, ft);
vOffset += 4;
output.setFloat32(vOffset, nz, ft);
vOffset += 4;
}
// uv
if (u !== null && v !== null) {
output.setFloat32(vOffset, u, ft);
vOffset += 4;
output.setFloat32(vOffset, v, ft);
vOffset += 4;
}
// rgb
if (r !== null && g !== null && b !== null) {
output.setUint8(vOffset, r);
vOffset += 1;
output.setUint8(vOffset, g);
vOffset += 1;
output.setUint8(vOffset, b);
vOffset += 1;
}
},
writeFace: (n, x, y, z) => {
output.setUint8(fOffset, n);
fOffset += 1;
output.setUint32(fOffset, x, ft);
fOffset += indexByteCount;
output.setUint32(fOffset, y, ft);
fOffset += indexByteCount;
output.setUint32(fOffset, z, ft);
fOffset += indexByteCount;
},
writeFooter: (polyData) => {},
getOutputData: () => output,
};
};

const asciiWriter = () => {
let fileContent = '';
return {
init: (polyData) => {},
writeHeader: (
polyData,
fileFormat,
fileType,
headerComments,
textureFileName,
textureCoordinatesName,
numPts,
numPolys,
withNormals,
withUVs,
withColors,
withIndices
) => {
fileContent += writeHeader(
polyData,
fileFormat,
fileType,
headerComments,
textureFileName,
textureCoordinatesName,
numPts,
numPolys,
withNormals,
withUVs,
withColors,
withIndices
);
},
writeVertice: (x, y, z, nx, ny, nz, u, v, r, g, b) => {
fileContent += `${x} ${y} ${z}`;
if (nx !== null && ny !== null && nz !== null) {
fileContent += ` ${nx} ${ny} ${nz}`;
}
if (u !== null && v !== null) {
fileContent += ` ${u} ${v}`;
}
if (r !== null && g !== null && b !== null) {
fileContent += ` ${r} ${g} ${b}`;
}
fileContent += '\n';
},
writeFace: (n, x, y, z) => {
fileContent += `${n} ${x} ${y} ${z}\n`;
},
writeFooter: (polyData) => {},
getOutputData: () => fileContent,
};
};

function writePLY(
polyData,
format,
dataByteOrder,
headerComments,
textureFileName,
textureCoordinatesName,
transform,
withNormals,
withUVs,
withColors,
withIndices
) {
const inPts = polyData.getPoints();
const polys = polyData.getPolys();

if (inPts === null || polys === null) {
vtkErrorMacro('No data to write!');
}

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

let tCoordsName = textureCoordinatesName;
if (typeof textureCoordinatesName === 'undefined') {
vtkWarningMacro(
'Invalid TextureCoordinatesName value, fallback to default uv values'
);
tCoordsName = TextureCoordinatesName.UV;
}

writer.init(polyData);

const numPts = inPts.getNumberOfPoints();
const numPolys = polys.getNumberOfCells();

// textureCoords / uvs
const textureCoords = polyData.getPointData().getTCoords();
// eslint-disable-next-line no-param-reassign
withUVs = !(textureCoords === null);

// scalars / colors
const scalars = polyData.getPointData().getScalars();
// eslint-disable-next-line no-param-reassign
withColors = !(scalars === null);

const fileType = dataByteOrder ? 0 : 1;

writer.writeHeader(
polyData,
format,
fileType,
headerComments,
textureFileName,
tCoordsName,
numPts,
numPolys,
withNormals,
withUVs,
withColors,
withIndices
);

const normals = polyData.getPointData().getNormals();

// points / vertices
for (let i = 0; i < numPts; i++) {
// eslint-disable-next-line prefer-const
let p = inPts.getPoint(i);

// TODO: apply transform matrix
if (transform) {
// vec3.transformMat4(p, p, transform);
}

// coords
// divide by 1 to remove trailing zeros
const x = p[0].toPrecision(6) / 1;
const y = p[1].toPrecision(6) / 1;
const z = p[2].toPrecision(6) / 1;

// normals
let nx = null;
let ny = null;
let nz = null;

// uvs
let u = null;
let v = null;

// colors
let r = null;
let g = null;
let b = null;

if (textureCoords) {
u = textureCoords.getData()[i * 2];
v = textureCoords.getData()[i * 2 + 1];
}

if (scalars) {
r = scalars.getData()[i * 3];
g = scalars.getData()[i * 3 + 1];
b = scalars.getData()[i * 3 + 2];
}

if (normals) {
nx = normals.getData()[i * 3];
ny = normals.getData()[i * 3 + 1];
nz = normals.getData()[i * 3 + 2];
}

writer.writeVertice(x, y, z, nx, ny, nz, u, v, r, g, b);
}

// polys / indices
const pd = polys.getData();
for (let i = 0, l = pd.length; i < l; i += 4) {
writer.writeFace(pd[i + 0], pd[i + 1], pd[i + 2], pd[i + 3]);
}

writer.writeFooter(polyData);
return writer.getOutputData();
}

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

export const STATIC = {
writePLY,
};

// ----------------------------------------------------------------------------
// vtkPLYWriter methods
// ----------------------------------------------------------------------------

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

publicAPI.requestData = (inData, outData) => {
const input = inData[0];
if (!input || input.getClassName() !== 'vtkPolyData') {
vtkErrorMacro('Invalid or missing input');
return;
}
outData[0] = writePLY(
input,
model.format,
model.dataByteOrder,
model.headerComments,
model.textureFileName,
model.textureCoordinatesName,
model.transform,
model.withNormals,
model.withUVs,
model.withColors,
model.withIndices
);
};
}

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

const DEFAULT_VALUES = {
format: FormatTypes.ASCII,
dataByteOrder: 0,
headerComments: [],
textureFileName: null,
textureCoordinatesName: TextureCoordinatesName.UV,
transform: null,
withNormals: true,
withUVs: true,
withColors: true,
withIndices: true,
};

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

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',
'dataByteOrder', // binary_little_endian 0 binary_big_endian 1
'headerComments',
'textureFileName',
'textureCoordinatesName',
'transform',
'withNormals',
'withUVs',
'withColors',
'withIndices',
]);

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

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

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

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

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