Merge branch 'restructure' into develop

This commit is contained in:
casperlamboo 2016-07-19 14:28:20 +02:00
commit d016aa345b
29 changed files with 1079 additions and 1485 deletions

View File

@ -1,95 +0,0 @@
System.config({
baseURL: "/",
defaultJSExtensions: true,
transpiler: "babel",
babelOptions: {
"optional": [
"runtime"
]
},
paths: {
"github:*": "jspm_packages/github/*",
"npm:*": "jspm_packages/npm/*"
},
bundles: {
"bundle.js": []
},
map: {
"babel": "npm:babel-core@5.8.21",
"babel-runtime": "npm:babel-runtime@5.8.20",
"clipper-lib": "npm:clipper-lib@1.0.0",
"core-js": "npm:core-js@0.9.18",
"json": "github:systemjs/plugin-json@0.1.0",
"nodeca/js-yaml": "github:nodeca/js-yaml@3.3.1",
"read-yaml": "npm:read-yaml@1.0.0",
"systemjs/plugin-json": "github:systemjs/plugin-json@0.1.0",
"three.js": "github:mrdoob/three.js@r72",
"github:jspm/nodelibs-assert@0.1.0": {
"assert": "npm:assert@1.3.0"
},
"github:jspm/nodelibs-path@0.1.0": {
"path-browserify": "npm:path-browserify@0.0.0"
},
"github:jspm/nodelibs-process@0.1.1": {
"process": "npm:process@0.10.1"
},
"github:jspm/nodelibs-util@0.1.0": {
"util": "npm:util@0.10.3"
},
"npm:argparse@1.0.2": {
"assert": "github:jspm/nodelibs-assert@0.1.0",
"fs": "github:jspm/nodelibs-fs@0.1.2",
"lodash": "npm:lodash@3.10.1",
"path": "github:jspm/nodelibs-path@0.1.0",
"process": "github:jspm/nodelibs-process@0.1.1",
"sprintf-js": "npm:sprintf-js@1.0.3",
"util": "github:jspm/nodelibs-util@0.1.0"
},
"npm:assert@1.3.0": {
"util": "npm:util@0.10.3"
},
"npm:babel-runtime@5.8.20": {
"process": "github:jspm/nodelibs-process@0.1.1"
},
"npm:clipper-lib@1.0.0": {
"process": "github:jspm/nodelibs-process@0.1.1"
},
"npm:core-js@0.9.18": {
"fs": "github:jspm/nodelibs-fs@0.1.2",
"process": "github:jspm/nodelibs-process@0.1.1",
"systemjs-json": "github:systemjs/plugin-json@0.1.0"
},
"npm:esprima@2.2.0": {
"fs": "github:jspm/nodelibs-fs@0.1.2",
"process": "github:jspm/nodelibs-process@0.1.1"
},
"npm:inherits@2.0.1": {
"util": "github:jspm/nodelibs-util@0.1.0"
},
"npm:js-yaml@3.3.1": {
"argparse": "npm:argparse@1.0.2",
"esprima": "npm:esprima@2.2.0",
"fs": "github:jspm/nodelibs-fs@0.1.2",
"path": "github:jspm/nodelibs-path@0.1.0",
"process": "github:jspm/nodelibs-process@0.1.1",
"systemjs-json": "github:systemjs/plugin-json@0.1.0",
"util": "github:jspm/nodelibs-util@0.1.0"
},
"npm:lodash@3.10.1": {
"process": "github:jspm/nodelibs-process@0.1.1"
},
"npm:path-browserify@0.0.0": {
"process": "github:jspm/nodelibs-process@0.1.1"
},
"npm:read-yaml@1.0.0": {
"fs": "github:jspm/nodelibs-fs@0.1.2",
"js-yaml": "npm:js-yaml@3.3.1",
"xtend": "npm:xtend@4.0.0"
},
"npm:util@0.10.3": {
"inherits": "npm:inherits@2.0.1",
"process": "github:jspm/nodelibs-process@0.1.1"
}
}
});

View File

@ -1,19 +1,17 @@
import THREE from 'three.js';
import PRINTER_SETTINGS from 'settings/printer_settings.json!';
import USER_SETTINGS from 'settings/user_settings.json!';
import * as SLICER from 'src/index';
var settings = new SLICER.Settings();
settings.updateConfig(PRINTER_SETTINGS["ultimaker2go"]);
settings.updateConfig(USER_SETTINGS);
const settings = new SLICER.Settings({
...SLICER.printerSettings['ultimaker2go'],
...SLICER.userSettings
});
var geometry = new THREE.TorusGeometry(20, 10, 30, 30);
const geometry = new THREE.TorusGeometry(20, 10, 30, 30);
var slicer = new SLICER.Slicer();
//var slicer = new SLICER.SlicerWorker();
const slicer = new SLICER.Slicer();
slicer.setGeometry(geometry.clone());
slicer.onfinish = function (gCode) {
document.getElementById('gcode').innerHTML = gCode.replace(/(?:\r\n|\r|\n)/g, '<br />');
};
slicer.setGeometry(geometry);
slicer.addEventListener('finish', ({ gcode }) => {
document.getElementById('gcode').innerHTML = gcode.replace(/(?:\r\n|\r|\n)/g, '<br />');
});
slicer.slice(settings);

View File

@ -11,7 +11,7 @@
</style>
<script type="text/javascript" src="../jspm_packages/system.js"></script>
<script type="text/javascript" src="../config.js"></script>
<script type="text/javascript" src="../jspm.config.js"></script>
<script type="text/javascript">
System.import('example/app.js');

70
jspm.config.js Normal file
View File

@ -0,0 +1,70 @@
SystemJS.config({
paths: {
"github:": "jspm_packages/github/",
"npm:": "jspm_packages/npm/",
"slicer/": "src/"
},
browserConfig: {
"baseURL": "/"
},
devConfig: {
"map": {
"babel-runtime": "npm:babel-runtime@5.8.38",
"core-js": "npm:core-js@0.9.18",
"process": "github:jspm/nodelibs-process@0.2.0-alpha",
"fs": "github:jspm/nodelibs-fs@0.2.0-alpha",
"plugin-babel": "npm:systemjs-plugin-babel@0.0.12"
},
"packages": {
"npm:babel-runtime@5.8.38": {
"map": {}
},
"npm:core-js@0.9.18": {
"map": {
"systemjs-json": "github:systemjs/plugin-json@0.1.2"
}
}
}
},
transpiler: "plugin-babel",
babelOptions: {
"optional": [
"runtime"
]
},
packages: {
"slicer": {
"main": "index.js"
}
},
bundles: {
"bundle.js": []
},
map: {
"babel": "npm:babel-core@5.8.38"
}
});
SystemJS.config({
packageConfigPaths: [
"npm:@*/*.json",
"npm:*.json",
"github:*/*.json"
],
map: {
"json": "github:systemjs/plugin-json@0.1.2",
"Doodle3D/clipper-js": "github:Doodle3D/clipper-js@master",
"casperlamboo/EventDispatcher": "github:casperlamboo/EventDispatcher@master",
"three.js": "github:mrdoob/three.js@r72"
},
packages: {
"github:Doodle3D/clipper-js@master": {
"map": {
"clipper-lib": "npm:clipper-lib@1.0.0"
}
},
"npm:clipper-lib@1.0.0": {
"map": {}
}
}
});

View File

@ -1,20 +1,31 @@
{
"jspm": {
"main": "index",
"name": "slicer",
"main": "index.js",
"directories": {
"lib": "src"
},
"dependencies": {
"clipper-lib": "npm:clipper-lib@^1.0.0",
"nodeca/js-yaml": "github:nodeca/js-yaml@^3.3.1",
"read-yaml": "npm:read-yaml@^1.0.0",
"systemjs/plugin-json": "github:systemjs/plugin-json@^0.1.0",
"Doodle3D/clipper-js": "github:Doodle3D/clipper-js@master",
"casperlamboo/EventDispatcher": "github:casperlamboo/EventDispatcher@master",
"json": "github:systemjs/plugin-json@^0.1.2",
"three.js": "github:mrdoob/three.js@r72"
},
"devDependencies": {
"babel": "npm:babel-core@^5.1.13",
"babel-runtime": "npm:babel-runtime@^5.1.13",
"core-js": "npm:core-js@^0.9.4"
"core-js": "npm:core-js@^0.9.4",
"fs": "github:jspm/nodelibs-fs@^0.2.0-alpha",
"plugin-babel": "npm:systemjs-plugin-babel@^0.0.12",
"process": "github:jspm/nodelibs-process@^0.2.0-alpha"
},
"overrides": {
"npm:babel-runtime@5.8.38": {
"main": false,
"dependencies": {},
"optionalDependencies": {
"core-js": "^1.2.0"
}
}
}
}
}

2
src/constants.js Normal file
View File

@ -0,0 +1,2 @@
export const CLEAN_DELTA = 0.01;
export const PRECISION = 0.01;

View File

@ -1,7 +1,16 @@
import THREE from 'three.js';
const G_COMMAND = 'G';
const M_COMMAND = 'M';
const FAN_SPEED = 'S';
const SPEED = 'F';
const EXTRUDER = 'E';
const POSITION_X = 'X';
const POSITION_Y = 'Y';
const POSITION_Z = 'Z';
export default class {
constructor () {
constructor(settings) {
this.gcode = '';
this.current = {};
@ -10,26 +19,31 @@ export default class {
this.isRetracted = false;
this.isFanOn = false;
this._nozzlePosition = new THREE.Vector2(0, 0);
if (settings !== undefined) {
this.setSettings(settings);
}
}
_addGCode(command) {
var str = '';
var first = true;
let str = '';
let first = true;
for (var i in command) {
for (const action in command) {
const value = command[action];
const currentValue = this.current[action];
if (first) {
str = i + command[i];
str = action + value;
first = false;
}
else if (this.current[i] !== command[i]) {
str += ' ' + i + command[i];
} else if (currentValue !== value) {
str += ` ${action}${value}`;
this.current[i] = command[i];
this.current[action] = value;
}
}
this.gcode += str + '\n';
this.gcode += `${str}\n`;
}
setSettings(settings) {
@ -41,13 +55,8 @@ export default class {
turnFanOn(fanSpeed) {
this.isFanOn = true;
var gcode = {
'M': 106
}
if (fanSpeed !== undefined) {
gcode['S'] = fanSpeed;
}
const gcode = { [M_COMMAND]: 106 }
if (fanSpeed !== undefined) gcode[FAN_SPEED] = fanSpeed;
this._addGCode(gcode);
@ -57,24 +66,26 @@ export default class {
turnFanOff() {
this.isFanOn = false;
this._addGCode({
'M': 107
});
this._addGCode({ [M_COMMAND]: 107 });
return this;
}
moveTo(x, y, layer) {
var layerHeight = this.settings.config['layerHeight'];
var travelSpeed = this.settings.config['travelSpeed'];
const {
layerHeight,
travelSpeed
} = this.settings.config;
var z = (layer + 1) * layerHeight;
var speed = travelSpeed * 60;
const z = (layer + 1) * layerHeight;
const speed = travelSpeed * 60;
this._addGCode({
'G': 0,
'X': x.toFixed(3), 'Y': y.toFixed(3), 'Z': z.toFixed(3),
'F': speed.toFixed(3)
[G_COMMAND]: 0,
[POSITION_X]: x.toFixed(3),
[POSITION_Y]: y.toFixed(3),
[POSITION_Z]: z.toFixed(3),
[SPEED]: speed.toFixed(3)
});
this._nozzlePosition.set(x, y);
@ -83,29 +94,37 @@ export default class {
}
lineTo(x, y, layer, type) {
var newNozzlePosition = new THREE.Vector2(x, y);
const newNozzlePosition = new THREE.Vector2(x, y);
var layerHeight = this.settings.config['layerHeight'];
var nozzleDiameter = this.settings.config['nozzleDiameter'];
var filamentThickness = this.settings.config['filamentThickness'];
var travelSpeed = this.settings.config['travelSpeed'];
const {
layerHeight,
nozzleDiameter,
filamentThickness,
travelSpeed
} = this.settings.config;
var profile = this.settings.config[(this.bottom ? 'bottom' : type)];
const profile = this.settings.config[(this.bottom ? 'bottom' : type)];
var speed = profile['speed'] * 60;
var flowRate = profile['flowRate'];
var z = (layer + 1) * layerHeight;
let {
speed,
flowRate
} = profile;
var lineLength = this._nozzlePosition.distanceTo(newNozzlePosition);
speed *= 60;
const z = (layer + 1) * layerHeight;
var filamentSurfaceArea = Math.pow((filamentThickness / 2), 2) * Math.PI;
const lineLength = this._nozzlePosition.distanceTo(newNozzlePosition);
const filamentSurfaceArea = Math.pow((filamentThickness / 2), 2) * Math.PI;
this.extruder += lineLength * nozzleDiameter * layerHeight / filamentSurfaceArea * flowRate;
this._addGCode({
'G': 1,
'X': x.toFixed(3), 'Y': y.toFixed(3), 'Z': z.toFixed(3),
'F': speed.toFixed(3),
'E': this.extruder.toFixed(3)
[G_COMMAND]: 1,
[POSITION_X]: x.toFixed(3),
[POSITION_Y]: y.toFixed(3),
[POSITION_Z]: z.toFixed(3),
[SPEED]: speed.toFixed(3),
[EXTRUDER]: this.extruder.toFixed(3)
});
this._nozzlePosition.copy(newNozzlePosition);
@ -114,20 +133,22 @@ export default class {
}
unRetract() {
var retractionEnabled = this.settings.config['retractionEnabled'];
var retractionMinDistance = this.settings.config['retractionMinDistance'];
var retractionSpeed = this.settings.config['retractionSpeed'];
const {
retractionEnabled,
retractionMinDistance,
retractionSpeed
} = this.settings.config;
if (this.isRetracted && retractionEnabled) {
this.isRetracted = false;
var speed = retractionSpeed * 60;
const speed = retractionSpeed * 60;
if (this.extruder > retractionMinDistance) {
this._addGCode({
'G': 0,
'E': this.extruder.toFixed(3),
'F': speed.toFixed(3)
[G_COMMAND]: 0,
[EXTRUDER]: this.extruder.toFixed(3),
[SPEED]: speed.toFixed(3)
});
}
}
@ -136,21 +157,23 @@ export default class {
}
retract() {
var retractionAmount = this.settings.config['retractionAmount'];
var retractionEnabled = this.settings.config['retractionEnabled'];
var retractionMinDistance = this.settings.config['retractionMinDistance'];
var retractionSpeed = this.settings.config['retractionSpeed'];
const {
retractionAmount,
retractionEnabled,
retractionMinDistance,
retractionSpeed
} = this.settings.config;
if (!this.isRetracted && retractionEnabled) {
this.isRetracted = true;
var speed = retractionSpeed * 60;
const speed = retractionSpeed * 60;
if (this.extruder > retractionMinDistance && retractionEnabled) {
this._addGCode({
'G': 0,
'E': (this.extruder - retractionAmount).toFixed(3),
'F': speed.toFixed(3)
[G_COMMAND]: 0,
[EXTRUDER]: (this.extruder - retractionAmount).toFixed(3),
[SPEED]: speed.toFixed(3)
});
}
}

31
src/getFillTemplate.js Normal file
View File

@ -0,0 +1,31 @@
import Shape from 'Doodle3D/clipper-js';
export default function getFillTemplate(bounds, size, even, uneven) {
const paths = [];
const left = Math.floor(bounds.left / size) * size;
const right = Math.ceil(bounds.right / size) * size;
const top = Math.floor(bounds.top / size) * size;
const bottom = Math.ceil(bounds.bottom / size) * size;
const width = right - left;
if (even) {
for (let y = top; y <= bottom + width; y += size) {
paths.push([
{ x: left, y },
{ x: right, y: y - width }
]);
}
}
if (uneven) {
for (let y = top - width; y <= bottom; y += size) {
paths.push([
{ x: left, y },
{ x: right, y: y + width }
]);
}
}
return new Shape(paths, false, true);
}

View File

@ -1,10 +1,6 @@
import Slicer from './slicer.js';
import SlicerWorker from './slicerworker.js';
import Settings from './settings.js';
import ClipperLib from 'clipper-lib';
import printerSettings from './settings/printer_settings.json!json';
import userSettings from './settings/user_settings.json!json';
ClipperLib.Error = function (message) {
console.error(message);
};
export {Slicer, SlicerWorker, Settings};
export { Slicer, Settings, printerSettings, userSettings };

View File

@ -1,252 +0,0 @@
import ClipperLib from 'clipper-lib';
import THREE from 'three.js';
export default class Paths extends Array {
constructor (paths = [], closed = true) {
super();
this.setPaths(paths);
this.closed = closed;
}
setPaths (paths) {
for (var i = 0; i < paths.length; i ++) {
var path = paths[i];
if (path.length > 0) {
this.push(path);
}
}
return this;
}
_clip (path, type) {
var solution = new ClipperLib.PolyTree();
var clipper = new ClipperLib.Clipper();
clipper.AddPaths(this, ClipperLib.PolyType.ptSubject, this.closed);
clipper.AddPaths(path, ClipperLib.PolyType.ptClip, path.closed);
clipper.Execute(type, solution);
if (this.closed) {
var paths = ClipperLib.Clipper.ClosedPathsFromPolyTree(solution);
}
else {
var paths = ClipperLib.Clipper.OpenPathsFromPolyTree(solution);
}
return new Paths(paths, this.closed);
}
union (path) {
return this._clip(path, ClipperLib.ClipType.ctUnion);
}
difference (path) {
return this._clip(path, ClipperLib.ClipType.ctDifference);
}
intersect (path) {
return this._clip(path, ClipperLib.ClipType.ctIntersection);
}
xor (path) {
return this._clip(path, ClipperLib.ClipType.ctXor);
}
offset (offset) {
var solution = new ClipperLib.Paths();
var co = new ClipperLib.ClipperOffset(1, 1);
co.AddPaths(this, ClipperLib.JoinType.jtSquare, ClipperLib.EndType.etClosedPolygon);
co.Execute(solution, offset);
return new Paths(solution);
}
scaleUp (factor) {
ClipperLib.JS.ScaleUpPaths(this, factor);
return this;
}
scaleDown (factor) {
ClipperLib.JS.ScaleDownPaths(this, factor);
return this;
}
lastPoint () {
if (this.length === 0) {
return new THREE.Vector2();
}
var lastPath = this[this.length - 1];
var lastPoint = this.closed ? lastPath[0] : lastPath[lastPath.length - 1];
return new THREE.Vector2(lastPoint.X, lastPoint.Y);
}
optimizePath (start) {
var optimizedPaths = new Paths([], this.closed);
var donePaths = [];
while (optimizedPaths.length !== this.length) {
var minLength = false;
var reverse;
var minPath;
var offset;
var pathIndex;
for (var i = 0; i < this.length; i += 1) {
var path = this[i];
if (donePaths.indexOf(i) === -1) {
if (this.closed) {
for (var j = 0; j < path.length; j += 1) {
var point = new THREE.Vector2(path[j].X, path[j].Y);
var length = point.sub(start).length();
if (minLength === false || length < minLength) {
minPath = path;
minLength = length;
offset = j;
pathIndex = i;
}
}
}
else {
var startPoint = new THREE.Vector2(path[0].X, path[0].Y);
var length = startPoint.sub(start).length();
if (minLength === false || length < minLength) {
minPath = path;
minLength = length;
reverse = false;
pathIndex = i;
}
var endPoint = new THREE.Vector2(path[path.length - 1].X, path[path.length - 1].Y);
var length = endPoint.sub(start).length();
if (length < minLength) {
minPath = path;
minLength = length;
reverse = true;
pathIndex = i;
}
}
}
}
if (this.closed) {
minPath = minPath.concat(minPath.splice(0, offset));
var point = minPath[0];
}
else {
if (reverse) {
minPath.reverse();
}
var point = minPath[minPath.length - 1];
}
donePaths.push(pathIndex);
start = new THREE.Vector2(point.X, point.Y);
optimizedPaths.push(minPath);
}
return optimizedPaths;
}
areas () {
var areas = [];
for (var i = 0; i < this.length; i ++) {
var shape = this[i];
var area = Math.abs(ClipperLib.Clipper.Area(shape));
areas.push(area);
}
return areas;
}
area () {
var areas = this.areas();
var totalArea = 0;
for (var i = 0; i < areas.length; i ++) {
var area = areas[i];
totalArea += area;
}
return totalArea;
}
tresholdArea (minArea) {
// code not tested yet
for (var i = 0; i < this.length; i ++) {
var shape = this[i];
var area = ClipperLib.Clipper.Area(shape);
if (area < minArea) {
this.splice(i, 1);
i -= 1;
}
}
}
join (path) {
for (var i = 0; i < path.length; i += 1) {
this.push(path[i]);
}
return this;
}
clone () {
return new Paths(ClipperLib.JS.Clone(this), this.closed);
}
bounds () {
return ClipperLib.Clipper.GetBounds(this);
}
clean (cleanDelta) {
return new Paths(ClipperLib.Clipper.CleanPolygons(this, cleanDelta), this.closed);
}
isHole () {
return !ClipperLib.Clipper.Orientation(this[0]);
}
pointCollision (point) {
var collision = ClipperLib.Clipper.PointInPolygon(point, this[0]);
return ClipperLib.Clipper.PointInPolygon(point, this[0]);
}
boundSize () {
var bounds = this.bounds();
var width = bounds.right - bounds.left;
var height = bounds.bottom - bounds.top;
return width * height;
}
draw (context, color) {
context.strokeStyle = color;
for (var i = 0; i < this.length; i += 1) {
var shape = this[i];
// var point = shape[0];
// context.fillText(i, point.X*2, point.Y*2);
context.beginPath();
for (var j = 0; j < shape.length; j += 1) {
var point = shape[j % shape.length];
context.lineTo(point.X * 2, point.Y * 2);
}
if (this.closed) {
context.closePath();
}
context.stroke();
}
}
}

View File

@ -1,54 +1,49 @@
export default class {
constructor () {
this.config = {};
constructor(config = {}) {
this.config = config;
}
updateConfig(config) {
for (var i in config) {
this.config[i] = config[i];
}
this.config = { ...this.config, ...config };
return this;
}
startCode() {
var gcode = this.config["startCode"];
gcode = this._subsituteVariables(gcode);
const { startCode } = this.config;
const gcode = this._subsituteVariables(startCode);
return gcode;
}
endCode() {
var gcode = this.config["endCode"];
gcode = this._subsituteVariables(gcode);
const { endCode } = this.config;
const gcode = this._subsituteVariables(endCode);
return gcode;
}
_subsituteVariables(gcode) {
var temperature = this.config["temperature"];
var bedTemperature = this.config["bedTemperature"];
var preheatTemperature = this.config["heatupTemperature"];
var preheatBedTemperature = this.config["heatupBedTemperature"];
var travelSpeed = this.config["travelSpeed"] * 60;
var printerType = this.config["type"];
var heatedbed = this.config["heatedbed"];
let {
temperature,
bedTemperature,
heatTemperature,
heatBedTemperature,
travelSpeed,
printerType,
heatedbed
} = this.config;
travelSpeed *= 60;
switch (printerType) {
case "makerbot_replicator2": printerType = "r2"; break;
case "makerbot_replicator2x": printerType = "r2x"; break;
case "makerbot_thingomatic": printerType = "t6"; break;
case "makerbot_generic": printerType = "r2"; break;
case "_3Dison_plus": printerType = "r2"; break;
case 'makerbot_replicator2': printerType = 'r2'; break;
case 'makerbot_replicator2x': printerType = 'r2x'; break;
case 'makerbot_thingomatic': printerType = 't6'; break;
case 'makerbot_generic': printerType = 'r2'; break;
case '_3Dison_plus': printerType = 'r2'; break;
}
var heatedBedReplacement = heatedbed ? "" : ";";
const heatedBedReplacement = heatedbed ? '' : ';';
gcode = gcode.replace(/{printingTemp}/gi, temperature);
gcode = gcode.replace(/{printingBedTemp}/gi, bedTemperature);
gcode = gcode.replace(/{preheatTemp}/gi, preheatTemperature);
gcode = gcode.replace(/{preheatBedTemp}/gi, preheatBedTemperature);
gcode = gcode.replace(/{preheatTemp}/gi, heatTemperature);
gcode = gcode.replace(/{preheatBedTemp}/gi, heatBedTemperature);
gcode = gcode.replace(/{printerType}/gi, printerType);
gcode = gcode.replace(/{travelSpeed}/gi, travelSpeed);
gcode = gcode.replace(/{if heatedBed}/gi, heatedBedReplacement);

View File

@ -1,134 +1,31 @@
import Paths from './paths.js';
import Shape from 'Doodle3D/clipper-js';
export default class {
constructor() {
this.parts = [];
}
removeSelfIntersect () {
for (var i = 0; i < this.parts.length; i ++) {
var part1 = this.parts[i].intersect;
if (!part1.closed) {
continue;
}
for (var j = i + 1; j < this.parts.length; j ++) {
var part2 = this.parts[j].intersect;
if (!part2.closed) {
continue;
}
if (part2.intersect(part1).length > 0) {
part1 = this.parts[i].intersect = part1.union(part2);
this.parts.splice(j, 1);
j --;
}
}
}
}
optimizePaths (start) {
if (this.brim !== undefined && this.brim.length > 0) {
this.brim = this.brim.optimizePath(start);
start = this.brim.lastPoint();
}
var parts = [];
while (this.parts.length > 0) {
var closestDistance = Infinity;
var closestPart;
for (var i = 0; i < this.parts.length; i ++) {
var part = this.parts[i];
if (part.intersect.closed) {
var bounds = part.outerLine.bounds();
}
else {
var bounds = part.intersect.bounds();
}
var top = bounds.top - start.y;
var bottom = start.y - bounds.bottom;
var left = bounds.left - start.x;
var right = start.x - bounds.right;
var distance = Math.max(top, bottom, left, right);
if (distance < closestDistance) {
closestDistance = distance;
closestPart = i;
}
}
var part = this.parts.splice(closestPart, 1)[0];
parts.push(part);
if (part.intersect.closed) {
if (part.outerLine.length > 0) {
part.outerLine = part.outerLine.optimizePath(start);
start = part.outerLine.lastPoint();
}
for (var j = 0; j < part.innerLines.length; j ++) {
var innerLine = part.innerLines[j];
if (innerLine.length > 0) {
part.innerLines[j] = innerLine.optimizePath(start);
start = part.innerLines[j].lastPoint();
}
}
if (part.fill.length > 0) {
part.fill = part.fill.optimizePath(start);
start = part.fill.lastPoint();
}
}
else {
part.intersect.optimizePath(start);
start = part.intersect.lastPoint();
}
}
this.parts = parts;
if (this.support !== undefined && this.support.length > 0) {
this.support = this.support.optimizePath(start);
start = this.support.lastPoint();
}
return start;
}
getOutline() {
var outLines = new Paths([], true);
const outLines = new Shape([], true);
for (var i = 0; i < this.parts.length; i ++) {
var part = this.parts[i];
for (let i = 0; i < this.parts.length; i ++) {
const part = this.parts[i];
if (part.intersect.closed) {
if (part.shape.closed) {
outLines.join(this.parts[i].outerLine);
}
}
return outLines;
}
add(shape) {
const part = { shape };
add (intersect) {
var parts = {
intersect
};
if (intersect.closed) {
parts.innerLines = [];
parts.outerLine = new Paths([], true);
parts.fill = new Paths([], false);
if (shape.closed) {
part.innerLines = [];
part.outerLine = new Shape([], true);
part.fill = new Shape([], false);
}
this.parts.push(parts);
this.parts.push(part);
}
}

View File

@ -0,0 +1,19 @@
import THREE from 'three.js';
import { PRECISION } from '../constants.js';
const offsetOptions = {
jointType: 'jtSquare',
endType: 'etClosedPolygon',
miterLimit: 2.0,
roundPrecision: 0.25
};
export default function addBrim(slices, settings) {
console.log('add brim');
let { brimOffset } = settings.config;
brimOffset /= PRECISION;
const fistLayer = slices[0];
fistLayer.brim = fistLayer.getOutline().offset(brimOffset, offsetOptions);
}

View File

@ -0,0 +1,22 @@
import { PRECISION } from '../constants.js'
export default function applyPrecision(shapes) {
for (let i = 0; i < shapes.length; i ++) {
const { closedShapes, openShapes } = shapes[i];
scaleUpShape(closedShapes);
scaleUpShape(openShapes);
}
}
function scaleUpShape(shape) {
for (let i = 0; i < shape.length; i ++) {
const path = shape[i];
for (let i = 0; i < path.length; i ++) {
const point = path[i];
point.copy(point.divideScalar(PRECISION));
}
}
}

View File

@ -0,0 +1,47 @@
import THREE from 'three.js';
export default function calculateLayersIntersections(lines, settings) {
console.log('calculating layer intersections');
const { layerHeight, dimensionsZ } = settings.config;
const numLayers = Math.floor(dimensionsZ / layerHeight);
const layerIntersectionIndexes = [];
const layerIntersectionPoints = [];
for (let layer = 0; layer < numLayers; layer ++) {
layerIntersectionIndexes[layer] = [];
layerIntersectionPoints[layer] = [];
}
for (let lineIndex = 0; lineIndex < lines.length; lineIndex ++) {
const line = lines[lineIndex].line;
const min = Math.ceil(Math.min(line.start.y, line.end.y) / layerHeight);
const max = Math.floor(Math.max(line.start.y, line.end.y) / layerHeight);
for (let layerIndex = min; layerIndex <= max; layerIndex ++) {
if (layerIndex >= 0 && layerIndex < numLayers) {
layerIntersectionIndexes[layerIndex].push(lineIndex);
const y = layerIndex * layerHeight;
let x, z;
if (line.start.y === line.end.y) {
x = line.start.x;
z = line.start.z;
}
else {
const alpha = (y - line.start.y) / (line.end.y - line.start.y);
x = line.end.x * alpha + line.start.x * (1 - alpha);
z = line.end.z * alpha + line.start.z * (1 - alpha);
}
layerIntersectionPoints[layerIndex][lineIndex] = new THREE.Vector2(z, x);
}
}
}
return { layerIntersectionIndexes, layerIntersectionPoints };
}

View File

@ -0,0 +1,49 @@
import THREE from 'three.js';
function addLine(geometry, lineLookup, lines, a, b) {
const index = lines.length;
lineLookup[`${a}_${b}`] = index;
lines.push({
line: new THREE.Line3(geometry.vertices[a], geometry.vertices[b]),
connects: [],
normals: []
});
return index;
}
export default function createLines(geometry, settings) {
console.log('constructing unique lines from geometry');
const lines = [];
const lineLookup = {};
for (let i = 0; i < geometry.faces.length; i ++) {
const face = geometry.faces[i];
if (face.normal.y !== 1 && face.normal.y !== -1) {
const normal = new THREE.Vector2(face.normal.z, face.normal.x).normalize();
const lookupA = lineLookup[`${face.b}_${face.a}`];
const lookupB = lineLookup[`${face.c}_${face.b}`];
const lookupC = lineLookup[`${face.a}_${face.c}`];
// only add unique lines
// returns index of said line
const indexA = lookupA !== undefined ? lookupA : addLine(geometry, lineLookup, lines, face.a, face.b);
const indexB = lookupB !== undefined ? lookupB : addLine(geometry, lineLookup, lines, face.b, face.c);
const indexC = lookupC !== undefined ? lookupC : addLine(geometry, lineLookup, lines, face.c, face.a);
// set connecting lines (based on face)
lines[indexA].connects.push(indexB, indexC);
lines[indexB].connects.push(indexC, indexA);
lines[indexC].connects.push(indexA, indexB);
lines[indexA].normals.push(normal);
lines[indexB].normals.push(normal);
lines[indexC].normals.push(normal);
}
}
return lines;
}

View File

@ -0,0 +1,81 @@
import { PRECISION } from '../constants.js'
import getFillTemplate from '../getFillTemplate.js';
import Shape from 'Doodle3D/clipper-js';
export default function generateInfills(slices, settings) {
console.log('generating infills');
let {
layerHeight,
fillGridSize,
bottomThickness,
topThickness,
nozzleDiameter,
infillOverlap
} = settings.config;
fillGridSize /= PRECISION;
nozzleDiameter /= PRECISION;
infillOverlap /= PRECISION;
const bottomSkinCount = Math.ceil(bottomThickness/layerHeight);
const topSkinCount = Math.ceil(topThickness/layerHeight);
const nozzleRadius = nozzleDiameter / 2;
const hightemplateSize = Math.sqrt(2 * Math.pow(nozzleDiameter, 2));
for (let layer = 0; layer < slices.length; layer ++) {
const slice = slices[layer];
let surroundingLayer;
if (layer - bottomSkinCount >= 0 && layer + topSkinCount < slices.length) {
const downSkin = slices[layer - bottomSkinCount].getOutline();
const upSkin = slices[layer + topSkinCount].getOutline();
surroundingLayer = upSkin.intersect(downSkin);
}
for (let i = 0; i < slice.parts.length; i ++) {
const part = slice.parts[i];
if (!part.shape.closed) {
continue;
}
const outerLine = part.outerLine;
if (outerLine.paths.length > 0) {
const inset = (part.innerLines.length > 0) ? part.innerLines[part.innerLines.length - 1] : outerLine;
const fillArea = inset.offset(-nozzleRadius);
let lowFillArea;
let highFillArea;
if (surroundingLayer) {
highFillArea = fillArea.difference(surroundingLayer);
if (infillOverlap > 0) {
highFillArea = highFillArea.offset(infillOverlap);
}
highFillArea = highFillArea.intersect(fillArea);
lowFillArea = fillArea.difference(highFillArea);
} else {
highFillArea = fillArea;
}
if (lowFillArea && lowFillArea.paths.length > 0) {
const bounds = lowFillArea.shapeBounds();
const lowFillTemplate = getFillTemplate(bounds, fillGridSize, true, true);
part.fill.join(lowFillTemplate.intersect(lowFillArea));
}
if (highFillArea.paths.length > 0) {
const bounds = highFillArea.shapeBounds();
const even = (layer % 2 === 0);
const highFillTemplate = getFillTemplate(bounds, hightemplateSize, even, !even);
part.fill.join(highFillTemplate.intersect(highFillArea));
}
}
}
}
}

View File

@ -0,0 +1,48 @@
import { PRECISION } from '../constants.js'
const offsetOptions = {
jointType: 'jtSquare',
endType: 'etClosedPolygon',
miterLimit: 2.0,
roundPrecision: 0.25
};
export default function generateInnerLines(slices, settings) {
console.log('generating outer lines and inner lines');
// need to scale up everything because of clipper rounding errors
let { layerHeight, nozzleDiameter, shellThickness } = settings.config;
nozzleDiameter /= PRECISION;
shellThickness /= PRECISION;
const nozzleRadius = nozzleDiameter / 2;
const shells = Math.round(shellThickness / nozzleDiameter);
for (let layer = 0; layer < slices.length; layer ++) {
const slice = slices[layer];
for (let i = 0; i < slice.parts.length; i ++) {
const part = slice.parts[i];
if (!part.shape.closed) continue;
const outerLine = part.shape.offset(-nozzleRadius, offsetOptions);
if (outerLine.paths.length > 0) {
part.outerLine.join(outerLine);
for (let shell = 1; shell < shells; shell += 1) {
const offset = shell * nozzleDiameter;
const innerLine = outerLine.offset(-offset, offsetOptions);
if (innerLine.paths.length > 0) {
part.innerLines.push(innerLine);
}
else {
break;
}
}
}
}
}
}

View File

@ -0,0 +1,77 @@
import getFillTemplate from '../getFillTemplate.js';
import Shape from 'Doodle3D/clipper-js';
import { PRECISION } from '../constants.js';
export default function generateSupport(slices, settings) {
console.log('generating support');
if (!settings.config.supportEnabled) return;
let {
layerHeight,
supportGridSize,
supportAcceptanceMargin,
supportPlateSize: plateSize,
supportDistanceY,
nozzleDiameter
} = settings.config;
supportGridSize /= PRECISION;
supportMargin /= PRECISION;
plateSize /= PRECISION;
nozzleDiameter /= PRECISION;
var supportDistanceLayers = Math.max(Math.ceil(supportDistanceY / layerHeight), 1);
var supportAreas = new Shape([], true);
for (var layer = slices.length - 1 - supportDistanceLayers; layer >= 0; layer --) {
var currentSlice = slices[layer];
if (supportAreas.length > 0) {
if (layer >= supportDistanceLayers) {
var sliceSkin = slices[layer - supportDistanceLayers].getOutline();
sliceSkin = sliceSkin;
var supportAreasSlimmed = supportAreas.difference(sliceSkin.offset(supportMargin));
if (supportAreasSlimmed.area() < 100.0) {
supportAreas = supportAreas.difference(sliceSkin);
}
else {
supportAreas = supportAreasSlimmed;
}
}
var supportTemplate = getFillTemplate(supportAreas.bounds(), supportGridSize, true, true);
var supportFill = supportTemplate.intersect(supportAreas);
if (supportFill.length === 0) {
currentSlice.support = supportAreas.clone();
}
else {
currentSlice.support = supportFill;
}
}
var supportSkin = slices[layer + supportDistanceLayers - 1].getOutline();
var slice = slices[layer + supportDistanceLayers];
for (var i = 0; i < slice.parts.length; i ++) {
var slicePart = slice.parts[i];
if (slicePart.intersect.closed) {
var outerLine = slicePart.outerLine;
}
else {
var outerLine = slicePart.intersect.offset(supportAcceptanceMargin);
}
var overlap = supportSkin.offset(supportAcceptanceMargin).intersect(outerLine);
var overhang = outerLine.difference(overlap);
if (overlap.length === 0 || overhang.length > 0) {
supportAreas = supportAreas.join(overhang);
}
}
}
}

View File

@ -0,0 +1,121 @@
import THREE from 'three.js';
import Shape from 'Doodle3D/clipper-js';
export default function intersectionsToShapes(layerIntersectionIndexes, layerIntersectionPoints, lines, settings) {
console.log('generating slices');
const layers = [];
for (let layer = 1; layer < layerIntersectionIndexes.length; layer ++) {
const intersectionIndexes = layerIntersectionIndexes[layer];
const intersectionPoints = layerIntersectionPoints[layer];
if (intersectionIndexes.length === 0) continue;
const closedShapes = [];
const openShapes = [];
for (let i = 0; i < intersectionIndexes.length; i ++) {
let index = intersectionIndexes[i];
if (intersectionPoints[index] === undefined) continue;
const shape = [];
const firstPoints = [index];
let isFirstPoint = true;
let closed = false;
while (index !== -1) {
const intersection = intersectionPoints[index];
// uppercase X and Y because clipper vector
shape.push(intersection);
delete intersectionPoints[index];
const connects = lines[index].connects;
const faceNormals = lines[index].normals;
for (let i = 0; i < connects.length; i ++) {
index = connects[i];
if (firstPoints.indexOf(index) !== -1 && shape.length > 2) {
closed = true;
index = -1;
break;
}
// Check if index has an intersection or is already used
if (intersectionPoints[index] !== undefined) {
const faceNormal = faceNormals[Math.floor(i / 2)];
const a = new THREE.Vector2(intersection.x, intersection.y);
const b = new THREE.Vector2(intersectionPoints[index].x, intersectionPoints[index].y);
// can't calculate normal between points if distance is smaller as 0.0001
if ((faceNormal.x === 0 && faceNormal.y === 0) || a.distanceTo(b) < 0.0001) {
if (isFirstPoint) {
firstPoints.push(index);
}
delete intersectionPoints[index];
connects.push(...lines[index].connects);
faceNormals.push(...lines[index].normals);
index = -1;
} else {
// make sure the path goes the right direction
// THREE.Vector2.normal is not yet implimented
// const normal = a.sub(b).normal().normalize();
const normal = a.sub(b);
normal.set(-normal.y, normal.x).normalize();
if (normal.dot(faceNormal) > 0) {
break;
} else {
index = -1;
}
}
} else {
index = -1;
}
}
isFirstPoint = false;
}
if (!closed) {
index = firstPoints[0];
while (index !== -1) {
if (firstPoints.indexOf(index) === -1) {
const intersection = intersectionPoints[index];
shape.unshift(intersection);
delete intersectionPoints[index];
}
const connects = lines[index].connects;
for (let i = 0; i < connects.length; i ++) {
index = connects[i];
if (intersectionPoints[index] !== undefined) {
break;
} else {
index = -1;
}
}
}
}
if (closed) {
closedShapes.push(shape);
} else {
openShapes.push(shape);
}
}
layers.push({ closedShapes, openShapes });
}
return layers;
}

View File

@ -0,0 +1,152 @@
import THREE from 'three.js';
import Shape from 'Doodle3D/clipper-js';
export default function optimizePaths(slices, settings) {
console.log('optimize paths');
const start = new THREE.Vector2(0, 0);
for (let layer = 0; layer < slices.length; layer ++) {
const slice = slices[layer];
if (slice.brim !== undefined && slice.brim.paths.length > 0) {
slice.brim = optimizeShape(slice.brim, start);
start.copy(slice.brim.lastPoint());
}
const parts = [];
while (slice.parts.length > 0) {
let closestDistance = Infinity;
let closestPart;
for (let i = 0; i < slice.parts.length; i ++) {
const part = slice.parts[i];
let bounds;
if (part.shape.closed) {
bounds = part.outerLine.shapeBounds();
} else {
bounds = part.shape.shapeBounds();
}
const top = bounds.top - start.y;
const bottom = start.y - bounds.bottom;
const left = bounds.left - start.x;
const right = start.x - bounds.right;
const distance = Math.max(top, bottom, left, right);
if (distance < closestDistance) {
closestDistance = distance;
closestPart = i;
}
}
const part = slice.parts.splice(closestPart, 1)[0];
parts.push(part);
if (part.shape.closed) {
if (part.outerLine.paths.length > 0) {
part.outerLine = optimizeShape(part.outerLine, start);
start.copy(part.outerLine.lastPoint());
}
for (let i = 0; i < part.innerLines.length; i ++) {
const innerLine = part.innerLines[i];
if (innerLine.paths.length > 0) {
part.innerLines[i] = optimizeShape(innerLine, start);
start.copy(part.innerLines[i].lastPoint());
}
}
if (part.fill.paths.length > 0) {
part.fill = optimizeShape(part.fill, start);
start.copy(part.fill.lastPoint());
}
} else {
part.shape = optimizeShape(part.shape, start);
start.copy(part.shape.lastPoint());
}
}
slice.parts = parts;
if (slice.support !== undefined && slice.support.length > 0) {
slice.support = optimizeShape(slice.support, start);
start.copy(slice.support.lastPoint());
}
}
}
function optimizeShape(shape, start) {
start = start.clone();
const inputPaths = shape.mapToLower();
const optimizedPaths = [];
const donePaths = [];
while (optimizedPaths.length !== inputPaths.length) {
let minLength = false;
let reverse;
let minPath;
let offset;
let pathIndex;
for (let i = 0; i < inputPaths.length; i ++) {
if (donePaths.indexOf(i) !== -1) continue;
const path = inputPaths[i];
if (shape.closed) {
for (let j = 0; j < path.length; j += 1) {
const point = new THREE.Vector2().copy(path[j]);
const length = point.sub(start).length();
if (minLength === false || length < minLength) {
minPath = path;
minLength = length;
offset = j;
pathIndex = i;
}
}
} else {
const startPoint = new THREE.Vector2().copy(path[0]);
const lengthToStart = startPoint.sub(start).length();
if (minLength === false || lengthToStart < minLength) {
minPath = path;
minLength = lengthToStart;
reverse = false;
pathIndex = i;
}
const endPoint = new THREE.Vector2().copy(path[path.length - 1]);
const lengthToEnd = endPoint.sub(start).length();
if (lengthToEnd < minLength) {
minPath = path;
minLength = lengthToEnd;
reverse = true;
pathIndex = i;
}
}
}
let point;
if (shape.closed) {
minPath = minPath.concat(minPath.splice(0, offset));
point = minPath[0];
} else {
if (reverse) {
minPath.reverse();
}
point = minPath[minPath.length - 1];
}
donePaths.push(pathIndex);
start.copy(point);
optimizedPaths.push(minPath);
}
return new Shape(optimizedPaths, shape.closed, true);
}

View File

@ -0,0 +1,32 @@
import THREE from 'three.js';
import { PRECISION } from '../constants.js';
export default function removePrecision(slices) {
console.log('remove precision');
const start = new THREE.Vector2(0, 0);
for (let layer = 0; layer < slices.length; layer ++) {
const slice = slices[layer];
for (let i = 0; i < slice.parts.length; i ++) {
const part = slice.parts[i];
if (part.shape.closed) {
part.outerLine.scaleDown(1 / PRECISION);
for (let i = 0; i < part.innerLines.length; i ++) {
const innerLine = part.innerLines[i];
innerLine.scaleDown(1 / PRECISION);
}
part.fill.scaleDown(1 / PRECISION);
}
}
if (slice.support !== undefined) {
slice.support.scaleDown(1 / PRECISION);
}
if (slice.brim !== undefined) {
slice.brim.scaleDown(1 / PRECISION);
}
}
}

View File

@ -0,0 +1,40 @@
import Shape from 'Doodle3D/clipper-js';
import Slice from '../slice.js';
import { CLEAN_DELTA } from '../constants.js';
export default function shapesToSlices(shapes, settings) {
const sliceLayers = [];
for (let layer = 0; layer < shapes.length; layer ++) {
let { closedShapes, openShapes } = shapes[layer];
closedShapes = new Shape(closedShapes, true, true)
.clean(CLEAN_DELTA)
.fixOrientation()
.removeOverlap()
.seperateShapes();
openShapes = new Shape(openShapes, false, true)
.clean(CLEAN_DELTA);
const slice = new Slice();
for (let i = 0; i < closedShapes.length; i ++) {
const closedShape = closedShapes[i];
slice.add(closedShape);
// if (openShapes.path.length > 0) {
// openShapes = openShapes.difference(closedShape);
// }
}
if (openShapes.paths.length > 0) {
slice.add(openShapes);
}
sliceLayers.push(slice);
}
return sliceLayers;
}

View File

@ -0,0 +1,71 @@
import GCode from '../gcode.js';
export default function slicesToGCode(slices, settings) {
console.log('slices to gcode');
const gcode = new GCode(settings);
for (let layer = 0; layer < slices.length; layer ++) {
const slice = slices[layer];
if (layer === 1) {
gcode.turnFanOn();
gcode.bottom = false;
}
if (slice.brim !== undefined) {
pathToGCode(gcode, slice.brim, true, true, layer, 'brim');
}
for (let i = 0; i < slice.parts.length; i ++) {
const part = slice.parts[i];
if (part.shape.closed) {
pathToGCode(gcode, part.outerLine, false, true, layer, 'outerLine');
for (let i = 0; i < part.innerLines.length; i ++) {
const innerLine = part.innerLines[i];
pathToGCode(gcode, innerLine, false, false, layer, 'innerLine');
}
pathToGCode(gcode, part.fill, true, false, layer, 'fill');
} else {
const retract = !(slice.parts.length === 1 && slice.support === undefined);
pathToGCode(gcode, part.shape, retract, retract, layer, 'outerLine');
}
}
if (slice.support !== undefined) {
pathToGCode(gcode, slice.support, true, true, layer, 'support');
}
}
return gcode.getGCode();
}
function pathToGCode(gcode, shape, retract, unRetract, layer, type) {
for (let i = 0; i < shape.paths.length; i ++) {
const line = shape.paths[i];
const length = shape.closed ? (line.length + 1) : line.length;
for (let i = 0; i < length; i ++) {
const point = line[i % line.length];
if (i === 0) {
// TODO
// moveTo should impliment combing
gcode.moveTo(point.X, point.Y, layer);
if (unRetract) {
gcode.unRetract();
}
} else {
gcode.lineTo(point.X, point.Y, layer, type);
}
}
}
if (retract) {
gcode.retract();
}
}

View File

@ -1,23 +1,19 @@
import THREE from 'three.js';
import Paths from './paths.js';
import Slice from './slice.js';
import GCode from './gcode.js';
export default class {
constructor () {
this.progress = {
createdLines: false,
calculatedLayerIntersections: false,
sliced: false,
generatedSlices: false,
generatedInnerLines: false,
generatedInfills: false,
generatedSupport: false,
optimizedPaths: false,
generatedGCode: false
};
}
import EventDispatcher from 'casperlamboo/EventDispatcher';
import calculateLayersIntersections from './sliceActions/calculateLayersIntersections.js';
import createLines from './sliceActions/createLines.js';
import generateInfills from './sliceActions/generateInfills.js';
import generateInnerLines from './sliceActions/generateInnerLines.js';
import generateSupport from './sliceActions/generateSupport.js';
import intersectionsToShapes from './sliceActions/intersectionsToShapes.js';
import addBrim from './sliceActions/addBrim.js';
import optimizePaths from './sliceActions/optimizePaths.js';
import shapesToSlices from './sliceActions/shapesToSlices.js';
import slicesToGCode from './sliceActions/slicesToGCode.js';
import applyPrecision from './sliceActions/applyPrecision.js';
import removePrecision from './sliceActions/removePrecision.js';
export default class extends EventDispatcher {
setMesh(mesh) {
mesh.updateMatrix();
@ -25,17 +21,13 @@ export default class {
return this;
}
setGeometry(geometry, matrix) {
if (geometry.type === 'BufferGeometry') {
if (geometry instanceof THREE.BufferGeometry) {
geometry = new THREE.Geometry().fromBufferGeometry(geometry);
}
else if (geometry.type.endsWith('Geometry')) {
} else if (geometry instanceof THREE.Geometry) {
geometry = geometry.clone();
}
else {
console.warn('Geometry is not an instance of BufferGeometry or Geometry');
return;
} else {
throw 'Geometry is not an instance of BufferGeometry or Geometry';
}
if (matrix instanceof THREE.Matrix4) {
@ -49,711 +41,31 @@ export default class {
return this;
}
slice(settings) {
var supportEnabled = settings.config['supportEnabled'];
// get unique lines from geometry;
var lines = this._createLines(settings);
const lines = createLines(this.geometry, settings);
var {layerIntersectionIndexes, layerIntersectionPoints} = this._calculateLayersIntersections(lines, settings);
var shapes = this._intersectionsToShapes(layerIntersectionIndexes, layerIntersectionPoints, lines, settings);
var slices = this._shapesToSlices(shapes, settings);
this._generateInnerLines(slices, settings);
this._generateInfills(slices, settings);
if (supportEnabled) {
this._generateSupport(slices, settings);
}
this._optimizePaths(slices, settings);
var gcode = this._slicesToGCode(slices, settings);
if (this.onfinish !== undefined) {
this.onfinish(gcode);
}
return gcode;
}
_createLines (settings) {
console.log('constructing unique lines from geometry');
var lines = [];
var lineLookup = {};
var addLine = (a, b) => {
var index = lineLookup[b + '_' + a];
if (index === undefined) {
index = lines.length;
lineLookup[a + '_' + b] = index;
lines.push({
line: new THREE.Line3(this.geometry.vertices[a], this.geometry.vertices[b]),
connects: [],
normals: []
});
}
return index;
}
for (var i = 0; i < this.geometry.faces.length; i ++) {
var face = this.geometry.faces[i];
if (face.normal.y !== 1 && face.normal.y !== -1) {
var normal = new THREE.Vector2(face.normal.z, face.normal.x).normalize();
// check for only adding unique lines
// returns index of said line
var a = addLine(face.a, face.b);
var b = addLine(face.b, face.c);
var c = addLine(face.c, face.a);
// set connecting lines (based on face)
lines[a].connects.push(b, c);
lines[b].connects.push(c, a);
lines[c].connects.push(a, b);
lines[a].normals.push(normal);
lines[b].normals.push(normal);
lines[c].normals.push(normal);
}
}
this.progress.createdLines = true;
this._updateProgress(settings);
return lines;
}
_calculateLayersIntersections (lines, settings) {
console.log('calculating layer intersections');
var layerHeight = settings.config["layerHeight"];
var height = settings.config["dimensionsZ"];
var numLayers = Math.floor(height / layerHeight);
var layerIntersectionIndexes = [];
var layerIntersectionPoints = [];
for (var layer = 0; layer < numLayers; layer ++) {
layerIntersectionIndexes[layer] = [];
layerIntersectionPoints[layer] = [];
}
for (var lineIndex = 0; lineIndex < lines.length; lineIndex ++) {
var line = lines[lineIndex].line;
var min = Math.ceil(Math.min(line.start.y, line.end.y) / layerHeight);
var max = Math.floor(Math.max(line.start.y, line.end.y) / layerHeight);
for (var layerIndex = min; layerIndex <= max; layerIndex ++) {
if (layerIndex >= 0 && layerIndex < numLayers) {
layerIntersectionIndexes[layerIndex].push(lineIndex);
var y = layerIndex * layerHeight;
if (line.start.y === line.end.y) {
var x = line.start.x;
var z = line.start.z;
}
else {
var alpha = (y - line.start.y) / (line.end.y - line.start.y);
var x = line.end.x * alpha + line.start.x * (1 - alpha);
var z = line.end.z * alpha + line.start.z * (1 - alpha);
}
layerIntersectionPoints[layerIndex][lineIndex] = new THREE.Vector2(z, x);
}
}
}
this.progress.calculatedLayerIntersections = true;
this._updateProgress(settings);
return {
const {
layerIntersectionIndexes,
layerIntersectionPoints
};
}
_intersectionsToShapes (layerIntersectionIndexes, layerIntersectionPoints, lines, settings) {
console.log("generating slices");
var shapes = [];
for (var layer = 1; layer < layerIntersectionIndexes.length; layer ++) {
var intersectionIndexes = layerIntersectionIndexes[layer];
var intersectionPoints = layerIntersectionPoints[layer];
if (intersectionIndexes.length === 0) {
continue;
}
var shapeParts = [];
for (var i = 0; i < intersectionIndexes.length; i ++) {
var index = intersectionIndexes[i];
if (intersectionPoints[index] === undefined) {
continue;
}
var firstPoints = [index];
var isFirstPoint = true;
var closed = false;
var shape = [];
while (index !== -1) {
var intersection = intersectionPoints[index];
// uppercase X and Y because clipper vector
shape.push({X: intersection.x, Y: intersection.y});
delete intersectionPoints[index];
var connects = lines[index].connects;
var faceNormals = lines[index].normals;
for (var j = 0; j < connects.length; j ++) {
var index = connects[j];
if (firstPoints.indexOf(index) !== -1 && shape.length > 2) {
closed = true;
index = -1;
break;
}
// Check if index has an intersection or is already used
if (intersectionPoints[index] !== undefined) {
var faceNormal = faceNormals[Math.floor(j / 2)];
var a = new THREE.Vector2(intersection.x, intersection.y);
var b = new THREE.Vector2(intersectionPoints[index].x, intersectionPoints[index].y);
// can't calculate normal between points if distance is smaller as 0.0001
if ((faceNormal.x === 0 && faceNormal.y === 0) || a.distanceTo(b) < 0.0001) {
if (isFirstPoint) {
firstPoints.push(index);
}
delete intersectionPoints[index];
connects = connects.concat(lines[index].connects);
faceNormals = faceNormals.concat(lines[index].normals);
index = -1;
}
else {
// make sure the path goes the right direction
// THREE.Vector2.normal is not yet implimented
// var normal = a.sub(b).normal().normalize();
var normal = a.sub(b);
normal.set(-normal.y, normal.x).normalize();
if (normal.dot(faceNormal) > 0) {
break;
}
else {
index = -1;
}
}
}
else {
index = -1;
}
}
isFirstPoint = false;
}
if (!closed) {
var index = firstPoints[0];
while (index !== -1) {
if (firstPoints.indexOf(index) === -1) {
var intersection = intersectionPoints[index];
shape.unshift({X: intersection.x, Y: intersection.y});
delete intersectionPoints[index];
}
var connects = lines[index].connects;
for (var i = 0; i < connects.length; i ++) {
var index = connects[i];
if (intersectionPoints[index] !== undefined) {
break;
}
else {
index = -1;
}
}
}
}
var part = new Paths([shape], closed).clean(0.01);
if (part.length > 0) {
shapeParts.push(part);
}
}
shapes.push(shapeParts);
}
this.progress.sliced = true;
this._updateProgress(settings);
return shapes;
}
_shapesToSlices (shapes, settings) {
var slices = [];
for (var layer = 0; layer < shapes.length; layer ++) {
var shapeParts = shapes[layer];
var slice = new Slice();
var holes = [];
var outlines = [];
for (var i = 0; i < shapeParts.length; i ++) {
var shape = shapeParts[i];
} = calculateLayersIntersections(lines, settings);
if (!shape.closed) {
slice.add(shape);
}
else if (shape.isHole()) {
holes.push(shape);
}
else {
slice.add(shape);
outlines.push(shape);
}
}
outlines.sort((a, b) => {
return a.boundSize() - b.boundSize();
});
console.log('test');
if (holes.length > outlines.length) {
[holes, outlines] = [outlines, holes];
}
else if (holes.length === outlines.length) {
holes.sort((a, b) => {
return a.boundSize() - b.boundSize();
});
const shapes = intersectionsToShapes(layerIntersectionIndexes, layerIntersectionPoints, lines, settings);
if (holes[0].boundSize > outlines[0].boundSize()) {
[holes, outlines] = [outlines, holes];
}
}
for (var i = 0; i < holes.length; i ++) {
var hole = holes[i];
for (var j = 0; j < outlines.length; j ++) {
var outline = outlines[j];
if (outline.pointCollision(hole[0][0])) {
outline.join(hole);
break;
}
}
}
slice.removeSelfIntersect();
slices.push(slice);
}
this.progress.generatedSlices = true;
this._updateProgress(settings);
return slices;
}
_generateInnerLines (slices, settings) {
console.log("generating outer lines and inner lines");
// need to scale up everything because of clipper rounding errors
var scale = 100;
var layerHeight = settings.config["layerHeight"];
var nozzleDiameter = settings.config["nozzleDiameter"] * scale;
var shellThickness = settings.config["shellThickness"] * scale;
var nozzleRadius = nozzleDiameter / 2;
var shells = Math.round(shellThickness / nozzleDiameter);
for (var layer = 0; layer < slices.length; layer ++) {
var slice = slices[layer];
for (var i = 0; i < slice.parts.length; i ++) {
var part = slice.parts[i];
if (!part.intersect.closed) {
continue;
}
applyPrecision(shapes);
// var outerLine = part.intersect.clone().scaleUp(scale).offset(-nozzleRadius);
var outerLine = part.intersect.scaleUp(scale).offset(-nozzleRadius);
const slices = shapesToSlices(shapes, settings);
if (outerLine.length > 0) {
part.outerLine = outerLine;
generateInnerLines(slices, settings);
generateInfills(slices, settings);
generateSupport(slices, settings);
addBrim(slices, settings);
optimizePaths(slices, settings);
removePrecision(slices);
for (var shell = 1; shell < shells; shell += 1) {
var offset = shell * nozzleDiameter;
const gcode = slicesToGCode(slices, settings);
var innerLine = outerLine.offset(-offset);
if (innerLine.length > 0) {
part.innerLines.push(innerLine);
}
else {
break;
}
}
}
}
}
this.progress.generatedInnerLines = true;
this._updateProgress(settings);
}
_generateInfills (slices, settings) {
console.log("generating infills");
// need to scale up everything because of clipper rounding errors
var scale = 100;
var layerHeight = settings.config["layerHeight"];
var fillGridSize = settings.config["fillGridSize"] * scale;
var bottomThickness = settings.config["bottomThickness"];
var topThickness = settings.config["topThickness"];
var nozzleDiameter = settings.config["nozzleDiameter"] * scale;
var infillOverlap = settings.config["infillOverlap"] * scale;
var bottomSkinCount = Math.ceil(bottomThickness/layerHeight);
var topSkinCount = Math.ceil(topThickness/layerHeight);
var nozzleRadius = nozzleDiameter / 2;
var hightemplateSize = Math.sqrt(2 * Math.pow(nozzleDiameter, 2));
for (var layer = 0; layer < slices.length; layer ++) {
var slice = slices[layer];
if (layer - bottomSkinCount >= 0 && layer + topSkinCount < slices.length) {
var downSkin = slices[layer - bottomSkinCount].getOutline();
var upSkin = slices[layer + topSkinCount].getOutline();
var surroundingLayer = upSkin.intersect(downSkin);
}
else {
var surroundingLayer = false;
}
for (var i = 0; i < slice.parts.length; i ++) {
var part = slice.parts[i];
if (!part.intersect.closed) {
continue;
}
var outerLine = part.outerLine;
if (outerLine.length > 0) {
var inset = (part.innerLines.length > 0) ? part.innerLines[part.innerLines.length - 1] : outerLine;
var fillArea = inset.offset(-nozzleRadius);
var lowFillArea = false;
if (surroundingLayer) {
var highFillArea = fillArea.difference(surroundingLayer);
if (infillOverlap > 0) {
highFillArea = highFillArea.offset(infillOverlap);
}
highFillArea = highFillArea.intersect(fillArea);
var lowFillArea = fillArea.difference(highFillArea);
}
else {
var highFillArea = fillArea;
}
var fill = new Paths([], false);
if (lowFillArea && lowFillArea.length > 0) {
var bounds = lowFillArea.bounds();
var lowFillTemplate = this._getFillTemplate(bounds, fillGridSize, true, true);
part.fill.join(lowFillTemplate.intersect(lowFillArea));
}
if (highFillArea.length > 0) {
var bounds = highFillArea.bounds();
var even = (layer % 2 === 0);
var highFillTemplate = this._getFillTemplate(bounds, hightemplateSize, even, !even);
part.fill.join(highFillTemplate.intersect(highFillArea));
}
}
}
}
this.progress.generatedInfills = true;
this._updateProgress(settings);
}
_generateSupport (slices, settings) {
console.log("generating support");
// need to scale up everything because of clipper rounding errors
var scale = 100;
var layerHeight = settings.config["layerHeight"];
var supportGridSize = settings.config["supportGridSize"] * scale;
var supportAcceptanceMargin = settings.config["supportAcceptanceMargin"] * scale;
var supportMargin = settings.config["supportMargin"] * scale;
var plateSize = settings.config["supportPlateSize"] * scale;
var supportDistanceY = settings.config["supportDistanceY"];
var supportDistanceLayers = Math.max(Math.ceil(supportDistanceY / layerHeight), 1);
var nozzleDiameter = settings.config["nozzleDiameter"] * scale;
var supportAreas = new Paths([], true);
for (var layer = slices.length - 1 - supportDistanceLayers; layer >= 0; layer --) {
var currentSlice = slices[layer];
if (supportAreas.length > 0) {
if (layer >= supportDistanceLayers) {
var sliceSkin = slices[layer - supportDistanceLayers].getOutline();
sliceSkin = sliceSkin;
var supportAreasSlimmed = supportAreas.difference(sliceSkin.offset(supportMargin));
if (supportAreasSlimmed.area() < 100.0) {
supportAreas = supportAreas.difference(sliceSkin);
}
else {
supportAreas = supportAreasSlimmed;
}
}
var supportTemplate = this._getFillTemplate(supportAreas.bounds(), supportGridSize, true, true);
var supportFill = supportTemplate.intersect(supportAreas);
if (supportFill.length === 0) {
currentSlice.support = supportAreas.clone();
}
else {
currentSlice.support = supportFill;
}
}
var supportSkin = slices[layer + supportDistanceLayers - 1].getOutline();
var slice = slices[layer + supportDistanceLayers];
for (var i = 0; i < slice.parts.length; i ++) {
var slicePart = slice.parts[i];
if (slicePart.intersect.closed) {
var outerLine = slicePart.outerLine;
}
else {
var outerLine = slicePart.intersect.offset(supportAcceptanceMargin);
}
var overlap = supportSkin.offset(supportAcceptanceMargin).intersect(outerLine);
var overhang = outerLine.difference(overlap);
if (overlap.length === 0 || overhang.length > 0) {
supportAreas = supportAreas.join(overhang);
}
}
}
this.progress.generatedSupport = true;
this._updateProgress(settings);
}
_optimizePaths (slices, settings) {
console.log("opimize paths");
// need to scale up everything because of clipper rounding errors
var scale = 100;
var brimOffset = settings.config["brimOffset"] * scale;
var start = new THREE.Vector2(0, 0);
for (var layer = 0; layer < slices.length; layer ++) {
var slice = slices[layer];
if (layer === 0) {
slice.brim = slice.getOutline().offset(brimOffset);
}
start = slice.optimizePaths(start);
for (var i = 0; i < slice.parts.length; i ++) {
var part = slice.parts[i];
if (part.intersect.closed) {
part.outerLine.scaleDown(scale);
for (var j = 0; j < part.innerLines.length; j ++) {
var innerLine = part.innerLines[j];
innerLine.scaleDown(scale);
}
part.fill.scaleDown(scale);
}
}
if (slice.support !== undefined) {
slice.support.scaleDown(scale);
}
if (slice.brim !== undefined) {
slice.brim.scaleDown(scale);
}
}
this.progress.optimizedPaths = true;
this._updateProgress(settings);
}
_getFillTemplate (bounds, size, even, uneven) {
var paths = new Paths([], false);
var left = Math.floor(bounds.left / size) * size;
var right = Math.ceil(bounds.right / size) * size;
var top = Math.floor(bounds.top / size) * size;
var bottom = Math.ceil(bounds.bottom / size) * size;
var width = right - left;
if (even) {
for (var y = top; y <= bottom + width; y += size) {
paths.push([
{X: left, Y: y},
{X: right, Y: y - width}
]);
}
}
if (uneven) {
for (var y = top - width; y <= bottom; y += size) {
paths.push([
{X: left, Y: y},
{X: right, Y: y + width}
]);
}
}
return paths;
}
_slicesToGCode (slices, settings) {
var gcode = new GCode().setSettings(settings);
function pathToGCode (path, retract, unRetract, type) {
for (var i = 0; i < path.length; i ++) {
var shape = path[i];
var length = path.closed ? (shape.length + 1) : shape.length;
for (var j = 0; j < length; j ++) {
var point = shape[j % shape.length];
if (j === 0) {
// TODO
// moveTo should impliment combing
gcode.moveTo(point.X, point.Y, layer);
if (unRetract) {
gcode.unRetract();
}
}
else {
gcode.lineTo(point.X, point.Y, layer, type);
}
}
}
if (retract) {
gcode.retract();
}
}
for (var layer = 0; layer < slices.length; layer ++) {
var slice = slices[layer];
if (layer === 1) {
gcode.turnFanOn();
gcode.bottom = false;
}
if (slice.brim !== undefined) {
pathToGCode(slice.brim, true, true, "brim");
}
for (var i = 0; i < slice.parts.length; i ++) {
var part = slice.parts[i];
if (part.intersect.closed) {
pathToGCode(part.outerLine, false, true, "outerLine");
for (var j = 0; j < part.innerLines.length; j ++) {
var innerLine = part.innerLines[j];
pathToGCode(innerLine, false, false, "innerLine");
}
pathToGCode(part.fill, true, false, "fill");
}
else {
var retract = !(slice.parts.length === 1 && slice.support === undefined);
pathToGCode(part.intersect, retract, retract, "outerLine");
}
}
if (slice.support !== undefined) {
pathToGCode(slice.support, true, true, "support");
}
}
this.progress.generatedGCode = true;
this._updateProgress(settings);
return gcode.getGCode();
}
_updateProgress (settings) {
if (this.onprogress !== undefined) {
var supportEnabled = settings.config["supportEnabled"];
var progress = {};
var procent = 0;
var length = 0;
for (var i in this.progress) {
if (!(!supportEnabled && i === "generatedSupport")) {
progress[i] = this.progress[i];
if (progress[i]) {
procent += 1;
}
length += 1;
}
}
progress.procent = procent / length;
this.onprogress(progress);
}
this.dispatchEvent({ type: 'finish', gcode });
return gcode;
}
}

View File

@ -1,98 +0,0 @@
import THREE from 'three.js';
import Settings from './settings.js';
export default class {
constructor () {
this.worker = new Worker('./worker.js');
this.worker.addEventListener('message', (event) => {
switch (event.data['cmd']) {
case 'PROGRESS':
if (this.onprogress !== undefined) {
var progress = event.data['progress'];
this.onprogress(progress);
}
break;
case 'GCODE':
if (this.onfinish !== undefined) {
var reader = new FileReader();
reader.addEventListener("loadend", () => {
var gcode = reader.result;
this.onfinish(gcode);
});
reader.readAsBinaryString(event.data['gcode']);
}
break;
}
}, false);
this.worker.onerror = function (error) {
console.warn(error);
};
}
setMesh (mesh) {
mesh.updateMatrix();
this.setGeometry(mesh.geometry, mesh.matrix);
return this;
}
setGeometry (geometry, matrix) {
if (geometry.type === 'Geometry') {
geometry = new THREE.BufferGeometry().fromGeometry(geometry);
}
else if (geometry.type === 'BufferGeometry') {
geometry = geometry.clone();
}
else {
console.warn('Geometry is not an instance of BufferGeometry or Geometry');
return;
}
if (!(matrix instanceof THREE.Matrix4)) {
matrix = new THREE.Matrix4();
}
var buffers = [];
for (var i = 0; i < geometry.attributesKeys.length; i ++) {
var key = geometry.attributesKeys[i];
buffers.push(geometry.attributes[key].array.buffer);
}
delete geometry.boundingBox;
delete geometry.boundingSphere;
this.worker.postMessage({
'cmd': 'SET_MESH',
'geometry': {
'attributes': geometry.attributes,
'attributesKeys': geometry.attributesKeys
},
'matrix': matrix.toArray()
}, buffers);
return this;
}
slice (settings) {
this.worker.postMessage({
'cmd': 'SLICE',
'settings': settings.config
});
return this;
}
close () {
this.worker.postMessage({
'cmd': 'CLOSE'
});
return this;
}
}

View File

@ -1,55 +0,0 @@
importScripts('../jspm_packages/system.js');
importScripts('../config.js');
var Slicer, Settings, THREE;
function init () {
var slicer = new Slicer();
slicer.onProgress = function (progress) {
self.postMessage({
'cmd': 'PROGRESS',
'progress': progress
});
};
self.addEventListener('message', function (event) {
switch (event.data['cmd']) {
case 'SET_MESH':
var geometry = new THREE.Geometry().fromBufferGeometry(event.data['geometry']);
var matrix = new THREE.Matrix4().fromArray(event.data['matrix']);
slicer.setGeometry(geometry, matrix);
break;
case 'SLICE':
var settings = new Settings().updateConfig(event.data['settings']);
var gcode = slicer.slice(settings);
var blob = new Blob([gcode], {type: 'text/plain'});
self.postMessage({
'cmd': 'GCODE',
'gcode': blob
});
//self.close();
break;
case 'CLOSE':
self.close();
break;
}
});
}
Promise.all([
System.import('./slicer'),
System.import('./settings'),
System.import('three.js')
]).then(function(modules) {
Slicer = modules[0].default;
Settings = modules[1].default;
THREE = modules[2];
init();
});