add base .pm4n output
This commit is contained in:
parent
fa7bc3db15
commit
6e3a34f6b9
4 changed files with 558 additions and 4 deletions
|
|
@ -10,6 +10,6 @@
|
||||||
"resolution_y": 5120,
|
"resolution_y": 5120,
|
||||||
"sla_format": "pm4n",
|
"sla_format": "pm4n",
|
||||||
"sla_file_ext": "pm4n",
|
"sla_file_ext": "pm4n",
|
||||||
"sla_format_version": 1
|
"sla_format_version": 517
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -155,7 +155,7 @@ export const SLA_FORMATS = {
|
||||||
id: 'pm4n',
|
id: 'pm4n',
|
||||||
ext: 'pm4n',
|
ext: 'pm4n',
|
||||||
proprietary: true,
|
proprietary: true,
|
||||||
available: false,
|
available: true,
|
||||||
family: 'photon-workshop'
|
family: 'photon-workshop'
|
||||||
},
|
},
|
||||||
pm5: {
|
pm5: {
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,12 @@ export function sla_export(print, online, ondone) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PW.supports(format)) {
|
if (PW.supports(format)) {
|
||||||
return PW.encode(format);
|
return PW.encode(print, (progress, message) => {
|
||||||
|
online({ progress, message });
|
||||||
|
}, photon).then(output => {
|
||||||
|
let { file, layers, volume } = output;
|
||||||
|
ondone({ width, height, file, layers, volume }, [file]);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (format === 'photon' || format === 'photons') {
|
if (format === 'photon' || format === 'photons') {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||||
|
|
||||||
|
import { imageToRowMajor, renderRasterLayers, round } from './raster.js';
|
||||||
|
|
||||||
const FAMILY = "Photon Workshop";
|
const FAMILY = "Photon Workshop";
|
||||||
|
const MARK_SIZE = 12;
|
||||||
|
const VERSION_517 = 517;
|
||||||
|
const RLE4_ENCODING_LIMIT = 0xfff;
|
||||||
const FORMAT_RECORDS = {
|
const FORMAT_RECORDS = {
|
||||||
pw0: { ext: "pw0", generation: "pw", notes: "Photon Zero" },
|
pw0: { ext: "pw0", generation: "pw", notes: "Photon Zero" },
|
||||||
pwx: { ext: "pwx", generation: "pw", notes: "Photon X" },
|
pwx: { ext: "pwx", generation: "pw", notes: "Photon X" },
|
||||||
|
|
@ -24,10 +29,20 @@ const FORMAT_RECORDS = {
|
||||||
pm7m: { ext: "pm7m", generation: "m7", notes: "Photon Mono M7 Pro class" },
|
pm7m: { ext: "pm7m", generation: "m7", notes: "Photon Mono M7 Pro class" },
|
||||||
pwsz: { ext: "pwsz", generation: "v3-zip", notes: "Photon Workshop v3 zip container" }
|
pwsz: { ext: "pwsz", generation: "v3-zip", notes: "Photon Workshop v3 zip container" }
|
||||||
};
|
};
|
||||||
|
const ENCODE_FORMATS = new Set(["pm4n"]);
|
||||||
|
|
||||||
function encode(format) {
|
function encode(print, progress, renderer) {
|
||||||
|
let format = print.settings.device.slaFormat || "pm4n";
|
||||||
let rec = FORMAT_RECORDS[format];
|
let rec = FORMAT_RECORDS[format];
|
||||||
|
|
||||||
|
if (format === "pm4n") {
|
||||||
|
if (renderer) {
|
||||||
|
return Promise.resolve(encodePM4N(print, progress, renderer));
|
||||||
|
}
|
||||||
|
return import('./x_photon.js').then(({ photon }) =>
|
||||||
|
encodePM4N(print, progress, photon));
|
||||||
|
}
|
||||||
|
|
||||||
return Promise.reject(new Error([
|
return Promise.reject(new Error([
|
||||||
`${FAMILY} format .${format} is registered but not implemented.`,
|
`${FAMILY} format .${format} is registered but not implemented.`,
|
||||||
rec ? `Detected family generation: ${rec.generation} (${rec.notes}).` : "",
|
rec ? `Detected family generation: ${rec.generation} (${rec.notes}).` : "",
|
||||||
|
|
@ -35,12 +50,546 @@ function encode(format) {
|
||||||
].join(" ")));
|
].join(" ")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function encodePM4N(print, progress, photon) {
|
||||||
|
let layers = [];
|
||||||
|
let preview = createPreview(224, 168);
|
||||||
|
|
||||||
|
let { ctx, volume } = renderRasterLayers(print, photon, params => {
|
||||||
|
let { index, rendered, bottom, z, ctx } = params;
|
||||||
|
let { process, width, height } = ctx;
|
||||||
|
let raster = imageToRowMajor(rendered.image, width, height);
|
||||||
|
let encoded = encodePW0(raster);
|
||||||
|
|
||||||
|
accumulatePreview(preview, rendered.image, width, height);
|
||||||
|
layers.push({
|
||||||
|
index,
|
||||||
|
data: encoded,
|
||||||
|
nonzero: countNonZero(raster),
|
||||||
|
liftHeight: bottom ? process.slaBasePeelDist : process.slaPeelDist,
|
||||||
|
liftSpeed: (bottom ? process.slaBasePeelLiftRate : process.slaPeelLiftRate) * 60,
|
||||||
|
exposure: bottom ? process.slaBaseOn : process.slaLayerOn,
|
||||||
|
z
|
||||||
|
});
|
||||||
|
}, progress ? (value) => progress(value * 0.85, "pm4n_encode") : null);
|
||||||
|
|
||||||
|
let file = writeAnycubic({ ctx, layers, volume, preview });
|
||||||
|
if (progress) progress(1, "pm4n_write");
|
||||||
|
|
||||||
|
return { file, layers: layers.length, volume };
|
||||||
|
}
|
||||||
|
|
||||||
|
function read(input) {
|
||||||
|
let view = toDataView(input);
|
||||||
|
let mark = readString(view, 0, MARK_SIZE);
|
||||||
|
if (mark !== "ANYCUBIC") {
|
||||||
|
throw new Error(`invalid Anycubic mark ${mark}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let file = {
|
||||||
|
mark,
|
||||||
|
version: view.getUint32(12, true),
|
||||||
|
tableCount: view.getUint32(16, true),
|
||||||
|
headerOffset: view.getUint32(20, true),
|
||||||
|
softwareOffset: view.getUint32(24, true),
|
||||||
|
previewOffset: view.getUint32(28, true),
|
||||||
|
colorTableOffset: view.getUint32(32, true),
|
||||||
|
layerDefOffset: view.getUint32(36, true),
|
||||||
|
extraOffset: view.getUint32(40, true),
|
||||||
|
machineOffset: view.getUint32(44, true),
|
||||||
|
layerImageOffset: view.getUint32(48, true),
|
||||||
|
modelOffset: view.getUint32(52, true)
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
file,
|
||||||
|
header: readHeader(view, file.headerOffset),
|
||||||
|
preview: file.previewOffset ? readPreview(view, file.previewOffset) : null,
|
||||||
|
layers: readLayerDefs(view, file.layerDefOffset),
|
||||||
|
machine: file.machineOffset ? readMachine(view, file.machineOffset) : null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function decodeLayer(view, layer, pixelCount) {
|
||||||
|
let data = new Uint8Array(
|
||||||
|
view.buffer,
|
||||||
|
view.byteOffset + layer.dataOffset,
|
||||||
|
layer.dataLength
|
||||||
|
);
|
||||||
|
return decodePW0(data, pixelCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeAnycubic(params) {
|
||||||
|
let { ctx, layers, volume, preview } = params;
|
||||||
|
let { device, process, width, height } = ctx;
|
||||||
|
let fileMarkSize = MARK_SIZE + 11 * 4;
|
||||||
|
let headerOffset = fileMarkSize;
|
||||||
|
let headerSize = 16 + 92;
|
||||||
|
let previewOffset = headerOffset + headerSize;
|
||||||
|
let previewData = encodePreview(preview);
|
||||||
|
let previewSize = 28 + previewData.length;
|
||||||
|
let colorTableOffset = previewOffset + previewSize;
|
||||||
|
let colorTableSize = 28;
|
||||||
|
let layerDefOffset = colorTableOffset + colorTableSize;
|
||||||
|
let layerDefSize = 16 + 4 + layers.length * 32;
|
||||||
|
let extraOffset = layerDefOffset + layerDefSize;
|
||||||
|
let extraSize = 16 + 56;
|
||||||
|
let machineOffset = extraOffset + extraSize;
|
||||||
|
let machineSize = 156;
|
||||||
|
let softwareOffset = machineOffset + machineSize;
|
||||||
|
let softwareSize = 164;
|
||||||
|
let modelOffset = softwareOffset + softwareSize;
|
||||||
|
let modelSize = 48;
|
||||||
|
let layerImageOffset = modelOffset + modelSize;
|
||||||
|
let total = layerImageOffset;
|
||||||
|
|
||||||
|
layers.forEach(layer => {
|
||||||
|
layer.dataOffset = total;
|
||||||
|
layer.dataLength = layer.data.length;
|
||||||
|
total += layer.dataLength;
|
||||||
|
});
|
||||||
|
|
||||||
|
let writer = new BinaryWriter(new ArrayBuffer(total));
|
||||||
|
writeFileMark(writer, {
|
||||||
|
version: VERSION_517,
|
||||||
|
tableCount: 9,
|
||||||
|
headerOffset,
|
||||||
|
softwareOffset,
|
||||||
|
previewOffset,
|
||||||
|
colorTableOffset,
|
||||||
|
layerDefOffset,
|
||||||
|
extraOffset,
|
||||||
|
machineOffset,
|
||||||
|
layerImageOffset,
|
||||||
|
modelOffset
|
||||||
|
});
|
||||||
|
writer.seek(headerOffset);
|
||||||
|
writeHeader(writer, { device, process, width, height, layers, volume });
|
||||||
|
writer.seek(previewOffset);
|
||||||
|
writePreview(writer, preview, previewData);
|
||||||
|
writer.seek(colorTableOffset);
|
||||||
|
writeColorTable(writer);
|
||||||
|
writer.seek(layerDefOffset);
|
||||||
|
writeLayerDefinitions(writer, layers);
|
||||||
|
writer.seek(extraOffset);
|
||||||
|
writeExtra(writer, process);
|
||||||
|
writer.seek(machineOffset);
|
||||||
|
writeMachine(writer, { device, width, height });
|
||||||
|
writer.seek(softwareOffset);
|
||||||
|
writeSoftware(writer);
|
||||||
|
writer.seek(modelOffset);
|
||||||
|
writeModel(writer, { device, process, layers });
|
||||||
|
layers.forEach(layer => {
|
||||||
|
writer.seek(layer.dataOffset);
|
||||||
|
writer.writeBytes(layer.data);
|
||||||
|
});
|
||||||
|
|
||||||
|
return writer.buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeFileMark(writer, mark) {
|
||||||
|
writer.writeString("ANYCUBIC", MARK_SIZE);
|
||||||
|
writer.writeU32(mark.version);
|
||||||
|
writer.writeU32(mark.tableCount);
|
||||||
|
writer.writeU32(mark.headerOffset);
|
||||||
|
writer.writeU32(mark.softwareOffset);
|
||||||
|
writer.writeU32(mark.previewOffset);
|
||||||
|
writer.writeU32(mark.colorTableOffset);
|
||||||
|
writer.writeU32(mark.layerDefOffset);
|
||||||
|
writer.writeU32(mark.extraOffset);
|
||||||
|
writer.writeU32(mark.machineOffset);
|
||||||
|
writer.writeU32(mark.layerImageOffset);
|
||||||
|
writer.writeU32(mark.modelOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeHeader(writer, params) {
|
||||||
|
let { device, process, width, height, layers, volume } = params;
|
||||||
|
let printTime = Math.round((process.slaBaseLayers * process.slaBaseOn) +
|
||||||
|
Math.max(0, layers.length - process.slaBaseLayers) * process.slaLayerOn);
|
||||||
|
|
||||||
|
writer.writeTable("HEADER", 92);
|
||||||
|
writer.writeF32(pixelSizeMicrons(device.bedWidth, width));
|
||||||
|
writer.writeF32(process.slaSlice);
|
||||||
|
writer.writeF32(process.slaLayerOn);
|
||||||
|
writer.writeF32(process.slaLayerOff);
|
||||||
|
writer.writeF32(process.slaBaseOn);
|
||||||
|
writer.writeF32(process.slaBaseLayers);
|
||||||
|
writer.writeF32(process.slaPeelDist);
|
||||||
|
writer.writeF32(process.slaPeelLiftRate);
|
||||||
|
writer.writeF32(process.slaPeelDropRate);
|
||||||
|
writer.writeF32(volume);
|
||||||
|
writer.writeU32(process.slaAntiAlias || 1);
|
||||||
|
writer.writeU32(width);
|
||||||
|
writer.writeU32(height);
|
||||||
|
writer.writeF32(0);
|
||||||
|
writer.writeF32(0);
|
||||||
|
writer.writeString("$", 4);
|
||||||
|
writer.writeU32(0);
|
||||||
|
writer.writeU32(printTime);
|
||||||
|
writer.writeU32(0);
|
||||||
|
writer.writeU32(0);
|
||||||
|
writer.writeU32(0);
|
||||||
|
writer.writeU16(0);
|
||||||
|
writer.writeU16(0);
|
||||||
|
writer.writeU32(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function writePreview(writer, preview, data) {
|
||||||
|
writer.writeTable("PREVIEW", 28 + data.length);
|
||||||
|
writer.writeU32(preview.width);
|
||||||
|
writer.writeString("x", 4);
|
||||||
|
writer.writeU32(preview.height);
|
||||||
|
writer.writeBytes(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeColorTable(writer) {
|
||||||
|
writer.writeU32(0);
|
||||||
|
writer.writeU32(16);
|
||||||
|
for (let i=1; i<=16; i++) {
|
||||||
|
writer.writeU8(Math.min(255, Math.round(i * 255 / 16)));
|
||||||
|
}
|
||||||
|
writer.writeU32(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeLayerDefinitions(writer, layers) {
|
||||||
|
writer.writeTable("LAYERDEF", 4 + layers.length * 32);
|
||||||
|
writer.writeU32(layers.length);
|
||||||
|
layers.forEach(layer => {
|
||||||
|
writer.writeU32(layer.dataOffset);
|
||||||
|
writer.writeU32(layer.dataLength);
|
||||||
|
writer.writeF32(layer.liftHeight);
|
||||||
|
writer.writeF32(layer.liftSpeed / 60);
|
||||||
|
writer.writeF32(layer.exposure);
|
||||||
|
writer.writeF32(layer.z);
|
||||||
|
writer.writeU32(layer.nonzero);
|
||||||
|
writer.writeU32(0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeExtra(writer, process) {
|
||||||
|
writer.writeTable("EXTRA", 24);
|
||||||
|
writer.writeU32(2);
|
||||||
|
writer.writeF32(process.slaBasePeelDist);
|
||||||
|
writer.writeF32(process.slaBasePeelLiftRate);
|
||||||
|
writer.writeF32(process.slaPeelDropRate);
|
||||||
|
writer.writeF32(0);
|
||||||
|
writer.writeF32(0);
|
||||||
|
writer.writeF32(process.slaPeelDropRate);
|
||||||
|
writer.writeU32(2);
|
||||||
|
writer.writeF32(process.slaPeelDist);
|
||||||
|
writer.writeF32(process.slaPeelLiftRate);
|
||||||
|
writer.writeF32(process.slaPeelDropRate);
|
||||||
|
writer.writeF32(0);
|
||||||
|
writer.writeF32(0);
|
||||||
|
writer.writeF32(process.slaPeelDropRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeMachine(writer, params) {
|
||||||
|
let { device, width, height } = params;
|
||||||
|
|
||||||
|
writer.writeTable("MACHINE", 156);
|
||||||
|
writer.writeString("Photon Mono 4", 96);
|
||||||
|
writer.writeString("pw0Img", 16);
|
||||||
|
writer.writeU32(16);
|
||||||
|
writer.writeU32(7);
|
||||||
|
writer.writeF32(device.bedWidth);
|
||||||
|
writer.writeF32(device.bedDepth);
|
||||||
|
writer.writeF32(device.maxHeight || device.bedHeight);
|
||||||
|
writer.writeU32(VERSION_517);
|
||||||
|
writer.writeU32(6506241);
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeSoftware(writer) {
|
||||||
|
writer.writeString("GridSpace Kiri:Moto", 32);
|
||||||
|
writer.writeU32(164);
|
||||||
|
writer.writeString("dev", 32);
|
||||||
|
writer.writeString("js", 64);
|
||||||
|
writer.writeString("none", 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeModel(writer, params) {
|
||||||
|
let { device, process, layers } = params;
|
||||||
|
let maxZ = layers.length ? layers[layers.length - 1].z : process.slaSlice;
|
||||||
|
|
||||||
|
writer.writeTable("MODEL", 48);
|
||||||
|
writer.writeF32(-device.bedWidth / 2);
|
||||||
|
writer.writeF32(-device.bedDepth / 2);
|
||||||
|
writer.writeF32(0);
|
||||||
|
writer.writeF32(device.bedWidth / 2);
|
||||||
|
writer.writeF32(device.bedDepth / 2);
|
||||||
|
writer.writeF32(maxZ);
|
||||||
|
writer.writeU32(0);
|
||||||
|
writer.writeF32(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createPreview(width, height) {
|
||||||
|
return {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
image: new Uint8Array(width * height * 2)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function accumulatePreview(preview, image, width, height) {
|
||||||
|
let xscale = width / preview.width;
|
||||||
|
let yscale = height / preview.height;
|
||||||
|
|
||||||
|
for (let y=0; y<preview.height; y++) {
|
||||||
|
let sy = Math.min(height - 1, Math.floor(y * yscale));
|
||||||
|
for (let x=0; x<preview.width; x++) {
|
||||||
|
let sx = Math.min(width - 1, Math.floor(x * xscale));
|
||||||
|
let pos = (y * preview.width + x) * 2;
|
||||||
|
if (image[sx * height + sy]) {
|
||||||
|
preview.image[pos] = 0xff;
|
||||||
|
preview.image[pos + 1] = 0xff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function encodePreview(preview) {
|
||||||
|
return preview.image;
|
||||||
|
}
|
||||||
|
|
||||||
|
function encodePW0(image) {
|
||||||
|
let output = [];
|
||||||
|
let lastColor = -1;
|
||||||
|
let reps = 0;
|
||||||
|
|
||||||
|
function flush() {
|
||||||
|
while (reps > 0) {
|
||||||
|
let done = reps;
|
||||||
|
if (lastColor === 0 || lastColor === 0xf) {
|
||||||
|
done = Math.min(done, RLE4_ENCODING_LIMIT);
|
||||||
|
let code = done | (lastColor << 12);
|
||||||
|
output.push((code >> 8) & 0xff, code & 0xff);
|
||||||
|
} else {
|
||||||
|
done = Math.min(done, 0xf);
|
||||||
|
output.push(((lastColor << 4) | done) & 0xff);
|
||||||
|
}
|
||||||
|
reps -= done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i=0; i<image.length; i++) {
|
||||||
|
let color = image[i] >> 4;
|
||||||
|
if (color === lastColor) {
|
||||||
|
reps++;
|
||||||
|
} else {
|
||||||
|
flush();
|
||||||
|
lastColor = color;
|
||||||
|
reps = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flush();
|
||||||
|
return new Uint8Array(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
function decodePW0(input, pixelCount) {
|
||||||
|
let image = new Uint8Array(pixelCount);
|
||||||
|
let pixel = 0;
|
||||||
|
|
||||||
|
for (let i=0; i<input.length && pixel<pixelCount; i++) {
|
||||||
|
let b = input[i];
|
||||||
|
let code = b >> 4;
|
||||||
|
let repeat = b & 0x0f;
|
||||||
|
let color;
|
||||||
|
|
||||||
|
if (code === 0 || code === 0x0f) {
|
||||||
|
color = code === 0 ? 0 : 255;
|
||||||
|
i++;
|
||||||
|
if (i >= input.length) {
|
||||||
|
repeat = pixelCount - pixel;
|
||||||
|
} else {
|
||||||
|
repeat = (repeat << 8) + input[i];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
color = (code << 4) | code;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pixel + repeat > pixelCount) {
|
||||||
|
throw new Error(`PW0 RLE overrun (${pixel + repeat} > ${pixelCount})`);
|
||||||
|
}
|
||||||
|
image.fill(color, pixel, pixel + repeat);
|
||||||
|
pixel += repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pixel !== pixelCount) {
|
||||||
|
throw new Error(`PW0 RLE underrun (${pixel} < ${pixelCount})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
function countNonZero(image) {
|
||||||
|
let count = 0;
|
||||||
|
for (let i=0; i<image.length; i++) {
|
||||||
|
if (image[i]) count++;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readHeader(view, offset) {
|
||||||
|
expectTable(view, offset, "HEADER");
|
||||||
|
let at = offset + 16;
|
||||||
|
return {
|
||||||
|
pixelSizeUm: view.getFloat32(at, true),
|
||||||
|
layerHeight: view.getFloat32(at + 4, true),
|
||||||
|
exposure: view.getFloat32(at + 8, true),
|
||||||
|
waitTimeBeforeCure: view.getFloat32(at + 12, true),
|
||||||
|
bottomExposure: view.getFloat32(at + 16, true),
|
||||||
|
bottomLayers: view.getFloat32(at + 20, true),
|
||||||
|
liftHeight: view.getFloat32(at + 24, true),
|
||||||
|
liftSpeed: view.getFloat32(at + 28, true),
|
||||||
|
retractSpeed: view.getFloat32(at + 32, true),
|
||||||
|
volume: view.getFloat32(at + 36, true),
|
||||||
|
antiAlias: view.getUint32(at + 40, true),
|
||||||
|
resolutionX: view.getUint32(at + 44, true),
|
||||||
|
resolutionY: view.getUint32(at + 48, true)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function readPreview(view, offset) {
|
||||||
|
let length = expectTable(view, offset, "PREVIEW");
|
||||||
|
return {
|
||||||
|
offset,
|
||||||
|
length,
|
||||||
|
width: view.getUint32(offset + 16, true),
|
||||||
|
height: view.getUint32(offset + 24, true),
|
||||||
|
dataOffset: offset + 28,
|
||||||
|
dataLength: length - 28
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function readLayerDefs(view, offset) {
|
||||||
|
expectTable(view, offset, "LAYERDEF");
|
||||||
|
let count = view.getUint32(offset + 16, true);
|
||||||
|
let layers = [];
|
||||||
|
let at = offset + 20;
|
||||||
|
|
||||||
|
for (let index=0; index<count; index++) {
|
||||||
|
layers.push({
|
||||||
|
index,
|
||||||
|
dataOffset: view.getUint32(at, true),
|
||||||
|
dataLength: view.getUint32(at + 4, true),
|
||||||
|
liftHeight: view.getFloat32(at + 8, true),
|
||||||
|
liftSpeed: view.getFloat32(at + 12, true),
|
||||||
|
exposure: view.getFloat32(at + 16, true),
|
||||||
|
z: view.getFloat32(at + 20, true),
|
||||||
|
nonzero: view.getUint32(at + 24, true),
|
||||||
|
padding: view.getUint32(at + 28, true)
|
||||||
|
});
|
||||||
|
at += 32;
|
||||||
|
}
|
||||||
|
|
||||||
|
return layers;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readMachine(view, offset) {
|
||||||
|
expectTable(view, offset, "MACHINE");
|
||||||
|
return {
|
||||||
|
name: readString(view, offset + 16, 96),
|
||||||
|
imageFormat: readString(view, offset + 112, 16),
|
||||||
|
maxAntiAlias: view.getUint32(offset + 128, true),
|
||||||
|
propertyFields: view.getUint32(offset + 132, true),
|
||||||
|
displayWidth: view.getFloat32(offset + 136, true),
|
||||||
|
displayHeight: view.getFloat32(offset + 140, true),
|
||||||
|
machineZ: view.getFloat32(offset + 144, true),
|
||||||
|
maxFileVersion: view.getUint32(offset + 148, true)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function expectTable(view, offset, name) {
|
||||||
|
let found = readString(view, offset, MARK_SIZE);
|
||||||
|
if (found !== name) {
|
||||||
|
throw new Error(`expected ${name} table at ${offset}, found ${found}`);
|
||||||
|
}
|
||||||
|
return view.getUint32(offset + MARK_SIZE, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function pixelSizeMicrons(width, resolution) {
|
||||||
|
return round((width / resolution) * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toDataView(input) {
|
||||||
|
return input instanceof DataView
|
||||||
|
? input
|
||||||
|
: new DataView(input.buffer || input, input.byteOffset || 0, input.byteLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
function readString(view, offset, length) {
|
||||||
|
let chars = [];
|
||||||
|
for (let i=0; i<length; i++) {
|
||||||
|
let c = view.getUint8(offset + i);
|
||||||
|
if (c === 0) break;
|
||||||
|
chars.push(String.fromCharCode(c));
|
||||||
|
}
|
||||||
|
return chars.join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
class BinaryWriter {
|
||||||
|
constructor(buffer) {
|
||||||
|
this.buffer = buffer;
|
||||||
|
this.view = new DataView(buffer);
|
||||||
|
this.pos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
seek(pos) {
|
||||||
|
this.pos = pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
writeTable(name, length) {
|
||||||
|
this.writeString(name, MARK_SIZE);
|
||||||
|
this.writeU32(length);
|
||||||
|
}
|
||||||
|
|
||||||
|
writeBytes(bytes) {
|
||||||
|
new Uint8Array(this.buffer, this.pos, bytes.length).set(bytes);
|
||||||
|
this.pos += bytes.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
writeString(value, length) {
|
||||||
|
for (let i=0; i<length; i++) {
|
||||||
|
this.writeU8(i < value.length ? value.charCodeAt(i) : 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writeU8(value) {
|
||||||
|
this.view.setUint8(this.pos, value);
|
||||||
|
this.pos += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
writeU16(value) {
|
||||||
|
this.view.setUint16(this.pos, value, true);
|
||||||
|
this.pos += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
writeU32(value) {
|
||||||
|
this.view.setUint32(this.pos, value >>> 0, true);
|
||||||
|
this.pos += 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
writeF32(value) {
|
||||||
|
this.view.setFloat32(this.pos, Number(value || 0), true);
|
||||||
|
this.pos += 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function supports(format) {
|
function supports(format) {
|
||||||
return FORMAT_RECORDS[format] !== undefined;
|
return FORMAT_RECORDS[format] !== undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function canEncode(format) {
|
||||||
|
return ENCODE_FORMATS.has(format);
|
||||||
|
}
|
||||||
|
|
||||||
export const PW = {
|
export const PW = {
|
||||||
encode,
|
encode,
|
||||||
supports,
|
supports,
|
||||||
|
canEncode,
|
||||||
|
read,
|
||||||
|
decodeLayer,
|
||||||
|
encodePW0,
|
||||||
|
decodePW0,
|
||||||
formats: FORMAT_RECORDS
|
formats: FORMAT_RECORDS
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue