initial commit of gyroid infill
This commit is contained in:
parent
f9756c8844
commit
a6615fcde1
7 changed files with 214 additions and 13 deletions
156
js/geo-gyroid.js
Normal file
156
js/geo-gyroid.js
Normal 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 };
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
@ -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");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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}),
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue