From b65d4ae57e281dae606c3d650a87e350a7502a82 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Sat, 16 May 2026 21:22:08 -0400 Subject: [PATCH] add tilt tray for .goo and new elegoo targets --- docs/refs/MSLA_FORMAT_REFERENCE.md | 4 +- src/kiri/app/conf/defaults.js | 1 + src/kiri/dev/sla/Elegoo.Mars.5.Ultra.GOO.json | 17 ++++ .../sla/Elegoo.Saturn.4.Ultra.12K.GOO.json | 17 ++++ .../sla/Elegoo.Saturn.4.Ultra.16K.GOO.json | 17 ++++ src/kiri/mode/sla/work/x_goo.js | 97 +++++++++++++++---- 6 files changed, 131 insertions(+), 22 deletions(-) create mode 100644 src/kiri/dev/sla/Elegoo.Mars.5.Ultra.GOO.json create mode 100644 src/kiri/dev/sla/Elegoo.Saturn.4.Ultra.12K.GOO.json create mode 100644 src/kiri/dev/sla/Elegoo.Saturn.4.Ultra.16K.GOO.json diff --git a/docs/refs/MSLA_FORMAT_REFERENCE.md b/docs/refs/MSLA_FORMAT_REFERENCE.md index f8b459c0..3f6114af 100644 --- a/docs/refs/MSLA_FORMAT_REFERENCE.md +++ b/docs/refs/MSLA_FORMAT_REFERENCE.md @@ -32,7 +32,7 @@ | **Other Binary Formats** | | AnetFile | ✓ | ✓ | n4, n7 | 3 | Yes (N4/N7) | RLE | Anet printers | | FDGFile | ✓ | ✓ | fdg | 2 | No | RLE | Voxelab | -| GooFile | ✓ | ✓ | goo, prz | - | No | RLE+delim | Elegoo/Phrozen | +| GooFile | ✓ | ✓ | goo, prz | - | No | Chunked RLE + checksum | Elegoo/Phrozen | | LGSFile | ✓ | ✓ | lgs, lgs30, lgs120, lgs4k | - | Yes | Struct | Longer Orange | | MDLPFile | ✓ | ✓ | mdlp | 1 | No | Vector | Makerbase MKS | | GR1File | ✓ | ✓ | gr1 | - | No | Vector | GR Workshop | @@ -559,6 +559,8 @@ Non-delta maximum repeat: 0x0FFFFFFF pixels. Layer record delimiter after payload: 0x0D 0x0A. ``` +**Tilt-vat motion:** Mars 5 Ultra and Saturn 4 Ultra GOO files are treated as tilt-vat machines. UVtools identifies them by machine name and substitutes tiny synthetic motion values before encode: lift height `0.05`, lift speed `0.05`, retract height `0`, retract speed `0.05`, with about 4 seconds of motor time in print-time estimates. Kiri models this as `sla_motion: "tilt"` on the device profile instead of overloading user peel distance/speed fields. + ### CXDLP (Creality) **Extension:** `.cxdlp` diff --git a/src/kiri/app/conf/defaults.js b/src/kiri/app/conf/defaults.js index 098a586d..976e0b26 100644 --- a/src/kiri/app/conf/defaults.js +++ b/src/kiri/app/conf/defaults.js @@ -104,6 +104,7 @@ function device_from_code(code,mode) { slaFormatVersion: valueOf(set.sla_format_version, 1), slaMachineEditable: valueOf(set.sla_machine_editable, false), slaMachineName: valueOf(set.sla_machine_name, ''), + slaMotion: valueOf(set.sla_motion, ''), deviceZMax: valueOf(set.z_move_max, 0), gcodeTime: valueOf(set.time_factor, 1), maxHeight: valueOf(set.build_height || set.max_height, 150), diff --git a/src/kiri/dev/sla/Elegoo.Mars.5.Ultra.GOO.json b/src/kiri/dev/sla/Elegoo.Mars.5.Ultra.GOO.json new file mode 100644 index 00000000..ff608610 --- /dev/null +++ b/src/kiri/dev/sla/Elegoo.Mars.5.Ultra.GOO.json @@ -0,0 +1,17 @@ +{ + "no_clone": true, + "cmd": {}, + "settings": { + "bed_width": 153.36, + "bed_depth": 77.76, + "bed_height": 1, + "max_height": 165, + "resolution_x": 8520, + "resolution_y": 4320, + "sla_format": "goo", + "sla_file_ext": "goo", + "sla_machine_name": "Mars 5 Ultra", + "sla_motion": "tilt", + "sla_format_version": 3 + } +} diff --git a/src/kiri/dev/sla/Elegoo.Saturn.4.Ultra.12K.GOO.json b/src/kiri/dev/sla/Elegoo.Saturn.4.Ultra.12K.GOO.json new file mode 100644 index 00000000..757cc8ca --- /dev/null +++ b/src/kiri/dev/sla/Elegoo.Saturn.4.Ultra.12K.GOO.json @@ -0,0 +1,17 @@ +{ + "no_clone": true, + "cmd": {}, + "settings": { + "bed_width": 218.88, + "bed_depth": 122.88, + "bed_height": 1, + "max_height": 220, + "resolution_x": 11520, + "resolution_y": 5120, + "sla_format": "goo", + "sla_file_ext": "goo", + "sla_machine_name": "Saturn 4 Ultra 12K", + "sla_motion": "tilt", + "sla_format_version": 3 + } +} diff --git a/src/kiri/dev/sla/Elegoo.Saturn.4.Ultra.16K.GOO.json b/src/kiri/dev/sla/Elegoo.Saturn.4.Ultra.16K.GOO.json new file mode 100644 index 00000000..15c74503 --- /dev/null +++ b/src/kiri/dev/sla/Elegoo.Saturn.4.Ultra.16K.GOO.json @@ -0,0 +1,17 @@ +{ + "no_clone": true, + "cmd": {}, + "settings": { + "bed_width": 211.68, + "bed_depth": 118.37, + "bed_height": 1, + "max_height": 220, + "resolution_x": 15120, + "resolution_y": 6230, + "sla_format": "goo", + "sla_file_ext": "goo", + "sla_machine_name": "Saturn 4 Ultra 16K", + "sla_motion": "tilt", + "sla_format_version": 3 + } +} diff --git a/src/kiri/mode/sla/work/x_goo.js b/src/kiri/mode/sla/work/x_goo.js index f71c1891..4f33cdfe 100644 --- a/src/kiri/mode/sla/work/x_goo.js +++ b/src/kiri/mode/sla/work/x_goo.js @@ -11,6 +11,13 @@ const HEADER_SIZE = 195477; const LAYER_DEF_SIZE = 70; const FOOTER_SIZE = 11; const MAX_RUN = 0x0fffffff; +const TILT_MOTION = { + liftHeight: 0.05, + liftSpeed: 0.05, + retractHeight: 0, + retractSpeed: 0.05, + motorTime: 4 +}; const FORMAT_RECORDS = { goo: { ext: "goo", machine: "Elegoo GOO", notes: "Elegoo GOO" }, @@ -40,8 +47,9 @@ function encodeGOO(print, progress, photon, rec) { 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, value => value ? 255 : 0); + let { device, process, width, height } = ctx; + let motion = layerMotion(device, process, bottom); + let raster = imageToRowMajor(rendered.image, width, height, normalizePixel); accumulatePreview(previewSmall, rendered.image, width, height); accumulatePreview(previewLarge, rendered.image, width, height); @@ -56,12 +64,12 @@ function encodeGOO(print, progress, photon, rec) { waitTimeAfterCure: 0, waitTimeAfterLift: 0, waitTimeBeforeCure: bottom ? process.slaBaseOff : process.slaLayerOff, - liftHeight: bottom ? process.slaBasePeelDist : process.slaPeelDist, - liftSpeed: (bottom ? process.slaBasePeelLiftRate : process.slaPeelLiftRate) * 60, + liftHeight: motion.liftHeight, + liftSpeed: motion.liftSpeed, liftHeight2: 0, liftSpeed2: 0, - retractHeight: bottom ? process.slaBasePeelDist : process.slaPeelDist, - retractSpeed: (bottom ? process.slaBasePeelDropRate : process.slaPeelDropRate) * 60, + retractHeight: motion.retractHeight, + retractSpeed: motion.retractSpeed, retractHeight2: 0, retractSpeed2: 0, lightPWM: 255 @@ -101,6 +109,8 @@ function writeHeader(writer, params) { let { device, process, width, height } = ctx; let bottomOff = process.slaBaseOff || 0; let layerOff = process.slaLayerOff || 0; + let bottomMotion = layerMotion(device, process, true); + let layerMotionValue = layerMotion(device, process, false); writer.writeString(FILE_VERSION, 4); writer.writeBytes(FILE_MAGIC); @@ -137,19 +147,19 @@ function writeHeader(writer, params) { writer.writeF32(layerOff); writer.writeF32(process.slaBaseOn); writer.writeU32(process.slaBaseLayers || 0); - writer.writeF32(process.slaBasePeelDist); - writer.writeF32(process.slaBasePeelLiftRate * 60); - writer.writeF32(process.slaPeelDist); - writer.writeF32(process.slaPeelLiftRate * 60); - writer.writeF32(process.slaBasePeelDist); - writer.writeF32(process.slaBasePeelDropRate * 60); - writer.writeF32(process.slaPeelDist); - writer.writeF32(process.slaPeelDropRate * 60); + writer.writeF32(bottomMotion.liftHeight); + writer.writeF32(bottomMotion.liftSpeed); + writer.writeF32(layerMotionValue.liftHeight); + writer.writeF32(layerMotionValue.liftSpeed); + writer.writeF32(bottomMotion.retractHeight); + writer.writeF32(bottomMotion.retractSpeed); + writer.writeF32(layerMotionValue.retractHeight); + writer.writeF32(layerMotionValue.retractSpeed); for (let i=0; i<8; i++) writer.writeF32(0); writer.writeU16(255); writer.writeU16(255); writer.writeU8(0); - writer.writeU32(estimatePrintTime(process, layers.length)); + writer.writeU32(estimatePrintTime(device, process, layers.length)); writer.writeF32(round(volume)); writer.writeF32(0); writer.writeF32(0); @@ -187,6 +197,20 @@ function writeLayer(writer, layer) { writer.writeBytes(DELIMITER); } +function layerMotion(device, process, bottom) { + if (device.slaMotion === "tilt") { + return TILT_MOTION; + } + + return { + liftHeight: bottom ? process.slaBasePeelDist : process.slaPeelDist, + liftSpeed: (bottom ? process.slaBasePeelLiftRate : process.slaPeelLiftRate) * 60, + retractHeight: bottom ? process.slaBasePeelDist : process.slaPeelDist, + retractSpeed: (bottom ? process.slaBasePeelDropRate : process.slaPeelDropRate) * 60, + motorTime: 0 + }; +} + function writeFooter(writer) { writer.writeU8(0); writer.writeU8(0); @@ -307,6 +331,7 @@ function decodeLayer(view, layer, pixelCount) { function encodeRLE(input) { let output = [LAYER_MAGIC]; + let previous = 0; let current = input[0] || 0; let run = 0; @@ -315,20 +340,38 @@ function encodeRLE(input) { if (value === current && run < MAX_RUN) { run++; } else { - writeRun(output, current, run); + writeRun(output, current, previous, run); + previous = current; current = value; run = 1; } } - writeRun(output, current, run); + writeRun(output, current, previous, run); output.push(checksum(output)); return Uint8Array.from(output); } -function writeRun(output, color, run) { +function writeRun(output, color, previous, run) { while (run > 0) { let stride = Math.min(run, MAX_RUN); + let diff = Math.abs(color - previous); + if (color > 0 && color < 255 && diff <= 0x0f && stride <= 0xff) { + let first = 0x80 | (diff & 0x0f); + if (stride > 1) { + first |= 0x10; + } + if (color < previous) { + first |= 0x20; + } + output.push(first); + if (stride > 1) { + output.push(stride); + } + run -= stride; + continue; + } + let lengthCode = stride <= 0x0f ? 0 : stride <= 0x0fff ? 1 : stride <= 0x0fffff ? 2 @@ -350,6 +393,17 @@ function writeRun(output, color, run) { } } +function normalizePixel(value) { + value = value || 0; + if (value === 0 || value === 255) { + return value; + } + + // The JS fallback rasterizer paints red at 200, so rescale that range to + // retain binary cure strength while preserving antialiased edge values. + return Math.min(255, Math.round(value * 255 / 200)); +} + function decodeRLE(input, pixelCount) { let rle = input instanceof Uint8Array ? input : new Uint8Array(input); if (rle[0] !== LAYER_MAGIC) { @@ -452,11 +506,12 @@ function accumulatePreview(preview, image, width, height) { } } -function estimatePrintTime(process, layers) { +function estimatePrintTime(device, process, layers) { let bottom = Math.min(process.slaBaseLayers || 0, layers); let normal = Math.max(0, layers - bottom); - let bottomTime = number(process.slaBaseOn) + number(process.slaBaseOff); - let normalTime = number(process.slaLayerOn) + number(process.slaLayerOff); + let motorTime = device.slaMotion === "tilt" ? TILT_MOTION.motorTime : 0; + let bottomTime = number(process.slaBaseOn) + number(process.slaBaseOff) + motorTime; + let normalTime = number(process.slaLayerOn) + number(process.slaLayerOff) + motorTime; return Math.ceil(bottom * bottomTime + normal * normalTime); }