diff --git a/docs/refs/MSLA_FORMAT_REFERENCE.md b/docs/refs/MSLA_FORMAT_REFERENCE.md index 3f6114af..72d16bf6 100644 --- a/docs/refs/MSLA_FORMAT_REFERENCE.md +++ b/docs/refs/MSLA_FORMAT_REFERENCE.md @@ -210,6 +210,23 @@ Same header structure as CTB v3, with additional support for: - PageNumber field in layer table - Transition layers +#### 1.4 CTB Encrypted v4-5 + +**Extensions:** `.ctb`, `.encrypted.ctb` +**Magic:** `0x12FD0107` (LE) +**Versions:** 4, 5 (default: 5) +**Settings block:** 288-byte slicer-settings table encrypted with AES-256-CBC, no padding, zero-padded to 16-byte blocks +**Layer data:** CTB variable-length 7-bit grayscale RLE, then CTB layer XOR using `LayerXorKey` (`0xEFBEADDE` by default); some files additionally AES-encrypt layer-data subranges + +Encrypted CTB is a different container from plain CTB v3/v4/v5, not just a flag on the v3 layout. The fixed 48-byte header points at the encrypted settings block and a final encrypted SHA-256 signature. The AES key/IV are derived from UVtools-compatible Chitubox constants with the `UVtools` XOR mask. Kiri keeps these primitives native in JS in `x_ctb_crypto.js`; full writer support still needs the encrypted settings table, layer pointer table, resin parameters, previews, and final signature wired into `x_ctb`. + +Key tables: +- File header: 48 bytes +- Slicer settings: 288 bytes +- Layer pointer: 16 bytes +- Layer definition: 88 bytes +- Per-layer settings flag: `0x40` allow, `0x00` disallow + ### 2. Anycubic Family #### 2.1 Legacy Anycubic Formats (v1) @@ -654,6 +671,46 @@ PrinterModel = SL1 } ``` +### RSLA / VSLA (Grid.Space Generic) + +**Extensions:** `.rsla`, `.vsla` +**Type:** ZIP with JSON manifest +**Layer Format:** RSLA stores raster PNG layers; VSLA stores SVG-like vector layer geometry + +These formats are intended as a vendor-neutral interchange and debugging target. They carry enough process metadata to preserve behavior before conversion to a proprietary printer format. + +**Shared manifest process fields:** +```json +{ + "process": { + "bottomLayers": 5, + "transitionLayers": 8, + "exposure": 2.4, + "bottomExposure": 28, + "lightOffDelay": 0, + "bottomLightOffDelay": 0, + "waitBeforeCure": 0.5, + "bottomWaitBeforeCure": 2, + "waitAfterCure": 0, + "waitAfterLift": 0, + "liftHeight": 5, + "bottomLiftHeight": 6, + "liftSpeed": 180, + "bottomLiftSpeed": 60, + "retractHeight": 5, + "retractSpeed": 180, + "lightPWM": 255, + "bottomLightPWM": 255, + "motion": "normal", + "antiAlias": 1 + } +} +``` + +Each layer also carries a resolved per-layer `process` object with `bottom`, `transition`, `transitionIndex`, effective exposure, waits, motion, and PWM. Proprietary writers should prefer this resolved per-layer model where their format supports it, then fall back to global process fields where it does not. + +VSLA layer SVG roots additionally include process data attributes such as `data-exposure`, `data-light-off-delay`, `data-light-pwm`, `data-bottom`, and `data-transition` so the per-layer intent is visible without parsing the manifest. + --- ## Implementation Guidance for Sandboxed Environments @@ -898,6 +955,7 @@ def decode_ctb_variable_rle(data: bytes, width: int, height: int) -> bytes: **CTB:** - `UVtools.Core/FileFormats/ChituboxFile.cs`: `DecodeCtbImage`, `EncodeCtbImage`, `LayerRleCryptBuffer` - `UVtools.Core/FileFormats/CTBEncryptedFile.cs`: encrypted CTB v4-v5 container and AES handling +- `src/kiri/mode/sla/work/x_ctb_crypto.js`: native JS AES-256-CBC/no-padding, CTB layer XOR, encrypted CTB constants **Anycubic:** - `UVtools.Core/FileFormats/AnycubicFile.cs`: `FileMark`, `Header`, `LayerDef`, `GetAvailableVersionsForExtension`, `PrinterModel` diff --git a/src/kiri/dev/sla/Creality.Halot.Max.json b/src/kiri/dev/sla/Creality.Halot.Max.json index 538efb5e..49f8aa68 100644 --- a/src/kiri/dev/sla/Creality.Halot.Max.json +++ b/src/kiri/dev/sla/Creality.Halot.Max.json @@ -7,6 +7,8 @@ "bed_height": 1, "max_height": 300, "resolution_x": 3840, - "resolution_y": 2160 + "resolution_y": 2160, + "sla_format": "cxdlp", + "sla_file_ext": "cxdlp" } } diff --git a/src/kiri/dev/sla/Creality.Halot.One.json b/src/kiri/dev/sla/Creality.Halot.One.json index d76dd651..c725c426 100644 --- a/src/kiri/dev/sla/Creality.Halot.One.json +++ b/src/kiri/dev/sla/Creality.Halot.One.json @@ -7,6 +7,8 @@ "bed_height": 1, "max_height": 160, "resolution_x": 1620, - "resolution_y": 2560 + "resolution_y": 2560, + "sla_format": "cxdlp", + "sla_file_ext": "cxdlp" } } diff --git a/src/kiri/dev/sla/Creality.Halot.Sky.json b/src/kiri/dev/sla/Creality.Halot.Sky.json index d1cb0dc4..64e679d2 100644 --- a/src/kiri/dev/sla/Creality.Halot.Sky.json +++ b/src/kiri/dev/sla/Creality.Halot.Sky.json @@ -7,6 +7,8 @@ "bed_height": 1, "max_height": 200, "resolution_x": 3840, - "resolution_y": 2400 + "resolution_y": 2400, + "sla_format": "cxdlp", + "sla_file_ext": "cxdlp" } } diff --git a/src/kiri/mode/sla/core/formats.js b/src/kiri/mode/sla/core/formats.js index 83279757..1977c413 100644 --- a/src/kiri/mode/sla/core/formats.js +++ b/src/kiri/mode/sla/core/formats.js @@ -46,6 +46,14 @@ export const SLA_FORMATS = { priority: 1, family: 'chitubox' }, + ctbe: { + id: 'ctbe', + ext: 'encrypted.ctb', + proprietary: true, + available: false, + priority: 1, + family: 'chitubox-encrypted' + }, goo: { id: 'goo', ext: 'goo', diff --git a/src/kiri/mode/sla/work/meta.js b/src/kiri/mode/sla/work/meta.js new file mode 100644 index 00000000..7b56d2a5 --- /dev/null +++ b/src/kiri/mode/sla/work/meta.js @@ -0,0 +1,128 @@ +/** Copyright Stewart Allen -- All Rights Reserved */ + +const DEFAULT_PWM = 255; +const TILT_MOTION = { + liftHeight: 0.05, + liftSpeed: 0.05, + retractHeight: 0, + retractSpeed: 0.05, + motorTime: 4 +}; + +export function createProcessMetadata(device, process) { + let transitionLayers = intValue(process.slaTransitionLayers); + let lightPWM = pwmValue(process.slaLightPWM); + let bottomLightPWM = pwmValue(process.slaBaseLightPWM, lightPWM); + let normalMotion = motionFor(device, process, false); + let bottomMotion = motionFor(device, process, true); + + return { + bottomLayers: intValue(process.slaBaseLayers), + transitionLayers, + exposure: number(process.slaLayerOn), + bottomExposure: number(process.slaBaseOn), + lightOffDelay: number(process.slaLayerOff), + bottomLightOffDelay: number(process.slaBaseOff), + waitBeforeCure: number(process.slaWaitBeforeCure ?? process.slaLayerOff), + bottomWaitBeforeCure: number(process.slaBaseWaitBeforeCure ?? process.slaBaseOff), + waitAfterCure: number(process.slaWaitAfterCure), + bottomWaitAfterCure: number(process.slaBaseWaitAfterCure), + waitAfterLift: number(process.slaWaitAfterLift), + bottomWaitAfterLift: number(process.slaBaseWaitAfterLift), + liftHeight: normalMotion.liftHeight, + bottomLiftHeight: bottomMotion.liftHeight, + liftSpeed: normalMotion.liftSpeed, + bottomLiftSpeed: bottomMotion.liftSpeed, + retractHeight: normalMotion.retractHeight, + bottomRetractHeight: bottomMotion.retractHeight, + retractSpeed: normalMotion.retractSpeed, + bottomRetractSpeed: bottomMotion.retractSpeed, + lightPWM, + bottomLightPWM, + motion: device.slaMotion || "normal", + firstLayerOffset: number(process.slaFirstOffset), + antiAlias: intValue(process.slaAntiAlias, 1) + }; +} + +export function createLayerMetadata(device, process, index, z, area) { + let meta = createProcessMetadata(device, process); + let bottom = index < meta.bottomLayers; + let transitionIndex = bottom ? -1 : index - meta.bottomLayers; + let transition = transitionIndex >= 0 && transitionIndex < meta.transitionLayers; + let motion = motionFor(device, process, bottom); + + return { + index, + z: round(z), + area: round(area), + bottom, + transition, + transitionIndex: transition ? transitionIndex : -1, + exposure: round(layerExposure(meta, bottom, transitionIndex)), + lightOffDelay: bottom ? meta.bottomLightOffDelay : meta.lightOffDelay, + waitBeforeCure: bottom ? meta.bottomWaitBeforeCure : meta.waitBeforeCure, + waitAfterCure: bottom ? meta.bottomWaitAfterCure : meta.waitAfterCure, + waitAfterLift: bottom ? meta.bottomWaitAfterLift : meta.waitAfterLift, + liftHeight: motion.liftHeight, + liftSpeed: motion.liftSpeed, + retractHeight: motion.retractHeight, + retractSpeed: motion.retractSpeed, + lightPWM: bottom ? meta.bottomLightPWM : meta.lightPWM + }; +} + +export function motionFor(device, process, bottom) { + if (device.slaMotion === "tilt") { + return TILT_MOTION; + } + + let normalDrop = process.slaPeelDropRate; + let bottomDrop = process.slaBasePeelDropRate ?? normalDrop; + return { + liftHeight: number(bottom ? process.slaBasePeelDist : process.slaPeelDist), + liftSpeed: number(bottom ? process.slaBasePeelLiftRate : process.slaPeelLiftRate) * 60, + retractHeight: number(bottom ? process.slaBasePeelDist : process.slaPeelDist), + retractSpeed: number(bottom ? bottomDrop : normalDrop) * 60, + motorTime: 0 + }; +} + +export function estimatePrintTime(device, process, layers) { + let meta = createProcessMetadata(device, process); + let seconds = 0; + for (let index=0; index= meta.transitionLayers) { + return bottom ? meta.bottomExposure : meta.exposure; + } + + let step = (transitionIndex + 1) / (meta.transitionLayers + 1); + return meta.bottomExposure + (meta.exposure - meta.bottomExposure) * step; +} + +function pwmValue(value, fallback = DEFAULT_PWM) { + value = intValue(value, fallback); + return Math.max(0, Math.min(255, value)); +} + +function intValue(value, fallback = 0) { + value = Number.parseInt(value); + return Number.isFinite(value) ? value : fallback; +} + +function number(value) { + value = Number(value); + return Number.isFinite(value) ? value : 0; +} diff --git a/src/kiri/mode/sla/work/x_ctb.js b/src/kiri/mode/sla/work/x_ctb.js index ecacdae3..70b8f1b7 100644 --- a/src/kiri/mode/sla/work/x_ctb.js +++ b/src/kiri/mode/sla/work/x_ctb.js @@ -3,6 +3,7 @@ import { JSZip } from '../../../../ext/jszip-esm.js'; import { PNG } from '../../../../ext/pngjs.esm.js'; import { imageToRowMajor, renderRasterLayers, round } from './raster.js'; +import { ctbLayerCrypt } from './x_ctb_crypto.js'; const CTB_V3_MAGIC = 0x12fd0086; const HEADER_SIZE = 0x70; @@ -715,37 +716,11 @@ function requireBytes(input, offset, 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>> 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; - } - }; + return ctbLayerCrypt(seed, layerIndex, input); } function imageToPNG(image, width, height) { diff --git a/src/kiri/mode/sla/work/x_ctb_crypto.js b/src/kiri/mode/sla/work/x_ctb_crypto.js new file mode 100644 index 00000000..8b69eb0b --- /dev/null +++ b/src/kiri/mode/sla/work/x_ctb_crypto.js @@ -0,0 +1,321 @@ +/** Copyright Stewart Allen -- All Rights Reserved */ + +const UVTOOLS_KEY = "UVtools"; +const SECRET_KEY = "hQ36XB6yTk+zO02ysyiowt8yC1buK+nbLWyfY40EXoU="; +const SECRET_IV = "Wld+ampndVJecmVjYH5cWQ=="; +const BLOCK_SIZE = 16; +const SBOX = new Uint8Array(256); +const INV_SBOX = new Uint8Array(256); +const RCON = new Uint8Array(15); + +initTables(); + +export const CTB_ENCRYPTED_MAGIC = 0x12fd0107; +export const CTB_ENCRYPTED_VERSION = 5; +export const CTB_LAYER_XOR_KEY = 0xefbeadde; +export const CTB_ENCRYPTED_HEADER_SIZE = 48; +export const CTB_ENCRYPTED_SETTINGS_SIZE = 288; +export const CTB_ENCRYPTED_LAYER_POINTER_SIZE = 16; +export const CTB_ENCRYPTED_LAYER_DEF_SIZE = 88; +export const CTB_ENCRYPTED_MIN_RLE_AES_LENGTH = 512; +export const CTB_PER_LAYER_SETTINGS_ALLOW = 0x40; +export const CTB_PER_LAYER_SETTINGS_DISALLOW = 0x00; + +export const CTB_ENCRYPTED_KEY = xorCipher(base64Bytes(SECRET_KEY), UVTOOLS_KEY); +export const CTB_ENCRYPTED_IV = xorCipher(base64Bytes(SECRET_IV), UVTOOLS_KEY); + +export function ctbLayerCrypt(seed, layerIndex, input) { + let output = new Uint8Array(input); + ctbLayerCryptInPlace(seed, layerIndex, output); + return output; +} + +export function ctbLayerCryptInPlace(seed, layerIndex, input) { + if (!seed) return input; + + 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; + + for (let i=0; i>> (8 * index)) & 0xff; + index++; + if ((index & 3) === 0) { + key = (key + init) >>> 0; + index = 0; + } + } + + return input; +} + +export function ctbEncrypt(data) { + return aesCbcCrypt(data, true, CTB_ENCRYPTED_KEY, CTB_ENCRYPTED_IV); +} + +export function ctbDecrypt(data) { + return aesCbcCrypt(data, false, CTB_ENCRYPTED_KEY, CTB_ENCRYPTED_IV); +} + +export async function ctbSignature(checksumValue = 0xcafebabe) { + let bytes = new Uint8Array(8); + let view = new DataView(bytes.buffer); + view.setUint32(0, checksumValue >>> 0, true); + view.setUint32(4, Math.floor(checksumValue / 0x100000000), true); + return ctbEncrypt(await sha256(bytes)); +} + +function aesCbcCrypt(data, encrypt, key, iv) { + if (key.length !== 32) { + throw new Error(`CTB AES key must be 32 bytes (${key.length})`); + } + if (iv.length !== BLOCK_SIZE) { + throw new Error(`CTB AES IV must be 16 bytes (${iv.length})`); + } + + let keys = expandKey(key); + let input = encrypt ? zeroPad(data) : toBytes(data); + if (input.length % BLOCK_SIZE !== 0) { + throw new Error(`CTB AES input must align to 16 bytes (${input.length})`); + } + + let output = new Uint8Array(input.length); + let previous = new Uint8Array(iv); + + for (let offset=0; offset0; round--) { + invShiftRows(state); + invSubBytes(state); + addRoundKey(state, keys.bytes, round); + invMixColumns(state); + } + invShiftRows(state); + invSubBytes(state); + addRoundKey(state, keys.bytes, 0); + + return state; +} + +function expandKey(key) { + let nk = 8; + let nb = 4; + let rounds = 14; + let bytes = new Uint8Array(nb * (rounds + 1) * 4); + bytes.set(key); + + let temp = new Uint8Array(4); + let words = nb * (rounds + 1); + for (let word=nk; word>= 1; + } + return out; +} + +function gfPow(value, power) { + let out = 1; + while (power) { + if (power & 1) out = mul(out, value); + value = mul(value, value); + power >>= 1; + } + return out; +} + +function rot(value, shift) { + return ((value << shift) | (value >> (8 - shift))) & 0xff; +} + +function zeroPad(data) { + data = toBytes(data); + let length = Math.ceil(data.length / BLOCK_SIZE) * BLOCK_SIZE; + if (length === data.length) return data; + + let output = new Uint8Array(length); + output.set(data); + return output; +} + +function xorBlock(block, key) { + for (let i=0; i -- All Rights Reserved */ import { imageToRowMajor, renderRasterLayers, round } from './raster.js'; +import { createLayerMetadata, createProcessMetadata, estimatePrintTime, motionFor } from './meta.js'; const FAMILY = "GOO"; const FILE_VERSION = "V3.0"; @@ -11,13 +12,6 @@ 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" }, @@ -46,9 +40,9 @@ function encodeGOO(print, progress, photon, rec) { let previewLarge = createPreview(290, 290); let { ctx, volume } = renderRasterLayers(print, photon, params => { - let { index, rendered, bottom, z, ctx } = params; + let { index, rendered, z, ctx } = params; let { device, process, width, height } = ctx; - let motion = layerMotion(device, process, bottom); + let layerMeta = createLayerMetadata(device, process, index, z, rendered.area); let raster = imageToRowMajor(rendered.image, width, height, normalizePixel); accumulatePreview(previewSmall, rendered.image, width, height); @@ -57,22 +51,22 @@ function encodeGOO(print, progress, photon, rec) { layers.push({ index, data: encodeRLE(raster), - z: round(z), + z: layerMeta.z, pausePositionZ: ctx.device.maxHeight || ctx.device.bedHeight || 0, - exposure: bottom ? process.slaBaseOn : process.slaLayerOn, - lightOffDelay: 0, - waitTimeAfterCure: 0, - waitTimeAfterLift: 0, - waitTimeBeforeCure: bottom ? process.slaBaseOff : process.slaLayerOff, - liftHeight: motion.liftHeight, - liftSpeed: motion.liftSpeed, + exposure: layerMeta.exposure, + lightOffDelay: layerMeta.lightOffDelay, + waitTimeAfterCure: layerMeta.waitAfterCure, + waitTimeAfterLift: layerMeta.waitAfterLift, + waitTimeBeforeCure: layerMeta.waitBeforeCure, + liftHeight: layerMeta.liftHeight, + liftSpeed: layerMeta.liftSpeed, liftHeight2: 0, liftSpeed2: 0, - retractHeight: motion.retractHeight, - retractSpeed: motion.retractSpeed, + retractHeight: layerMeta.retractHeight, + retractSpeed: layerMeta.retractSpeed, retractHeight2: 0, retractSpeed2: 0, - lightPWM: 255 + lightPWM: layerMeta.lightPWM }); }, progress ? (value) => progress(value * 0.85, `${rec.ext}_encode`) : null); @@ -107,10 +101,9 @@ function writeGOO(params) { function writeHeader(writer, params) { let { ctx, layers, volume, previewSmall, previewLarge, rec } = 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); + let meta = createProcessMetadata(device, process); + let bottomMotion = motionFor(device, process, true); + let layerMotionValue = motionFor(device, process, false); writer.writeString(FILE_VERSION, 4); writer.writeBytes(FILE_MAGIC); @@ -136,17 +129,17 @@ function writeHeader(writer, params) { writer.writeF32(device.bedDepth); writer.writeF32(device.maxHeight || device.bedHeight || 0); writer.writeF32(process.slaSlice); - writer.writeF32(process.slaLayerOn); + writer.writeF32(meta.exposure); writer.writeU8(1); writer.writeF32(0); writer.writeF32(0); writer.writeF32(0); - writer.writeF32(bottomOff); + writer.writeF32(meta.bottomWaitBeforeCure); writer.writeF32(0); writer.writeF32(0); - writer.writeF32(layerOff); - writer.writeF32(process.slaBaseOn); - writer.writeU32(process.slaBaseLayers || 0); + writer.writeF32(meta.waitBeforeCure); + writer.writeF32(meta.bottomExposure); + writer.writeU32(meta.bottomLayers); writer.writeF32(bottomMotion.liftHeight); writer.writeF32(bottomMotion.liftSpeed); writer.writeF32(layerMotionValue.liftHeight); @@ -156,8 +149,8 @@ function writeHeader(writer, params) { 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.writeU16(meta.bottomLightPWM); + writer.writeU16(meta.lightPWM); writer.writeU8(0); writer.writeU32(estimatePrintTime(device, process, layers.length)); writer.writeF32(round(volume)); @@ -197,20 +190,6 @@ 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); @@ -506,20 +485,6 @@ function accumulatePreview(preview, image, width, height) { } } -function estimatePrintTime(device, process, layers) { - let bottom = Math.min(process.slaBaseLayers || 0, layers); - let normal = Math.max(0, layers - bottom); - 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); -} - -function number(value) { - value = Number(value); - return Number.isFinite(value) ? value : 0; -} - function checksum(bytes) { let sum = 0; for (let i=1; i { let { index, rendered, ctx } = params; - let { process, width, height } = ctx; + let { device, process, width, height } = ctx; + let z = process.slaFirstOffset + process.slaSlice * index; + let layerMeta = createLayerMetadata(device, process, index, z, rendered.area); let file = `layers/${index.toString().padStart(6, "0")}.png`; let layer = { index, - z: round(process.slaFirstOffset + process.slaSlice * index), - area: round(rendered.area), - file + z: layerMeta.z, + area: layerMeta.area, + file, + process: layerMeta }; layers.push(layer); @@ -79,18 +83,7 @@ function createManifest(params) { black: "off" }, process: { - bottomLayers: process.slaBaseLayers, - exposure: process.slaLayerOn, - bottomExposure: process.slaBaseOn, - lightOffDelay: process.slaLayerOff, - bottomLightOffDelay: process.slaBaseOff, - liftHeight: process.slaPeelDist, - bottomLiftHeight: process.slaBasePeelDist, - liftSpeed: process.slaPeelLiftRate * 60, - bottomLiftSpeed: process.slaBasePeelLiftRate * 60, - retractSpeed: process.slaPeelDropRate * 60, - firstLayerOffset: process.slaFirstOffset, - antiAlias: process.slaAntiAlias + ...createProcessMetadata(device, process) }, layers }; diff --git a/src/kiri/mode/sla/work/x_vsla.js b/src/kiri/mode/sla/work/x_vsla.js index 6c5961d5..6786174e 100644 --- a/src/kiri/mode/sla/work/x_vsla.js +++ b/src/kiri/mode/sla/work/x_vsla.js @@ -1,6 +1,7 @@ /** Copyright Stewart Allen -- All Rights Reserved */ import { JSZip } from '../../../../ext/jszip-esm.js'; +import { createLayerMetadata, createProcessMetadata, round } from './meta.js'; const FORMAT = "vsla"; const MIME = "application/vnd.gridspace.vsla+zip"; @@ -18,7 +19,7 @@ function encode(print, progress) { let layers = []; let volume = 0; for (let index=0; index`; }).join("\n"); + let proc = layer.process; return [ ``, - ``, + ``, body, ``, `` @@ -184,10 +179,6 @@ function appendPath(parts, poly, device) { parts.push("Z"); } -function round(value) { - return Number.parseFloat(Number(value || 0).toFixed(5)); -} - export const VSLA = { encode };