add sla .ctb format and a few related printer targets
This commit is contained in:
parent
95eadc60df
commit
a1af70b496
11 changed files with 937 additions and 11 deletions
39
bin/ctb2rsla.mjs
Executable file
39
bin/ctb2rsla.mjs
Executable file
|
|
@ -0,0 +1,39 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import fs from 'fs/promises';
|
||||||
|
import path from 'path';
|
||||||
|
import { CTB } from '../src/kiri/mode/sla/work/x_ctb.js';
|
||||||
|
|
||||||
|
let [,, input, output] = process.argv;
|
||||||
|
|
||||||
|
if (!input) {
|
||||||
|
console.error("usage: node bin/ctb2rsla.mjs input.ctb [output.rsla]");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
output = output || input.replace(/\.ctb$/i, "") + ".rsla";
|
||||||
|
|
||||||
|
let data = await fs.readFile(input);
|
||||||
|
let ctb = CTB.read(data);
|
||||||
|
let { header, machine } = ctb;
|
||||||
|
let lastPercent = -1;
|
||||||
|
|
||||||
|
console.log([
|
||||||
|
"ctb",
|
||||||
|
machine || "unknown-machine",
|
||||||
|
`${header.resolutionX}x${header.resolutionY}`,
|
||||||
|
`${header.layerCount} layers`,
|
||||||
|
`${path.basename(output)}`
|
||||||
|
].join(" "));
|
||||||
|
|
||||||
|
let file = await CTB.toRSLA(data, (progress, message) => {
|
||||||
|
let percent = Math.floor(progress * 100);
|
||||||
|
if (percent !== lastPercent) {
|
||||||
|
lastPercent = percent;
|
||||||
|
process.stderr.write(`\r${message} ${percent}%`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (lastPercent >= 0) process.stderr.write("\n");
|
||||||
|
await fs.writeFile(output, Buffer.from(file));
|
||||||
|
console.log(`wrote ${output}`);
|
||||||
|
|
@ -102,6 +102,7 @@ function device_from_code(code,mode) {
|
||||||
slaFormat: valueOf(set.sla_format, ''),
|
slaFormat: valueOf(set.sla_format, ''),
|
||||||
slaFileExt: valueOf(set.sla_file_ext, ''),
|
slaFileExt: valueOf(set.sla_file_ext, ''),
|
||||||
slaFormatVersion: valueOf(set.sla_format_version, 1),
|
slaFormatVersion: valueOf(set.sla_format_version, 1),
|
||||||
|
slaMachineEditable: valueOf(set.sla_machine_editable, false),
|
||||||
deviceZMax: valueOf(set.z_move_max, 0),
|
deviceZMax: valueOf(set.z_move_max, 0),
|
||||||
gcodeTime: valueOf(set.time_factor, 1),
|
gcodeTime: valueOf(set.time_factor, 1),
|
||||||
maxHeight: valueOf(set.build_height, 150),
|
maxHeight: valueOf(set.build_height, 150),
|
||||||
|
|
@ -373,7 +374,8 @@ export const conf = {
|
||||||
resolutionY: 900,
|
resolutionY: 900,
|
||||||
slaFormat: "",
|
slaFormat: "",
|
||||||
slaFileExt: "",
|
slaFileExt: "",
|
||||||
slaFormatVersion: 1
|
slaFormatVersion: 1,
|
||||||
|
slaMachineEditable: false
|
||||||
},
|
},
|
||||||
// process defaults SLA:Process
|
// process defaults SLA:Process
|
||||||
p:{
|
p:{
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
import { $, h } from '../../moto/webui.js';
|
import { $, h } from '../../moto/webui.js';
|
||||||
import { api } from './api.js';
|
import { api } from './api.js';
|
||||||
import { conf } from './conf/defaults.js';
|
import { conf } from './conf/defaults.js';
|
||||||
|
import { canEditSLAMachine } from '../mode/sla/core/formats.js';
|
||||||
import { space } from '../../moto/space.js';
|
import { space } from '../../moto/space.js';
|
||||||
import { devices as devlist } from '../../pack/kiri-devs.js';
|
import { devices as devlist } from '../../pack/kiri-devs.js';
|
||||||
import { settings, conf as setconf } from './conf/manager.js';
|
import { settings, conf as setconf } from './conf/manager.js';
|
||||||
|
|
@ -185,7 +186,7 @@ function setDeviceCode(code, devicename) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let allowSLADeviceEdit = mode !== 'SLA' || dev.slaFormat === 'vsla' || dev.slaFormat === 'rsla';
|
let allowSLADeviceEdit = mode !== 'SLA' || canEditSLAMachine(dev);
|
||||||
|
|
||||||
// disable editing for non-local devices
|
// disable editing for non-local devices
|
||||||
[
|
[
|
||||||
|
|
|
||||||
16
src/kiri/dev/sla/Any.Generic.Chitubox.CTB.json
Normal file
16
src/kiri/dev/sla/Any.Generic.Chitubox.CTB.json
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"no_clone": false,
|
||||||
|
"cmd": {},
|
||||||
|
"settings": {
|
||||||
|
"bed_width": 192,
|
||||||
|
"bed_depth": 120,
|
||||||
|
"bed_height": 1,
|
||||||
|
"max_height": 250,
|
||||||
|
"resolution_x": 3840,
|
||||||
|
"resolution_y": 2400,
|
||||||
|
"sla_format": "ctb",
|
||||||
|
"sla_file_ext": "ctb",
|
||||||
|
"sla_format_version": 3,
|
||||||
|
"sla_machine_editable": true
|
||||||
|
}
|
||||||
|
}
|
||||||
15
src/kiri/dev/sla/Creality.LD-006.json
Normal file
15
src/kiri/dev/sla/Creality.LD-006.json
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"no_clone": true,
|
||||||
|
"cmd": {},
|
||||||
|
"settings": {
|
||||||
|
"bed_width": 192,
|
||||||
|
"bed_depth": 120,
|
||||||
|
"bed_height": 1,
|
||||||
|
"max_height": 250,
|
||||||
|
"resolution_x": 3840,
|
||||||
|
"resolution_y": 2400,
|
||||||
|
"sla_format": "ctb",
|
||||||
|
"sla_file_ext": "ctb",
|
||||||
|
"sla_format_version": 3
|
||||||
|
}
|
||||||
|
}
|
||||||
15
src/kiri/dev/sla/Elegoo.Mars.3.CTB.json
Normal file
15
src/kiri/dev/sla/Elegoo.Mars.3.CTB.json
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"no_clone": true,
|
||||||
|
"cmd": {},
|
||||||
|
"settings": {
|
||||||
|
"bed_width": 143,
|
||||||
|
"bed_depth": 89.6,
|
||||||
|
"bed_height": 1,
|
||||||
|
"max_height": 175,
|
||||||
|
"resolution_x": 4098,
|
||||||
|
"resolution_y": 2560,
|
||||||
|
"sla_format": "ctb",
|
||||||
|
"sla_file_ext": "ctb",
|
||||||
|
"sla_format_version": 3
|
||||||
|
}
|
||||||
|
}
|
||||||
15
src/kiri/dev/sla/Elegoo.Saturn.2.CTB.json
Normal file
15
src/kiri/dev/sla/Elegoo.Saturn.2.CTB.json
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"no_clone": true,
|
||||||
|
"cmd": {},
|
||||||
|
"settings": {
|
||||||
|
"bed_width": 218.88,
|
||||||
|
"bed_depth": 123.12,
|
||||||
|
"bed_height": 1,
|
||||||
|
"max_height": 250,
|
||||||
|
"resolution_x": 7680,
|
||||||
|
"resolution_y": 4320,
|
||||||
|
"sla_format": "ctb",
|
||||||
|
"sla_file_ext": "ctb",
|
||||||
|
"sla_format_version": 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import { $ } from '../../../../moto/webui.js';
|
import { $ } from '../../../../moto/webui.js';
|
||||||
import { api } from '../../../app/api.js';
|
import { api } from '../../../app/api.js';
|
||||||
|
import { getSLAFileExt } from '../core/formats.js';
|
||||||
|
|
||||||
export const SLA = {
|
export const SLA = {
|
||||||
init,
|
init,
|
||||||
|
|
@ -63,11 +64,7 @@ export function printDownload(output, api, names) {
|
||||||
$('print-layers').value = layers;
|
$('print-layers').value = layers;
|
||||||
$('print-time').value = `${print_hrs}:${print_min}:${print_sec}`;
|
$('print-time').value = `${print_hrs}:${print_min}:${print_sec}`;
|
||||||
|
|
||||||
let ext = device.slaFileExt || ({
|
let ext = getSLAFileExt(device);
|
||||||
'Anycubic.Photon': 'photon',
|
|
||||||
'Anycubic.Photon.S': 'photons'
|
|
||||||
}[device.deviceName]) || 'cxdlp';
|
|
||||||
|
|
||||||
ext = ext.startsWith(".") ? ext : `.${ext}`;
|
ext = ext.startsWith(".") ? ext : `.${ext}`;
|
||||||
download.innerText = `${api.language.current.ex_dwnl || 'download'} ${ext}`;
|
download.innerText = `${api.language.current.ex_dwnl || 'download'} ${ext}`;
|
||||||
download.onclick = () => { saveFile(api, file, ext) };
|
download.onclick = () => { saveFile(api, file, ext) };
|
||||||
|
|
|
||||||
68
src/kiri/mode/sla/core/formats.js
Normal file
68
src/kiri/mode/sla/core/formats.js
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||||
|
|
||||||
|
const DEVICE_FORMAT = {
|
||||||
|
'Anycubic.Photon': 'photon',
|
||||||
|
'Anycubic.Photon.S': 'photons'
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SLA_FORMATS = {
|
||||||
|
photon: {
|
||||||
|
id: 'photon',
|
||||||
|
ext: 'photon',
|
||||||
|
proprietary: true,
|
||||||
|
available: true
|
||||||
|
},
|
||||||
|
photons: {
|
||||||
|
id: 'photons',
|
||||||
|
ext: 'photons',
|
||||||
|
proprietary: true,
|
||||||
|
available: true
|
||||||
|
},
|
||||||
|
cxdlp: {
|
||||||
|
id: 'cxdlp',
|
||||||
|
ext: 'cxdlp',
|
||||||
|
proprietary: true,
|
||||||
|
available: true
|
||||||
|
},
|
||||||
|
vsla: {
|
||||||
|
id: 'vsla',
|
||||||
|
ext: 'vsla',
|
||||||
|
proprietary: false,
|
||||||
|
available: true,
|
||||||
|
editableMachine: true
|
||||||
|
},
|
||||||
|
rsla: {
|
||||||
|
id: 'rsla',
|
||||||
|
ext: 'rsla',
|
||||||
|
proprietary: false,
|
||||||
|
available: true,
|
||||||
|
editableMachine: true
|
||||||
|
},
|
||||||
|
ctb: {
|
||||||
|
id: 'ctb',
|
||||||
|
ext: 'ctb',
|
||||||
|
proprietary: true,
|
||||||
|
available: true,
|
||||||
|
priority: 1,
|
||||||
|
family: 'chitubox'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export function getSLAFormat(device) {
|
||||||
|
return device.slaFormat || DEVICE_FORMAT[device.deviceName] || 'cxdlp';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSLAFormatRecord(device) {
|
||||||
|
let id = getSLAFormat(device);
|
||||||
|
return SLA_FORMATS[id] || SLA_FORMATS.cxdlp;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSLAFileExt(device) {
|
||||||
|
let rec = getSLAFormatRecord(device);
|
||||||
|
return device.slaFileExt || rec.ext || rec.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function canEditSLAMachine(device) {
|
||||||
|
return device.slaMachineEditable === true ||
|
||||||
|
getSLAFormatRecord(device).editableMachine === true;
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||||
|
|
||||||
import { CXDLP } from './x_cxdlp.js';
|
import { CXDLP } from './x_cxdlp.js';
|
||||||
|
import { CTB } from './x_ctb.js';
|
||||||
import { photon } from './x_photon.js';
|
import { photon } from './x_photon.js';
|
||||||
import { SLA } from './init-work.js';
|
import { SLA } from './init-work.js';
|
||||||
import { RSLA } from './x_rsla.js';
|
import { RSLA } from './x_rsla.js';
|
||||||
import { VSLA } from './x_vsla.js';
|
import { VSLA } from './x_vsla.js';
|
||||||
|
import { getSLAFormat } from '../core/formats.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DRIVER CONTRACT - runs in worker
|
* DRIVER CONTRACT - runs in worker
|
||||||
|
|
@ -35,10 +37,7 @@ export function sla_export(print, online, ondone) {
|
||||||
layermax = Math.max(widget.slices.length);
|
layermax = Math.max(widget.slices.length);
|
||||||
});
|
});
|
||||||
|
|
||||||
let format = device.slaFormat || ({
|
let format = getSLAFormat(device);
|
||||||
'Anycubic.Photon': 'photon',
|
|
||||||
'Anycubic.Photon.S': 'photons',
|
|
||||||
}[device.deviceName]) || 'cxdlp';
|
|
||||||
|
|
||||||
if (format === 'vsla') {
|
if (format === 'vsla') {
|
||||||
return VSLA.encode(print, (progress, message) => {
|
return VSLA.encode(print, (progress, message) => {
|
||||||
|
|
@ -58,6 +57,15 @@ export function sla_export(print, online, ondone) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (format === 'ctb') {
|
||||||
|
return CTB.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') {
|
||||||
let legacyMode = SLA.legacy || alias > 1,
|
let legacyMode = SLA.legacy || alias > 1,
|
||||||
part1 = legacyMode ? 0.25 : 0.85,
|
part1 = legacyMode ? 0.25 : 0.85,
|
||||||
|
|
|
||||||
750
src/kiri/mode/sla/work/x_ctb.js
Normal file
750
src/kiri/mode/sla/work/x_ctb.js
Normal file
|
|
@ -0,0 +1,750 @@
|
||||||
|
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||||
|
|
||||||
|
import { JSZip } from '../../../../ext/jszip-esm.js';
|
||||||
|
import { PNG } from '../../../../ext/pngjs.esm.js';
|
||||||
|
|
||||||
|
const CTB_V3_MAGIC = 0x12fd0086;
|
||||||
|
const HEADER_SIZE = 0x70;
|
||||||
|
const LAYER_TABLE_RECORD_SIZE = 36;
|
||||||
|
const LAYER_FULL_RECORD_SIZE = 84;
|
||||||
|
const PRINT_PARAMS_SIZE = 60;
|
||||||
|
const MACHINE_INFO_SIZE = 76;
|
||||||
|
const RSLA_MIME = "application/vnd.gridspace.rsla+zip";
|
||||||
|
|
||||||
|
function encode(print, progress, renderer) {
|
||||||
|
if (renderer) {
|
||||||
|
return Promise.resolve(encodeWithPhoton(print, progress, renderer));
|
||||||
|
}
|
||||||
|
return import('./x_photon.js').then(({ photon }) =>
|
||||||
|
encodeWithPhoton(print, progress, photon));
|
||||||
|
}
|
||||||
|
|
||||||
|
function encodeWithPhoton(print, progress, photon) {
|
||||||
|
let { settings, widgets } = print;
|
||||||
|
let { device, process } = settings;
|
||||||
|
let width = device.resolutionX;
|
||||||
|
let height = device.resolutionY;
|
||||||
|
let scaleX = width / device.bedWidth;
|
||||||
|
let scaleY = height / device.bedDepth;
|
||||||
|
let layermax = 0;
|
||||||
|
let layers = [];
|
||||||
|
let volume = 0;
|
||||||
|
let seed = makeSeed(print);
|
||||||
|
|
||||||
|
widgets = widgets.filter(w => !w.track.ignore && !w.meta.disabled);
|
||||||
|
widgets.forEach(widget => {
|
||||||
|
layermax = Math.max(layermax, widget.slices.length);
|
||||||
|
});
|
||||||
|
|
||||||
|
for (let index=0; index<layermax; index++) {
|
||||||
|
let rendered = photon.renderLayerWasm({
|
||||||
|
index,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
widgets,
|
||||||
|
scaleX,
|
||||||
|
scaleY,
|
||||||
|
masks: []
|
||||||
|
});
|
||||||
|
if (rendered.end) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let pixels = renderToCTBPixels(rendered.image, width, height);
|
||||||
|
let encoded = encipher(seed, index, encodeRLE(pixels));
|
||||||
|
let bottom = index < process.slaBaseLayers;
|
||||||
|
|
||||||
|
volume += rendered.area * process.slaSlice;
|
||||||
|
layers.push({
|
||||||
|
index,
|
||||||
|
z: process.slaFirstOffset + process.slaSlice * (index + 1),
|
||||||
|
exposure: bottom ? process.slaBaseOn : process.slaLayerOn,
|
||||||
|
lightOffDelay: bottom ? process.slaBaseOff : process.slaLayerOff,
|
||||||
|
liftDistance: bottom ? process.slaBasePeelDist : process.slaPeelDist,
|
||||||
|
liftSpeed: (bottom ? process.slaBasePeelLiftRate : process.slaPeelLiftRate) * 60,
|
||||||
|
data: encoded
|
||||||
|
});
|
||||||
|
|
||||||
|
if (progress) progress((index / layermax) * 0.85, "ctb_encode");
|
||||||
|
}
|
||||||
|
|
||||||
|
let file = writeCTB({ device, process, layers, volume, seed });
|
||||||
|
if (progress) progress(1, "ctb_write");
|
||||||
|
|
||||||
|
return { file, layers: layers.length, volume };
|
||||||
|
}
|
||||||
|
|
||||||
|
function read(input) {
|
||||||
|
let view = input instanceof DataView
|
||||||
|
? input
|
||||||
|
: new DataView(input.buffer || input, input.byteOffset || 0, input.byteLength);
|
||||||
|
|
||||||
|
let header = readHeader(view);
|
||||||
|
let layers = readLayerTable(view, header);
|
||||||
|
|
||||||
|
return {
|
||||||
|
header,
|
||||||
|
print: readPrintParams(view, header),
|
||||||
|
machine: readMachineName(view, header),
|
||||||
|
layers,
|
||||||
|
layout: inferLayout(header, layers)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function toRSLA(input, progress) {
|
||||||
|
let view = toDataView(input);
|
||||||
|
let ctb = read(view);
|
||||||
|
let { header, layers } = ctb;
|
||||||
|
let zip = new JSZip();
|
||||||
|
let rslaLayers = [];
|
||||||
|
|
||||||
|
layers.forEach((layer, index) => {
|
||||||
|
let pixels = decodeLayer(view, header, layer);
|
||||||
|
let file = `layers/${index.toString().padStart(6, "0")}.png`;
|
||||||
|
zip.file(file, imageToPNG(pixels, header.resolutionX, header.resolutionY));
|
||||||
|
rslaLayers.push({
|
||||||
|
index,
|
||||||
|
z: round(layer.z),
|
||||||
|
exposure: round(layer.exposure),
|
||||||
|
lightOffDelay: round(layer.lightOffDelay),
|
||||||
|
source: {
|
||||||
|
offset: layer.imageOffset,
|
||||||
|
length: layer.imageLength
|
||||||
|
},
|
||||||
|
file
|
||||||
|
});
|
||||||
|
if (progress) progress((index / layers.length) * 0.85, "ctb_decode");
|
||||||
|
});
|
||||||
|
|
||||||
|
zip.file("manifest.json", JSON.stringify(createRSLAManifest(ctb, rslaLayers), null, 2));
|
||||||
|
|
||||||
|
return zip.generateAsync({
|
||||||
|
type: "arraybuffer",
|
||||||
|
compression: "DEFLATE",
|
||||||
|
compressionOptions: { level: 6 },
|
||||||
|
streamFiles: true,
|
||||||
|
mimeType: RSLA_MIME
|
||||||
|
}, meta => {
|
||||||
|
if (progress) progress(0.85 + (meta.percent / 100) * 0.15, "zip_gen");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function decodeLayer(view, header, layer) {
|
||||||
|
let data = new Uint8Array(
|
||||||
|
view.buffer,
|
||||||
|
view.byteOffset + layer.imageOffset,
|
||||||
|
layer.imageLength
|
||||||
|
);
|
||||||
|
let decoded = decodeRLE(decipher(header.encryptionSeed, layer.index, data),
|
||||||
|
header.resolutionX * header.resolutionY);
|
||||||
|
return decoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeCTB(params) {
|
||||||
|
let { device, process, layers, volume, seed } = params;
|
||||||
|
let machine = device.deviceName || "Generic CTB";
|
||||||
|
let machineBytes = new TextEncoder().encode(machine);
|
||||||
|
let printTime = Math.round((process.slaBaseLayers * process.slaBaseOn) +
|
||||||
|
Math.max(0, layers.length - process.slaBaseLayers) * process.slaLayerOn);
|
||||||
|
let printParamsOffset = HEADER_SIZE;
|
||||||
|
let machineInfoOffset = printParamsOffset + PRINT_PARAMS_SIZE;
|
||||||
|
let nameOffset = machineInfoOffset + MACHINE_INFO_SIZE;
|
||||||
|
let layerTableOffset = nameOffset + machineBytes.length;
|
||||||
|
let imageOffset = layerTableOffset + layers.length * LAYER_TABLE_RECORD_SIZE;
|
||||||
|
|
||||||
|
layers.forEach(layer => {
|
||||||
|
layer.fullOffset = imageOffset;
|
||||||
|
layer.imageOffset = imageOffset + LAYER_FULL_RECORD_SIZE;
|
||||||
|
layer.imageLength = layer.data.length;
|
||||||
|
layer.blockLength = LAYER_FULL_RECORD_SIZE + layer.imageLength;
|
||||||
|
imageOffset = layer.imageOffset + layer.imageLength;
|
||||||
|
});
|
||||||
|
|
||||||
|
let buffer = new ArrayBuffer(imageOffset);
|
||||||
|
let writer = new BinaryWriter(buffer);
|
||||||
|
|
||||||
|
writeHeader(writer, {
|
||||||
|
device,
|
||||||
|
process,
|
||||||
|
layers,
|
||||||
|
seed,
|
||||||
|
printTime,
|
||||||
|
printParamsOffset,
|
||||||
|
machineInfoOffset,
|
||||||
|
layerTableOffset
|
||||||
|
});
|
||||||
|
writer.seek(printParamsOffset);
|
||||||
|
writePrintParams(writer, { process, volume });
|
||||||
|
writer.seek(machineInfoOffset);
|
||||||
|
writeMachineInfo(writer, { machineBytes, nameOffset });
|
||||||
|
writer.seek(nameOffset);
|
||||||
|
writer.writeBytes(machineBytes);
|
||||||
|
writer.seek(layerTableOffset);
|
||||||
|
layers.forEach(layer => writeLayerRecord(writer, layer));
|
||||||
|
layers.forEach(layer => {
|
||||||
|
writer.seek(layer.fullOffset);
|
||||||
|
writeFullLayerRecord(writer, layer);
|
||||||
|
writer.writeBytes(layer.data);
|
||||||
|
});
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeHeader(writer, params) {
|
||||||
|
let {
|
||||||
|
device, process, layers, seed, printTime,
|
||||||
|
printParamsOffset, machineInfoOffset, layerTableOffset
|
||||||
|
} = params;
|
||||||
|
|
||||||
|
writer.writeU32(CTB_V3_MAGIC);
|
||||||
|
writer.writeU32(3);
|
||||||
|
writer.writeF32(device.bedWidth);
|
||||||
|
writer.writeF32(device.bedDepth);
|
||||||
|
writer.writeF32(device.maxHeight || device.bedHeight);
|
||||||
|
writer.skip(12);
|
||||||
|
writer.writeF32(process.slaSlice);
|
||||||
|
writer.writeF32(process.slaLayerOn);
|
||||||
|
writer.writeF32(process.slaBaseOn);
|
||||||
|
writer.writeF32(process.slaLayerOff);
|
||||||
|
writer.writeU32(process.slaBaseLayers);
|
||||||
|
writer.writeU32(device.resolutionX);
|
||||||
|
writer.writeU32(device.resolutionY);
|
||||||
|
writer.writeU32(0);
|
||||||
|
writer.writeU32(layerTableOffset);
|
||||||
|
writer.writeU32(layers.length);
|
||||||
|
writer.writeU32(0);
|
||||||
|
writer.writeU32(printTime);
|
||||||
|
writer.writeU32(1);
|
||||||
|
writer.writeU32(printParamsOffset);
|
||||||
|
writer.writeU32(PRINT_PARAMS_SIZE);
|
||||||
|
writer.writeU32(process.slaAntiAlias || 1);
|
||||||
|
writer.writeU16(0x00ff);
|
||||||
|
writer.writeU16(0x00ff);
|
||||||
|
writer.writeU32(seed);
|
||||||
|
writer.writeU32(machineInfoOffset);
|
||||||
|
writer.writeU32(MACHINE_INFO_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
function writePrintParams(writer, params) {
|
||||||
|
let { process, volume } = params;
|
||||||
|
|
||||||
|
writer.writeF32(process.slaBasePeelDist);
|
||||||
|
writer.writeF32(process.slaBasePeelLiftRate * 60);
|
||||||
|
writer.writeF32(process.slaPeelDist);
|
||||||
|
writer.writeF32(process.slaPeelLiftRate * 60);
|
||||||
|
writer.writeF32(process.slaPeelDropRate * 60);
|
||||||
|
writer.writeF32(volume);
|
||||||
|
writer.writeF32(0);
|
||||||
|
writer.writeF32(0);
|
||||||
|
writer.writeF32(process.slaBaseOff);
|
||||||
|
writer.writeF32(process.slaLayerOff);
|
||||||
|
writer.writeU32(process.slaBaseLayers);
|
||||||
|
writer.writeU32(0);
|
||||||
|
writer.writeU32(0);
|
||||||
|
writer.writeU32(0);
|
||||||
|
writer.writeU32(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeMachineInfo(writer, params) {
|
||||||
|
let { machineBytes, nameOffset } = params;
|
||||||
|
|
||||||
|
writer.skip(0x1c);
|
||||||
|
writer.writeU32(nameOffset);
|
||||||
|
writer.writeU32(machineBytes.length);
|
||||||
|
writer.writeU32(machineBytes.length);
|
||||||
|
writer.writeU32(0x2000000f);
|
||||||
|
writer.writeU32(0x01c45dd1);
|
||||||
|
writer.writeU32(8);
|
||||||
|
writer.writeU32(0x01080000);
|
||||||
|
writer.writeF32(6.8);
|
||||||
|
writer.writeU32(0);
|
||||||
|
writer.writeU32(10);
|
||||||
|
writer.writeU32(0);
|
||||||
|
writer.writeU32(0);
|
||||||
|
writer.writeU32(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeLayerRecord(writer, layer) {
|
||||||
|
writer.writeF32(layer.z);
|
||||||
|
writer.writeF32(layer.exposure);
|
||||||
|
writer.writeF32(layer.lightOffDelay);
|
||||||
|
writer.writeU32(layer.imageOffset);
|
||||||
|
writer.writeU32(layer.imageLength);
|
||||||
|
writer.writeU32(0);
|
||||||
|
writer.writeU32(LAYER_FULL_RECORD_SIZE);
|
||||||
|
writer.writeU32(0);
|
||||||
|
writer.writeU32(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeFullLayerRecord(writer, layer) {
|
||||||
|
writeLayerRecord(writer, layer);
|
||||||
|
writer.writeU32(layer.blockLength);
|
||||||
|
writer.writeF32(layer.liftDistance);
|
||||||
|
writer.writeF32(layer.liftSpeed);
|
||||||
|
writer.writeU32(0);
|
||||||
|
writer.writeU32(0);
|
||||||
|
writer.writeU32(0);
|
||||||
|
writer.writeU32(0);
|
||||||
|
writer.writeU32(0);
|
||||||
|
writer.writeU32(0);
|
||||||
|
writer.writeU32(0);
|
||||||
|
writer.writeF32(0);
|
||||||
|
writer.writeF32(0x00ff);
|
||||||
|
}
|
||||||
|
|
||||||
|
function readHeader(view) {
|
||||||
|
let magic = view.getUint32(0, true);
|
||||||
|
let version = view.getUint32(4, true);
|
||||||
|
if (magic !== CTB_V3_MAGIC) {
|
||||||
|
throw new Error(`invalid CTB magic 0x${magic.toString(16)}`);
|
||||||
|
}
|
||||||
|
if (version !== 3) {
|
||||||
|
throw new Error(`unsupported CTB version ${version}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
magic,
|
||||||
|
version,
|
||||||
|
bedWidth: view.getFloat32(0x08, true),
|
||||||
|
bedDepth: view.getFloat32(0x0c, true),
|
||||||
|
bedHeight: view.getFloat32(0x10, true),
|
||||||
|
layerHeight: view.getFloat32(0x20, true),
|
||||||
|
exposure: view.getFloat32(0x24, true),
|
||||||
|
bottomExposure: view.getFloat32(0x28, true),
|
||||||
|
lightOffDelay: view.getFloat32(0x2c, true),
|
||||||
|
bottomLayers: view.getUint32(0x30, true),
|
||||||
|
resolutionX: view.getUint32(0x34, true),
|
||||||
|
resolutionY: view.getUint32(0x38, true),
|
||||||
|
previewOffset: view.getUint32(0x3c, true),
|
||||||
|
layerTableOffset: view.getUint32(0x40, true),
|
||||||
|
layerCount: view.getUint32(0x44, true),
|
||||||
|
previewOneLength: view.getUint32(0x48, true),
|
||||||
|
printParamsOffset: view.getUint32(0x54, true),
|
||||||
|
printParamsLength: view.getUint32(0x58, true),
|
||||||
|
antiAliasLevel: view.getUint32(0x5c, true),
|
||||||
|
lightPWM: view.getUint16(0x60, true),
|
||||||
|
bottomLightPWM: view.getUint16(0x62, true),
|
||||||
|
encryptionSeed: view.getUint32(0x64, true),
|
||||||
|
machineInfoOffset: view.getUint32(0x68, true),
|
||||||
|
machineInfoLength: view.getUint32(0x6c, true)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function readLayerTable(view, header) {
|
||||||
|
let layers = [];
|
||||||
|
let offset = header.layerTableOffset;
|
||||||
|
|
||||||
|
for (let index=0; index<header.layerCount; index++) {
|
||||||
|
layers.push(readLayerRecord(view, header, offset, index));
|
||||||
|
offset += LAYER_TABLE_RECORD_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return layers;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readLayerRecord(view, header, offset, index) {
|
||||||
|
let record = {
|
||||||
|
index,
|
||||||
|
tableOffset: offset,
|
||||||
|
z: view.getFloat32(offset + 0, true),
|
||||||
|
exposure: view.getFloat32(offset + 4, true),
|
||||||
|
lightOffDelay: view.getFloat32(offset + 8, true),
|
||||||
|
imageOffset: view.getUint32(offset + 12, true),
|
||||||
|
imageLength: view.getUint32(offset + 16, true),
|
||||||
|
unknown20: view.getUint32(offset + 20, true),
|
||||||
|
fullRecordLength: view.getUint32(offset + 24, true),
|
||||||
|
unknown28: view.getUint32(offset + 28, true),
|
||||||
|
unknown32: view.getUint32(offset + 32, true)
|
||||||
|
};
|
||||||
|
let fullOffset = record.imageOffset - LAYER_FULL_RECORD_SIZE;
|
||||||
|
if (fullOffset >= header.layerTableOffset) {
|
||||||
|
record.full = readFullLayerRecord(view, fullOffset);
|
||||||
|
}
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readFullLayerRecord(view, offset) {
|
||||||
|
return {
|
||||||
|
offset,
|
||||||
|
z: view.getFloat32(offset + 0, true),
|
||||||
|
exposure: view.getFloat32(offset + 4, true),
|
||||||
|
lightOffDelay: view.getFloat32(offset + 8, true),
|
||||||
|
imageOffset: view.getUint32(offset + 12, true),
|
||||||
|
imageLength: view.getUint32(offset + 16, true),
|
||||||
|
unknown20: view.getUint32(offset + 20, true),
|
||||||
|
fullRecordLength: view.getUint32(offset + 24, true),
|
||||||
|
unknown28: view.getUint32(offset + 28, true),
|
||||||
|
unknown32: view.getUint32(offset + 32, true),
|
||||||
|
blockLength: view.getUint32(offset + 36, true),
|
||||||
|
liftDistance: view.getFloat32(offset + 40, true),
|
||||||
|
liftSpeed: view.getFloat32(offset + 44, true),
|
||||||
|
unknown48: view.getUint32(offset + 48, true),
|
||||||
|
unknown52: view.getUint32(offset + 52, true),
|
||||||
|
unknown56: view.getUint32(offset + 56, true),
|
||||||
|
unknown60: view.getUint32(offset + 60, true),
|
||||||
|
unknown64: view.getUint32(offset + 64, true),
|
||||||
|
unknown68: view.getUint32(offset + 68, true),
|
||||||
|
unknown72: view.getUint32(offset + 72, true),
|
||||||
|
unknown76: view.getFloat32(offset + 76, true),
|
||||||
|
lightPWM: view.getFloat32(offset + 80, true)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function readPrintParams(view, header) {
|
||||||
|
let offset = header.printParamsOffset;
|
||||||
|
if (!offset || header.printParamsLength < 60) return {};
|
||||||
|
|
||||||
|
return {
|
||||||
|
bottomLiftDistance: view.getFloat32(offset + 0, true),
|
||||||
|
bottomLiftSpeed: view.getFloat32(offset + 4, true),
|
||||||
|
liftDistance: view.getFloat32(offset + 8, true),
|
||||||
|
liftSpeed: view.getFloat32(offset + 12, true),
|
||||||
|
retractSpeed: view.getFloat32(offset + 16, true),
|
||||||
|
volume: view.getFloat32(offset + 20, true),
|
||||||
|
weight: view.getFloat32(offset + 24, true),
|
||||||
|
cost: view.getFloat32(offset + 28, true),
|
||||||
|
bottomLightOffDelay: view.getFloat32(offset + 32, true),
|
||||||
|
lightOffDelay: view.getFloat32(offset + 36, true),
|
||||||
|
bottomLayers: view.getUint32(offset + 40, true),
|
||||||
|
unknown44: view.getUint32(offset + 44, true),
|
||||||
|
unknown48: view.getUint32(offset + 48, true),
|
||||||
|
unknown52: view.getUint32(offset + 52, true),
|
||||||
|
unknown56: view.getUint32(offset + 56, true)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function readMachineName(view, header) {
|
||||||
|
if (!header.machineInfoOffset || !header.machineInfoLength) return "";
|
||||||
|
|
||||||
|
let nameOffset = view.getUint32(header.machineInfoOffset + 0x1c, true);
|
||||||
|
let nameLength = view.getUint32(header.machineInfoOffset + 0x20, true);
|
||||||
|
if (!nameOffset || !nameLength) return "";
|
||||||
|
|
||||||
|
let chars = [];
|
||||||
|
for (let i=0; i<nameLength; i++) {
|
||||||
|
let c = view.getUint8(nameOffset + i);
|
||||||
|
if (c === 0) break;
|
||||||
|
chars.push(String.fromCharCode(c));
|
||||||
|
}
|
||||||
|
return chars.join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
function inferLayout(header, layers) {
|
||||||
|
let tableEnd = header.layerTableOffset + header.layerCount * LAYER_TABLE_RECORD_SIZE;
|
||||||
|
let firstLayer = layers[0];
|
||||||
|
let firstFullRecordOffset = firstLayer ? firstLayer.imageOffset - LAYER_FULL_RECORD_SIZE : 0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
headerSize: HEADER_SIZE,
|
||||||
|
layerTableRecordSize: LAYER_TABLE_RECORD_SIZE,
|
||||||
|
layerFullRecordSize: LAYER_FULL_RECORD_SIZE,
|
||||||
|
tableEnd,
|
||||||
|
firstFullRecordOffset,
|
||||||
|
hasFullRecordsBeforeImages: firstFullRecordOffset === tableEnd
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createRSLAManifest(ctb, layers) {
|
||||||
|
let { header, print, machine } = ctb;
|
||||||
|
|
||||||
|
return {
|
||||||
|
format: "rsla",
|
||||||
|
version: 1,
|
||||||
|
units: "mm",
|
||||||
|
coordinateSystem: {
|
||||||
|
layerImage: "top-left",
|
||||||
|
model: "bed-center",
|
||||||
|
x: "right",
|
||||||
|
y: "back",
|
||||||
|
z: "up"
|
||||||
|
},
|
||||||
|
source: {
|
||||||
|
format: "ctb",
|
||||||
|
version: header.version,
|
||||||
|
machine
|
||||||
|
},
|
||||||
|
bed: {
|
||||||
|
width: round(header.bedWidth),
|
||||||
|
depth: round(header.bedDepth),
|
||||||
|
height: round(header.bedHeight)
|
||||||
|
},
|
||||||
|
resolution: {
|
||||||
|
x: header.resolutionX,
|
||||||
|
y: header.resolutionY
|
||||||
|
},
|
||||||
|
layerHeight: round(header.layerHeight),
|
||||||
|
layerCount: layers.length,
|
||||||
|
raster: {
|
||||||
|
encoding: "png",
|
||||||
|
color: "grayscale",
|
||||||
|
cure: "nonzero",
|
||||||
|
white: "cure",
|
||||||
|
black: "off"
|
||||||
|
},
|
||||||
|
process: {
|
||||||
|
bottomLayers: print.bottomLayers ?? header.bottomLayers,
|
||||||
|
exposure: round(header.exposure),
|
||||||
|
bottomExposure: round(header.bottomExposure),
|
||||||
|
lightOffDelay: round(print.lightOffDelay ?? header.lightOffDelay),
|
||||||
|
bottomLightOffDelay: round(print.bottomLightOffDelay),
|
||||||
|
liftHeight: round(print.liftDistance),
|
||||||
|
bottomLiftHeight: round(print.bottomLiftDistance),
|
||||||
|
liftSpeed: round(print.liftSpeed),
|
||||||
|
bottomLiftSpeed: round(print.bottomLiftSpeed),
|
||||||
|
retractSpeed: round(print.retractSpeed),
|
||||||
|
antiAlias: header.antiAliasLevel,
|
||||||
|
lightPWM: header.lightPWM,
|
||||||
|
bottomLightPWM: header.bottomLightPWM
|
||||||
|
},
|
||||||
|
layers
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderToCTBPixels(image, width, height) {
|
||||||
|
let data = new Uint8Array(width * height);
|
||||||
|
for (let x=0; x<width; x++) {
|
||||||
|
for (let y=0; y<height; y++) {
|
||||||
|
data[y * width + x] = image[x * height + y] || 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
function encodeRLE(input) {
|
||||||
|
let output = [];
|
||||||
|
let color = quantize(input[0]);
|
||||||
|
let run = 1;
|
||||||
|
|
||||||
|
for (let offset=1; offset<input.length; offset++) {
|
||||||
|
let next = quantize(input[offset]);
|
||||||
|
if (next === color) {
|
||||||
|
run++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
writeRun(output, color, run);
|
||||||
|
color = next;
|
||||||
|
run = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
writeRun(output, color, run);
|
||||||
|
return new Uint8Array(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeRun(output, color, length) {
|
||||||
|
if (length === 1) {
|
||||||
|
output.push(color);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
output.push(color | 0x80);
|
||||||
|
if (length < 0x80) {
|
||||||
|
output.push(length);
|
||||||
|
} else if (length < 0x4000) {
|
||||||
|
output.push(0x80 | (length >> 8), length & 0xff);
|
||||||
|
} else if (length < 0x200000) {
|
||||||
|
output.push(0xc0 | (length >> 16), (length >> 8) & 0xff, length & 0xff);
|
||||||
|
} else if (length < 0x10000000) {
|
||||||
|
output.push(
|
||||||
|
0xe0 | (length >> 24),
|
||||||
|
(length >> 16) & 0xff,
|
||||||
|
(length >> 8) & 0xff,
|
||||||
|
length & 0xff
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw new Error(`CTB RLE run too long (${length})`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function quantize(value) {
|
||||||
|
return value ? Math.max(1, Math.min(0x7f, Math.round(value / 2))) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function decodeRLE(input, pixelCount) {
|
||||||
|
let output = new Uint8Array(pixelCount);
|
||||||
|
let offset = 0;
|
||||||
|
let out = 0;
|
||||||
|
|
||||||
|
while (offset < input.length && out < pixelCount) {
|
||||||
|
let code = input[offset++];
|
||||||
|
let repeat = (code & 0x80) !== 0;
|
||||||
|
code &= 0x7f;
|
||||||
|
|
||||||
|
let stride = repeat ? readRunLength(input, offset) : 1;
|
||||||
|
if (repeat) offset += stride.bytes;
|
||||||
|
|
||||||
|
let value = code === 0 ? 0 : (code << 1) | 1;
|
||||||
|
let end = out + (repeat ? stride.length : stride);
|
||||||
|
if (end > pixelCount) {
|
||||||
|
throw new Error(`CTB RLE overrun in decoded layer (${end} > ${pixelCount})`);
|
||||||
|
}
|
||||||
|
output.fill(value, out, end);
|
||||||
|
out = end;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (out !== pixelCount) {
|
||||||
|
throw new Error(`CTB RLE underrun in decoded layer (${out} < ${pixelCount})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readRunLength(input, offset) {
|
||||||
|
let slen = input[offset];
|
||||||
|
if (slen === undefined) throw new Error("truncated CTB RLE run length");
|
||||||
|
|
||||||
|
if ((slen & 0x80) === 0) {
|
||||||
|
return { length: slen, bytes: 1 };
|
||||||
|
}
|
||||||
|
if ((slen & 0xc0) === 0x80) {
|
||||||
|
requireBytes(input, offset, 2);
|
||||||
|
return {
|
||||||
|
length: ((slen & 0x3f) << 8) | input[offset + 1],
|
||||||
|
bytes: 2
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if ((slen & 0xe0) === 0xc0) {
|
||||||
|
requireBytes(input, offset, 3);
|
||||||
|
return {
|
||||||
|
length: ((slen & 0x1f) << 16) | (input[offset + 1] << 8) | input[offset + 2],
|
||||||
|
bytes: 3
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if ((slen & 0xf0) === 0xe0) {
|
||||||
|
requireBytes(input, offset, 4);
|
||||||
|
return {
|
||||||
|
length: (((slen & 0x0f) << 24) | (input[offset + 1] << 16) |
|
||||||
|
(input[offset + 2] << 8) | input[offset + 3]) >>> 0,
|
||||||
|
bytes: 4
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`unsupported CTB RLE run length prefix 0x${slen.toString(16)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function requireBytes(input, offset, length) {
|
||||||
|
if (offset + length > input.length) {
|
||||||
|
throw new Error("truncated CTB RLE run length");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function decipher(seed, layerIndex, input) {
|
||||||
|
if (!seed) return input.slice();
|
||||||
|
|
||||||
|
let output = new Uint8Array(input.length);
|
||||||
|
let keyring = newKeyring(seed, layerIndex);
|
||||||
|
for (let i=0; i<input.length; i++) {
|
||||||
|
output[i] = input[i] ^ keyring.next();
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
function encipher(seed, layerIndex, input) {
|
||||||
|
return decipher(seed, layerIndex, input);
|
||||||
|
}
|
||||||
|
|
||||||
|
function newKeyring(seed, layerIndex) {
|
||||||
|
let init = (Math.imul(seed >>> 0, 0x2d83cdac) + 0xd8a83423) >>> 0;
|
||||||
|
let keySeed = (Math.imul(layerIndex >>> 0, 0x1e1530cd) + 0xec3d47cd) >>> 0;
|
||||||
|
let key = Math.imul(keySeed, init) >>> 0;
|
||||||
|
let index = 0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
next() {
|
||||||
|
let byte = (key >>> (8 * index)) & 0xff;
|
||||||
|
index++;
|
||||||
|
if ((index & 3) === 0) {
|
||||||
|
key = (key + init) >>> 0;
|
||||||
|
index = 0;
|
||||||
|
}
|
||||||
|
return byte;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function imageToPNG(image, width, height) {
|
||||||
|
return PNG.sync.write({ width, height, data: image }, {
|
||||||
|
colorType: 0,
|
||||||
|
inputColorType: 0,
|
||||||
|
inputHasAlpha: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function toDataView(input) {
|
||||||
|
return input instanceof DataView
|
||||||
|
? input
|
||||||
|
: new DataView(input.buffer || input, input.byteOffset || 0, input.byteLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
function round(value) {
|
||||||
|
return Number.parseFloat(Number(value || 0).toFixed(5));
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeSeed(print) {
|
||||||
|
let { device, process } = print.settings;
|
||||||
|
let hash = 0x811c9dc5;
|
||||||
|
let text = [
|
||||||
|
device.deviceName,
|
||||||
|
device.resolutionX,
|
||||||
|
device.resolutionY,
|
||||||
|
device.bedWidth,
|
||||||
|
device.bedDepth,
|
||||||
|
process.slaSlice,
|
||||||
|
process.slaLayerOn,
|
||||||
|
process.slaBaseOn
|
||||||
|
].join("|");
|
||||||
|
|
||||||
|
for (let i=0; i<text.length; i++) {
|
||||||
|
hash ^= text.charCodeAt(i);
|
||||||
|
hash = Math.imul(hash, 0x01000193) >>> 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return hash || 0x5eed1234;
|
||||||
|
}
|
||||||
|
|
||||||
|
class BinaryWriter {
|
||||||
|
constructor(buffer) {
|
||||||
|
this.view = new DataView(buffer);
|
||||||
|
this.pos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
seek(pos) {
|
||||||
|
this.pos = pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
skip(length) {
|
||||||
|
this.pos += length;
|
||||||
|
}
|
||||||
|
|
||||||
|
writeBytes(bytes) {
|
||||||
|
new Uint8Array(this.view.buffer, this.pos, bytes.length).set(bytes);
|
||||||
|
this.pos += bytes.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CTB = {
|
||||||
|
encode,
|
||||||
|
read,
|
||||||
|
toRSLA,
|
||||||
|
decodeLayer,
|
||||||
|
encodeRLE
|
||||||
|
};
|
||||||
Loading…
Reference in a new issue