ply-js
Version:
A TypeScript port based on python-plyfile for reading and writing .ply files
160 lines (159 loc) • 6.72 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PlyElement = void 0;
const property_1 = require("./property");
const errors_1 = require("./errors");
const utils_1 = require("./utils");
class PlyElement {
constructor(name, properties, count, comments = []) {
this._propertyLookup = new Map();
this._comments = [];
this.data = [];
this._name = String(name);
this._properties = [...properties];
this._count = count;
this.comments = comments;
this._index();
this._haveList = this._properties.some(p => p instanceof property_1.PlyListProperty);
}
get name() { return this._name; }
get count() { return this._count; }
get properties() { return this._properties; }
set dataArray(rows) { this.data = rows; this._count = rows.length; this._checkSanity(); }
get comments() { return [...this._comments]; }
set comments(c) { this._comments = [...c]; }
_index() {
this._propertyLookup = new Map(this._properties.map(p => [p.name, p]));
if (this._propertyLookup.size !== this._properties.length)
throw new Error('two properties with same name');
}
_checkSanity() {
for (const prop of this._properties) {
for (const rec of this.data) {
if (!(prop.name in rec))
throw new Error(`dangling property '${prop.name}'`);
}
}
}
plyProperty(name) { return this._propertyLookup.get(name); }
dtype(byteOrder = '=') {
// TS version: return tuple list; consumers can map as needed
return this._properties.map(p => [p.name, p.dtype(byteOrder)]);
}
header() {
const lines = [`element ${this.name} ${this.count}`];
for (const c of this._comments)
lines.push(`comment ${c}`);
for (const p of this._properties)
lines.push(String(p));
return lines.join('\n');
}
// When reading binary, return the number of bytes consumed from the buffer
_read(stream, isText, byteOrder, _mmap, knownListLen = {}) {
if (isText)
return this._readTxt(stream.toString('utf8'));
return this._readBin(stream, byteOrder);
}
_readTxt(text) {
const lines = text.split(/\r?\n/).filter(Boolean).slice(0, this.count);
if (lines.length < this.count)
throw new errors_1.PlyElementParseError('early end-of-file', this, lines.length);
this.data = new Array(this.count);
for (let k = 0; k < this.count; k++) {
const parts = lines[k].trim().split(/\s+/);
const it = parts[Symbol.iterator]();
const rec = {};
for (const prop of this._properties) {
try {
const v = prop._fromFields(it);
rec[prop.name] = v;
}
catch (e) {
if (e?.message === 'StopIteration')
throw new errors_1.PlyElementParseError('early end-of-line', this, k, prop);
if (e?.message === 'ValueError')
throw new errors_1.PlyElementParseError('malformed input', this, k, prop);
throw e;
}
}
// Ensure no extra tokens
if (!it.next().done)
throw new errors_1.PlyElementParseError('expected end-of-line', this, k);
this.data[k] = rec;
}
}
_readBin(buffer, byteOrder) {
const bo = (0, utils_1.normalizeByteOrder)(false, byteOrder);
let offset = 0;
this.data = new Array(this.count);
for (let k = 0; k < this.count; k++) {
const rec = {};
for (const prop of this._properties) {
if (process.env.PLY_DEBUG === '1') {
try {
// eslint-disable-next-line no-console
console.debug(`PlyElement._readBin: element=${this._name} row=${k} prop=${prop.name} offset=${offset} bufferLen=${buffer.length}`);
}
catch { /** ignore debug errors */ }
}
if (prop instanceof property_1.PlyListProperty) {
try {
const { value, next } = prop._readBin(buffer, offset, bo);
offset = next;
rec[prop.name] = value;
}
catch {
// include more context when debugging
if (process.env.PLY_DEBUG === '1') {
// eslint-disable-next-line no-console
console.debug(`PlyElement._readBin: early EOF while reading list prop=${prop.name} at offset=${offset} element=${this._name} row=${k}`);
}
throw new errors_1.PlyElementParseError('early end-of-file', this, k, prop);
}
}
else {
try {
const { value, next } = prop._readBin(buffer, offset, bo);
offset = next;
rec[prop.name] = value;
}
catch {
if (process.env.PLY_DEBUG === '1') {
// eslint-disable-next-line no-console
console.debug(`PlyElement._readBin: early EOF while reading scalar prop=${prop.name} at offset=${offset} element=${this._name} row=${k}`);
}
throw new errors_1.PlyElementParseError('early end-of-file', this, k, prop);
}
}
}
this.data[k] = rec;
}
// return number of bytes consumed from buffer
return offset;
}
_write(streams, isText, byteOrder) {
if (isText)
return this._writeTxt(streams);
return this._writeBin(streams, byteOrder);
}
_writeTxt(out) {
for (const rec of this.data) {
const fields = [];
for (const prop of this._properties) {
for (const v of prop._toFields(rec[prop.name]))
fields.push(Number(v));
}
out.push(fields.join(' ') + '\n');
}
}
_writeBin(out, byteOrder) {
for (const rec of this.data) {
for (const prop of this._properties) {
const data = rec[prop.name];
const buf = prop._writeBin(data, byteOrder);
out.push(buf);
}
}
}
}
exports.PlyElement = PlyElement;