modularize infill functionality

This commit is contained in:
Stewart Allen 2019-05-02 15:09:54 -04:00
commit c8297f12ea
9 changed files with 174 additions and 129 deletions

View file

@ -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");
}

121
js/kiri-fill.js Normal file
View file

@ -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);
});
}
}
});
}
})();

View file

@ -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);
}

View file

@ -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(''));

View file

@ -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 = '<option></option>';
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}),

View file

@ -1,7 +1,7 @@
var exports = {
COPYRIGHT:"Copyright (C) 2014-2019 Stewart Allen <sa@grid.space> - 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;

View file

@ -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) {

View file

@ -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",

View file

@ -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;