diff --git a/js/geo-gyroid.js b/js/geo-gyroid.js new file mode 100644 index 00000000..b6c8bf4a --- /dev/null +++ b/js/geo-gyroid.js @@ -0,0 +1,156 @@ +/** Copyright 2014-2018 Stewart Allen -- All Rights Reserved */ + +"use strict"; + +var gs_base_gyroid = exports; + +(function() { + if (!self.base) self.base = {}; + if (self.base.gyroid) return; + + var base = self.base; + + let PI2 = Math.PI * 2; + + /** + * @param off {number} z offset value from 0-1 + * @param res {number} resolution (pixels/slices per side) + */ + function slice(off, res) { + let rez = parseInt(res || 200); + let inc = PI2 / rez; + let z = PI2 * off; + + let edge = []; + let vals = []; + let points = 0; + let points_lr = 0; + let points_td = 0; + for (let x=0; x { + let erow = edge[y]; + let lval = vrow[vrow.length - 1]; + vrow.forEach((val, x) => { + if (lval <= 0 && val >= 0) { + erow[x] = 1; + points++; + points_lr++; + } + lval = val; + }) + }); + + // top-down threshold search (green) + for (let x=0; x= 0) { + if (edge[y][x]) { + edge[y][x] = 3; + } else { + edge[y][x] = 2; + points++ + } + points_td++; + } + lval = val; + } + } + + // deterime prevailing direction for chaining + let dir = points_td > points_lr ? 'lr' : 'td'; + + // create sparse representation + let sparse = []; + let center = rez / 2; + edge.forEach((row,y) => { + row.forEach((val,x) => { + if (val) { + let dx = Math.abs(x - center); + let dy = Math.abs(y - center); + sparse.push({x: x/rez, y: y/rez, val, dist: Math.max(dx,dy)}); + } + }); + }); + sparse.sort((a,b) => { + return b.dist - a.dist; + }); + + // join sparse points array by closest distance + let polys = []; + let chain; + let added; + let cleared = 0; + let maxdist = 0.05; + + do { + chain = null; + for (let i=0; i maxdist) { + break; + } + sparse[cl_idx] = null; + cleared++; + chain.push(cl_elm); + added = true; + } + } while (added); + } while (cleared < sparse.length); + + return {edge, points, dir, polys}; + } + + function dist(a, b, dir) { + let dx = a.x - b.x; + let dy = a.y - b.y; + // bias distance by prevailing direction of discovery to join stragglers + if (dir === 'lr') dx = dx / 2; + if (dir === 'td') dy = dy / 2; + return Math.sqrt(dx * dx + dy * dy); + } + + base.gyroid = { slice }; + +})(); diff --git a/js/kiri-driver-fdm.js b/js/kiri-driver-fdm.js index 338a0873..8f9b8512 100644 --- a/js/kiri-driver-fdm.js +++ b/js/kiri-driver-fdm.js @@ -65,7 +65,7 @@ var gs_kiri_fdm = exports; SLICER.sliceWidget(widget, { height: spro.sliceHeight, - minHeight: spro.sliceMinHeight, + minHeight: spro.sliceHeight > spro.sliceMinHeight ? spro.sliceMinHeight : 0, firstHeight: spro.firstSliceHeight, view: view }, onSliceDone, onSliceUpdate); @@ -149,7 +149,13 @@ var gs_kiri_fdm = exports; // sparse layers only present when non-vase mose and sparse % > 0 if (!spro.sliceVase && spro.sliceFillSparse > 0.0) { forSlices(0.8, 1.0, function(slice) { - slice.doSparseLayerFill(fillSpacing, spro.sliceFillSparse, widget.getBoundingBox()); + slice.doSparseLayerFill({ + spacing: fillSpacing, + density: spro.sliceFillSparse, + bounds: widget.getBoundingBox(), + height: spro.sliceHeight, + type: spro.sliceFillGyroid ? 'gyroid' : 'hex' + }); }, "infill"); } diff --git a/js/kiri-slice.js b/js/kiri-slice.js index 99f41c42..d7db77a7 100644 --- a/js/kiri-slice.js +++ b/js/kiri-slice.js @@ -593,14 +593,16 @@ var gs_kiri_slice = exports; /** * Take output from pluggable sparse infill algorithm and clip to * the bounds of the top polygons and their inner solid areas. - * - * @param {number} spacing space between fill lines - * @param {number} percent infill 0.0 - 1.0 - * @param {Object} bounds -- TODO calc w/out mesh so it can run in a worker */ - PRO.doSparseLayerFill = function(spacing, percent, bounds) { + PRO.doSparseLayerFill = function(options) { + let spacing = options.spacing, // spacing space between fill lines + density = options.density, // density of infill 0.0 - 1.0 + bounds = options.bounds, // bounding box of widget + height = options.height, // z layer height + type = options.type || 'hex'; + this.isSparseFill = false; - if (this.tops.length === 0 || percent === 0.0 || this.isSolidFill) return; + if (this.tops.length === 0 || density === 0.0 || this.isSolidFill) return; var scope = this, tops = scope.tops, @@ -619,8 +621,9 @@ var gs_kiri_slice = exports; bounds: function() { return bounds }, zIndex: function() { return scope.index }, zValue: function() { return scope.z }, - density: function() { return percent }, + zHeight: function() { return height }, offset: function() { return spacing }, + density: function() { return density }, emit: function(x,y) { line.push(newPoint(x,y,scope.z)) }, newline: function() { if (line.length > 0) { @@ -631,8 +634,13 @@ var gs_kiri_slice = exports; }; scope.isSparseFill = true; - // this is pluggable - sparseFillHex(target, true); + + // use specified fill type + switch (type) { + case 'hex': sparseFillHex(target, true); break; + case 'gyroid': sparseFillGyroid(target); break; + } + // force emit of last line target.newline(); @@ -1280,6 +1288,32 @@ var gs_kiri_slice = exports; } } + 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 f54f9664..15656066 100644 --- a/js/kiri-work.js +++ b/js/kiri-work.js @@ -154,7 +154,7 @@ 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-line","geo-slope","geo-polygon","geo-polygons", "geo-gyroid", "kiri-slice","kiri-slicer","kiri-driver-fdm","kiri-driver-cam", "kiri-driver-laser","kiri-widget","kiri-pack","kiri-print","kiri-codec" ].forEach(function(scr) { diff --git a/js/kiri.js b/js/kiri.js index 0c9a8653..e49977ed 100644 --- a/js/kiri.js +++ b/js/kiri.js @@ -60,6 +60,7 @@ self.kiri.license = exports.LICENSE; sliceFillOverlap: 1, sliceFillSpacing: 1, sliceFillSparse: 1, + sliceFillGyroid: 1, sliceSupportEnable: 1, sliceSupportDensity: 1, sliceSupportOffset: 1, @@ -270,6 +271,7 @@ self.kiri.license = exports.LICENSE; sliceFillOverlap: 0.3, sliceFillSpacing: 1.0, sliceFillSparse: 0.5, + sliceFillGyroid: false, sliceSupportEnable: false, sliceSupportDensity: 0.25, @@ -2462,6 +2464,7 @@ self.kiri.license = exports.LICENSE; 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}), + sliceFillGyroid: UC.newBoolean("fill gyroid", onBooleanClick, {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}), diff --git a/js/license.js b/js/license.js index 770960cb..87c3b76b 100644 --- a/js/license.js +++ b/js/license.js @@ -1,7 +1,7 @@ var exports = { COPYRIGHT:"Copyright (C) 2014-2018 Stewart Allen - All Rights Reserved", LICENSE:"See the license.md file included with the source distribution", - VERSION:"1.2.5a" + VERSION:"1.2.6" }; if (!module) var module = {}; module.exports = exports; diff --git a/js/web-server.js b/js/web-server.js index 1fd9c1e0..352348d6 100644 --- a/js/web-server.js +++ b/js/web-server.js @@ -743,6 +743,7 @@ var ver = require('../js/license.js'), "geo-bounds", "geo-polygon", "geo-polygons", + "geo-gyroid", "moto-kv", "moto-ajax", "moto-ctrl", @@ -795,6 +796,7 @@ var ver = require('../js/license.js'), "geo-bounds", "geo-polygon", "geo-polygons", + "geo-gyroid", "kiri-slice", "kiri-slicer", "kiri-driver-fdm",