From c8297f12ea3f5255efaa63133e7de12a210a884c Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Thu, 2 May 2019 15:09:54 -0400 Subject: [PATCH] modularize infill functionality --- js/kiri-driver-fdm.js | 13 ++--- js/kiri-fill.js | 121 ++++++++++++++++++++++++++++++++++++++++++ js/kiri-slice.js | 103 ++--------------------------------- js/kiri-work.js | 4 +- js/kiri.js | 53 +++++++++++------- js/license.js | 2 +- js/moto-ui.js | 3 +- js/web-server.js | 2 + web/kiri/style.css | 2 +- 9 files changed, 174 insertions(+), 129 deletions(-) create mode 100644 js/kiri-fill.js diff --git a/js/kiri-driver-fdm.js b/js/kiri-driver-fdm.js index ed1d0f7f..ac83e367 100644 --- a/js/kiri-driver-fdm.js +++ b/js/kiri-driver-fdm.js @@ -45,11 +45,12 @@ var gs_kiri_fdm = exports; update_start = time(), minSolid = spro.sliceSolidMinArea, solidLayers = spro.sliceSolidLayers, - doSolidLayers = solidLayers && !spro.sliceVase, + vaseMode = spro.sliceFillType === 'vase', + doSolidLayers = solidLayers && !vaseMode, firstOffset = sdev.nozzleSize / 2, shellOffset = sdev.nozzleSize * spro.sliceShellSpacing, fillOffset = shellOffset * settings.synth.fillOffsetMult, - fillSpacing = sdev.nozzleSize * spro.sliceFillSpacing, + fillSpacing = sdev.nozzleSize, sliceFillAngle = spro.sliceFillAngle, view = widget.mesh && widget.mesh.newGroup ? widget.mesh.newGroup() : null; @@ -111,9 +112,9 @@ var gs_kiri_fdm = exports; slice.index < spro.sliceBottomLayers || slice.index > slices.length - spro.sliceTopLayers-1 || spro.sliceFillSparse > 0.95 - ) && !spro.sliceVase; + ) && !vaseMode; slice.doShells(spro.sliceShells, firstOffset, shellOffset, fillOffset, { - vase: spro.sliceVase, + vase: vaseMode, thin: false && spro.detectThinWalls }); if (solid) slice.doSolidLayerFill(fillSpacing, sliceFillAngle); @@ -147,14 +148,14 @@ var gs_kiri_fdm = exports; } // sparse layers only present when non-vase mose and sparse % > 0 - if (!spro.sliceVase && spro.sliceFillSparse > 0.0) { + if (!vaseMode && spro.sliceFillSparse > 0.0) { forSlices(0.8, 1.0, function(slice) { slice.doSparseLayerFill({ spacing: fillSpacing, density: spro.sliceFillSparse, bounds: widget.getBoundingBox(), height: spro.sliceHeight, - type: spro.sliceFillGyroid ? 'gyroid' : 'hex' + type: spro.sliceFillType }); }, "infill"); } diff --git a/js/kiri-fill.js b/js/kiri-fill.js new file mode 100644 index 00000000..aeec2d13 --- /dev/null +++ b/js/kiri-fill.js @@ -0,0 +1,121 @@ +/** Copyright 2014-2019 Stewart Allen -- All Rights Reserved */ + +"use strict"; + +var gs_kiri_fill = exports; + +(function() { + + if (!self.kiri) self.kiri = {}; + if (self.kiri.fill) return; + + var KIRI = self.kiri, + BASE = self.base, + UTIL = BASE.util, + ROUND = UTIL.round, + DEG2RAD = Math.PI / 180, + FILL = self.kiri.fill = { + hex: sparseFillHexFull, + gyroid: sparseFillGyroid + }; + + function sparseFillHexFull(target) { + sparseFillHex(target, true); + } + + /** + * emitter creates a hex infill pattern and sends to target + * + * @param {Object} target + * @param {boolean} full continuous walls + */ + function sparseFillHex(target, full) { + // compute segment lengths (vert/horiz and 45) + var spacing = target.offset() / 2, + vhlen = (1 - target.density()) * 4 + spacing, + anxlen = ROUND(Math.cos(30 * DEG2RAD) * vhlen, 7), + anylen = ROUND(Math.sin(30 * DEG2RAD) * vhlen, 7), + bounds = target.bounds(), + even = true, + evenZ = target.zIndex() % 2 === 0, + maxy = bounds.max.y + (vhlen + anylen * 2), + x, y; + + if (full || evenZ) { + x = bounds.min.x; + for (;;) { + if (even && x > bounds.max.x) break; + if (!even && x > bounds.max.x + anxlen + spacing) break; + y = bounds.min.y; + target.newline(); + while (y <= maxy) { + target.emit(x,y); + y += vhlen; + target.emit(x,y); + if (even) x += anxlen; else x -= anxlen; + y += anylen; + target.emit(x,y); + y += vhlen; + target.emit(x,y); + if (even) x -= anxlen; else x += anxlen; + y += anylen; + } + x += spacing; + if (even) x += (anxlen * 2); + even = !even; + target.newline(); + } + } else { + y = bounds.min.y + vhlen; + for (;;) { + if (even && y > bounds.max.y) break; + if (!even && y > bounds.max.y + anylen) break; + x = bounds.min.x; + target.newline(); + while (x < bounds.max.x) { + target.emit(x,y); + if (even) y += anylen; else y -= anylen; + x += anxlen; + target.emit(x,y); + x += spacing; + target.emit(x,y); + if (even) y -= anylen; else y += anylen; + x += anxlen; + target.emit(x,y); + x += spacing; + } + y += vhlen; + if (even) y += (anylen * 2); + even = !even; + target.newline(); + } + } + } + + function sparseFillGyroid(target) { + let bounds = target.bounds(); + let height = target.zHeight(); + let span_x = bounds.max.x - bounds.min.x; + let span_y = bounds.max.y - bounds.min.y; + let density = target.density(); + let tile = 1 + (1 - density) * 15; + let tile_x = span_x / tile; + let tile_y = span_y / tile; + let tile_z = 1 / tile; + let gyroid = BASE.gyroid.slice(target.zValue() * tile_z, (1 - density) * 500); + + gyroid.polys.forEach(poly => { + for (let tx=0; tx<=tile_x; tx++) { + for (let ty=0; ty<=tile_y; ty++) { + target.newline(); + let bx = tx * tile + bounds.min.x; + let by = ty * tile + bounds.min.y; + poly.forEach(point => { + target.emit(bx + point.x * tile, by + point.y * tile); + }); + } + } + }); + } + +})(); diff --git a/js/kiri-slice.js b/js/kiri-slice.js index 01752b00..ec1e4905 100644 --- a/js/kiri-slice.js +++ b/js/kiri-slice.js @@ -10,16 +10,15 @@ var gs_kiri_slice = exports; if (self.kiri.Slice) return; var KIRI = self.kiri, + FILL = KIRI.fill, BASE = self.base, UTIL = BASE.util, POLY = BASE.polygons, PRO = Slice.prototype, fillArea = POLY.fillArea, newPoint = BASE.newPoint, - ROUND = UTIL.round, MIN = Math.min, MAX = Math.max, - DEG2RAD = Math.PI / 180, NOKEY = BASE.key.NONE, outline_colors = [0xffff00, 0x00ffff, 0xff00ff, 0xff0000, 0x00ff00, 0x0000ff, 0xffffff, 0x000000], trace_color = 0x000000, @@ -636,9 +635,8 @@ var gs_kiri_slice = exports; scope.isSparseFill = true; // use specified fill type - switch (type) { - case 'hex': sparseFillHex(target, true); break; - case 'gyroid': sparseFillGyroid(target); break; + if (type && FILL[type]) { + FILL[type](target); } // force emit of last line @@ -1219,101 +1217,6 @@ var gs_kiri_slice = exports; } } - /** - * emitter creates a hex infill pattern and sends to target - * - * @param {Object} target - * @param {boolean} full continuous walls - */ - function sparseFillHex(target, full) { - // compute segment lengths (vert/horiz and 45) - var spacing = target.offset() / 2, - vhlen = (1 - target.density()) * 4 + spacing, - anxlen = ROUND(Math.cos(30 * DEG2RAD) * vhlen, 7), - anylen = ROUND(Math.sin(30 * DEG2RAD) * vhlen, 7), - bounds = target.bounds(), - even = true, - evenZ = target.zIndex() % 2 === 0, - maxy = bounds.max.y + (vhlen + anylen * 2), - x, y; - - if (full || evenZ) { - x = bounds.min.x; - for (;;) { - if (even && x > bounds.max.x) break; - if (!even && x > bounds.max.x + anxlen + spacing) break; - y = bounds.min.y; - target.newline(); - while (y <= maxy) { - target.emit(x,y); - y += vhlen; - target.emit(x,y); - if (even) x += anxlen; else x -= anxlen; - y += anylen; - target.emit(x,y); - y += vhlen; - target.emit(x,y); - if (even) x -= anxlen; else x += anxlen; - y += anylen; - } - x += spacing; - if (even) x += (anxlen * 2); - even = !even; - target.newline(); - } - } else { - y = bounds.min.y + vhlen; - for (;;) { - if (even && y > bounds.max.y) break; - if (!even && y > bounds.max.y + anylen) break; - x = bounds.min.x; - target.newline(); - while (x < bounds.max.x) { - target.emit(x,y); - if (even) y += anylen; else y -= anylen; - x += anxlen; - target.emit(x,y); - x += spacing; - target.emit(x,y); - if (even) y -= anylen; else y += anylen; - x += anxlen; - target.emit(x,y); - x += spacing; - } - y += vhlen; - if (even) y += (anylen * 2); - even = !even; - target.newline(); - } - } - } - - function sparseFillGyroid(target) { - let bounds = target.bounds(); - let height = target.zHeight(); - let span_x = bounds.max.x - bounds.min.x; - let span_y = bounds.max.y - bounds.min.y; - let density = target.density(); - let tile = 1 + (1 - density) * 15; - let tile_x = span_x / tile; - let tile_y = span_y / tile; - let tile_z = 1 / tile; - let gyroid = BASE.gyroid.slice(target.zValue() * tile_z, (1 - density) * 500); - - gyroid.polys.forEach(poly => { - for (let tx=0; tx<=tile_x; tx++) { - for (let ty=0; ty<=tile_y; ty++) { - target.newline(); - let bx = tx * tile + bounds.min.x; - let by = ty * tile + bounds.min.y; - poly.forEach(point => { - target.emit(bx + point.x * tile, by + point.y * tile); - }); - } - } - }); - } - function newTop(poly) { return new Top(poly); } diff --git a/js/kiri-work.js b/js/kiri-work.js index 124de09f..869534ef 100644 --- a/js/kiri-work.js +++ b/js/kiri-work.js @@ -154,8 +154,8 @@ if (self.window) { [ "license","ext-n3d","ext-clip","add-array", "add-three","geo","geo-point","geo-debug","geo-bounds", - "geo-line","geo-slope","geo-polygon","geo-polygons", "geo-gyroid", - "kiri-slice","kiri-slicer","kiri-driver-fdm","kiri-driver-cam", + "geo-line","geo-slope","geo-polygon","geo-polygons","geo-gyroid", + "kiri-fill","kiri-slice","kiri-slicer","kiri-driver-fdm","kiri-driver-cam", "kiri-driver-laser","kiri-widget","kiri-pack","kiri-print","kiri-codec" ].forEach(function(scr) { importScripts(["/js/",scr,".js","/v"+ver].join('')); diff --git a/js/kiri.js b/js/kiri.js index 2842228d..58e51b65 100644 --- a/js/kiri.js +++ b/js/kiri.js @@ -58,9 +58,8 @@ self.kiri.license = exports.LICENSE; sliceShellSpacing: 1, sliceFillAngle: 1, sliceFillOverlap: 1, - sliceFillSpacing: 1, sliceFillSparse: 1, - sliceFillGyroid: 1, + sliceFillType: 1, sliceSupportEnable: 1, sliceSupportDensity: 1, sliceSupportOffset: 1, @@ -73,7 +72,6 @@ self.kiri.license = exports.LICENSE; sliceSolidLayers: 1, sliceBottomLayers: 1, sliceTopLayers: 1, - sliceVase: 1, firstSliceHeight: 1, firstLayerRate: 1, firstLayerFillRate: 1, @@ -202,6 +200,17 @@ self.kiri.license = exports.LICENSE; }, // --------------- (default) settings = { + infill:[ + { + name: "vase" + }, + { + name: "hex" + }, + { + name: "gyroid" + } + ], // CAM only tools:[ { @@ -272,9 +281,8 @@ self.kiri.license = exports.LICENSE; sliceShellSpacing: 1.0, sliceFillAngle: 45, sliceFillOverlap: 0.3, - sliceFillSpacing: 1.0, sliceFillSparse: 0.5, - sliceFillGyroid: false, + sliceFillType: "hex", sliceSupportEnable: false, sliceSupportDensity: 0.25, @@ -289,7 +297,6 @@ self.kiri.license = exports.LICENSE; sliceSolidLayers: 3, sliceBottomLayers: 3, sliceTopLayers: 3, - sliceVase: false, firstSliceHeight: 0.25, firstLayerRate: 30, @@ -1707,6 +1714,7 @@ self.kiri.license = exports.LICENSE; var key, val; fillMissingSettings(settingsDefault, settings); + settings.infill = settingsDefault.infill; for (key in scope) { if (!scope.hasOwnProperty(key)) continue; @@ -1721,11 +1729,16 @@ self.kiri.license = exports.LICENSE; } else if (typ === 'select-one') { uie.innerHTML = ''; var chosen = null; - settings.tools.forEach(function(tool,index) { - if (val === tool.id) chosen = index+1; + var source = uie.parentNode.getAttribute('source'); + var list = settings[source]; + list.forEach(function(tool, index) { + let id = tool.id || tool.name; + if (val === id) { + chosen = index + 1; + } var opt = DOC.createElement('option'); opt.appendChild(DOC.createTextNode(tool.name)); - opt.setAttribute('value', tool.id); + opt.setAttribute('value', id); uie.appendChild(opt); }); if (chosen) uie.selectedIndex = chosen; @@ -1750,7 +1763,8 @@ self.kiri.license = exports.LICENSE; if (!scope.hasOwnProperty(key)) continue; if (UI.hasOwnProperty(key)) { var nval = null, - uie = UI[key]; + uie = UI[key], + src = uie.parentNode.getAttribute('source'); // skip empty UI values if (!uie || uie === '') continue; if (uie.type === 'text') { @@ -1759,7 +1773,10 @@ self.kiri.license = exports.LICENSE; nval = UI[key].checked; } else if (uie.type === 'select-one') { if (uie.selectedIndex > 0) { - nval = parseInt(uie.options[uie.selectedIndex].value); + nval = uie.options[uie.selectedIndex].value; + if (src === 'tools') { + nval = parseInt(nval); + } } } if (scope[key] != nval) { @@ -2479,22 +2496,22 @@ self.kiri.license = exports.LICENSE; bedWidth: UC.newInput("width", {title:"millimeters", convert:UC.toInt, modes:LASER}), bedDepth: UC.newInput("depth", {title:"millimeters", convert:UC.toInt, modes:LASER}), - process: UC.newGroup("process", control, {modes:FDM_LASER}), + process: UC.newGroup("slicing", control, {modes:FDM_LASER}), // 3d print sliceHeight: UC.newInput("layer height", {title:"millimeters", convert:UC.toFloat, modes:FDM}), sliceShells: UC.newInput("shell count", {convert:UC.toInt, modes:FDM}), sliceShellSpacing: UC.newInput("shell spacing", {title:"as a percentage of nozzle width\n< 1.0 causes shell overlap\nrecommended 0.85 - 1.0", convert:UC.toFloat, bound:UC.bound(0.5,1.0), modes:FDM}), - sliceFillOverlap: UC.newInput("fill overlap", {title:"overlap with shell\nas % of nozzle width\nhigher bonds better\n0.0 - 1.0", convert:UC.toFloat, bound:UC.bound(0.0,2.0), modes:FDM}), - sliceFillSpacing: UC.newInput("fill spacing", {title:"for solid fill areas\nas a percentage of nozzle width\n< 1.0 causes fill overlap\nrecommended 0.85 - 1.0", convert:UC.toFloat, bound:UC.bound(0.0,2.0), modes:FDM}), - sliceFillAngle: UC.newInput("fill angle", {title:"base angle in degrees", convert:UC.toFloat, modes:FDM}), - sliceFillSparse: UC.newInput("fill ratio", {title:"for infill areas\n0.0 - 1.0", convert:UC.toFloat, bound:UC.bound(0.0,1.0), modes:FDM}), sliceSolidMinArea: UC.newInput("solid area", {title:"minimum area (mm^2)\nrequired to keep solid\nmust be > 0.1", convert:UC.toFloat, modes:FDM}), sliceSolidLayers: UC.newInput("solid layers", {title:"flat area fill projections\nbased on layer deltas", convert:UC.toInt, modes:FDM}), sliceBottomLayers: UC.newInput("base layers", {title:"bottom solid layer count", convert:UC.toInt, modes:FDM}), sliceTopLayers: UC.newInput("top layers", {title:"top solid layer count", convert:UC.toInt, modes:FDM}), - sliceVase: UC.newBoolean("vase mode", onBooleanClick, {modes:FDM}), - sliceFillGyroid: UC.newBoolean("gyroid infill", onBooleanClick, {title: "best with fine layers\nand large interior voids", modes:FDM}), + + process: UC.newGroup("fill", control, {modes:FDM}), + sliceFillOverlap: UC.newInput("shell overlap", {title:"overlap with shell\nas % of nozzle width\nhigher bonds better\n0.0 - 1.0", convert:UC.toFloat, bound:UC.bound(0.0,2.0), modes:FDM}), + sliceFillAngle: UC.newInput("solid angle", {title:"base angle in degrees", convert:UC.toFloat, modes:FDM}), + sliceFillSparse: UC.newInput("percentage", {title:"for infill areas\n0.0 - 1.0", convert:UC.toFloat, bound:UC.bound(0.0,1.0), modes:FDM}), + sliceFillType: UC.newSelectField("type", {modes:FDM}, "infill"), // laser laserOffset: UC.newInput("cut offset", {title:"millimeters\nadjust for beam width", convert:UC.toFloat, modes:LASER}), diff --git a/js/license.js b/js/license.js index 031f4f7b..c753573a 100644 --- a/js/license.js +++ b/js/license.js @@ -1,7 +1,7 @@ var exports = { COPYRIGHT:"Copyright (C) 2014-2019 Stewart Allen - All Rights Reserved", LICENSE:"See the license.md file included with the source distribution", - VERSION:"1.3.3" + VERSION:"1.4.0" }; if (!module) var module = {}; module.exports = exports; diff --git a/js/moto-ui.js b/js/moto-ui.js index 60c09a7e..05337420 100644 --- a/js/moto-ui.js +++ b/js/moto-ui.js @@ -291,7 +291,7 @@ var gs_moto_ui = exports; return ip; } - function newSelectField(label, options) { + function newSelectField(label, options, source) { var row = newDiv(options), ip = DOC.createElement('select'), hide = options && options.hide, @@ -299,6 +299,7 @@ var gs_moto_ui = exports; lastDiv.appendChild(row); row.appendChild(newLabel(label)); row.appendChild(ip); + row.setAttribute("source", source || "tools"); row.setAttribute("class", ["flow-row",lastGroup].join(" ")); row.style.display = hide ? 'none' : ''; if (options) { diff --git a/js/web-server.js b/js/web-server.js index 44427faf..4cf408ec 100644 --- a/js/web-server.js +++ b/js/web-server.js @@ -759,6 +759,7 @@ var ver = require('../js/license.js'), "moto-db", "moto-ui", "kiri-lang", + "kiri-fill", "kiri-db", "kiri-slice", "kiri-slicer", @@ -805,6 +806,7 @@ var ver = require('../js/license.js'), "geo-polygons", "geo-gyroid", "kiri-lang", + "kiri-fill", "kiri-slice", "kiri-slicer", "kiri-driver-fdm", diff --git a/web/kiri/style.css b/web/kiri/style.css index a92d09ee..cc76196b 100644 --- a/web/kiri/style.css +++ b/web/kiri/style.css @@ -345,7 +345,7 @@ button.selected { flex-grow: 1; width: 100%; height: 100%; - color: white; + color: black; border: 0; margin-bottom: 2px; padding-bottom: 2px;