From 3158276984fd9c2def0484fd6488fc89de54007f Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Sat, 24 Jul 2021 23:30:45 -0400 Subject: [PATCH] complete basic export for cxdlp --- app.js | 2 + src/add/class.js | 38 +++ src/mode/fdm/export.js | 2 +- src/mode/sla/client.js | 8 + src/mode/sla/export.js | 665 ++++++--------------------------------- src/mode/sla/x_halot.js | 216 ++++++++++++- src/mode/sla/x_photon.js | 535 +++++++++++++++++++++++++++++++ 7 files changed, 883 insertions(+), 583 deletions(-) create mode 100644 src/mode/sla/x_photon.js diff --git a/app.js b/app.js index b1b870d0..01b55b6d 100644 --- a/app.js +++ b/app.js @@ -309,6 +309,8 @@ const script = { "mode/sla/driver", "mode/sla/slice", "mode/sla/export", + "mode/sla/x_halot", + "mode/sla/x_photon", "mode/cam/driver", "mode/cam/ops", "mode/cam/tool", diff --git a/src/add/class.js b/src/add/class.js index 8fcabc7d..0b86a52a 100644 --- a/src/add/class.js +++ b/src/add/class.js @@ -1,3 +1,41 @@ +self.ArrayWriter = class ArrayWriter { + constructor() { + this.pos = 0; + this.array = []; + } + + seek(pos) { + let last = this.pos; + this.pos = pos; + return last; + } + + skip(len) { + this.pos += len; + return this.pos; + } + + writeU8(v) { + this.array[this.pos++] = v & 0xff; + } + + writeU16(v) { + this.array[this.pos++] = (v >> 8) & 0xff; + this.array[this.pos++] = v & 0xff; + } + + writeU32(v) { + this.array[this.pos++] = (v >> 24) & 0xff; + this.array[this.pos++] = (v >> 16) & 0xff; + this.array[this.pos++] = (v >> 8) & 0xff; + this.array[this.pos++] = v & 0xff; + } + + toBuffer() { + return new Uint8ClampedArray(this.array).buffer; + } +}; + self.DataWriter = class DataWriter { constructor(view, pos) { this.view = view; diff --git a/src/mode/fdm/export.js b/src/mode/fdm/export.js index f91f5491..cbe1e7ee 100644 --- a/src/mode/fdm/export.js +++ b/src/mode/fdm/export.js @@ -395,7 +395,7 @@ pos.z = zpos; if (peelGuard && bmax > peelGuard && blast < peelGuard) { peelGuard += 50; - append(`G0 Z${(blast + 55).round(decimals)} F200 ; peel guard`); + append(`G0 Z${(blast + 100).round(decimals)} F200 ; peel guard (100)`); append(`G0 Z${blastz.round(decimals)} F200 ; unpeel`); } blast = bmax; diff --git a/src/mode/sla/client.js b/src/mode/sla/client.js index 0b634617..78330ed1 100644 --- a/src/mode/sla/client.js +++ b/src/mode/sla/client.js @@ -73,9 +73,17 @@ download.innerText += " .photons"; download.onclick = () => { saveFile(API, file, ".photons") }; break; + case 'Creality.Halot.Sky': + default: + download.innerText += " .cxdlp"; + download.onclick = () => { saveFile(API, file, ".cxdlp") }; + break; } let canvas = $('print-canvas'); + // rotate 90 degrees for export view + canvas.width = height; + canvas.height = width; let ctx = canvas.getContext('2d'); let img = ctx.createImageData(height, width); let imgDV = new DataView(img.data.buffer); diff --git a/src/mode/sla/export.js b/src/mode/sla/export.js index 59464498..68d166c5 100644 --- a/src/mode/sla/export.js +++ b/src/mode/sla/export.js @@ -20,81 +20,112 @@ settings = print.settings, device = settings.device, process = settings.process, - output = print.output, - layermax = 0, width = device.resolutionX, height = device.resolutionY, - width2 = width/2, - height2 = height/2, scaleX = width / device.bedWidth, scaleY = height / device.bedDepth, + alias = process.slaAntiAlias || 1, mark = Date.now(), - layers = process.slaAntiAlias || 1, - masks = [], - images = [], - slices = [], - legacyMode = SLA.legacy || layers > 1, - part1 = legacyMode ? 0.25 : 0.85, - part2 = legacyMode ? 0.75 : 0.15; - - let d = 8 / layers; - for (let i=0; i { layermax = Math.max(widget.slices.length); }); - let render = legacyMode ? renderLayer : renderLayerWasm; - - // generate layer bitmaps - // in wasm mode, rle layers generated here, too - for (let index=0; index < layermax; index++) { - let param = { index, width, height, widgets, scaleX, scaleY, masks }; - let {image, layers, end} = render(param); - images.push(image); - slices.push(layers); - // transfer images to browser main - image = image.buffer; - online({ - progress: (index / layermax) * part1, - message: "image_gen", - data: image - },[image]); - if (end) break; - } - - let exp_func; + let isPhoton = false; switch (device.deviceName) { case 'Anycubic.Photon': - exp_func = generatePhoton; - break; case 'Anycubic.Photon.S': - exp_func = generatePhotons; - break; - case 'Creality.Halot.Sky': - exp_func = generateCXDLP; + isPhoton = true; break; } - let file = exp_func(print, { - width: width, - height: height, - small: SLA.previewSmall.data, - large: SLA.previewLarge.data, - lines: images, - slices: slices - }, (progress, message) => { - online({progress: progress * part2 + part1, message}); - }); - ondone({ - width: width, - height: height, - file: file - },[file]); + if (isPhoton) { + let legacyMode = SLA.legacy || alias > 1, + part1 = legacyMode ? 0.25 : 0.85, + part2 = (1 - part1), + images = [], + slices = []; + + // generate layer bitmaps + // in wasm mode, rle layers generated here, too + let d = 8 / alias; + let masks = []; + for (let i=0; i { + online({progress: progress * part2 + part1, message}); + }); + ondone({ + width: width, + height: height, + file: file + },[file]); + } else { + let part1 = 0.25; + let part2 = 1 - part1; + let images = []; + let slices = []; + + for (let index=0; index < layermax; index++) { + let param = { index, width, height, widgets, scaleX, scaleY }; + let {image, lines} = CXDLP.render(param); + images.push(image); + slices.push(lines); + // transfer image memory to browser main + // it *should* be sampled to save memory + image = image.buffer; + online({ + progress: (index / layermax) * part1, + message: "image_gen", + data: image + }, [image]); + // bail on an empty layer + if (lines.length === 0) { + break; + } + } + + let file = CXDLP.export({settings, width, height, slices}); + ondone({ + width: width, + height: height, + file: file + }, [file]); + } console.log('print.export', Date.now() - mark); }; @@ -103,524 +134,4 @@ console.log({generateCXDLP: print, conf, progress}); } - function generatePhoton(print, conf, progress) { - let printset = print.settings, - process = printset.process, - device = printset.device, - width = conf.width, - height = conf.height, - layerCount = conf.lines.length, - layerBytes = width * height, - small = conf.small, - large = conf.large, - slices = conf.slices, - subcount = process.slaAntiAlias || 1, - masks = [], - coded; - - if (SLA.legacy || subcount > 1) { - let d = 8 / subcount; - for (let i=0; i { - let count = line.length; - let lineDV = new DataView(line.buffer); - let bits = new Uint8Array(line.length); - let bitsDV = new DataView(bits.buffer); - let subs = [{ data: bits, view: bitsDV }]; - for (let sl=1; sl { - progress(pro * 0.4 + 0.4, "layer_encode"); - })); - } else { - let codedlen = slices.reduce((t,l) => { - return t + l.reduce((t,a) => { - return t + a.length - }, 0); - }, 0); - coded = { - layers: slices.map(slice => { return { sublayers: slice }}), - length: codedlen - }; - } - - let codelen = coded.layers.length; - let buflen = 3000 + coded.length + (codelen * subcount * 28) + small.byteLength + large.byteLength; - let filebuf = new ArrayBuffer(buflen); - let filedat = new self.DataWriter(new DataView(filebuf)); - let printtime = (process.slaBaseLayers * process.slaBaseOn) + - (coded.layers.length - process.slaBaseLayers) * process.slaLayerOn; - - filedat.writeU32(0x1900fd12); // header - filedat.writeU32(2,true); // version - filedat.writeF32(68.04, true); // bed x - filedat.writeF32(120.96, true); // bed y - filedat.writeF32(150.0, true); // bed z - filedat.skip(12); // padding - filedat.writeF32(process.slaSlice, true); // layer height - filedat.writeF32(process.slaLayerOn, true); // default lamp on - filedat.writeF32(process.slaBaseOn, true); // base lamp on - filedat.writeF32(process.slaLayerOff, true); // lamp off - filedat.writeU32(process.slaBaseLayers, true); // base layers - filedat.writeU32(1440, true); // device x - filedat.writeU32(2560, true); // device y - let hirez = filedat.skip(4); // hirez preview address filled pater - let layerpos = filedat.skip(4); // layer data address filled later - filedat.writeU32(codelen, true); - let lorez = filedat.skip(4); // hirez preview address filled later - filedat.writeU32(printtime, true); // print time seconds - filedat.writeU32(1, true); // projection type (1=lcd, 0=cast) - let proppos = filedat.skip(4); // print properties address filled later - let proplen = filedat.skip(4); // print properties length filled later - filedat.writeU32(subcount, true); // AA level (sub layers) - filedat.writeU16(0x00ff, true); // light pwm (TODO); - filedat.writeU16(0x00ff, true); // light pwm bottom (TODO); - - let propstart = filedat.pos; - filedat.view.setUint32(proppos, filedat.pos, true); - // write print properties - filedat.writeF32(process.slaBasePeelDist, true); - filedat.writeF32(process.slaBasePeelLiftRate * 60 , true); - filedat.writeF32(process.slaPeelDist, true); - filedat.writeF32(process.slaPeelLiftRate * 60 , true); - filedat.writeF32(process.slaPeelDropRate * 60, true); - filedat.writeF32(0, true); // volume of used - filedat.writeF32(0, true); // weight of used - filedat.writeF32(0, true); // cost of used - filedat.writeF32(0, true); // bottom off delay time - filedat.writeF32(0, true); // light off delay time - filedat.writeU32(process.slaBaseLayers, true); - filedat.writeF32(0, true); // p1 ? - filedat.writeF32(0, true); // p2 ? - filedat.writeF32(0, true); // p3 ? - filedat.writeF32(0, true); // p4 ? - filedat.view.setUint32(proplen, filedat.pos - propstart, true); - - filedat.view.setUint32(layerpos, filedat.pos, true); - // write layer headers - let layers = coded.layers; - let layerat = []; - - for (let sc=0; sc { - let count = line.length / 4; - let bits = new Uint8Array(line.length / 4); - let bitsDV = new DataView(bits.buffer); - let lineDV = new DataView(line.buffer); - // reduce RGB to R = 0||1 - for (let i = 0; i < count; i++) { - // defeat anti-aliasing for the moment - bitsDV.setUint8(i, lineDV.getUint8(i * 4) > 0 ? 1 : 0); - } - progress(index / conf.lines.length); - return { subs: [{ - exposureTime: process.slaLayerOn, - data: bits - }] }; - }); - coded = encodeLayers(converted, "photons"); - } else { - let codedlen = slices.reduce((t,l) => { - return t + l.reduce((t,a) => { - return t + a.length - }, 0); - }, 0); - coded = { - layers: slices.map(slice => { return { sublayers: slice }}), - length: codedlen - }; - } - - let filebuf = new ArrayBuffer(75366 + coded.length + 28 * layerCount); - let filedat = new DataView(filebuf); - let filePos = 0; - - filedat.setUint32 (0, 2, false); - filedat.setUint32 (4, 3227560, false); - filedat.setUint32 (8, 824633720, false); - filedat.setUint16 (12, 10, false); - filedat.setFloat64(14, process.slaSlice, false); - filedat.setFloat64(22, process.slaLayerOn, false); - filedat.setFloat64(30, process.slaLayerOff, false); - filedat.setFloat64(38, process.slaBaseOn, false); - filedat.setUint32 (46, process.slaBaseLayers, false); - filedat.setFloat64(50, process.slaPeelDist, false); - filedat.setFloat64(58, process.slaPeelLift, false); - filedat.setFloat64(66, process.slaPeelDrop, false); - filedat.setFloat64(74, 69420, false); - filedat.setUint32 (82, 224, false); - filedat.setUint32 (86, 42, false); - filedat.setUint32 (90, 168, false); - filedat.setUint32 (94, 10, false); - filedat.setUint32 (75362, layerCount, false); - - filePos = 75366; - for (let i = 0; i < layerCount; i++) { - let layer = coded.layers[i], - sublayer = layer.sublayers[0], - numbytes = sublayer.length; - - filedat.setUint32 (filePos + 0, 69420, false); - filedat.setFloat64(filePos + 4, 0); - filedat.setUint32 (filePos + 12, height, false); - filedat.setUint32 (filePos + 16, width, false); - filedat.setUint32 (filePos + 20, numbytes * 8 + 32, false); - filedat.setUint32 (filePos + 24, 2684702720, false); - filePos += 28; - for (let j = 0; j < numbytes; j++) { - filedat.setUint8(filePos + j, sublayer[j], false); - } - filePos += numbytes; - progress((i / layerCount) / 2 + 0.5); - } - - return filebuf; - } - - function encodeLayers(input, type, progress) { - let layers = [], length = 0, total = 0, count = 0; - input.forEach(layer => { - layer.subs.forEach(sub => total++); - }); - for (let index = 0; index < input.length; index++) { - let subs = input[index].subs, - sublayers = [], - sublength = 0; - for (let subindex = 0; subindex < subs.length; subindex++) { - let data = subs[subindex].data; - let encoded = rleEncode(data, type); - sublength += encoded.length; - sublayers.push(encoded); - if (progress) progress(count++/total); - if (type == "photons") break; - } - length += sublength; - layers.push({ - sublength, - sublayers - }); - } - return { length, layers }; - } - - function rleEncode(data, type) { - let maxlen = (type === 'photons') ? 128 : 125, - color = data[0], - runlen = 1, - output = []; - for (let index = 1; index < data.length; index++) { - let newColor = data[index]; - if (newColor !== color) { - output.push(rleByte(color, runlen, type)); - color = newColor; - runlen = 1; - } else { - if (runlen === maxlen) { - output.push(rleByte(color, runlen, type)); - runlen = 1; - } else { - runlen++; - } - } - } - if (runlen > 0) { - output.push(rleByte(color, runlen, type)); - } - return output; - } - - function rleByte(color, length, type) { - switch (type) { - case 'photon': - return (length & 0x7f) | ((color << 7) & 0x80); - case 'photons': - length--; - return (length & 1 ? 128 : 0) | - (length & 2 ? 64 : 0) | - (length & 4 ? 32 : 0) | - (length & 8 ? 16 : 0) | - (length & 16 ? 8 : 0) | - (length & 32 ? 4 : 0) | - (length & 64 ? 2 : 0) | color; - } - } - - function rleDecode(data, type) { - let bytes = []; - if (type === 'photon') { - for (let i = 0; i < data.length; i++) { - let val = data[i], - color = val >> 7, - count = val & 0x7f; - for (let j = 0; j < count; j++) { - bytes.push(color); - } - } - } else { - for (let i = 0; i < data.length; i++) { - let val = data[i], - color = val & 1, - count = - ((val & 128 ? 1 : 0) | - (val & 64 ? 2 : 0) | - (val & 32 ? 4 : 0) | - (val & 16 ? 8 : 0) | - (val & 8 ? 16 : 0) | - (val & 4 ? 32 : 0) | - (val & 2 ? 64 : 0)) + 1; - for (let j = 0; j < count; j++) { - bytes.push(color); - } - } - } - return bytes; - } - - // write out a thumbnail image - function writePhotonImage(preview, writer) { - let data = new Uint8Array(preview.data), len = data.byteLength; - writer.writeU32(preview.width, true); - writer.writeU32(preview.height, true); - let hpos = writer.skip(4); - writer.writeU32(len/2, true); - writer.view.setUint32(hpos, writer.pos, true); - let pos = 0; - while (pos < len) { - let r = data[pos++], - g = data[pos++], - b = data[pos++], - a = data[pos++], - v = (((r/4)&0x1f) << 11) | - (((g/4)&0x1f) << 6) | - (((b/4)&0x1f) << 0) ; - writer.writeU16(v, true); - } - } - - // for unbound workers - // if (self.WASM) { - // let {exports} = wasmInstance; - // let heap = new Uint8Array(exports.memory.buffer); - // self.wasm = { - // heap, - // memory: exports.memory, - // // heap: wasmMemory, - // // memory: memoryBytes, - // render: exports.render, - // rle_encode: exports.rle_encode - // }; - // } else - - // new WebAssembly rasterizer - function renderLayerWasm(params) { - let { width, height, index, widgets, scaleX, scaleY, masks } = params; - let width2 = width / 2, height2 = height / 2; - let array = []; - let count = 0; - - function scaleMovePoly(poly) { - let points = poly.points; - poly._bounds = undefined; - for (let i=0, il=points.length; i { - let slice = widget.slices[index]; - if (slice) { - if (slice.synth) count++; - let polys = slice.unioned; - if (!polys) polys = slice.tops.map(t => t.poly); - if (slice.supports) polys.appendAll(slice.supports); - array.appendAll(polys.map(poly => { - return poly.clone(true).move(widget.track.pos); - })); - count += polys.length; - } - }); - - let wasm = SLA.wasm; - let imagelen = width * height; - let writer = new self.DataWriter(new DataView(wasm.memory.buffer), imagelen); - writer.writeU16(width, true); - writer.writeU16(height, true); - writer.writeU16(array.length, true); - - // scale and move all polys to fit in rendered platform coordinates - for (let i=0, il=array.length; i { - let slice = widget.slices[index]; - if (slice) { - // prevent premature exit on empty synth slice - if (slice.synth) count++; - let polys = slice.unioned; - if (!polys) polys = slice.tops.map(t => t.poly); - if (slice.supports) polys.appendAll(slice.supports); - polys.forEach(poly => { - poly.move(widget.track.pos); - ctx.beginPath(); - polyout(poly.setClockwise(), ctx, opt); - if (poly.inner) { - poly.inner.forEach(inner => { - polyout(inner.setCounterClockwise(), ctx, opt); - }); - } - ctx.fill(); - count++; - }); - } else { - // console.log({no_slice_at: index}) - } - }); - let data = ctx.getImageData(0,0,height,width).data; - // reduce RGBA to R - let red = new Uint8ClampedArray(data.length / 4); - for (let i=0; i { - if (i === 0) { - ctx.moveTo(height - (p.y * scaleY + height2), p.x * scaleX + width2); - } else { - ctx.lineTo(height - (p.y * scaleY + height2), p.x * scaleX + width2); - } - }, true); - ctx.closePath(); - } - })(); diff --git a/src/mode/sla/x_halot.js b/src/mode/sla/x_halot.js index f7e3229d..596fa936 100644 --- a/src/mode/sla/x_halot.js +++ b/src/mode/sla/x_halot.js @@ -2,7 +2,7 @@ const default_values = { magic1: 'CXSW3DV2', - magic2: '', + magic2: 'CXSW3DV2', model: 'CL-89', version: 1, layer_count: 0, @@ -79,7 +79,7 @@ lift_dist: read.readU16(), lift_speed: read.readU16(), down_speed: read.readU16(), - light_pwm: read.readU16(), + base_light_pwm: read.readU16(), light_pwm: read.readU16() }; @@ -96,7 +96,7 @@ for (let i=0; i> 5); + let b2 = ((line.y_start << 3) | (line.y_end >> 10)) & 0xff; + let b3 = (line.y_end >> 2) & 0xff; + let b4 = ((line.y_end << 6) | (line.x_end >> 8)) & 0xff; + let b5 = (line.x_end) & 0xff; + output.writeU8(b1); + output.writeU8(b2); + output.writeU8(b3); + output.writeU8(b4); + output.writeU8(b5); + output.writeU8(line.color); + } + layer.length = output.pos - start; + output.writeU16(data_term); + } + + output.write_string(this.magic2); + let ckpos = output.pos; + + // retrace and write layer lengths + for (let layer of layers) { + output.seek(layer.l1); + output.writeU32(layer.length); + output.seek(layer.l2); + output.writeU32(layer.length); + } + + output.seek(ckpos); + // compute xor checksum + let cksum = 0; + let array = output.array; + for (let i=0; i {return { lines: a }}); + + return cxdlp.write(); + }; + + CXDLP.render = function(params) { + let { width, height, index, widgets, scaleX, scaleY } = params; + let width2 = width / 2, height2 = height / 2; + let array = []; + let count = 0; + + function scaleMovePoly(poly) { + let points = poly.points; + poly._bounds = undefined; + for (let i=0, il=points.length; i { + let slice = widget.slices[index]; + if (slice) { + if (slice.synth) count++; + let polys = slice.unioned; + if (!polys) polys = slice.tops.map(t => t.poly); + if (slice.supports) polys.appendAll(slice.supports); + array.appendAll(polys.map(poly => { + return poly.clone(true).move(widget.track.pos); + })); + count += polys.length; + } + }); + + let wasm = kiri.driver.SLA.wasm; + let imagelen = width * height; + let writer = new self.DataWriter(new DataView(wasm.memory.buffer), imagelen); + writer.writeU16(width, true); + writer.writeU16(height, true); + writer.writeU16(array.length, true); + + // scale and move all polys to fit in rendered platform coordinates + for (let i=0, il=array.length; i 1) { + let d = 8 / subcount; + for (let i=0; i { + let count = line.length; + let lineDV = new DataView(line.buffer); + let bits = new Uint8Array(line.length); + let bitsDV = new DataView(bits.buffer); + let subs = [{ data: bits, view: bitsDV }]; + for (let sl=1; sl { + progress(pro * 0.4 + 0.4, "layer_encode"); + })); + } else { + let codedlen = slices.reduce((t,l) => { + return t + l.reduce((t,a) => { + return t + a.length + }, 0); + }, 0); + coded = { + layers: slices.map(slice => { return { sublayers: slice }}), + length: codedlen + }; + } + + let codelen = coded.layers.length; + let buflen = 3000 + coded.length + (codelen * subcount * 28) + small.byteLength + large.byteLength; + let filebuf = new ArrayBuffer(buflen); + let filedat = new self.DataWriter(new DataView(filebuf)); + let printtime = (process.slaBaseLayers * process.slaBaseOn) + + (coded.layers.length - process.slaBaseLayers) * process.slaLayerOn; + + filedat.writeU32(0x1900fd12); // header + filedat.writeU32(2,true); // version + filedat.writeF32(68.04, true); // bed x + filedat.writeF32(120.96, true); // bed y + filedat.writeF32(150.0, true); // bed z + filedat.skip(12); // padding + filedat.writeF32(process.slaSlice, true); // layer height + filedat.writeF32(process.slaLayerOn, true); // default lamp on + filedat.writeF32(process.slaBaseOn, true); // base lamp on + filedat.writeF32(process.slaLayerOff, true); // lamp off + filedat.writeU32(process.slaBaseLayers, true); // base layers + filedat.writeU32(1440, true); // device x + filedat.writeU32(2560, true); // device y + let hirez = filedat.skip(4); // hirez preview address filled pater + let layerpos = filedat.skip(4); // layer data address filled later + filedat.writeU32(codelen, true); + let lorez = filedat.skip(4); // hirez preview address filled later + filedat.writeU32(printtime, true); // print time seconds + filedat.writeU32(1, true); // projection type (1=lcd, 0=cast) + let proppos = filedat.skip(4); // print properties address filled later + let proplen = filedat.skip(4); // print properties length filled later + filedat.writeU32(subcount, true); // AA level (sub layers) + filedat.writeU16(0x00ff, true); // light pwm (TODO); + filedat.writeU16(0x00ff, true); // light pwm bottom (TODO); + + let propstart = filedat.pos; + filedat.view.setUint32(proppos, filedat.pos, true); + // write print properties + filedat.writeF32(process.slaBasePeelDist, true); + filedat.writeF32(process.slaBasePeelLiftRate * 60 , true); + filedat.writeF32(process.slaPeelDist, true); + filedat.writeF32(process.slaPeelLiftRate * 60 , true); + filedat.writeF32(process.slaPeelDropRate * 60, true); + filedat.writeF32(0, true); // volume of used + filedat.writeF32(0, true); // weight of used + filedat.writeF32(0, true); // cost of used + filedat.writeF32(0, true); // bottom off delay time + filedat.writeF32(0, true); // light off delay time + filedat.writeU32(process.slaBaseLayers, true); + filedat.writeF32(0, true); // p1 ? + filedat.writeF32(0, true); // p2 ? + filedat.writeF32(0, true); // p3 ? + filedat.writeF32(0, true); // p4 ? + filedat.view.setUint32(proplen, filedat.pos - propstart, true); + + filedat.view.setUint32(layerpos, filedat.pos, true); + // write layer headers + let layers = coded.layers; + let layerat = []; + + for (let sc=0; sc { + let count = line.length / 4; + let bits = new Uint8Array(line.length / 4); + let bitsDV = new DataView(bits.buffer); + let lineDV = new DataView(line.buffer); + // reduce RGB to R = 0||1 + for (let i = 0; i < count; i++) { + // defeat anti-aliasing for the moment + bitsDV.setUint8(i, lineDV.getUint8(i * 4) > 0 ? 1 : 0); + } + progress(index / conf.lines.length); + return { subs: [{ + exposureTime: process.slaLayerOn, + data: bits + }] }; + }); + coded = encodeLayers(converted, "photons"); + } else { + let codedlen = slices.reduce((t,l) => { + return t + l.reduce((t,a) => { + return t + a.length + }, 0); + }, 0); + coded = { + layers: slices.map(slice => { return { sublayers: slice }}), + length: codedlen + }; + } + + let filebuf = new ArrayBuffer(75366 + coded.length + 28 * layerCount); + let filedat = new DataView(filebuf); + let filePos = 0; + + filedat.setUint32 (0, 2, false); + filedat.setUint32 (4, 3227560, false); + filedat.setUint32 (8, 824633720, false); + filedat.setUint16 (12, 10, false); + filedat.setFloat64(14, process.slaSlice, false); + filedat.setFloat64(22, process.slaLayerOn, false); + filedat.setFloat64(30, process.slaLayerOff, false); + filedat.setFloat64(38, process.slaBaseOn, false); + filedat.setUint32 (46, process.slaBaseLayers, false); + filedat.setFloat64(50, process.slaPeelDist, false); + filedat.setFloat64(58, process.slaPeelLift, false); + filedat.setFloat64(66, process.slaPeelDrop, false); + filedat.setFloat64(74, 69420, false); + filedat.setUint32 (82, 224, false); + filedat.setUint32 (86, 42, false); + filedat.setUint32 (90, 168, false); + filedat.setUint32 (94, 10, false); + filedat.setUint32 (75362, layerCount, false); + + filePos = 75366; + for (let i = 0; i < layerCount; i++) { + let layer = coded.layers[i], + sublayer = layer.sublayers[0], + numbytes = sublayer.length; + + filedat.setUint32 (filePos + 0, 69420, false); + filedat.setFloat64(filePos + 4, 0); + filedat.setUint32 (filePos + 12, height, false); + filedat.setUint32 (filePos + 16, width, false); + filedat.setUint32 (filePos + 20, numbytes * 8 + 32, false); + filedat.setUint32 (filePos + 24, 2684702720, false); + filePos += 28; + for (let j = 0; j < numbytes; j++) { + filedat.setUint8(filePos + j, sublayer[j], false); + } + filePos += numbytes; + progress((i / layerCount) / 2 + 0.5); + } + + return filebuf; + } + + function encodeLayers(input, type, progress) { + let layers = [], length = 0, total = 0, count = 0; + input.forEach(layer => { + layer.subs.forEach(sub => total++); + }); + for (let index = 0; index < input.length; index++) { + let subs = input[index].subs, + sublayers = [], + sublength = 0; + for (let subindex = 0; subindex < subs.length; subindex++) { + let data = subs[subindex].data; + let encoded = rleEncode(data, type); + sublength += encoded.length; + sublayers.push(encoded); + if (progress) progress(count++/total); + if (type == "photons") break; + } + length += sublength; + layers.push({ + sublength, + sublayers + }); + } + return { length, layers }; + } + + function rleEncode(data, type) { + let maxlen = (type === 'photons') ? 128 : 125, + color = data[0], + runlen = 1, + output = []; + for (let index = 1; index < data.length; index++) { + let newColor = data[index]; + if (newColor !== color) { + output.push(rleByte(color, runlen, type)); + color = newColor; + runlen = 1; + } else { + if (runlen === maxlen) { + output.push(rleByte(color, runlen, type)); + runlen = 1; + } else { + runlen++; + } + } + } + if (runlen > 0) { + output.push(rleByte(color, runlen, type)); + } + return output; + } + + function rleByte(color, length, type) { + switch (type) { + case 'photon': + return (length & 0x7f) | ((color << 7) & 0x80); + case 'photons': + length--; + return (length & 1 ? 128 : 0) | + (length & 2 ? 64 : 0) | + (length & 4 ? 32 : 0) | + (length & 8 ? 16 : 0) | + (length & 16 ? 8 : 0) | + (length & 32 ? 4 : 0) | + (length & 64 ? 2 : 0) | color; + } + } + + function rleDecode(data, type) { + let bytes = []; + if (type === 'photon') { + for (let i = 0; i < data.length; i++) { + let val = data[i], + color = val >> 7, + count = val & 0x7f; + for (let j = 0; j < count; j++) { + bytes.push(color); + } + } + } else { + for (let i = 0; i < data.length; i++) { + let val = data[i], + color = val & 1, + count = + ((val & 128 ? 1 : 0) | + (val & 64 ? 2 : 0) | + (val & 32 ? 4 : 0) | + (val & 16 ? 8 : 0) | + (val & 8 ? 16 : 0) | + (val & 4 ? 32 : 0) | + (val & 2 ? 64 : 0)) + 1; + for (let j = 0; j < count; j++) { + bytes.push(color); + } + } + } + return bytes; + } + + // write out a thumbnail image + function writePhotonImage(preview, writer) { + let data = new Uint8Array(preview.data), len = data.byteLength; + writer.writeU32(preview.width, true); + writer.writeU32(preview.height, true); + let hpos = writer.skip(4); + writer.writeU32(len/2, true); + writer.view.setUint32(hpos, writer.pos, true); + let pos = 0; + while (pos < len) { + let r = data[pos++], + g = data[pos++], + b = data[pos++], + a = data[pos++], + v = (((r/4)&0x1f) << 11) | + (((g/4)&0x1f) << 6) | + (((b/4)&0x1f) << 0) ; + writer.writeU16(v, true); + } + } + + // for unbound workers + // if (self.WASM) { + // let {exports} = wasmInstance; + // let heap = new Uint8Array(exports.memory.buffer); + // self.wasm = { + // heap, + // memory: exports.memory, + // // heap: wasmMemory, + // // memory: memoryBytes, + // render: exports.render, + // rle_encode: exports.rle_encode + // }; + // } else + + // new WebAssembly rasterizer + function renderLayerWasm(params) { + let { width, height, index, widgets, scaleX, scaleY, masks } = params; + let width2 = width / 2, height2 = height / 2; + let array = []; + let count = 0; + + function scaleMovePoly(poly) { + let points = poly.points; + poly._bounds = undefined; + for (let i=0, il=points.length; i { + let slice = widget.slices[index]; + if (slice) { + if (slice.synth) count++; + let polys = slice.unioned; + if (!polys) polys = slice.tops.map(t => t.poly); + if (slice.supports) polys.appendAll(slice.supports); + array.appendAll(polys.map(poly => { + return poly.clone(true).move(widget.track.pos); + })); + count += polys.length; + } + }); + + let wasm = SLA.wasm; + let imagelen = width * height; + let writer = new self.DataWriter(new DataView(wasm.memory.buffer), imagelen); + writer.writeU16(width, true); + writer.writeU16(height, true); + writer.writeU16(array.length, true); + + // scale and move all polys to fit in rendered platform coordinates + for (let i=0, il=array.length; i { + let slice = widget.slices[index]; + if (slice) { + // prevent premature exit on empty synth slice + if (slice.synth) count++; + let polys = slice.unioned; + if (!polys) polys = slice.tops.map(t => t.poly); + if (slice.supports) polys.appendAll(slice.supports); + polys.forEach(poly => { + poly.move(widget.track.pos); + ctx.beginPath(); + polyout(poly.setClockwise(), ctx, opt); + if (poly.inner) { + poly.inner.forEach(inner => { + polyout(inner.setCounterClockwise(), ctx, opt); + }); + } + ctx.fill(); + count++; + }); + } else { + // console.log({no_slice_at: index}) + } + }); + let data = ctx.getImageData(0,0,height,width).data; + // reduce RGBA to R + let red = new Uint8ClampedArray(data.length / 4); + for (let i=0; i { + if (i === 0) { + ctx.moveTo(height - (p.y * scaleY + height2), p.x * scaleX + width2); + } else { + ctx.lineTo(height - (p.y * scaleY + height2), p.x * scaleX + width2); + } + }, true); + ctx.closePath(); + } + + this.photon = { + generatePhoton, + generatePhotons, + renderLayer, + renderLayerWasm + }; + +})();