From 304a2a4e24051027c20dddc672e1fade5c14eebc Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Fri, 15 May 2026 17:10:37 -0400 Subject: [PATCH] register photon extensions. add more .ctb target machines --- src/kiri/dev/sla/Creality.LD-002H.CTB.json | 15 ++ src/kiri/dev/sla/Creality.LD-002R.CTB.json | 15 ++ src/kiri/dev/sla/Elegoo.Mars.2.Pro.CTB.json | 15 ++ src/kiri/dev/sla/Elegoo.Saturn.CTB.json | 15 ++ src/kiri/mode/sla/core/formats.js | 35 ++++ src/kiri/mode/sla/work/export.js | 6 + src/kiri/mode/sla/work/raster.js | 76 +++++++++ src/kiri/mode/sla/work/x_ctb.js | 179 ++++++++++++++------ src/kiri/mode/sla/work/x_pw.js | 14 ++ src/kiri/mode/sla/work/x_rsla.js | 48 +----- 10 files changed, 329 insertions(+), 89 deletions(-) create mode 100644 src/kiri/dev/sla/Creality.LD-002H.CTB.json create mode 100644 src/kiri/dev/sla/Creality.LD-002R.CTB.json create mode 100644 src/kiri/dev/sla/Elegoo.Mars.2.Pro.CTB.json create mode 100644 src/kiri/dev/sla/Elegoo.Saturn.CTB.json create mode 100644 src/kiri/mode/sla/work/raster.js create mode 100644 src/kiri/mode/sla/work/x_pw.js diff --git a/src/kiri/dev/sla/Creality.LD-002H.CTB.json b/src/kiri/dev/sla/Creality.LD-002H.CTB.json new file mode 100644 index 00000000..4643c529 --- /dev/null +++ b/src/kiri/dev/sla/Creality.LD-002H.CTB.json @@ -0,0 +1,15 @@ +{ + "no_clone": true, + "cmd": {}, + "settings": { + "bed_width": 127, + "bed_depth": 80, + "bed_height": 1, + "max_height": 160, + "resolution_x": 2560, + "resolution_y": 1620, + "sla_format": "ctb", + "sla_file_ext": "ctb", + "sla_format_version": 3 + } +} diff --git a/src/kiri/dev/sla/Creality.LD-002R.CTB.json b/src/kiri/dev/sla/Creality.LD-002R.CTB.json new file mode 100644 index 00000000..6fe18002 --- /dev/null +++ b/src/kiri/dev/sla/Creality.LD-002R.CTB.json @@ -0,0 +1,15 @@ +{ + "no_clone": true, + "cmd": {}, + "settings": { + "bed_width": 119, + "bed_depth": 65, + "bed_height": 1, + "max_height": 160, + "resolution_x": 2560, + "resolution_y": 1440, + "sla_format": "ctb", + "sla_file_ext": "ctb", + "sla_format_version": 3 + } +} diff --git a/src/kiri/dev/sla/Elegoo.Mars.2.Pro.CTB.json b/src/kiri/dev/sla/Elegoo.Mars.2.Pro.CTB.json new file mode 100644 index 00000000..fccdbab6 --- /dev/null +++ b/src/kiri/dev/sla/Elegoo.Mars.2.Pro.CTB.json @@ -0,0 +1,15 @@ +{ + "no_clone": true, + "cmd": {}, + "settings": { + "bed_width": 129, + "bed_depth": 80, + "bed_height": 1, + "max_height": 160, + "resolution_x": 1620, + "resolution_y": 2560, + "sla_format": "ctb", + "sla_file_ext": "ctb", + "sla_format_version": 3 + } +} diff --git a/src/kiri/dev/sla/Elegoo.Saturn.CTB.json b/src/kiri/dev/sla/Elegoo.Saturn.CTB.json new file mode 100644 index 00000000..c5b13393 --- /dev/null +++ b/src/kiri/dev/sla/Elegoo.Saturn.CTB.json @@ -0,0 +1,15 @@ +{ + "no_clone": true, + "cmd": {}, + "settings": { + "bed_width": 192, + "bed_depth": 120, + "bed_height": 1, + "max_height": 200, + "resolution_x": 3840, + "resolution_y": 2400, + "sla_format": "ctb", + "sla_file_ext": "ctb", + "sla_format_version": 3 + } +} diff --git a/src/kiri/mode/sla/core/formats.js b/src/kiri/mode/sla/core/formats.js index d71e5f41..e07da79f 100644 --- a/src/kiri/mode/sla/core/formats.js +++ b/src/kiri/mode/sla/core/formats.js @@ -45,6 +45,41 @@ export const SLA_FORMATS = { available: true, priority: 1, family: 'chitubox' + }, + pwmo: { + id: 'pwmo', + ext: 'pwmo', + proprietary: true, + available: false, + family: 'photon-workshop' + }, + pwma: { + id: 'pwma', + ext: 'pwma', + proprietary: true, + available: false, + family: 'photon-workshop' + }, + pwmb: { + id: 'pwmb', + ext: 'pwmb', + proprietary: true, + available: false, + family: 'photon-workshop' + }, + pm3n: { + id: 'pm3n', + ext: 'pm3n', + proprietary: true, + available: false, + family: 'photon-workshop' + }, + pm4n: { + id: 'pm4n', + ext: 'pm4n', + proprietary: true, + available: false, + family: 'photon-workshop' } }; diff --git a/src/kiri/mode/sla/work/export.js b/src/kiri/mode/sla/work/export.js index 65e5f7d0..0dfb6d0b 100644 --- a/src/kiri/mode/sla/work/export.js +++ b/src/kiri/mode/sla/work/export.js @@ -3,6 +3,7 @@ import { CXDLP } from './x_cxdlp.js'; import { CTB } from './x_ctb.js'; import { photon } from './x_photon.js'; +import { PW } from './x_pw.js'; import { SLA } from './init-work.js'; import { RSLA } from './x_rsla.js'; import { VSLA } from './x_vsla.js'; @@ -66,6 +67,11 @@ export function sla_export(print, online, ondone) { }); } + if (format === 'pwmo' || format === 'pwma' || format === 'pwmb' || + format === 'pm3n' || format === 'pm4n') { + return PW.encode(format); + } + if (format === 'photon' || format === 'photons') { let legacyMode = SLA.legacy || alias > 1, part1 = legacyMode ? 0.25 : 0.85, diff --git a/src/kiri/mode/sla/work/raster.js b/src/kiri/mode/sla/work/raster.js new file mode 100644 index 00000000..4b2e43ee --- /dev/null +++ b/src/kiri/mode/sla/work/raster.js @@ -0,0 +1,76 @@ +/** Copyright Stewart Allen -- All Rights Reserved */ + +export function createRasterContext(print) { + let { settings, widgets } = print; + let { device, process } = settings; + let width = device.resolutionX; + let height = device.resolutionY; + let layermax = 0; + + widgets = widgets.filter(w => !w.track.ignore && !w.meta.disabled); + widgets.forEach(widget => { + layermax = Math.max(layermax, widget.slices.length); + }); + + return { + settings, + device, + process, + widgets, + width, + height, + scaleX: width / device.bedWidth, + scaleY: height / device.bedDepth, + layermax + }; +} + +export function renderRasterLayers(print, renderer, onlayer, progress) { + let ctx = createRasterContext(print); + let { process, widgets, width, height, scaleX, scaleY, layermax } = ctx; + let volume = 0; + let count = 0; + + for (let index=0; index !w.track.ignore && !w.meta.disabled); - widgets.forEach(widget => { - layermax = Math.max(layermax, widget.slices.length); - }); - - for (let index=0; index { + let { index, rendered, bottom, z, ctx } = params; + let { process, width, height } = ctx; let pixels = renderToCTBPixels(rendered.image, width, height); let encoded = encipher(seed, index, encodeRLE(pixels)); - let bottom = index < process.slaBaseLayers; + previews.forEach(preview => accumulatePreview(preview, rendered.image, width, height)); - volume += rendered.area * process.slaSlice; layers.push({ index, - z: process.slaFirstOffset + process.slaSlice * (index + 1), + z, 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 }); + }, progress ? (value) => progress(value * 0.85, "ctb_encode") : null); - if (progress) progress((index / layermax) * 0.85, "ctb_encode"); - } - - let file = writeCTB({ device, process, layers, volume, seed }); + let { device, process } = ctx; + let file = writeCTB({ device, process, layers, volume, seed, previews }); if (progress) progress(1, "ctb_write"); return { file, layers: layers.length, volume }; @@ -86,6 +66,7 @@ function read(input) { header, print: readPrintParams(view, header), machine: readMachineName(view, header), + previews: readPreviews(view, header), layers, layout: inferLayout(header, layers) }; @@ -141,12 +122,17 @@ function decodeLayer(view, header, layer) { } function writeCTB(params) { - let { device, process, layers, volume, seed } = params; + let { device, process, layers, volume, seed, previews } = params; let machine = device.deviceName || "Generic CTB"; let machineBytes = new TextEncoder().encode(machine); + let previewData = previews.map(encodePreview); let printTime = Math.round((process.slaBaseLayers * process.slaBaseOn) + Math.max(0, layers.length - process.slaBaseLayers) * process.slaLayerOn); - let printParamsOffset = HEADER_SIZE; + let previewLargeOffset = HEADER_SIZE; + let previewLargeDataOffset = previewLargeOffset + 32; + let previewSmallOffset = previewLargeDataOffset + previewData[0].length; + let previewSmallDataOffset = previewSmallOffset + 32; + let printParamsOffset = previewSmallDataOffset + previewData[1].length; let machineInfoOffset = printParamsOffset + PRINT_PARAMS_SIZE; let nameOffset = machineInfoOffset + MACHINE_INFO_SIZE; let layerTableOffset = nameOffset + machineBytes.length; @@ -169,10 +155,20 @@ function writeCTB(params) { layers, seed, printTime, + previewLargeOffset, + previewSmallOffset, printParamsOffset, machineInfoOffset, layerTableOffset }); + writer.seek(previewLargeOffset); + writePreviewRecord(writer, previews[0], previewLargeDataOffset, previewData[0]); + writer.seek(previewLargeDataOffset); + writer.writeBytes(previewData[0]); + writer.seek(previewSmallOffset); + writePreviewRecord(writer, previews[1], previewSmallDataOffset, previewData[1]); + writer.seek(previewSmallDataOffset); + writer.writeBytes(previewData[1]); writer.seek(printParamsOffset); writePrintParams(writer, { process, volume }); writer.seek(machineInfoOffset); @@ -193,6 +189,7 @@ function writeCTB(params) { function writeHeader(writer, params) { let { device, process, layers, seed, printTime, + previewLargeOffset, previewSmallOffset, printParamsOffset, machineInfoOffset, layerTableOffset } = params; @@ -209,10 +206,10 @@ function writeHeader(writer, params) { writer.writeU32(process.slaBaseLayers); writer.writeU32(device.resolutionX); writer.writeU32(device.resolutionY); - writer.writeU32(0); + writer.writeU32(previewLargeOffset); writer.writeU32(layerTableOffset); writer.writeU32(layers.length); - writer.writeU32(0); + writer.writeU32(previewSmallOffset); writer.writeU32(printTime); writer.writeU32(1); writer.writeU32(printParamsOffset); @@ -225,6 +222,17 @@ function writeHeader(writer, params) { writer.writeU32(MACHINE_INFO_SIZE); } +function writePreviewRecord(writer, preview, dataOffset, data) { + writer.writeU32(preview.width); + writer.writeU32(preview.height); + writer.writeU32(dataOffset); + writer.writeU32(data.length); + writer.writeU32(0); + writer.writeU32(0); + writer.writeU32(0); + writer.writeU32(0); +} + function writePrintParams(writer, params) { let { process, volume } = params; @@ -316,9 +324,10 @@ function readHeader(view) { resolutionX: view.getUint32(0x34, true), resolutionY: view.getUint32(0x38, true), previewOffset: view.getUint32(0x3c, true), + previewLargeOffset: view.getUint32(0x3c, true), layerTableOffset: view.getUint32(0x40, true), layerCount: view.getUint32(0x44, true), - previewOneLength: view.getUint32(0x48, true), + previewSmallOffset: view.getUint32(0x48, true), printParamsOffset: view.getUint32(0x54, true), printParamsLength: view.getUint32(0x58, true), antiAliasLevel: view.getUint32(0x5c, true), @@ -330,6 +339,27 @@ function readHeader(view) { }; } +function readPreviews(view, header) { + return [header.previewLargeOffset, header.previewSmallOffset] + .filter(offset => offset > 0) + .map((offset, index) => readPreviewRecord(view, offset, index)); +} + +function readPreviewRecord(view, offset, index) { + return { + index, + offset, + width: view.getUint32(offset + 0, true), + height: view.getUint32(offset + 4, true), + imageOffset: view.getUint32(offset + 8, true), + imageLength: view.getUint32(offset + 12, true), + unknown16: view.getUint32(offset + 16, true), + unknown20: view.getUint32(offset + 20, true), + unknown24: view.getUint32(offset + 24, true), + unknown28: view.getUint32(offset + 28, true) + }; +} + function readLayerTable(view, header) { let layers = []; let offset = header.layerTableOffset; @@ -500,14 +530,71 @@ function createRSLAManifest(ctb, layers) { }; } -function renderToCTBPixels(image, width, height) { - let data = new Uint8Array(width * height); - for (let x=0; x> 8) & 0xff); +} + +function renderToCTBPixels(image, width, height) { + return imageToRowMajor(image, width, height); } function encodeRLE(input) { @@ -675,10 +762,6 @@ function toDataView(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; diff --git a/src/kiri/mode/sla/work/x_pw.js b/src/kiri/mode/sla/work/x_pw.js new file mode 100644 index 00000000..fcf7a17c --- /dev/null +++ b/src/kiri/mode/sla/work/x_pw.js @@ -0,0 +1,14 @@ +/** Copyright Stewart Allen -- All Rights Reserved */ + +const FAMILY = "Photon Workshop"; + +function encode(format) { + return Promise.reject(new Error([ + `${FAMILY} format .${format} is registered but not implemented.`, + "Add a fixture sliced by Photon Workshop before enabling this output target." + ].join(" "))); +} + +export const PW = { + encode +}; diff --git a/src/kiri/mode/sla/work/x_rsla.js b/src/kiri/mode/sla/work/x_rsla.js index 1e0054ef..641b6d08 100644 --- a/src/kiri/mode/sla/work/x_rsla.js +++ b/src/kiri/mode/sla/work/x_rsla.js @@ -3,41 +3,18 @@ import { JSZip } from '../../../../ext/jszip-esm.js'; import { PNG } from '../../../../ext/pngjs.esm.js'; import { photon } from './x_photon.js'; +import { imageToRowMajor, renderRasterLayers, round } from './raster.js'; const FORMAT = "rsla"; const MIME = "application/vnd.gridspace.rsla+zip"; function encode(print, progress) { - 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; - - widgets = widgets.filter(w => !w.track.ignore && !w.meta.disabled); - widgets.forEach(widget => { - layermax = Math.max(layermax, widget.slices.length); - }); - let layers = []; - let volume = 0; let zip = new JSZip(); - for (let index=0; index { + let { index, rendered, ctx } = params; + let { process, width, height } = ctx; let file = `layers/${index.toString().padStart(6, "0")}.png`; let layer = { @@ -47,13 +24,11 @@ function encode(print, progress) { file }; - volume += rendered.area * process.slaSlice; layers.push(layer); zip.file(file, imageToPNG(rendered.image, width, height)); + }, progress ? (value) => progress(value * 0.85, "raster_gen") : null); - if (progress) progress((index / layermax) * 0.85, "raster_gen"); - } - + let { device, process } = ctx; let manifest = createManifest({ device, process, layers, volume }); zip.file("manifest.json", JSON.stringify(manifest, null, 2)); @@ -122,12 +97,7 @@ function createManifest(params) { } function imageToPNG(image, width, height) { - let data = new Uint8Array(width * height); - for (let x=0; x value ? 255 : 0); return PNG.sync.write({ width, height, data }, { colorType: 0, inputColorType: 0, @@ -135,10 +105,6 @@ function imageToPNG(image, width, height) { }); } -function round(value) { - return Number.parseFloat(Number(value || 0).toFixed(5)); -} - export const RSLA = { encode };