initial commit of gyroid infill

This commit is contained in:
Stewart Allen 2019-01-21 10:42:24 -05:00
commit a6615fcde1
7 changed files with 214 additions and 13 deletions

156
js/geo-gyroid.js Normal file
View file

@ -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<PI2; x += inc) {
let vrow = []; // raw values row
let erow = []; // edge values row
edge.push(erow);
vals.push(vrow);
for (let y=0; y<PI2; y += inc) {
erow.push(0);
vrow.push(
Math.sin(x) * Math.cos(y) +
Math.sin(y) * Math.cos(z) +
Math.sin(z) * Math.cos(x)
);
}
}
// left-right threshold search (red)
vals.forEach((vrow, y) => {
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<rez; x++) {
let lval = vals[vals.length-1][x];
for (let y=0; y<rez; y++) {
let val = vals[y][x];
if (lval <= 0 && val >= 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<sparse.length; i++) {
if (sparse[i]) {
chain = [ sparse[i] ];
polys.push(chain);
sparse[i] = null;
cleared++;
break;
}
}
do {
added = false;
let target = chain[chain.length - 1];
let cl_elm = null;
let cl_idx = null;
let cl_dst = Infinity;
for (let i=0; i<sparse.length; i++) {
let test_el = sparse[i];
if (test_el) {
let dst = dist(target, test_el, dir);
if (cl_idx === null || dst < cl_dst) {
cl_idx = i;
cl_elm = test_el;
cl_dst = dst;
}
}
}
if (cl_elm) {
if (cl_dst > 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 };
})();

View file

@ -65,7 +65,7 @@ var gs_kiri_fdm = exports;
SLICER.sliceWidget(widget, { SLICER.sliceWidget(widget, {
height: spro.sliceHeight, height: spro.sliceHeight,
minHeight: spro.sliceMinHeight, minHeight: spro.sliceHeight > spro.sliceMinHeight ? spro.sliceMinHeight : 0,
firstHeight: spro.firstSliceHeight, firstHeight: spro.firstSliceHeight,
view: view view: view
}, onSliceDone, onSliceUpdate); }, onSliceDone, onSliceUpdate);
@ -149,7 +149,13 @@ var gs_kiri_fdm = exports;
// sparse layers only present when non-vase mose and sparse % > 0 // sparse layers only present when non-vase mose and sparse % > 0
if (!spro.sliceVase && spro.sliceFillSparse > 0.0) { if (!spro.sliceVase && spro.sliceFillSparse > 0.0) {
forSlices(0.8, 1.0, function(slice) { 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"); }, "infill");
} }

View file

@ -593,14 +593,16 @@ var gs_kiri_slice = exports;
/** /**
* Take output from pluggable sparse infill algorithm and clip to * Take output from pluggable sparse infill algorithm and clip to
* the bounds of the top polygons and their inner solid areas. * 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; 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, var scope = this,
tops = scope.tops, tops = scope.tops,
@ -619,8 +621,9 @@ var gs_kiri_slice = exports;
bounds: function() { return bounds }, bounds: function() { return bounds },
zIndex: function() { return scope.index }, zIndex: function() { return scope.index },
zValue: function() { return scope.z }, zValue: function() { return scope.z },
density: function() { return percent }, zHeight: function() { return height },
offset: function() { return spacing }, offset: function() { return spacing },
density: function() { return density },
emit: function(x,y) { line.push(newPoint(x,y,scope.z)) }, emit: function(x,y) { line.push(newPoint(x,y,scope.z)) },
newline: function() { newline: function() {
if (line.length > 0) { if (line.length > 0) {
@ -631,8 +634,13 @@ var gs_kiri_slice = exports;
}; };
scope.isSparseFill = true; 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 // force emit of last line
target.newline(); 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) { function newTop(poly) {
return new Top(poly); return new Top(poly);
} }

View file

@ -154,7 +154,7 @@ if (self.window) {
[ [
"license","ext-n3d","ext-clip","add-array", "license","ext-n3d","ext-clip","add-array",
"add-three","geo","geo-point","geo-debug","geo-bounds", "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-slice","kiri-slicer","kiri-driver-fdm","kiri-driver-cam",
"kiri-driver-laser","kiri-widget","kiri-pack","kiri-print","kiri-codec" "kiri-driver-laser","kiri-widget","kiri-pack","kiri-print","kiri-codec"
].forEach(function(scr) { ].forEach(function(scr) {

View file

@ -60,6 +60,7 @@ self.kiri.license = exports.LICENSE;
sliceFillOverlap: 1, sliceFillOverlap: 1,
sliceFillSpacing: 1, sliceFillSpacing: 1,
sliceFillSparse: 1, sliceFillSparse: 1,
sliceFillGyroid: 1,
sliceSupportEnable: 1, sliceSupportEnable: 1,
sliceSupportDensity: 1, sliceSupportDensity: 1,
sliceSupportOffset: 1, sliceSupportOffset: 1,
@ -270,6 +271,7 @@ self.kiri.license = exports.LICENSE;
sliceFillOverlap: 0.3, sliceFillOverlap: 0.3,
sliceFillSpacing: 1.0, sliceFillSpacing: 1.0,
sliceFillSparse: 0.5, sliceFillSparse: 0.5,
sliceFillGyroid: false,
sliceSupportEnable: false, sliceSupportEnable: false,
sliceSupportDensity: 0.25, 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}), 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}), 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}), 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}), 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}), 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}), sliceBottomLayers: UC.newInput("base layers", {title:"bottom solid layer count", convert:UC.toInt, modes:FDM}),

View file

@ -1,7 +1,7 @@
var exports = { var exports = {
COPYRIGHT:"Copyright (C) 2014-2018 Stewart Allen <sa@grid.space> - All Rights Reserved", COPYRIGHT:"Copyright (C) 2014-2018 Stewart Allen <sa@grid.space> - All Rights Reserved",
LICENSE:"See the license.md file included with the source distribution", LICENSE:"See the license.md file included with the source distribution",
VERSION:"1.2.5a" VERSION:"1.2.6"
}; };
if (!module) var module = {}; if (!module) var module = {};
module.exports = exports; module.exports = exports;

View file

@ -743,6 +743,7 @@ var ver = require('../js/license.js'),
"geo-bounds", "geo-bounds",
"geo-polygon", "geo-polygon",
"geo-polygons", "geo-polygons",
"geo-gyroid",
"moto-kv", "moto-kv",
"moto-ajax", "moto-ajax",
"moto-ctrl", "moto-ctrl",
@ -795,6 +796,7 @@ var ver = require('../js/license.js'),
"geo-bounds", "geo-bounds",
"geo-polygon", "geo-polygon",
"geo-polygons", "geo-polygons",
"geo-gyroid",
"kiri-slice", "kiri-slice",
"kiri-slicer", "kiri-slicer",
"kiri-driver-fdm", "kiri-driver-fdm",