mirror of
https://github.com/Doodle3D/Doodle3D-Slicer.git
synced 2024-11-25 23:17:57 +01:00
Merge branch 'development'
This commit is contained in:
commit
b0ac8313fd
@ -16,7 +16,7 @@ doodleBox.onload = function () {
|
||||
var geometry = new THREE.TorusGeometry(40, 20, 10, 10);
|
||||
|
||||
var slicer = new D3D.Slicer().setGeometry(geometry);
|
||||
var gcode = slicer.getGcode(doodlBox.printer.config);
|
||||
var gcode = slicer.getGcode(doodleBox.printer);
|
||||
|
||||
doodleBox.print(gcode);
|
||||
};
|
||||
|
@ -4,7 +4,6 @@
|
||||
<title>Doedel Drie Dee</title>
|
||||
<!--<script src="http://code.jquery.com/jquery-1.11.0.min.js"></script>-->
|
||||
<script src="library/jquery.js"></script>
|
||||
<script src="library/cal.js"></script>
|
||||
<script src="library/three.js"></script>
|
||||
|
||||
<script src="src/utils.js"></script>
|
||||
|
2329
library/cal.js
2329
library/cal.js
File diff suppressed because it is too large
Load Diff
595
library/csg.js
595
library/csg.js
@ -1,595 +0,0 @@
|
||||
// Constructive Solid Geometry (CSG) is a modeling technique that uses Boolean
|
||||
// operations like union and intersection to combine 3D solids. This library
|
||||
// implements CSG operations on meshes elegantly and concisely using BSP trees,
|
||||
// and is meant to serve as an easily understandable implementation of the
|
||||
// algorithm. All edge cases involving overlapping coplanar polygons in both
|
||||
// solids are correctly handled.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// var cube = CSG.cube();
|
||||
// var sphere = CSG.sphere({ radius: 1.3 });
|
||||
// var polygons = cube.subtract(sphere).toPolygons();
|
||||
//
|
||||
// ## Implementation Details
|
||||
//
|
||||
// All CSG operations are implemented in terms of two functions, `clipTo()` and
|
||||
// `invert()`, which remove parts of a BSP tree inside another BSP tree and swap
|
||||
// solid and empty space, respectively. To find the union of `a` and `b`, we
|
||||
// want to remove everything in `a` inside `b` and everything in `b` inside `a`,
|
||||
// then combine polygons from `a` and `b` into one solid:
|
||||
//
|
||||
// a.clipTo(b);
|
||||
// b.clipTo(a);
|
||||
// a.build(b.allPolygons());
|
||||
//
|
||||
// The only tricky part is handling overlapping coplanar polygons in both trees.
|
||||
// The code above keeps both copies, but we need to keep them in one tree and
|
||||
// remove them in the other tree. To remove them from `b` we can clip the
|
||||
// inverse of `b` against `a`. The code for union now looks like this:
|
||||
//
|
||||
// a.clipTo(b);
|
||||
// b.clipTo(a);
|
||||
// b.invert();
|
||||
// b.clipTo(a);
|
||||
// b.invert();
|
||||
// a.build(b.allPolygons());
|
||||
//
|
||||
// Subtraction and intersection naturally follow from set operations. If
|
||||
// union is `A | B`, subtraction is `A - B = ~(~A | B)` and intersection is
|
||||
// `A & B = ~(~A | ~B)` where `~` is the complement operator.
|
||||
//
|
||||
// ## License
|
||||
//
|
||||
// Copyright (c) 2011 Evan Wallace (http://madebyevan.com/), under the MIT license.
|
||||
|
||||
// # class CSG
|
||||
|
||||
// Holds a binary space partition tree representing a 3D solid. Two solids can
|
||||
// be combined using the `union()`, `subtract()`, and `intersect()` methods.
|
||||
|
||||
CSG = function() {
|
||||
this.polygons = [];
|
||||
};
|
||||
|
||||
// Construct a CSG solid from a list of `CSG.Polygon` instances.
|
||||
CSG.fromPolygons = function(polygons) {
|
||||
var csg = new CSG();
|
||||
csg.polygons = polygons;
|
||||
return csg;
|
||||
};
|
||||
|
||||
CSG.prototype = {
|
||||
clone: function() {
|
||||
var csg = new CSG();
|
||||
csg.polygons = this.polygons.map(function(p) { return p.clone(); });
|
||||
return csg;
|
||||
},
|
||||
|
||||
toPolygons: function() {
|
||||
return this.polygons;
|
||||
},
|
||||
|
||||
// Return a new CSG solid representing space in either this solid or in the
|
||||
// solid `csg`. Neither this solid nor the solid `csg` are modified.
|
||||
//
|
||||
// A.union(B)
|
||||
//
|
||||
// +-------+ +-------+
|
||||
// | | | |
|
||||
// | A | | |
|
||||
// | +--+----+ = | +----+
|
||||
// +----+--+ | +----+ |
|
||||
// | B | | |
|
||||
// | | | |
|
||||
// +-------+ +-------+
|
||||
//
|
||||
union: function(csg) {
|
||||
var a = new CSG.Node(this.clone().polygons);
|
||||
var b = new CSG.Node(csg.clone().polygons);
|
||||
a.clipTo(b);
|
||||
b.clipTo(a);
|
||||
b.invert();
|
||||
b.clipTo(a);
|
||||
b.invert();
|
||||
a.build(b.allPolygons());
|
||||
return CSG.fromPolygons(a.allPolygons());
|
||||
},
|
||||
|
||||
// Return a new CSG solid representing space in this solid but not in the
|
||||
// solid `csg`. Neither this solid nor the solid `csg` are modified.
|
||||
//
|
||||
// A.subtract(B)
|
||||
//
|
||||
// +-------+ +-------+
|
||||
// | | | |
|
||||
// | A | | |
|
||||
// | +--+----+ = | +--+
|
||||
// +----+--+ | +----+
|
||||
// | B |
|
||||
// | |
|
||||
// +-------+
|
||||
//
|
||||
subtract: function(csg) {
|
||||
var a = new CSG.Node(this.clone().polygons);
|
||||
var b = new CSG.Node(csg.clone().polygons);
|
||||
a.invert();
|
||||
a.clipTo(b);
|
||||
b.clipTo(a);
|
||||
b.invert();
|
||||
b.clipTo(a);
|
||||
b.invert();
|
||||
a.build(b.allPolygons());
|
||||
a.invert();
|
||||
return CSG.fromPolygons(a.allPolygons());
|
||||
},
|
||||
|
||||
// Return a new CSG solid representing space both this solid and in the
|
||||
// solid `csg`. Neither this solid nor the solid `csg` are modified.
|
||||
//
|
||||
// A.intersect(B)
|
||||
//
|
||||
// +-------+
|
||||
// | |
|
||||
// | A |
|
||||
// | +--+----+ = +--+
|
||||
// +----+--+ | +--+
|
||||
// | B |
|
||||
// | |
|
||||
// +-------+
|
||||
//
|
||||
intersect: function(csg) {
|
||||
var a = new CSG.Node(this.clone().polygons);
|
||||
var b = new CSG.Node(csg.clone().polygons);
|
||||
a.invert();
|
||||
b.clipTo(a);
|
||||
b.invert();
|
||||
a.clipTo(b);
|
||||
b.clipTo(a);
|
||||
a.build(b.allPolygons());
|
||||
a.invert();
|
||||
return CSG.fromPolygons(a.allPolygons());
|
||||
},
|
||||
|
||||
// Return a new CSG solid with solid and empty space switched. This solid is
|
||||
// not modified.
|
||||
inverse: function() {
|
||||
var csg = this.clone();
|
||||
csg.polygons.map(function(p) { p.flip(); });
|
||||
return csg;
|
||||
}
|
||||
};
|
||||
|
||||
// Construct an axis-aligned solid cuboid. Optional parameters are `center` and
|
||||
// `radius`, which default to `[0, 0, 0]` and `[1, 1, 1]`. The radius can be
|
||||
// specified using a single number or a list of three numbers, one for each axis.
|
||||
//
|
||||
// Example code:
|
||||
//
|
||||
// var cube = CSG.cube({
|
||||
// center: [0, 0, 0],
|
||||
// radius: 1
|
||||
// });
|
||||
CSG.cube = function(options) {
|
||||
options = options || {};
|
||||
var c = new CSG.Vector(options.center || [0, 0, 0]);
|
||||
var r = !options.radius ? [1, 1, 1] : options.radius.length ?
|
||||
options.radius : [options.radius, options.radius, options.radius];
|
||||
return CSG.fromPolygons([
|
||||
[[0, 4, 6, 2], [-1, 0, 0]],
|
||||
[[1, 3, 7, 5], [+1, 0, 0]],
|
||||
[[0, 1, 5, 4], [0, -1, 0]],
|
||||
[[2, 6, 7, 3], [0, +1, 0]],
|
||||
[[0, 2, 3, 1], [0, 0, -1]],
|
||||
[[4, 5, 7, 6], [0, 0, +1]]
|
||||
].map(function(info) {
|
||||
return new CSG.Polygon(info[0].map(function(i) {
|
||||
var pos = new CSG.Vector(
|
||||
c.x + r[0] * (2 * !!(i & 1) - 1),
|
||||
c.y + r[1] * (2 * !!(i & 2) - 1),
|
||||
c.z + r[2] * (2 * !!(i & 4) - 1)
|
||||
);
|
||||
return new CSG.Vertex(pos, new CSG.Vector(info[1]));
|
||||
}));
|
||||
}));
|
||||
};
|
||||
|
||||
// Construct a solid sphere. Optional parameters are `center`, `radius`,
|
||||
// `slices`, and `stacks`, which default to `[0, 0, 0]`, `1`, `16`, and `8`.
|
||||
// The `slices` and `stacks` parameters control the tessellation along the
|
||||
// longitude and latitude directions.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// var sphere = CSG.sphere({
|
||||
// center: [0, 0, 0],
|
||||
// radius: 1,
|
||||
// slices: 16,
|
||||
// stacks: 8
|
||||
// });
|
||||
CSG.sphere = function(options) {
|
||||
options = options || {};
|
||||
var c = new CSG.Vector(options.center || [0, 0, 0]);
|
||||
var r = options.radius || 1;
|
||||
var slices = options.slices || 16;
|
||||
var stacks = options.stacks || 8;
|
||||
var polygons = [], vertices;
|
||||
function vertex(theta, phi) {
|
||||
theta *= Math.PI * 2;
|
||||
phi *= Math.PI;
|
||||
var dir = new CSG.Vector(
|
||||
Math.cos(theta) * Math.sin(phi),
|
||||
Math.cos(phi),
|
||||
Math.sin(theta) * Math.sin(phi)
|
||||
);
|
||||
vertices.push(new CSG.Vertex(c.plus(dir.times(r)), dir));
|
||||
}
|
||||
for (var i = 0; i < slices; i++) {
|
||||
for (var j = 0; j < stacks; j++) {
|
||||
vertices = [];
|
||||
vertex(i / slices, j / stacks);
|
||||
if (j > 0) vertex((i + 1) / slices, j / stacks);
|
||||
if (j < stacks - 1) vertex((i + 1) / slices, (j + 1) / stacks);
|
||||
vertex(i / slices, (j + 1) / stacks);
|
||||
polygons.push(new CSG.Polygon(vertices));
|
||||
}
|
||||
}
|
||||
return CSG.fromPolygons(polygons);
|
||||
};
|
||||
|
||||
// Construct a solid cylinder. Optional parameters are `start`, `end`,
|
||||
// `radius`, and `slices`, which default to `[0, -1, 0]`, `[0, 1, 0]`, `1`, and
|
||||
// `16`. The `slices` parameter controls the tessellation.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// var cylinder = CSG.cylinder({
|
||||
// start: [0, -1, 0],
|
||||
// end: [0, 1, 0],
|
||||
// radius: 1,
|
||||
// slices: 16
|
||||
// });
|
||||
CSG.cylinder = function(options) {
|
||||
options = options || {};
|
||||
var s = new CSG.Vector(options.start || [0, -1, 0]);
|
||||
var e = new CSG.Vector(options.end || [0, 1, 0]);
|
||||
var ray = e.minus(s);
|
||||
var r = options.radius || 1;
|
||||
var slices = options.slices || 16;
|
||||
var axisZ = ray.unit(), isY = (Math.abs(axisZ.y) > 0.5);
|
||||
var axisX = new CSG.Vector(isY, !isY, 0).cross(axisZ).unit();
|
||||
var axisY = axisX.cross(axisZ).unit();
|
||||
var start = new CSG.Vertex(s, axisZ.negated());
|
||||
var end = new CSG.Vertex(e, axisZ.unit());
|
||||
var polygons = [];
|
||||
function point(stack, slice, normalBlend) {
|
||||
var angle = slice * Math.PI * 2;
|
||||
var out = axisX.times(Math.cos(angle)).plus(axisY.times(Math.sin(angle)));
|
||||
var pos = s.plus(ray.times(stack)).plus(out.times(r));
|
||||
var normal = out.times(1 - Math.abs(normalBlend)).plus(axisZ.times(normalBlend));
|
||||
return new CSG.Vertex(pos, normal);
|
||||
}
|
||||
for (var i = 0; i < slices; i++) {
|
||||
var t0 = i / slices, t1 = (i + 1) / slices;
|
||||
polygons.push(new CSG.Polygon([start, point(0, t0, -1), point(0, t1, -1)]));
|
||||
polygons.push(new CSG.Polygon([point(0, t1, 0), point(0, t0, 0), point(1, t0, 0), point(1, t1, 0)]));
|
||||
polygons.push(new CSG.Polygon([end, point(1, t1, 1), point(1, t0, 1)]));
|
||||
}
|
||||
return CSG.fromPolygons(polygons);
|
||||
};
|
||||
|
||||
// # class Vector
|
||||
|
||||
// Represents a 3D vector.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// new CSG.Vector(1, 2, 3);
|
||||
// new CSG.Vector([1, 2, 3]);
|
||||
// new CSG.Vector({ x: 1, y: 2, z: 3 });
|
||||
|
||||
CSG.Vector = function(x, y, z) {
|
||||
if (arguments.length == 3) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
} else if ('x' in x) {
|
||||
this.x = x.x;
|
||||
this.y = x.y;
|
||||
this.z = x.z;
|
||||
} else {
|
||||
this.x = x[0];
|
||||
this.y = x[1];
|
||||
this.z = x[2];
|
||||
}
|
||||
};
|
||||
|
||||
CSG.Vector.prototype = {
|
||||
clone: function() {
|
||||
return new CSG.Vector(this.x, this.y, this.z);
|
||||
},
|
||||
|
||||
negated: function() {
|
||||
return new CSG.Vector(-this.x, -this.y, -this.z);
|
||||
},
|
||||
|
||||
plus: function(a) {
|
||||
return new CSG.Vector(this.x + a.x, this.y + a.y, this.z + a.z);
|
||||
},
|
||||
|
||||
minus: function(a) {
|
||||
return new CSG.Vector(this.x - a.x, this.y - a.y, this.z - a.z);
|
||||
},
|
||||
|
||||
times: function(a) {
|
||||
return new CSG.Vector(this.x * a, this.y * a, this.z * a);
|
||||
},
|
||||
|
||||
dividedBy: function(a) {
|
||||
return new CSG.Vector(this.x / a, this.y / a, this.z / a);
|
||||
},
|
||||
|
||||
dot: function(a) {
|
||||
return this.x * a.x + this.y * a.y + this.z * a.z;
|
||||
},
|
||||
|
||||
lerp: function(a, t) {
|
||||
return this.plus(a.minus(this).times(t));
|
||||
},
|
||||
|
||||
length: function() {
|
||||
return Math.sqrt(this.dot(this));
|
||||
},
|
||||
|
||||
unit: function() {
|
||||
return this.dividedBy(this.length());
|
||||
},
|
||||
|
||||
cross: function(a) {
|
||||
return new CSG.Vector(
|
||||
this.y * a.z - this.z * a.y,
|
||||
this.z * a.x - this.x * a.z,
|
||||
this.x * a.y - this.y * a.x
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// # class Vertex
|
||||
|
||||
// Represents a vertex of a polygon. Use your own vertex class instead of this
|
||||
// one to provide additional features like texture coordinates and vertex
|
||||
// colors. Custom vertex classes need to provide a `pos` property and `clone()`,
|
||||
// `flip()`, and `interpolate()` methods that behave analogous to the ones
|
||||
// defined by `CSG.Vertex`. This class provides `normal` so convenience
|
||||
// functions like `CSG.sphere()` can return a smooth vertex normal, but `normal`
|
||||
// is not used anywhere else.
|
||||
|
||||
CSG.Vertex = function(pos, normal) {
|
||||
this.pos = new CSG.Vector(pos);
|
||||
this.normal = new CSG.Vector(normal);
|
||||
};
|
||||
|
||||
CSG.Vertex.prototype = {
|
||||
clone: function() {
|
||||
return new CSG.Vertex(this.pos.clone(), this.normal.clone());
|
||||
},
|
||||
|
||||
// Invert all orientation-specific data (e.g. vertex normal). Called when the
|
||||
// orientation of a polygon is flipped.
|
||||
flip: function() {
|
||||
this.normal = this.normal.negated();
|
||||
},
|
||||
|
||||
// Create a new vertex between this vertex and `other` by linearly
|
||||
// interpolating all properties using a parameter of `t`. Subclasses should
|
||||
// override this to interpolate additional properties.
|
||||
interpolate: function(other, t) {
|
||||
return new CSG.Vertex(
|
||||
this.pos.lerp(other.pos, t),
|
||||
this.normal.lerp(other.normal, t)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// # class Plane
|
||||
|
||||
// Represents a plane in 3D space.
|
||||
|
||||
CSG.Plane = function(normal, w) {
|
||||
this.normal = normal;
|
||||
this.w = w;
|
||||
};
|
||||
|
||||
// `CSG.Plane.EPSILON` is the tolerance used by `splitPolygon()` to decide if a
|
||||
// point is on the plane.
|
||||
CSG.Plane.EPSILON = 1e-5;
|
||||
|
||||
CSG.Plane.fromPoints = function(a, b, c) {
|
||||
var n = b.minus(a).cross(c.minus(a)).unit();
|
||||
return new CSG.Plane(n, n.dot(a));
|
||||
};
|
||||
|
||||
CSG.Plane.prototype = {
|
||||
clone: function() {
|
||||
return new CSG.Plane(this.normal.clone(), this.w);
|
||||
},
|
||||
|
||||
flip: function() {
|
||||
this.normal = this.normal.negated();
|
||||
this.w = -this.w;
|
||||
},
|
||||
|
||||
// Split `polygon` by this plane if needed, then put the polygon or polygon
|
||||
// fragments in the appropriate lists. Coplanar polygons go into either
|
||||
// `coplanarFront` or `coplanarBack` depending on their orientation with
|
||||
// respect to this plane. Polygons in front or in back of this plane go into
|
||||
// either `front` or `back`.
|
||||
splitPolygon: function(polygon, coplanarFront, coplanarBack, front, back) {
|
||||
var COPLANAR = 0;
|
||||
var FRONT = 1;
|
||||
var BACK = 2;
|
||||
var SPANNING = 3;
|
||||
|
||||
// Classify each point as well as the entire polygon into one of the above
|
||||
// four classes.
|
||||
var polygonType = 0;
|
||||
var types = [];
|
||||
for (var i = 0; i < polygon.vertices.length; i++) {
|
||||
var t = this.normal.dot(polygon.vertices[i].pos) - this.w;
|
||||
var type = (t < -CSG.Plane.EPSILON) ? BACK : (t > CSG.Plane.EPSILON) ? FRONT : COPLANAR;
|
||||
polygonType |= type;
|
||||
types.push(type);
|
||||
}
|
||||
|
||||
// Put the polygon in the correct list, splitting it when necessary.
|
||||
switch (polygonType) {
|
||||
case COPLANAR:
|
||||
(this.normal.dot(polygon.plane.normal) > 0 ? coplanarFront : coplanarBack).push(polygon);
|
||||
break;
|
||||
case FRONT:
|
||||
front.push(polygon);
|
||||
break;
|
||||
case BACK:
|
||||
back.push(polygon);
|
||||
break;
|
||||
case SPANNING:
|
||||
var f = [], b = [];
|
||||
for (var i = 0; i < polygon.vertices.length; i++) {
|
||||
var j = (i + 1) % polygon.vertices.length;
|
||||
var ti = types[i], tj = types[j];
|
||||
var vi = polygon.vertices[i], vj = polygon.vertices[j];
|
||||
if (ti != BACK) f.push(vi);
|
||||
if (ti != FRONT) b.push(ti != BACK ? vi.clone() : vi);
|
||||
if ((ti | tj) == SPANNING) {
|
||||
var t = (this.w - this.normal.dot(vi.pos)) / this.normal.dot(vj.pos.minus(vi.pos));
|
||||
var v = vi.interpolate(vj, t);
|
||||
f.push(v);
|
||||
b.push(v.clone());
|
||||
}
|
||||
}
|
||||
if (f.length >= 3) front.push(new CSG.Polygon(f, polygon.shared));
|
||||
if (b.length >= 3) back.push(new CSG.Polygon(b, polygon.shared));
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// # class Polygon
|
||||
|
||||
// Represents a convex polygon. The vertices used to initialize a polygon must
|
||||
// be coplanar and form a convex loop. They do not have to be `CSG.Vertex`
|
||||
// instances but they must behave similarly (duck typing can be used for
|
||||
// customization).
|
||||
//
|
||||
// Each convex polygon has a `shared` property, which is shared between all
|
||||
// polygons that are clones of each other or were split from the same polygon.
|
||||
// This can be used to define per-polygon properties (such as surface color).
|
||||
|
||||
CSG.Polygon = function(vertices, shared) {
|
||||
this.vertices = vertices;
|
||||
this.shared = shared;
|
||||
this.plane = CSG.Plane.fromPoints(vertices[0].pos, vertices[1].pos, vertices[2].pos);
|
||||
};
|
||||
|
||||
CSG.Polygon.prototype = {
|
||||
clone: function() {
|
||||
var vertices = this.vertices.map(function(v) { return v.clone(); });
|
||||
return new CSG.Polygon(vertices, this.shared);
|
||||
},
|
||||
|
||||
flip: function() {
|
||||
this.vertices.reverse().map(function(v) { v.flip(); });
|
||||
this.plane.flip();
|
||||
}
|
||||
};
|
||||
|
||||
// # class Node
|
||||
|
||||
// Holds a node in a BSP tree. A BSP tree is built from a collection of polygons
|
||||
// by picking a polygon to split along. That polygon (and all other coplanar
|
||||
// polygons) are added directly to that node and the other polygons are added to
|
||||
// the front and/or back subtrees. This is not a leafy BSP tree since there is
|
||||
// no distinction between internal and leaf nodes.
|
||||
|
||||
CSG.Node = function(polygons) {
|
||||
this.plane = null;
|
||||
this.front = null;
|
||||
this.back = null;
|
||||
this.polygons = [];
|
||||
if (polygons) this.build(polygons);
|
||||
};
|
||||
|
||||
CSG.Node.prototype = {
|
||||
clone: function() {
|
||||
var node = new CSG.Node();
|
||||
node.plane = this.plane && this.plane.clone();
|
||||
node.front = this.front && this.front.clone();
|
||||
node.back = this.back && this.back.clone();
|
||||
node.polygons = this.polygons.map(function(p) { return p.clone(); });
|
||||
return node;
|
||||
},
|
||||
|
||||
// Convert solid space to empty space and empty space to solid space.
|
||||
invert: function() {
|
||||
for (var i = 0; i < this.polygons.length; i++) {
|
||||
this.polygons[i].flip();
|
||||
}
|
||||
this.plane.flip();
|
||||
if (this.front) this.front.invert();
|
||||
if (this.back) this.back.invert();
|
||||
var temp = this.front;
|
||||
this.front = this.back;
|
||||
this.back = temp;
|
||||
},
|
||||
|
||||
// Recursively remove all polygons in `polygons` that are inside this BSP
|
||||
// tree.
|
||||
clipPolygons: function(polygons) {
|
||||
if (!this.plane) return polygons.slice();
|
||||
var front = [], back = [];
|
||||
for (var i = 0; i < polygons.length; i++) {
|
||||
this.plane.splitPolygon(polygons[i], front, back, front, back);
|
||||
}
|
||||
if (this.front) front = this.front.clipPolygons(front);
|
||||
if (this.back) back = this.back.clipPolygons(back);
|
||||
else back = [];
|
||||
return front.concat(back);
|
||||
},
|
||||
|
||||
// Remove all polygons in this BSP tree that are inside the other BSP tree
|
||||
// `bsp`.
|
||||
clipTo: function(bsp) {
|
||||
this.polygons = bsp.clipPolygons(this.polygons);
|
||||
if (this.front) this.front.clipTo(bsp);
|
||||
if (this.back) this.back.clipTo(bsp);
|
||||
},
|
||||
|
||||
// Return a list of all polygons in this BSP tree.
|
||||
allPolygons: function() {
|
||||
var polygons = this.polygons.slice();
|
||||
if (this.front) polygons = polygons.concat(this.front.allPolygons());
|
||||
if (this.back) polygons = polygons.concat(this.back.allPolygons());
|
||||
return polygons;
|
||||
},
|
||||
|
||||
// Build a BSP tree out of `polygons`. When called on an existing tree, the
|
||||
// new polygons are filtered down to the bottom of the tree and become new
|
||||
// nodes there. Each set of polygons is partitioned using the first polygon
|
||||
// (no heuristic is used to pick a good split).
|
||||
build: function(polygons) {
|
||||
if (!polygons.length) return;
|
||||
if (!this.plane) this.plane = polygons[0].plane.clone();
|
||||
var front = [], back = [];
|
||||
for (var i = 0; i < polygons.length; i++) {
|
||||
this.plane.splitPolygon(polygons[i], this.polygons, this.polygons, front, back);
|
||||
}
|
||||
if (front.length) {
|
||||
if (!this.front) this.front = new CSG.Node();
|
||||
this.front.build(front);
|
||||
}
|
||||
if (back.length) {
|
||||
if (!this.back) this.back = new CSG.Node();
|
||||
this.back.build(back);
|
||||
}
|
||||
}
|
||||
};
|
@ -5,7 +5,6 @@
|
||||
<!--<script src="http://code.jquery.com/jquery-1.11.0.min.js"></script>-->
|
||||
<script src="library/jquery.js"></script>
|
||||
<script src="library/three.js"></script>
|
||||
<script src="library/cal.js"></script>
|
||||
<script src="library/clipper.js"></script>
|
||||
|
||||
<script src="src/utils.js"></script>
|
||||
@ -25,15 +24,17 @@ canvas {border: 1px solid black;}
|
||||
<canvas id="canvas" width="400" height="400"></canvas>
|
||||
|
||||
<script>
|
||||
//nieuwe config
|
||||
//geimplimenteerd worden in de doodlebox?
|
||||
var printerConfig = {
|
||||
"printer.baudrate": "115200",
|
||||
"printer.baudrate": "115200", //wat is dit?
|
||||
"printer.bed.temperature": 70,
|
||||
"printer.bottomFlowRate": 1.0,
|
||||
"printer.bottomLayerSpeed": 35,
|
||||
"printer.dimensions.x": 200,
|
||||
"printer.dimensions.y": 200,
|
||||
"printer.dimensions.z": 200,
|
||||
"printer.enableTraveling": true,
|
||||
"printer.enableTraveling": true, //wat is dit?
|
||||
"printer.endcode": "M107 ;fan off\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-5 X-20 Y-20 F9000 ;move Z up a bit and retract filament even more\nG28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\nM84 ;disable axes / steppers\nG90 ;absolute positioning\nM104 S{preheatTemp}\n{if heatedBed}M140 S{preheatBedTemp}\nM117 Done ;display message (20 characters to clear whole screen)",
|
||||
"printer.filamentThickness": 2.89,
|
||||
"printer.firstLayerSlow": true,
|
||||
@ -43,7 +44,7 @@ var printerConfig = {
|
||||
"printer.heatup.temperature": 180,
|
||||
"printer.layerHeight": 0.3,
|
||||
"printer.retraction.amount": 3,
|
||||
"printer.retraction.enabled": true,
|
||||
"printer.retraction.enabled": false,
|
||||
"printer.retraction.minDistance": 5,
|
||||
"printer.retraction.speed": 50,
|
||||
"printer.screenToMillimeterScale": 0.3,
|
||||
@ -52,15 +53,20 @@ var printerConfig = {
|
||||
"printer.temperature": 230,
|
||||
"printer.travelSpeed": 200,
|
||||
"printer.type": "ultimaker",
|
||||
"printer.useSubLayers": true,
|
||||
"printer.wallThickness": 0.5
|
||||
};
|
||||
"printer.useSubLayers": true, //wat is dit?
|
||||
"printer.wallThickness": 0.4,
|
||||
|
||||
//var localIp = location.hash.substring(1);
|
||||
//var doodleBox = new D3D.Box(localIp);
|
||||
var doodleBox = {
|
||||
printer: new D3D.Printer(printerConfig)
|
||||
//variabele toevoegen;
|
||||
//overleg met rick;
|
||||
//-snelheid, retraction etc voor verschillende types (outerlayer, innerlayer, fill)
|
||||
"printer.shellThickness": 0.8,
|
||||
"printer.fillSize": 5, //dit is het raster aan de binnen kant van de geometry
|
||||
"printer.brimOffset": 5
|
||||
};
|
||||
var printer = new D3D.Printer(printerConfig);
|
||||
|
||||
var localIp = location.hash.substring(1);
|
||||
var doodleBox = new D3D.Box(localIp);
|
||||
|
||||
var scene = new THREE.Scene();
|
||||
|
||||
@ -72,6 +78,8 @@ var camera = new THREE.PerspectiveCamera(75, renderer.domElement.width/renderer.
|
||||
applyMouseControls(renderer, camera, 1000);
|
||||
|
||||
var geometry = (function () {
|
||||
"use strict";
|
||||
|
||||
var circle = new THREE.Shape();
|
||||
circle.absarc(0, 0, 20, 0, Math.PI*2, false);
|
||||
|
||||
@ -94,8 +102,8 @@ var geometry = (function () {
|
||||
})();
|
||||
|
||||
var material = new THREE.MeshLambertMaterial({color: 0x000000, wireframe: true});
|
||||
//var geometry = new THREE.TorusGeometry(40, 20, 10, 10);
|
||||
//var geometry = new THREE.BoxGeometry(10, 10, 10, 1, 1, 1);
|
||||
var geometry = new THREE.TorusGeometry(20, 10, 10, 10);
|
||||
//var geometry = new THREE.BoxGeometry(20, 5, 20, 1, 1, 1);
|
||||
//var geometry = new THREE.SphereGeometry(10, 10, 10);
|
||||
var mesh = new THREE.Mesh(geometry, material);
|
||||
scene.add(mesh);
|
||||
@ -108,12 +116,15 @@ var context = canvas.getContext("2d");
|
||||
var slicer = new D3D.Slicer().setGeometry(geometry);
|
||||
//slicer.draw(1, context);
|
||||
|
||||
var gcode = slicer.getGcode(printerConfig);
|
||||
var gcode = slicer.getGcode(printer);
|
||||
|
||||
/*
|
||||
var canvas = document.getElementById("canvas");
|
||||
var context = canvas.getContext("2d");
|
||||
|
||||
function drawPolygons (paths, color) {
|
||||
"use strict";
|
||||
|
||||
context.fillStyle = color;
|
||||
context.strokeStyle = color;
|
||||
context.beginPath();
|
||||
@ -121,30 +132,37 @@ function drawPolygons (paths, color) {
|
||||
for (var i = 0; i < paths.length; i ++) {
|
||||
var path = paths[i];
|
||||
|
||||
context.moveTo((path[0].X- 100) * 8.0 + 200, (path[0].Y- 100) * 8.0 + 200);
|
||||
context.moveTo((path[0].X- 100) * 6.0 + 200, (path[0].Y- 100) * 6.0 + 200);
|
||||
|
||||
for (var j = 0; j < path.length; j ++) {
|
||||
var point = path[j];
|
||||
context.lineTo((point.X- 100) * 8.0 + 200, (point.Y- 100) * 8.0 + 200);
|
||||
context.lineTo((point.X- 100) * 6.0 + 200, (point.Y- 100) * 6.0 + 200);
|
||||
}
|
||||
context.closePath();
|
||||
}
|
||||
context.stroke();
|
||||
}
|
||||
|
||||
var layer = 0;
|
||||
setInterval(function () {
|
||||
context.clearRect(0, 0, 400, 400);
|
||||
//for (var layer = 0; layer < gcode.length; layer ++) {
|
||||
var layer = 0;
|
||||
var slice = gcode[layer];
|
||||
//var layer = 31;
|
||||
var slice = gcode[layer % gcode.length];
|
||||
|
||||
console.log(gcode.length);
|
||||
|
||||
drawPolygons(slice.outerLayer, "red");
|
||||
drawPolygons(slice.innerLayer, "green");
|
||||
//drawPolygons(slice.outerLayer, "red");
|
||||
//drawPolygons(slice.innerLayer, "green");
|
||||
drawPolygons(slice.fill, "blue");
|
||||
//}*/
|
||||
|
||||
context.fillText("layer: " + layer, 10, 10);
|
||||
//}
|
||||
layer ++;
|
||||
}, 100);
|
||||
*/
|
||||
|
||||
(function animate () {
|
||||
"use strict";
|
||||
|
||||
requestAnimationFrame(animate);
|
||||
|
||||
renderer.render(scene, camera);
|
||||
|
131
src/slicer.js
131
src/slicer.js
@ -36,7 +36,6 @@ D3D.Slicer.prototype.addLine = function (a, b) {
|
||||
//think lookup can only be b_a, a_b is only possible when face is flipped
|
||||
var index = this.lineLookup[a + "_" + b] || this.lineLookup[b + "_" + a];
|
||||
|
||||
//if (!index) {
|
||||
if (index === undefined) {
|
||||
index = this.lines.length;
|
||||
this.lineLookup[a + "_" + b] = index;
|
||||
@ -197,28 +196,25 @@ D3D.Slicer.prototype.getFillTemplate = function (dimension, size, even, uneven)
|
||||
|
||||
return paths;
|
||||
};
|
||||
D3D.Slicer.prototype.slicesToData = function (slices, config) {
|
||||
D3D.Slicer.prototype.slicesToData = function (slices, printer) {
|
||||
"use strict";
|
||||
|
||||
var data = [];
|
||||
|
||||
//scale because of clipper crap
|
||||
var scale = 100;
|
||||
|
||||
var layerHeight = config["printer.layerHeight"] * scale;
|
||||
var dimensionsZ = config["printer.dimensions.z"] * scale;
|
||||
//variables should come from config
|
||||
//aan rick voorleggen
|
||||
var nozzleSize = 0.4 * scale;
|
||||
var shellThickness = 0.8 * scale;
|
||||
var fillSize = 5 * scale;
|
||||
var layerHeight = printer.config["printer.layerHeight"] * scale;
|
||||
var dimensionsZ = printer.config["printer.dimensions.z"] * scale;
|
||||
var wallThickness = printer.config["printer.wallThickness"] * scale;
|
||||
var shellThickness = printer.config["printer.shellThickness"] * scale;
|
||||
var fillSize = printer.config["printer.fillSize"] * scale;
|
||||
var brimOffset = printer.config["printer.brimOffset"] * scale;
|
||||
|
||||
var data = [];
|
||||
|
||||
var lowFillTemplate = this.getFillTemplate(dimensionsZ, fillSize, true, true);
|
||||
|
||||
|
||||
for (var layer = 0; layer < slices.length; layer ++) {
|
||||
var slice = slices[layer];
|
||||
var highFillTemplate = this.getFillTemplate(dimensionsZ, nozzleSize*2, (layer % 2 === 0), (layer % 2 === 1));
|
||||
|
||||
//var outerLayer = ClipperLib.JS.Clean(slice, 1.0);
|
||||
var outerLayer = slice.clone();
|
||||
@ -226,42 +222,37 @@ D3D.Slicer.prototype.slicesToData = function (slices, config) {
|
||||
|
||||
var innerLayer = [];
|
||||
|
||||
for (var i = nozzleSize; i < shellThickness; i += nozzleSize) {
|
||||
for (var i = wallThickness; i < shellThickness; i += wallThickness) {
|
||||
var inset = this.getInset(outerLayer, i);
|
||||
|
||||
innerLayer = innerLayer.concat(inset);
|
||||
}
|
||||
|
||||
var fillArea = this.getInset((inset || outerLayer), nozzleSize);
|
||||
var fillArea = this.getInset((inset || outerLayer), wallThickness);
|
||||
|
||||
var highFill;
|
||||
|
||||
var fillAbove;
|
||||
var fillAbove = undefined;
|
||||
for (var i = 1; i < shellThickness/layerHeight; i ++) {
|
||||
var newLayer = ClipperLib.JS.Clone(slices[layer + i]);
|
||||
ClipperLib.JS.ScaleUpPaths(newLayer, scale);
|
||||
|
||||
if (newLayer.length === 0) {
|
||||
if (newLayer.length === 0 || (fillAbove && fillAbove.length === 0)) {
|
||||
fillAbove = [];
|
||||
|
||||
break;
|
||||
}
|
||||
else if (fillAbove === undefined) {
|
||||
|
||||
}
|
||||
else {
|
||||
|
||||
}
|
||||
|
||||
if (fillAbove === undefined) {
|
||||
fillAbove = newLayer;
|
||||
}
|
||||
else {
|
||||
// var c = new ClipperLib.Clipper();
|
||||
// var solution = new ClipperLib.Paths();
|
||||
// c.AddPaths(fillArea, ClipperLib.PolyType.ptSubject, true);
|
||||
// c.AddPaths(fillAbove, ClipperLib.PolyType.ptClip, true);
|
||||
// c.Execute(ClipperLib.ClipType.ctDifference, solution);
|
||||
var c = new ClipperLib.Clipper();
|
||||
var solution = new ClipperLib.Paths();
|
||||
c.AddPaths(fillArea, ClipperLib.PolyType.ptSubject, true);
|
||||
c.AddPaths(fillAbove, ClipperLib.PolyType.ptClip, true);
|
||||
c.Execute(ClipperLib.ClipType.ctIntersection, solution);
|
||||
|
||||
fillAbove = solution;
|
||||
}
|
||||
}
|
||||
//kijkt alleen nog naar boven
|
||||
@ -291,6 +282,10 @@ D3D.Slicer.prototype.slicesToData = function (slices, config) {
|
||||
|
||||
fill = fill.concat(lowFillStrokes);
|
||||
|
||||
//optimize
|
||||
//make as big as bounding box of highfillArea
|
||||
var highFillTemplate = this.getFillTemplate(dimensionsZ, wallThickness, (layer % 2 === 0), (layer % 2 === 1));
|
||||
|
||||
var clipper = new ClipperLib.Clipper();
|
||||
var highFillStrokes = new ClipperLib.Paths();
|
||||
clipper.AddPaths(highFillTemplate, ClipperLib.PolyType.ptSubject, false);
|
||||
@ -299,6 +294,12 @@ D3D.Slicer.prototype.slicesToData = function (slices, config) {
|
||||
|
||||
fill = fill.concat(highFillStrokes);
|
||||
|
||||
//create brim
|
||||
if (layer === 0) {
|
||||
var brim = this.getInset(outerLayer, -brimOffset);
|
||||
outerLayer = brim.concat(outerLayer);
|
||||
}
|
||||
|
||||
ClipperLib.JS.ScaleDownPaths(outerLayer, scale);
|
||||
ClipperLib.JS.ScaleDownPaths(innerLayer, scale);
|
||||
ClipperLib.JS.ScaleDownPaths(fill, scale);
|
||||
@ -312,10 +313,26 @@ D3D.Slicer.prototype.slicesToData = function (slices, config) {
|
||||
|
||||
return data;
|
||||
};
|
||||
D3D.Slicer.prototype.getGcode = function (config) {
|
||||
D3D.Slicer.prototype.dataToGcode = function (data, printer) {
|
||||
"use strict";
|
||||
|
||||
var layerHeight = printer.config["printer.layerHeight"];
|
||||
var normalSpeed = printer.config["printer.speed"];
|
||||
var bottomSpeed = printer.config["printer.bottomLayerSpeed"];
|
||||
var firstLayerSlow = printer.config["printer.firstLayerSlow"];
|
||||
var bottomFlowRate = printer.config["printer.bottomFlowRate"];
|
||||
var travelSpeed = printer.config["printer.travelSpeed"];
|
||||
var filamentThickness = printer.config["printer.filamentThickness"];
|
||||
var wallThickness = printer.config["printer.wallThickness"];
|
||||
var enableTraveling = printer.config["printer.enableTraveling"];
|
||||
var retractionEnabled = printer.config["printer.retraction.enabled"];
|
||||
var retractionSpeed = printer.config["printer.retraction.speed"];
|
||||
var retractionMinDistance = printer.config["printer.retraction.minDistance"];
|
||||
var retractionAmount = printer.config["printer.retraction.amount"];
|
||||
|
||||
function sliceToGcode (slice) {
|
||||
"use strict";
|
||||
|
||||
var gcode = [];
|
||||
|
||||
for (var i = 0; i < slice.length; i ++) {
|
||||
@ -374,41 +391,13 @@ D3D.Slicer.prototype.getGcode = function (config) {
|
||||
return gcode;
|
||||
}
|
||||
|
||||
var normalSpeed = config["printer.speed"];
|
||||
var bottomSpeed = config["printer.bottomLayerSpeed"];
|
||||
var firstLayerSlow = config["printer.firstLayerSlow"];
|
||||
var bottomFlowRate = config["printer.bottomFlowRate"];
|
||||
var travelSpeed = config["printer.travelSpeed"];
|
||||
var filamentThickness = config["printer.filamentThickness"];
|
||||
var wallThickness = config["printer.wallThickness"];
|
||||
var layerHeight = config["printer.layerHeight"];
|
||||
var enableTraveling = config["printer.enableTraveling"];
|
||||
var retractionEnabled = config["printer.retraction.enabled"];
|
||||
var retractionSpeed = config["printer.retraction.speed"];
|
||||
var retractionMinDistance = config["printer.retraction.minDistance"];
|
||||
var retractionAmount = config["printer.retraction.amount"];
|
||||
var dimensionsZ = config["printer.dimensions.z"];
|
||||
|
||||
var gcode = doodleBox.printer.getStartCode();
|
||||
var gcode = printer.getStartCode();
|
||||
|
||||
var extruder = 0.0;
|
||||
var speed = firstLayerSlow ? (bottomSpeed*60).toFixed(3) : (normalSpeed*60).toFixed(3);
|
||||
var filamentSurfaceArea = Math.pow((filamentThickness/2), 2) * Math.PI;
|
||||
var flowRate = bottomFlowRate;
|
||||
|
||||
var slices = [];
|
||||
|
||||
var slices = this.slice(dimensionsZ, layerHeight);
|
||||
//still error in first layer, so remove first layer
|
||||
//see https://github.com/Doodle3D/Doodle3D-Slicer/issues/1
|
||||
slices.shift();
|
||||
|
||||
//code for only printing the first layer
|
||||
//var slices = [slices.shift()];
|
||||
|
||||
var data = this.slicesToData(slices, config);
|
||||
return data;
|
||||
|
||||
for (var layer = 0; layer < data.length; layer ++) {
|
||||
var slice = data[layer];
|
||||
|
||||
@ -426,6 +415,28 @@ D3D.Slicer.prototype.getGcode = function (config) {
|
||||
gcode = gcode.concat(sliceToGcode(slice.fill));
|
||||
}
|
||||
|
||||
gcode = gcode.concat(doodleBox.printer.getEndCode());
|
||||
gcode = gcode.concat(printer.getEndCode());
|
||||
return gcode;
|
||||
};
|
||||
D3D.Slicer.prototype.getGcode = function (printer) {
|
||||
"use strict";
|
||||
|
||||
var layerHeight = printer.config["printer.layerHeight"];
|
||||
var dimensionsZ = printer.config["printer.dimensions.z"];
|
||||
|
||||
var slices = this.slice(dimensionsZ, layerHeight);
|
||||
|
||||
//still error in first layer, so remove first layer
|
||||
//see https://github.com/Doodle3D/Doodle3D-Slicer/issues/1
|
||||
slices.shift();
|
||||
|
||||
var data = this.slicesToData(slices, printer);
|
||||
//return data;
|
||||
|
||||
//TODO
|
||||
//make the path more optimized for 3d printers
|
||||
//make the printer follow the shortest path from line to line
|
||||
|
||||
var gcode = this.dataToGcode(data, printer);
|
||||
return gcode;
|
||||
};
|
12
src/utils.js
12
src/utils.js
@ -13,6 +13,8 @@ var D3D = {
|
||||
|
||||
//add normal function to Three.js Vector class
|
||||
THREE.Vector2.prototype.normal = function () {
|
||||
"use strict";
|
||||
|
||||
var x = this.y;
|
||||
var y = -this.x;
|
||||
|
||||
@ -29,6 +31,8 @@ function sendAPI (url, data, callback) {
|
||||
dataType: "json",
|
||||
timeout: 10000,
|
||||
success: function (response) {
|
||||
"use strict";
|
||||
|
||||
if (response.status === "success") {
|
||||
if (callback !== undefined) {
|
||||
callback(response.data);
|
||||
@ -39,6 +43,7 @@ function sendAPI (url, data, callback) {
|
||||
}
|
||||
}
|
||||
}).fail(function () {
|
||||
"use strict";
|
||||
|
||||
console.warn("failed connecting to " + url);
|
||||
sendAPI(url, data, callback);
|
||||
@ -53,6 +58,8 @@ function getAPI (url, callback) {
|
||||
dataType: "json",
|
||||
timeout: 5000,
|
||||
success: function (response) {
|
||||
"use strict";
|
||||
|
||||
if (response.status === "success") {
|
||||
if (callback !== undefined) {
|
||||
callback(response.data);
|
||||
@ -63,6 +70,7 @@ function getAPI (url, callback) {
|
||||
}
|
||||
}
|
||||
}).fail(function () {
|
||||
"use strict";
|
||||
|
||||
console.warn("failed connecting to " + url);
|
||||
getAPI(url, callback);
|
||||
@ -70,6 +78,8 @@ function getAPI (url, callback) {
|
||||
}
|
||||
|
||||
function downloadFile (file, data) {
|
||||
"use strict";
|
||||
|
||||
$(document.createElement("a")).attr({
|
||||
download: file,
|
||||
href: "data:text/plain," + data
|
||||
@ -98,6 +108,7 @@ function applyMouseControls (renderer, camera, maxDistance) {
|
||||
var rotX = 0;
|
||||
var rotY = 0;
|
||||
var moveCamera = false;
|
||||
|
||||
function updateCamera () {
|
||||
"use strict";
|
||||
|
||||
@ -127,6 +138,7 @@ function applyMouseControls (renderer, camera, maxDistance) {
|
||||
moveCamera = false;
|
||||
}).on("mousemove", function (e) {
|
||||
"use strict";
|
||||
var event = e.originalEvent;
|
||||
|
||||
if (moveCamera === true) {
|
||||
rotX = (rotX - event.webkitMovementX/100) % (2*Math.PI);
|
||||
|
Loading…
Reference in New Issue
Block a user