diff --git a/debug/viewer.css b/debug/viewer.css
index 52cd4bd64..5a0af41e6 100644
--- a/debug/viewer.css
+++ b/debug/viewer.css
@@ -92,4 +92,8 @@ svg text {
.selectModelCode * {
font-size: 18px;
font-weight: 200;
+}
+
+#crosshairs-horizontal_text, #crosshairs-vertical_text {
+ display: none;
}
\ No newline at end of file
diff --git a/debug/viewer.html b/debug/viewer.html
index b9d9d166f..e3227b2ab 100644
--- a/debug/viewer.html
+++ b/debug/viewer.html
@@ -42,6 +42,7 @@
+
diff --git a/index.js b/index.js
index c2414bf35..d30624d6e 100644
--- a/index.js
+++ b/index.js
@@ -169,10 +169,12 @@ var MakerJs;
* @param b Second angle.
* @returns true if angles are the same, false if they are not
*/
- function areEqual(angle1, angle2) {
- var a1 = noRevolutions(MakerJs.round(angle1));
- var a2 = noRevolutions(MakerJs.round(angle2));
- return a1 == a2 || a1 + 360 == a2 || a1 - 360 == a2;
+ function areEqual(angle1, angle2, accuracy) {
+ if (accuracy === void 0) { accuracy = .0001; }
+ var a1 = noRevolutions(angle1);
+ var a2 = noRevolutions(angle2);
+ var d = noRevolutions(MakerJs.round(a2 - a1, accuracy));
+ return d == 0;
}
angle.areEqual = areEqual;
/**
@@ -322,8 +324,14 @@ var MakerJs;
* @param b Second point.
* @returns true if points are the same, false if they are not
*/
- function areEqual(a, b) {
- return a[0] == b[0] && a[1] == b[1];
+ function areEqual(a, b, withinDistance) {
+ if (!withinDistance) {
+ return a[0] == b[0] && a[1] == b[1];
+ }
+ else {
+ var distance = MakerJs.measure.pointDistance(a, b);
+ return distance <= withinDistance;
+ }
}
point.areEqual = areEqual;
/**
@@ -339,6 +347,20 @@ var MakerJs;
return MakerJs.round(a[0], accuracy) == MakerJs.round(b[0], accuracy) && MakerJs.round(a[1], accuracy) == MakerJs.round(b[1], accuracy);
}
point.areEqualRounded = areEqualRounded;
+ /**
+ * Get the average of two points.
+ *
+ * @param a First point.
+ * @param b Second point.
+ * @returns New point object which is the average of a and b.
+ */
+ function average(a, b) {
+ function avg(i) {
+ return (a[i] + b[i]) / 2;
+ }
+ return [avg(0), avg(1)];
+ }
+ point.average = average;
/**
* Clone a point into a new point.
*
@@ -506,7 +528,7 @@ var MakerJs;
var pointAngleInRadians = MakerJs.angle.ofPointInRadians(rotationOrigin, pointToRotate);
var d = MakerJs.measure.pointDistance(rotationOrigin, pointToRotate);
var rotatedPoint = fromPolar(pointAngleInRadians + MakerJs.angle.toRadians(angleInDegrees), d);
- return rounded(add(rotationOrigin, rotatedPoint));
+ return add(rotationOrigin, rotatedPoint);
}
point.rotate = rotate;
/**
@@ -533,7 +555,7 @@ var MakerJs;
*/
function serialize(pointContext, accuracy) {
var roundedPoint = rounded(pointContext, accuracy);
- return roundedPoint[0] + ',' + roundedPoint[1];
+ return JSON.stringify(roundedPoint);
}
point.serialize = serialize;
/**
@@ -567,14 +589,15 @@ var MakerJs;
* @private
*/
var pathAreEqualMap = {};
- pathAreEqualMap[MakerJs.pathType.Line] = function (line1, line2) {
- return (MakerJs.point.areEqual(line1.origin, line2.origin) && MakerJs.point.areEqual(line1.end, line2.end)) || (MakerJs.point.areEqual(line1.origin, line2.end) && MakerJs.point.areEqual(line1.end, line2.origin));
+ pathAreEqualMap[MakerJs.pathType.Line] = function (line1, line2, withinPointDistance) {
+ return (MakerJs.point.areEqual(line1.origin, line2.origin, withinPointDistance) && MakerJs.point.areEqual(line1.end, line2.end, withinPointDistance))
+ || (MakerJs.point.areEqual(line1.origin, line2.end, withinPointDistance) && MakerJs.point.areEqual(line1.end, line2.origin, withinPointDistance));
};
- pathAreEqualMap[MakerJs.pathType.Circle] = function (circle1, circle2) {
- return MakerJs.point.areEqual(circle1.origin, circle2.origin) && circle1.radius == circle2.radius;
+ pathAreEqualMap[MakerJs.pathType.Circle] = function (circle1, circle2, withinPointDistance) {
+ return MakerJs.point.areEqual(circle1.origin, circle2.origin, withinPointDistance) && circle1.radius == circle2.radius;
};
- pathAreEqualMap[MakerJs.pathType.Arc] = function (arc1, arc2) {
- return pathAreEqualMap[MakerJs.pathType.Circle](arc1, arc2) && MakerJs.angle.areEqual(arc1.startAngle, arc2.startAngle) && MakerJs.angle.areEqual(arc1.endAngle, arc2.endAngle);
+ pathAreEqualMap[MakerJs.pathType.Arc] = function (arc1, arc2, withinPointDistance) {
+ return pathAreEqualMap[MakerJs.pathType.Circle](arc1, arc2, withinPointDistance) && MakerJs.angle.areEqual(arc1.startAngle, arc2.startAngle) && MakerJs.angle.areEqual(arc1.endAngle, arc2.endAngle);
};
/**
* Find out if two paths are equal.
@@ -583,12 +606,12 @@ var MakerJs;
* @param b Second path.
* @returns true if paths are the same, false if they are not
*/
- function areEqual(path1, path2) {
+ function areEqual(path1, path2, withinPointDistance) {
var result = false;
if (path1.type == path2.type) {
var fn = pathAreEqualMap[path1.type];
if (fn) {
- result = fn(path1, path2);
+ result = fn(path1, path2, withinPointDistance);
}
}
return result;
@@ -687,8 +710,8 @@ var MakerJs;
line.end = MakerJs.point.rotate(line.end, angleInDegrees, rotationOrigin);
};
map[MakerJs.pathType.Arc] = function (arc) {
- arc.startAngle += angleInDegrees;
- arc.endAngle += angleInDegrees;
+ arc.startAngle = MakerJs.angle.noRevolutions(arc.startAngle + angleInDegrees);
+ arc.endAngle = MakerJs.angle.noRevolutions(arc.endAngle + angleInDegrees);
};
pathToRotate.origin = MakerJs.point.rotate(pathToRotate.origin, angleInDegrees, rotationOrigin);
var fn = map[pathToRotate.type];
@@ -740,12 +763,13 @@ var MakerJs;
return null;
}
function getAngleStrictlyBetweenArcAngles() {
- var endAngle = MakerJs.angle.ofArcEnd(arc);
+ var startAngle = MakerJs.angle.noRevolutions(arc.startAngle);
+ var endAngle = startAngle + MakerJs.angle.ofArcEnd(arc) - arc.startAngle;
var tries = [0, 1, -1];
for (var i = 0; i < tries.length; i++) {
var add = +360 * tries[i];
- if (MakerJs.measure.isBetween(angleAtBreakPoint + add, arc.startAngle, endAngle, true)) {
- return angleAtBreakPoint + add;
+ if (MakerJs.measure.isBetween(angleAtBreakPoint + add, startAngle, endAngle, true)) {
+ return arc.startAngle + angleAtBreakPoint + add - startAngle;
}
}
return null;
@@ -916,13 +940,33 @@ var MakerJs;
return count;
}
model.countChildModels = countChildModels;
+ /**
+ * Get an unused id in the models map with the same prefix.
+ *
+ * @param modelContext The model containing the models map.
+ * @param modelId The id to use directly (if unused), or as a prefix.
+ */
+ function getSimilarModelId(modelContext, modelId) {
+ if (!modelContext.models)
+ return modelId;
+ var i = 0;
+ var newModelId = modelId;
+ while (newModelId in modelContext.models) {
+ i++;
+ newModelId = modelId + '_' + i;
+ }
+ return newModelId;
+ }
+ model.getSimilarModelId = getSimilarModelId;
/**
* Get an unused id in the paths map with the same prefix.
*
* @param modelContext The model containing the paths map.
- * @param pathId The pathId to use directly (if unused), or as a prefix.
+ * @param pathId The id to use directly (if unused), or as a prefix.
*/
function getSimilarPathId(modelContext, pathId) {
+ if (!modelContext.paths)
+ return pathId;
var i = 0;
var newPathId = pathId;
while (newPathId in modelContext.paths) {
@@ -939,6 +983,8 @@ var MakerJs;
* @param origin Optional offset reference point.
*/
function originate(modelToOriginate, origin) {
+ if (!modelToOriginate)
+ return;
var newOrigin = MakerJs.point.add(modelToOriginate.origin, origin);
if (modelToOriginate.paths) {
for (var id in modelToOriginate.paths) {
@@ -1100,11 +1146,15 @@ var MakerJs;
function walkPaths(modelContext, callback) {
if (modelContext.paths) {
for (var pathId in modelContext.paths) {
+ if (!modelContext.paths[pathId])
+ continue;
callback(modelContext, pathId, modelContext.paths[pathId]);
}
}
if (modelContext.models) {
for (var id in modelContext.models) {
+ if (!modelContext.models[id])
+ continue;
walkPaths(modelContext.models[id], callback);
}
}
@@ -1115,29 +1165,33 @@ var MakerJs;
var MakerJs;
(function (MakerJs) {
var model;
- (function (model_1) {
+ (function (model) {
/**
* @private
*/
function getNonZeroSegments(pathToSegment, breakPoint) {
+ var segmentType = pathToSegment.type;
var segment1 = MakerJs.cloneObject(pathToSegment);
var segment2 = MakerJs.path.breakAtPoint(segment1, breakPoint);
if (segment2) {
var segments = [segment1, segment2];
for (var i = 2; i--;) {
- if (MakerJs.round(MakerJs.measure.pathLength(segments[i]), .00001) == 0) {
+ if (MakerJs.round(MakerJs.measure.pathLength(segments[i]), .0001) == 0) {
return null;
}
}
return segments;
}
+ else if (segmentType == MakerJs.pathType.Circle) {
+ return [segment1];
+ }
return null;
}
/**
* @private
*/
function breakAlongForeignPath(segments, overlappedSegments, foreignPath) {
- if (MakerJs.path.areEqual(segments[0].path, foreignPath)) {
+ if (MakerJs.path.areEqual(segments[0].path, foreignPath, .0001)) {
segments[0].overlapped = true;
segments[0].duplicate = true;
overlappedSegments.push(segments[0]);
@@ -1169,11 +1223,18 @@ var MakerJs;
}
if (subSegments) {
segments[i].path = subSegments[0];
- var newSegment = { path: subSegments[1], overlapped: segments[i].overlapped, uniqueForeignIntersectionPoints: [] };
- if (segments[i].overlapped) {
- overlappedSegments.push(newSegment);
+ if (subSegments[1]) {
+ var newSegment = {
+ path: subSegments[1],
+ pathId: segments[0].pathId,
+ overlapped: segments[i].overlapped,
+ uniqueForeignIntersectionPoints: []
+ };
+ if (segments[i].overlapped) {
+ overlappedSegments.push(newSegment);
+ }
+ segments.push(newSegment);
}
- segments.push(newSegment);
//re-check this segment for another deep intersection
i--;
}
@@ -1187,7 +1248,7 @@ var MakerJs;
var added = 0;
function addUniquePoint(pointToAdd) {
for (var i = 0; i < pointArray.length; i++) {
- if (MakerJs.point.areEqualRounded(pointArray[i], pointToAdd)) {
+ if (MakerJs.point.areEqual(pointArray[i], pointToAdd, .000000001)) {
return;
}
}
@@ -1202,7 +1263,7 @@ var MakerJs;
/**
* @private
*/
- function checkInsideForeignPath(segment, foreignPath, farPoint) {
+ function checkIntersectsForeignPath(segment, foreignPath, foreignPathId, farPoint) {
if (farPoint === void 0) { farPoint = [7654321, 1234567]; }
var origin = MakerJs.point.middle(segment.path);
var lineToFarPoint = new MakerJs.paths.Line(origin, farPoint);
@@ -1219,9 +1280,9 @@ var MakerJs;
* @private
*/
function checkInsideForeignModel(segment, modelToIntersect, farPoint) {
- model_1.walkPaths(modelToIntersect, function (mx, pathId2, path2) {
+ model.walkPaths(modelToIntersect, function (mx, pathId2, path2) {
if (path2) {
- checkInsideForeignPath(segment, path2, farPoint);
+ checkIntersectsForeignPath(segment, path2, pathId2, farPoint);
}
});
}
@@ -1242,19 +1303,30 @@ var MakerJs;
checkInsideForeignModel(segment, modelContext, farPoint);
return !!segment.isInside;
}
- model_1.isPathInsideModel = isPathInsideModel;
+ model.isPathInsideModel = isPathInsideModel;
+ /**
+ * Break a model's paths everywhere they intersect with another path.
+ *
+ * @param modelToBreak The model containing paths to be broken.
+ * @param modelToIntersect Optional model containing paths to look for intersection, or else the modelToBreak will be used.
+ */
+ function breakPathsAtIntersections(modelToBreak, modelToIntersect) {
+ breakAllPathsAtIntersections(modelToBreak, modelToIntersect || modelToBreak, false);
+ }
+ model.breakPathsAtIntersections = breakPathsAtIntersections;
/**
* @private
*/
- function breakAllPathsAtIntersections(modelToBreak, modelToIntersect, farPoint) {
+ function breakAllPathsAtIntersections(modelToBreak, modelToIntersect, checkIsInside, farPoint) {
var crossedPaths = [];
var overlappedSegments = [];
- model_1.walkPaths(modelToBreak, function (modelContext, pathId1, path1) {
+ model.walkPaths(modelToBreak, function (modelContext, pathId1, path1) {
if (!path1)
return;
//clone this path and make it the first segment
var segment = {
path: MakerJs.cloneObject(path1),
+ pathId: pathId1,
overlapped: false,
uniqueForeignIntersectionPoints: []
};
@@ -1264,14 +1336,16 @@ var MakerJs;
segments: [segment]
};
//keep breaking the segments anywhere they intersect with paths of the other model
- model_1.walkPaths(modelToIntersect, function (mx, pathId2, path2) {
- if (path2) {
+ model.walkPaths(modelToIntersect, function (mx, pathId2, path2) {
+ if (path2 && path1 !== path2) {
breakAlongForeignPath(thisPath.segments, overlappedSegments, path2);
}
});
- //check each segment whether it is inside or outside
- for (var i = 0; i < thisPath.segments.length; i++) {
- checkInsideForeignModel(thisPath.segments[i], modelToIntersect);
+ if (checkIsInside) {
+ //check each segment whether it is inside or outside
+ for (var i = 0; i < thisPath.segments.length; i++) {
+ checkInsideForeignModel(thisPath.segments[i], modelToIntersect, farPoint);
+ }
}
crossedPaths.push(thisPath);
});
@@ -1280,9 +1354,9 @@ var MakerJs;
/**
* @private
*/
- function checkForEqualOverlaps(crossedPathsA, crossedPathsB) {
+ function checkForEqualOverlaps(crossedPathsA, crossedPathsB, pointMatchingDistance) {
function compareSegments(segment1, segment2) {
- if (MakerJs.path.areEqual(segment1.path, segment2.path)) {
+ if (MakerJs.path.areEqual(segment1.path, segment2.path, pointMatchingDistance)) {
segment1.duplicate = segment2.duplicate = true;
}
}
@@ -1299,13 +1373,13 @@ var MakerJs;
* @private
*/
function addOrDeleteSegments(crossedPath, includeInside, includeOutside, keepDuplicates) {
- function addSegment(model, pathIdBase, segment) {
- var id = model_1.getSimilarPathId(model, pathIdBase);
- model.paths[id] = segment.path;
+ function addSegment(modelContext, pathIdBase, segment) {
+ var id = model.getSimilarPathId(modelContext, pathIdBase);
+ modelContext.paths[id] = segment.path;
}
- function checkAddSegment(model, pathIdBase, segment) {
+ function checkAddSegment(modelContext, pathIdBase, segment) {
if (segment.isInside && includeInside || !segment.isInside && includeOutside) {
- addSegment(model, pathIdBase, segment);
+ addSegment(modelContext, pathIdBase, segment);
}
}
//delete the original, its segments will be added
@@ -1322,7 +1396,7 @@ var MakerJs;
}
}
/**
- * Combine 2 models. The models should be originated.
+ * Combine 2 models. The models should be originated, and every path within each model should be part of a loop.
*
* @param modelA First model to combine.
* @param modelB Second model to combine.
@@ -1333,23 +1407,30 @@ var MakerJs;
* @param keepDuplicates Flag to include paths which are duplicate in both models.
* @param farPoint Optional point of reference which is outside the bounds of both models.
*/
- function combine(modelA, modelB, includeAInsideB, includeAOutsideB, includeBInsideA, includeBOutsideA, keepDuplicates, farPoint) {
+ function combine(modelA, modelB, includeAInsideB, includeAOutsideB, includeBInsideA, includeBOutsideA, options) {
if (includeAInsideB === void 0) { includeAInsideB = false; }
if (includeAOutsideB === void 0) { includeAOutsideB = true; }
if (includeBInsideA === void 0) { includeBInsideA = false; }
if (includeBOutsideA === void 0) { includeBOutsideA = true; }
- if (keepDuplicates === void 0) { keepDuplicates = true; }
- var pathsA = breakAllPathsAtIntersections(modelA, modelB, farPoint);
- var pathsB = breakAllPathsAtIntersections(modelB, modelA, farPoint);
- checkForEqualOverlaps(pathsA.overlappedSegments, pathsB.overlappedSegments);
+ var opts = {
+ trimDeadEnds: true,
+ pointMatchingDistance: .005
+ };
+ MakerJs.extendObject(opts, options);
+ var pathsA = breakAllPathsAtIntersections(modelA, modelB, true, opts.farPoint);
+ var pathsB = breakAllPathsAtIntersections(modelB, modelA, true, opts.farPoint);
+ checkForEqualOverlaps(pathsA.overlappedSegments, pathsB.overlappedSegments, opts.pointMatchingDistance);
for (var i = 0; i < pathsA.crossedPaths.length; i++) {
- addOrDeleteSegments(pathsA.crossedPaths[i], includeAInsideB, includeAOutsideB, keepDuplicates);
+ addOrDeleteSegments(pathsA.crossedPaths[i], includeAInsideB, includeAOutsideB, true);
}
for (var i = 0; i < pathsB.crossedPaths.length; i++) {
- addOrDeleteSegments(pathsB.crossedPaths[i], includeBInsideA, includeBOutsideA);
+ addOrDeleteSegments(pathsB.crossedPaths[i], includeBInsideA, includeBOutsideA, false);
+ }
+ if (opts.trimDeadEnds) {
+ model.removeDeadEnds({ models: { modelA: modelA, modelB: modelB } });
}
}
- model_1.combine = combine;
+ model.combine = combine;
})(model = MakerJs.model || (MakerJs.model = {}));
})(MakerJs || (MakerJs = {}));
var MakerJs;
@@ -1487,6 +1568,10 @@ var MakerJs;
function isBetweenArcAngles(angleInQuestion, arc, exclusive) {
var startAngle = arc.startAngle;
var endAngle = MakerJs.angle.ofArcEnd(arc);
+ var span = endAngle - startAngle;
+ startAngle = MakerJs.angle.noRevolutions(startAngle);
+ endAngle = startAngle + span;
+ angleInQuestion = MakerJs.angle.noRevolutions(angleInQuestion);
//computed angles will not be negative, but the arc may have specified a negative angle, so check against one revolution forward and backward
return (isBetween(angleInQuestion, startAngle, endAngle, exclusive) || isBetween(angleInQuestion, startAngle + 360, endAngle + 360, exclusive) || isBetween(angleInQuestion, startAngle - 360, endAngle - 360, exclusive));
}
@@ -1501,11 +1586,11 @@ var MakerJs;
*/
function isBetweenPoints(pointInQuestion, line, exclusive) {
for (var i = 2; i--;) {
- var origin_value = MakerJs.round(line.origin[i]);
- var end_value = MakerJs.round(line.end[i]);
- if (origin_value == end_value) {
+ if (MakerJs.round(line.origin[i] - line.end[i], .000001) == 0) {
continue;
}
+ var origin_value = MakerJs.round(line.origin[i]);
+ var end_value = MakerJs.round(line.end[i]);
if (!isBetween(MakerJs.round(pointInQuestion[i]), origin_value, end_value, exclusive))
return false;
}
@@ -1621,16 +1706,18 @@ var MakerJs;
getExtreme(totalMeasurement.low, pathMeasurement.low, Math.min);
getExtreme(totalMeasurement.high, pathMeasurement.high, Math.max);
}
- function measure(model, offsetOrigin) {
- var newOrigin = MakerJs.point.add(model.origin, offsetOrigin);
- if (model.paths) {
- for (var id in model.paths) {
- lowerOrHigher(newOrigin, pathExtents(model.paths[id]));
+ function measure(modelToMeasure, offsetOrigin) {
+ if (!modelToMeasure)
+ return;
+ var newOrigin = MakerJs.point.add(modelToMeasure.origin, offsetOrigin);
+ if (modelToMeasure.paths) {
+ for (var id in modelToMeasure.paths) {
+ lowerOrHigher(newOrigin, pathExtents(modelToMeasure.paths[id]));
}
}
- if (model.models) {
- for (var id in model.models) {
- measure(model.models[id], newOrigin);
+ if (modelToMeasure.models) {
+ for (var id in modelToMeasure.models) {
+ measure(modelToMeasure.models[id], newOrigin);
}
}
}
@@ -1699,12 +1786,18 @@ var MakerJs;
var newOffset = MakerJs.point.add((this.fixPoint ? this.fixPoint(modelToExport.origin) : modelToExport.origin), offset);
if (modelToExport.paths) {
for (var id in modelToExport.paths) {
- this.exportPath(id, modelToExport.paths[id], newOffset, modelToExport.layer);
+ var currPath = modelToExport.paths[id];
+ if (!currPath)
+ continue;
+ this.exportPath(id, currPath, newOffset, modelToExport.layer);
}
}
if (modelToExport.models) {
for (var id in modelToExport.models) {
- this.exportModel(id, modelToExport.models[id], newOffset);
+ var currModel = modelToExport.models[id];
+ if (!currModel)
+ continue;
+ this.exportModel(id, currModel, newOffset);
}
}
if (this.endModel) {
@@ -2086,14 +2179,14 @@ var MakerJs;
* @private
*/
function getSlope(line) {
- var dx = MakerJs.round(line.end[0] - line.origin[0]);
- if (dx == 0) {
+ var dx = line.end[0] - line.origin[0];
+ if (MakerJs.round(dx) == 0) {
return {
line: line,
hasSlope: false
};
}
- var dy = MakerJs.round(line.end[1] - line.origin[1]);
+ var dy = line.end[1] - line.origin[1];
var slope = dy / dx;
var yIntercept = line.origin[1] - slope * line.origin[0];
return {
@@ -2116,13 +2209,13 @@ var MakerJs;
*/
function checkAngleOverlap(arc1, arc2, options) {
var pointsOfIntersection = [];
- function checkAngles(index, a, b) {
+ function checkAngles(a, b) {
function checkAngle(n) {
return MakerJs.measure.isBetweenArcAngles(n, a, options.excludeTangents);
}
return checkAngle(b.startAngle) || checkAngle(b.endAngle);
}
- if (checkAngles(0, arc1, arc2) || checkAngles(1, arc2, arc1) || (arc1.startAngle == arc2.startAngle && arc1.endAngle == arc2.endAngle)) {
+ if (checkAngles(arc1, arc2) || checkAngles(arc2, arc1) || (arc1.startAngle == arc2.startAngle && arc1.endAngle == arc2.endAngle)) {
options.out_AreOverlapped = true;
}
}
@@ -2155,15 +2248,15 @@ var MakerJs;
var slope2 = getSlope(line2);
if (!slope1.hasSlope && !slope2.hasSlope) {
//lines are both vertical, see if x are the same
- if (slope1.line.origin[0] == slope2.line.origin[0]) {
+ if (MakerJs.round(slope1.line.origin[0] - slope2.line.origin[0]) == 0) {
//check for overlap
checkLineOverlap(line1, line2, options);
}
return null;
}
- if (slope1.hasSlope && slope2.hasSlope && (slope1.slope == slope2.slope)) {
+ if (slope1.hasSlope && slope2.hasSlope && (MakerJs.round(slope1.slope - slope2.slope, .00001) == 0)) {
//lines are parallel, but not vertical, see if y-intercept is the same
- if (slope1.yIntercept == slope2.yIntercept) {
+ if (MakerJs.round(slope1.yIntercept - slope2.yIntercept, .00001) == 0) {
//check for overlap
checkLineOverlap(line1, line2, options);
}
@@ -2205,21 +2298,22 @@ var MakerJs;
}
//line is horizontal, get the y value from any point
var lineY = MakerJs.round(clonedLine.origin[1]);
+ var lineYabs = Math.abs(lineY);
//if y is greater than radius, there is no intersection
- if (lineY > radius) {
+ if (lineYabs > radius) {
return null;
}
var anglesOfIntersection = [];
//if horizontal Y is the same as the radius, we know it's 90 degrees
- if (lineY == radius) {
+ if (lineYabs == radius) {
if (options.excludeTangents) {
return null;
}
- anglesOfIntersection.push(unRotate(90));
+ anglesOfIntersection.push(unRotate(lineY > 0 ? 90 : 270));
}
else {
function intersectionBetweenEndpoints(x, angleOfX) {
- if (MakerJs.measure.isBetween(x, clonedLine.origin[0], clonedLine.end[0], options.excludeTangents)) {
+ if (MakerJs.measure.isBetween(MakerJs.round(x), MakerJs.round(clonedLine.origin[0]), MakerJs.round(clonedLine.end[0]), options.excludeTangents)) {
anglesOfIntersection.push(unRotate(angleOfX));
}
}
@@ -2241,7 +2335,7 @@ var MakerJs;
*/
function circleToCircle(circle1, circle2, options) {
//see if circles are the same
- if (circle1.radius == circle2.radius && MakerJs.point.areEqual(circle1.origin, circle2.origin)) {
+ if (circle1.radius == circle2.radius && MakerJs.point.areEqual(circle1.origin, circle2.origin, .0001)) {
options.out_AreOverlapped = true;
return null;
}
@@ -2273,14 +2367,14 @@ var MakerJs;
return null;
}
//see if circles are tangent interior
- if (c2.radius - x == c1.radius) {
+ if (MakerJs.round(c2.radius - x - c1.radius) == 0) {
if (options.excludeTangents) {
return null;
}
return [[unRotate(180)], [unRotate(180)]];
}
//see if circles are tangent exterior
- if (x - c2.radius == c1.radius) {
+ if (MakerJs.round(x - c2.radius - c1.radius) == 0) {
if (options.excludeTangents) {
return null;
}
@@ -2327,7 +2421,7 @@ var MakerJs;
/**
* @private
*/
- function getMatchingPointProperties(path1, path2) {
+ function getMatchingPointProperties(path1, path2, options) {
var path1Properties = getPointProperties(path1);
var path2Properties = getPointProperties(path2);
var result = null;
@@ -2341,7 +2435,7 @@ var MakerJs;
};
}
function check(i1, i2) {
- if (MakerJs.point.areEqualRounded(path1Properties[i1].point, path2Properties[i2].point)) {
+ if (MakerJs.point.areEqual(path1Properties[i1].point, path2Properties[i2].point, .0001)) {
result = [
makeMatch(path1, path1Properties, i1),
makeMatch(path2, path2Properties, i2)
@@ -2365,7 +2459,7 @@ var MakerJs;
return false;
}
properties[i].shardPoint = circleIntersection.intersectionPoints[0];
- if (MakerJs.point.areEqualRounded(properties[i].point, circleIntersection.intersectionPoints[0], options.accuracy)) {
+ if (MakerJs.point.areEqual(properties[i].point, circleIntersection.intersectionPoints[0], .0001)) {
if (circleIntersection.intersectionPoints.length > 1) {
properties[i].shardPoint = circleIntersection.intersectionPoints[1];
}
@@ -2517,11 +2611,11 @@ var MakerJs;
function dogbone(line1, line2, filletRadius, options) {
if (MakerJs.isPathLine(line1) && MakerJs.isPathLine(line2) && filletRadius && filletRadius > 0) {
var opts = {
- accuracy: .0001
+ pointMatchingDistance: .005
};
MakerJs.extendObject(opts, options);
//first find the common point
- var commonProperty = getMatchingPointProperties(line1, line2);
+ var commonProperty = getMatchingPointProperties(line1, line2, options);
if (commonProperty) {
//get the ratio comparison of the two lines
var ratio = getLineRatio([line1, line2]);
@@ -2568,11 +2662,11 @@ var MakerJs;
function fillet(path1, path2, filletRadius, options) {
if (path1 && path2 && filletRadius && filletRadius > 0) {
var opts = {
- accuracy: .0001
+ pointMatchingDistance: .005
};
MakerJs.extendObject(opts, options);
//first find the common point
- var commonProperty = getMatchingPointProperties(path1, path2);
+ var commonProperty = getMatchingPointProperties(path1, path2, options);
if (commonProperty) {
//since arcs can curl beyond, we need a local reference point.
//An intersection with a circle of the same radius as the desired fillet should suffice.
@@ -2674,6 +2768,34 @@ var MakerJs;
(function (MakerJs) {
var model;
(function (model) {
+ /**
+ * @private
+ */
+ var PointMap = (function () {
+ function PointMap(matchingDistance) {
+ if (matchingDistance === void 0) { matchingDistance = .001; }
+ this.matchingDistance = matchingDistance;
+ this.list = [];
+ }
+ PointMap.prototype.add = function (pointToAdd, item) {
+ this.list.push({ averagePoint: pointToAdd, item: item });
+ };
+ PointMap.prototype.find = function (pointToFind, saveAverage) {
+ for (var i = 0; i < this.list.length; i++) {
+ var item = this.list[i];
+ var distance = MakerJs.measure.pointDistance(pointToFind, item.averagePoint);
+ if (distance <= this.matchingDistance) {
+ if (saveAverage) {
+ item.averagePoint = MakerJs.point.average(item.averagePoint, pointToFind);
+ }
+ return item.item;
+ }
+ }
+ return null;
+ };
+ return PointMap;
+ })();
+ model.PointMap = PointMap;
/**
* @private
*/
@@ -2720,7 +2842,7 @@ var MakerJs;
while (true) {
var currPath = currLink.path;
currPath.reversed = currLink.reversed;
- var id = model.getSimilarPathId(loopModel, currPath.primePathId);
+ var id = model.getSimilarPathId(loopModel, currPath.pathId);
loopModel.paths[id] = currPath;
if (!connections[currLink.nextConnection])
break;
@@ -2750,11 +2872,11 @@ var MakerJs;
var connections = {};
var result = { models: {} };
var opts = {
- accuracy: .0001
+ pointMatchingDistance: .005
};
MakerJs.extendObject(opts, options);
function getLinkedPathsOnConnectionPoint(p) {
- var serializedPoint = MakerJs.point.serialize(p, opts.accuracy);
+ var serializedPoint = MakerJs.point.serialize(p, .0001); //TODO convert to pointmap
if (!(serializedPoint in connections)) {
connections[serializedPoint] = [];
}
@@ -2773,14 +2895,15 @@ var MakerJs;
}
return result.models[id];
}
+ //todo: remove dead ends first
model.originate(modelContext);
//find loops by looking at all paths in this model
model.walkPaths(modelContext, function (modelContext, pathId, pathContext) {
if (!pathContext)
return;
var safePath = MakerJs.cloneObject(pathContext);
- safePath.primePathId = pathId;
- safePath.primeModel = modelContext;
+ safePath.pathId = pathId;
+ safePath.modelContext = modelContext;
//circles are loops by nature
if (safePath.type == MakerJs.pathType.Circle) {
var loopModel = {
@@ -2796,7 +2919,7 @@ var MakerJs;
for (var i = 2; i--;) {
var linkedPath = {
path: safePath,
- nextConnection: MakerJs.point.serialize(safePath.endPoints[1 - i], opts.accuracy),
+ nextConnection: MakerJs.point.serialize(safePath.endPoints[1 - i], .0001),
reversed: i != 0
};
getLinkedPathsOnConnectionPoint(safePath.endPoints[i]).push(linkedPath);
@@ -2836,13 +2959,110 @@ var MakerJs;
function detachLoop(loopToDetach) {
for (var id in loopToDetach.paths) {
var pathDirectionalWithOriginalContext = loopToDetach.paths[id];
- var primeModel = pathDirectionalWithOriginalContext.primeModel;
- if (primeModel && primeModel.paths && pathDirectionalWithOriginalContext.primePathId) {
- delete primeModel.paths[pathDirectionalWithOriginalContext.primePathId];
+ var primeModel = pathDirectionalWithOriginalContext.modelContext;
+ if (primeModel && primeModel.paths && pathDirectionalWithOriginalContext.pathId) {
+ delete primeModel.paths[pathDirectionalWithOriginalContext.pathId];
}
}
}
model.detachLoop = detachLoop;
+ /**
+ * @private
+ */
+ var DeadEndFinder = (function () {
+ function DeadEndFinder(pointMatchingDistance) {
+ this.pointMatchingDistance = pointMatchingDistance;
+ this.pointMap = new PointMap(pointMatchingDistance);
+ }
+ DeadEndFinder.prototype.addPathRef = function (p, pathRef) {
+ var found = this.pointMap.find(p, true);
+ if (found) {
+ found.push(pathRef);
+ }
+ else {
+ this.pointMap.add(p, [pathRef]);
+ }
+ };
+ DeadEndFinder.prototype.removeMatchingPathRefs = function (a, b) {
+ //see if any are the same in each array
+ for (var ai = 0; ai < a.length; ai++) {
+ for (var bi = 0; bi < b.length; bi++) {
+ if (a[ai] === b[bi]) {
+ var pathRef = a[ai];
+ a.splice(ai, 1);
+ b.splice(bi, 1);
+ return pathRef;
+ }
+ }
+ }
+ return null;
+ };
+ DeadEndFinder.prototype.removePathRef = function (pathRef) {
+ var _this = this;
+ var removePath = function (p) {
+ var pathRefs = _this.pointMap.find(p, false);
+ for (var i = 0; i < pathRefs.length; i++) {
+ if (pathRefs[i] === pathRef) {
+ pathRefs.splice(i, 1);
+ return;
+ }
+ }
+ };
+ for (var i = 2; i--;) {
+ removePath(pathRef.endPoints[i]);
+ }
+ };
+ DeadEndFinder.prototype.removeDeadEnd = function () {
+ var found = false;
+ var oddPathRefs = null;
+ for (var i = 0; i < this.pointMap.list.length; i++) {
+ var pathRefs = this.pointMap.list[i].item;
+ if (pathRefs.length % 2 == 0)
+ continue;
+ if (pathRefs.length == 1) {
+ var pathRef = pathRefs[0];
+ this.removePathRef(pathRef);
+ delete pathRef.modelContext.paths[pathRef.pathId];
+ found = true;
+ }
+ else {
+ if (!oddPathRefs) {
+ //save this for another iteration
+ oddPathRefs = pathRefs;
+ }
+ else {
+ //compare with the saved
+ var pathRef = this.removeMatchingPathRefs(oddPathRefs, pathRefs);
+ if (pathRef) {
+ delete pathRef.modelContext.paths[pathRef.pathId];
+ found = true;
+ //clear the saved
+ oddPathRefs = null;
+ }
+ }
+ }
+ }
+ return found;
+ };
+ return DeadEndFinder;
+ })();
+ function removeDeadEnds(modelContext, pointMatchingDistance) {
+ if (pointMatchingDistance === void 0) { pointMatchingDistance = .005; }
+ var serializedPointAccuracy = .0001;
+ var deadEndFinder = new DeadEndFinder(pointMatchingDistance);
+ model.walkPaths(modelContext, function (modelContext, pathId, pathContext) {
+ var endPoints = MakerJs.point.fromPathEnds(pathContext);
+ if (!endPoints)
+ return;
+ var pathRef = { modelContext: modelContext, pathId: pathId, endPoints: endPoints };
+ for (var i = 2; i--;) {
+ deadEndFinder.addPathRef(endPoints[i], pathRef);
+ }
+ });
+ while (deadEndFinder.removeDeadEnd())
+ ;
+ }
+ model.removeDeadEnds = removeDeadEnds;
})(model = MakerJs.model || (MakerJs.model = {}));
})(MakerJs || (MakerJs = {}));
var MakerJs;
@@ -3055,7 +3275,7 @@ var MakerJs;
var depthModel;
var opts = {
extrusion: 1,
- accuracy: .0001
+ pointMatchingDistance: .005
};
MakerJs.extendObject(opts, options);
var loops = MakerJs.model.findLoops(modelToExport, opts);
diff --git a/package.json b/package.json
index b80be17ce..445b637dd 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "makerjs",
- "version": "0.5.3",
+ "version": "0.6.0",
"description": "Maker.js, a Microsoft Garage project, is a JavaScript library for creating and sharing modular line drawings for CNC and laser cutters.",
"main": "index.js",
"scripts": {
diff --git a/src/core/angle.ts b/src/core/angle.ts
index c6ab4c9b8..d69d46af7 100644
--- a/src/core/angle.ts
+++ b/src/core/angle.ts
@@ -9,11 +9,11 @@ module MakerJs.angle {
* @param b Second angle.
* @returns true if angles are the same, false if they are not
*/
- export function areEqual(angle1: number, angle2: number) {
- var a1 = noRevolutions(round(angle1));
- var a2 = noRevolutions(round(angle2));
-
- return a1 == a2 || a1 + 360 == a2 || a1 - 360 == a2;
+ export function areEqual(angle1: number, angle2: number, accuracy: number = .0001) {
+ var a1 = noRevolutions(angle1);
+ var a2 = noRevolutions(angle2);
+ var d = noRevolutions(round(a2 - a1, accuracy));
+ return d == 0;
}
/**
diff --git a/src/core/break.ts b/src/core/break.ts
index 52f8d9b73..481fee73a 100644
--- a/src/core/break.ts
+++ b/src/core/break.ts
@@ -23,12 +23,14 @@ module MakerJs.path {
}
function getAngleStrictlyBetweenArcAngles() {
- var endAngle = angle.ofArcEnd(arc);
+ var startAngle = angle.noRevolutions(arc.startAngle);
+ var endAngle = startAngle + angle.ofArcEnd(arc) - arc.startAngle;
+
var tries = [0, 1, -1];
for (var i = 0; i < tries.length; i++) {
var add = + 360 * tries[i];
- if (measure.isBetween(angleAtBreakPoint + add, arc.startAngle, endAngle, true)) {
- return angleAtBreakPoint + add;
+ if (measure.isBetween(angleAtBreakPoint + add, startAngle, endAngle, true)) {
+ return arc.startAngle + angleAtBreakPoint + add - startAngle;
}
}
return null;
diff --git a/src/core/combine.ts b/src/core/combine.ts
index b80c954f4..5bbd2915e 100644
--- a/src/core/combine.ts
+++ b/src/core/combine.ts
@@ -6,17 +6,20 @@ module MakerJs.model {
* @private
*/
function getNonZeroSegments(pathToSegment: IPath, breakPoint: IPoint): IPath[] {
+ var segmentType = pathToSegment.type;
var segment1 = cloneObject(pathToSegment);
var segment2 = path.breakAtPoint(segment1, breakPoint);
if (segment2) {
var segments: IPath[] = [segment1, segment2];
for (var i = 2; i--;) {
- if (round(measure.pathLength(segments[i]), .00001) == 0) {
+ if (round(measure.pathLength(segments[i]), .0001) == 0) {
return null;
}
}
return segments;
+ } else if (segmentType == pathType.Circle) {
+ return [segment1];
}
return null;
}
@@ -26,7 +29,7 @@ module MakerJs.model {
*/
function breakAlongForeignPath(segments: ICrossedPathSegment[], overlappedSegments: ICrossedPathSegment[], foreignPath: IPath) {
- if (path.areEqual(segments[0].path, foreignPath)) {
+ if (path.areEqual(segments[0].path, foreignPath, .0001)) {
segments[0].overlapped = true;
segments[0].duplicate = true;
@@ -70,13 +73,20 @@ module MakerJs.model {
if (subSegments) {
segments[i].path = subSegments[0];
- var newSegment = { path: subSegments[1], overlapped: segments[i].overlapped, uniqueForeignIntersectionPoints: [] };
+ if (subSegments[1]) {
+ var newSegment: ICrossedPathSegment = {
+ path: subSegments[1],
+ pathId: segments[0].pathId,
+ overlapped: segments[i].overlapped,
+ uniqueForeignIntersectionPoints: []
+ };
- if (segments[i].overlapped) {
- overlappedSegments.push(newSegment);
- }
+ if (segments[i].overlapped) {
+ overlappedSegments.push(newSegment);
+ }
- segments.push(newSegment);
+ segments.push(newSegment);
+ }
//re-check this segment for another deep intersection
i--;
@@ -94,7 +104,7 @@ module MakerJs.model {
function addUniquePoint(pointToAdd: IPoint) {
for (var i = 0; i < pointArray.length; i++) {
- if (point.areEqualRounded(pointArray[i], pointToAdd)) {
+ if (point.areEqual(pointArray[i], pointToAdd, .000000001)) {
return;
}
}
@@ -112,7 +122,7 @@ module MakerJs.model {
/**
* @private
*/
- function checkInsideForeignPath(segment: IPathInside, foreignPath: IPath, farPoint: IPoint = [7654321, 1234567]) {
+ function checkIntersectsForeignPath(segment: IPathInside, foreignPath: IPath, foreignPathId: string, farPoint: IPoint = [7654321, 1234567]) {
var origin = point.middle(segment.path);
var lineToFarPoint = new paths.Line(origin, farPoint);
var farInt = path.intersection(lineToFarPoint, foreignPath);
@@ -133,7 +143,7 @@ module MakerJs.model {
function checkInsideForeignModel(segment: IPathInside, modelToIntersect: IModel, farPoint?: IPoint) {
walkPaths(modelToIntersect, function (mx: IModel, pathId2: string, path2: IPath) {
if (path2) {
- checkInsideForeignPath(segment, path2, farPoint);
+ checkIntersectsForeignPath(segment, path2, pathId2, farPoint);
}
});
}
@@ -171,6 +181,7 @@ module MakerJs.model {
* @private
*/
interface ICrossedPathSegment extends IPathInside {
+ pathId: string;
overlapped: boolean;
duplicate?: boolean;
}
@@ -178,9 +189,7 @@ module MakerJs.model {
/**
* @private
*/
- interface ICrossedPath {
- modelContext: IModel;
- pathId: string;
+ interface ICrossedPath extends IRefPathIdInModel {
segments: ICrossedPathSegment[];
}
@@ -192,10 +201,20 @@ module MakerJs.model {
overlappedSegments: ICrossedPathSegment[];
}
+ /**
+ * Break a model's paths everywhere they intersect with another path.
+ *
+ * @param modelToBreak The model containing paths to be broken.
+ * @param modelToIntersect Optional model containing paths to look for intersection, or else the modelToBreak will be used.
+ */
+ export function breakPathsAtIntersections(modelToBreak: IModel, modelToIntersect?: IModel) {
+ breakAllPathsAtIntersections(modelToBreak, modelToIntersect || modelToBreak, false);
+ }
+
/**
* @private
*/
- function breakAllPathsAtIntersections(modelToBreak: IModel, modelToIntersect: IModel, farPoint: IPoint): ICombinedModel {
+ function breakAllPathsAtIntersections(modelToBreak: IModel, modelToIntersect: IModel, checkIsInside: boolean, farPoint?: IPoint): ICombinedModel {
var crossedPaths: ICrossedPath[] = [];
var overlappedSegments: ICrossedPathSegment[] = [];
@@ -207,6 +226,7 @@ module MakerJs.model {
//clone this path and make it the first segment
var segment: ICrossedPathSegment = {
path: cloneObject(path1),
+ pathId: pathId1,
overlapped: false,
uniqueForeignIntersectionPoints: []
};
@@ -219,14 +239,16 @@ module MakerJs.model {
//keep breaking the segments anywhere they intersect with paths of the other model
walkPaths(modelToIntersect, function (mx: IModel, pathId2: string, path2: IPath) {
- if (path2) {
+ if (path2 && path1 !== path2) {
breakAlongForeignPath(thisPath.segments, overlappedSegments, path2);
}
});
- //check each segment whether it is inside or outside
- for (var i = 0; i < thisPath.segments.length; i++) {
- checkInsideForeignModel(thisPath.segments[i], modelToIntersect);
+ if (checkIsInside) {
+ //check each segment whether it is inside or outside
+ for (var i = 0; i < thisPath.segments.length; i++) {
+ checkInsideForeignModel(thisPath.segments[i], modelToIntersect, farPoint);
+ }
}
crossedPaths.push(thisPath);
@@ -238,10 +260,10 @@ module MakerJs.model {
/**
* @private
*/
- function checkForEqualOverlaps(crossedPathsA: ICrossedPathSegment[], crossedPathsB: ICrossedPathSegment[]) {
+ function checkForEqualOverlaps(crossedPathsA: ICrossedPathSegment[], crossedPathsB: ICrossedPathSegment[], pointMatchingDistance: number) {
function compareSegments(segment1: ICrossedPathSegment, segment2: ICrossedPathSegment) {
- if (path.areEqual(segment1.path, segment2.path)) {
+ if (path.areEqual(segment1.path, segment2.path, pointMatchingDistance)) {
segment1.duplicate = segment2.duplicate = true;
}
}
@@ -261,16 +283,16 @@ module MakerJs.model {
/**
* @private
*/
- function addOrDeleteSegments(crossedPath: ICrossedPath, includeInside: boolean, includeOutside: boolean, keepDuplicates?: boolean) {
+ function addOrDeleteSegments(crossedPath: ICrossedPath, includeInside: boolean, includeOutside: boolean, keepDuplicates: boolean) {
- function addSegment(model: IModel, pathIdBase: string, segment: ICrossedPathSegment) {
- var id = getSimilarPathId(model, pathIdBase);
- model.paths[id] = segment.path;
+ function addSegment(modelContext: IModel, pathIdBase: string, segment: ICrossedPathSegment) {
+ var id = getSimilarPathId(modelContext, pathIdBase);
+ modelContext.paths[id] = segment.path;
}
- function checkAddSegment(model: IModel, pathIdBase: string, segment: ICrossedPathSegment) {
+ function checkAddSegment(modelContext: IModel, pathIdBase: string, segment: ICrossedPathSegment) {
if (segment.isInside && includeInside || !segment.isInside && includeOutside) {
- addSegment(model, pathIdBase, segment);
+ addSegment(modelContext, pathIdBase, segment);
}
}
@@ -289,7 +311,7 @@ module MakerJs.model {
}
/**
- * Combine 2 models. The models should be originated.
+ * Combine 2 models. The models should be originated, and every path within each model should be part of a loop.
*
* @param modelA First model to combine.
* @param modelB Second model to combine.
@@ -300,19 +322,29 @@ module MakerJs.model {
* @param keepDuplicates Flag to include paths which are duplicate in both models.
* @param farPoint Optional point of reference which is outside the bounds of both models.
*/
- export function combine(modelA: IModel, modelB: IModel, includeAInsideB: boolean = false, includeAOutsideB: boolean = true, includeBInsideA: boolean = false, includeBOutsideA: boolean = true, keepDuplicates: boolean = true, farPoint?: IPoint) {
+ export function combine(modelA: IModel, modelB: IModel, includeAInsideB: boolean = false, includeAOutsideB: boolean = true, includeBInsideA: boolean = false, includeBOutsideA: boolean = true, options?: ICombineOptions) {
- var pathsA = breakAllPathsAtIntersections(modelA, modelB, farPoint);
- var pathsB = breakAllPathsAtIntersections(modelB, modelA, farPoint);
+ var opts: ICombineOptions = {
+ trimDeadEnds: true,
+ pointMatchingDistance: .005
+ };
+ extendObject(opts, options);
- checkForEqualOverlaps(pathsA.overlappedSegments, pathsB.overlappedSegments);
+ var pathsA = breakAllPathsAtIntersections(modelA, modelB, true, opts.farPoint);
+ var pathsB = breakAllPathsAtIntersections(modelB, modelA, true, opts.farPoint);
+
+ checkForEqualOverlaps(pathsA.overlappedSegments, pathsB.overlappedSegments, opts.pointMatchingDistance);
for (var i = 0; i < pathsA.crossedPaths.length; i++) {
- addOrDeleteSegments(pathsA.crossedPaths[i], includeAInsideB, includeAOutsideB, keepDuplicates);
+ addOrDeleteSegments(pathsA.crossedPaths[i], includeAInsideB, includeAOutsideB, true);
}
for (var i = 0; i < pathsB.crossedPaths.length; i++) {
- addOrDeleteSegments(pathsB.crossedPaths[i], includeBInsideA, includeBOutsideA);
+ addOrDeleteSegments(pathsB.crossedPaths[i], includeBInsideA, includeBOutsideA, false);
+ }
+
+ if (opts.trimDeadEnds) {
+ removeDeadEnds({ models: { modelA: modelA, modelB: modelB } });
}
}
diff --git a/src/core/exporter.ts b/src/core/exporter.ts
index e66c11e0e..ac4177d48 100644
--- a/src/core/exporter.ts
+++ b/src/core/exporter.ts
@@ -76,13 +76,17 @@ module MakerJs.exporter {
if (modelToExport.paths) {
for (var id in modelToExport.paths) {
- this.exportPath(id, modelToExport.paths[id], newOffset, modelToExport.layer);
+ var currPath = modelToExport.paths[id];
+ if (!currPath) continue;
+ this.exportPath(id, currPath, newOffset, modelToExport.layer);
}
}
if (modelToExport.models) {
for (var id in modelToExport.models) {
- this.exportModel(id, modelToExport.models[id], newOffset);
+ var currModel = modelToExport.models[id];
+ if (!currModel) continue;
+ this.exportModel(id, currModel, newOffset);
}
}
diff --git a/src/core/fillet.ts b/src/core/fillet.ts
index 4e8d1f0b5..64e5addce 100644
--- a/src/core/fillet.ts
+++ b/src/core/fillet.ts
@@ -63,7 +63,7 @@ module MakerJs.path {
/**
* @private
*/
- function getMatchingPointProperties(path1: IPath, path2: IPath): IMatchPointProperty[] {
+ function getMatchingPointProperties(path1: IPath, path2: IPath, options?: IPointMatchOptions): IMatchPointProperty[] {
var path1Properties = getPointProperties(path1);
var path2Properties = getPointProperties(path2);
@@ -80,7 +80,7 @@ module MakerJs.path {
}
function check(i1: number, i2: number) {
- if (point.areEqualRounded(path1Properties[i1].point, path2Properties[i2].point)) {
+ if (point.areEqual(path1Properties[i1].point, path2Properties[i2].point, .0001)) {
result = [
makeMatch(path1, path1Properties, i1),
makeMatch(path2, path2Properties, i2)
@@ -110,7 +110,7 @@ module MakerJs.path {
properties[i].shardPoint = circleIntersection.intersectionPoints[0];
- if (point.areEqualRounded(properties[i].point, circleIntersection.intersectionPoints[0], options.accuracy)) {
+ if (point.areEqual(properties[i].point, circleIntersection.intersectionPoints[0], .0001)) {
if (circleIntersection.intersectionPoints.length > 1) {
properties[i].shardPoint = circleIntersection.intersectionPoints[1];
} else {
@@ -296,12 +296,12 @@ module MakerJs.path {
if (isPathLine(line1) && isPathLine(line2) && filletRadius && filletRadius > 0) {
var opts: IPointMatchOptions = {
- accuracy: .0001
+ pointMatchingDistance: .005
};
extendObject(opts, options);
//first find the common point
- var commonProperty = getMatchingPointProperties(line1, line2);
+ var commonProperty = getMatchingPointProperties(line1, line2, options);
if (commonProperty) {
//get the ratio comparison of the two lines
@@ -360,12 +360,12 @@ module MakerJs.path {
if (path1 && path2 && filletRadius && filletRadius > 0) {
var opts: IPointMatchOptions = {
- accuracy: .0001
+ pointMatchingDistance: .005
};
extendObject(opts, options);
//first find the common point
- var commonProperty = getMatchingPointProperties(path1, path2);
+ var commonProperty = getMatchingPointProperties(path1, path2, options);
if (commonProperty) {
//since arcs can curl beyond, we need a local reference point.
diff --git a/src/core/intersect.ts b/src/core/intersect.ts
index d91af9473..99e3fc483 100644
--- a/src/core/intersect.ts
+++ b/src/core/intersect.ts
@@ -232,15 +232,15 @@ module MakerJs.path {
* @private
*/
function getSlope(line: IPathLine): ISlope {
- var dx = round(line.end[0] - line.origin[0]);
- if (dx == 0) {
+ var dx = line.end[0] - line.origin[0];
+ if (round(dx) == 0) {
return {
line: line,
hasSlope: false
};
}
- var dy = round(line.end[1] - line.origin[1]);
+ var dy = line.end[1] - line.origin[1];
var slope = dy / dx;
var yIntercept = line.origin[1] - slope * line.origin[0];
@@ -268,7 +268,7 @@ module MakerJs.path {
function checkAngleOverlap(arc1: IPathArc, arc2: IPathArc, options: IPathIntersectionOptions): void {
var pointsOfIntersection: IPoint[] = [];
- function checkAngles(index: number, a: IPathArc, b: IPathArc) {
+ function checkAngles(a: IPathArc, b: IPathArc) {
function checkAngle(n: number) {
return measure.isBetweenArcAngles(n, a, options.excludeTangents);
@@ -277,7 +277,7 @@ module MakerJs.path {
return checkAngle(b.startAngle) || checkAngle(b.endAngle);
}
- if (checkAngles(0, arc1, arc2) || checkAngles(1, arc2, arc1) || (arc1.startAngle == arc2.startAngle && arc1.endAngle == arc2.endAngle)) {
+ if (checkAngles(arc1, arc2) || checkAngles(arc2, arc1) || (arc1.startAngle == arc2.startAngle && arc1.endAngle == arc2.endAngle)) {
options.out_AreOverlapped = true;
}
}
@@ -318,7 +318,7 @@ module MakerJs.path {
if (!slope1.hasSlope && !slope2.hasSlope) {
//lines are both vertical, see if x are the same
- if (slope1.line.origin[0] == slope2.line.origin[0]) {
+ if (round(slope1.line.origin[0] - slope2.line.origin[0]) == 0) {
//check for overlap
checkLineOverlap(line1, line2, options);
@@ -327,10 +327,10 @@ module MakerJs.path {
return null;
}
- if (slope1.hasSlope && slope2.hasSlope && (slope1.slope == slope2.slope)) {
+ if (slope1.hasSlope && slope2.hasSlope && (round(slope1.slope - slope2.slope, .00001) == 0)) {
//lines are parallel, but not vertical, see if y-intercept is the same
- if (slope1.yIntercept == slope2.yIntercept) {
+ if (round(slope1.yIntercept - slope2.yIntercept, .00001) == 0) {
//check for overlap
checkLineOverlap(line1, line2, options);
@@ -382,27 +382,28 @@ module MakerJs.path {
//line is horizontal, get the y value from any point
var lineY = round(clonedLine.origin[1]);
+ var lineYabs = Math.abs(lineY);
//if y is greater than radius, there is no intersection
- if (lineY > radius) {
+ if (lineYabs > radius) {
return null;
}
var anglesOfIntersection: number[] = [];
//if horizontal Y is the same as the radius, we know it's 90 degrees
- if (lineY == radius) {
+ if (lineYabs == radius) {
if (options.excludeTangents) {
return null;
}
- anglesOfIntersection.push(unRotate(90));
+ anglesOfIntersection.push(unRotate(lineY > 0 ? 90 : 270));
} else {
function intersectionBetweenEndpoints(x: number, angleOfX: number) {
- if (measure.isBetween(x, clonedLine.origin[0], clonedLine.end[0], options.excludeTangents)) {
+ if (measure.isBetween(round(x), round(clonedLine.origin[0]), round(clonedLine.end[0]), options.excludeTangents)) {
anglesOfIntersection.push(unRotate(angleOfX));
}
}
@@ -430,7 +431,7 @@ module MakerJs.path {
function circleToCircle(circle1: IPathCircle, circle2: IPathCircle, options: IPathIntersectionOptions): number[][] {
//see if circles are the same
- if (circle1.radius == circle2.radius && point.areEqual(circle1.origin, circle2.origin)) {
+ if (circle1.radius == circle2.radius && point.areEqual(circle1.origin, circle2.origin, .0001)) {
options.out_AreOverlapped = true;
return null;
}
@@ -472,7 +473,7 @@ module MakerJs.path {
}
//see if circles are tangent interior
- if (c2.radius - x == c1.radius) {
+ if (round(c2.radius - x - c1.radius) == 0) {
if (options.excludeTangents) {
return null;
@@ -482,7 +483,7 @@ module MakerJs.path {
}
//see if circles are tangent exterior
- if (x - c2.radius == c1.radius) {
+ if (round(x - c2.radius - c1.radius) == 0) {
if (options.excludeTangents) {
return null;
diff --git a/src/core/loops.ts b/src/core/loops.ts
index 88d81f621..298b21b6f 100644
--- a/src/core/loops.ts
+++ b/src/core/loops.ts
@@ -5,9 +5,46 @@ module MakerJs.model {
/**
* @private
*/
- interface IPathDirectionalWithPrimeContext extends IPathDirectional {
- primePathId: string;
- primeModel: IModel;
+ export interface IPointMappedItem {
+ averagePoint: IPoint;
+ item: T;
+ }
+
+ /**
+ * @private
+ */
+ export class PointMap {
+ public list: IPointMappedItem[] = [];
+
+ constructor(public matchingDistance: number = .001) {
+ }
+
+ public add(pointToAdd: IPoint, item: T) {
+ this.list.push({ averagePoint: pointToAdd, item: item });
+ }
+
+ public find(pointToFind: IPoint, saveAverage: boolean): T {
+ for (var i = 0; i < this.list.length; i++) {
+ var item = this.list[i];
+ var distance = measure.pointDistance(pointToFind, item.averagePoint);
+
+ if (distance <= this.matchingDistance) {
+
+ if (saveAverage) {
+ item.averagePoint = point.average(item.averagePoint, pointToFind);
+ }
+
+ return item.item;
+ }
+ }
+ return null;
+ }
+ }
+
+ /**
+ * @private
+ */
+ interface IPathDirectionalWithPrimeContext extends IPathDirectional, IRefPathIdInModel {
}
/**
@@ -90,7 +127,7 @@ module MakerJs.model {
var currPath = currLink.path;
currPath.reversed = currLink.reversed;
- var id = model.getSimilarPathId(loopModel, currPath.primePathId);
+ var id = model.getSimilarPathId(loopModel, currPath.pathId);
loopModel.paths[id] = currPath;
if (!connections[currLink.nextConnection]) break;
@@ -126,12 +163,12 @@ module MakerJs.model {
var result: IModel = { models: {} };
var opts: IFindLoopsOptions = {
- accuracy: .0001
+ pointMatchingDistance: .005
};
extendObject(opts, options);
function getLinkedPathsOnConnectionPoint(p: IPoint) {
- var serializedPoint = point.serialize(p, opts.accuracy);
+ var serializedPoint = point.serialize(p, .0001); //TODO convert to pointmap
if (!(serializedPoint in connections)) {
connections[serializedPoint] = [];
@@ -157,6 +194,7 @@ module MakerJs.model {
return result.models[id];
}
+ //todo: remove dead ends first
model.originate(modelContext);
//find loops by looking at all paths in this model
@@ -165,8 +203,8 @@ module MakerJs.model {
if (!pathContext) return;
var safePath = cloneObject(pathContext);
- safePath.primePathId = pathId;
- safePath.primeModel = modelContext;
+ safePath.pathId = pathId;
+ safePath.modelContext = modelContext;
//circles are loops by nature
if (safePath.type == pathType.Circle) {
@@ -185,7 +223,7 @@ module MakerJs.model {
for (var i = 2; i--;) {
var linkedPath: ILinkedPath = {
path: safePath,
- nextConnection: point.serialize(safePath.endPoints[1 - i], opts.accuracy),
+ nextConnection: point.serialize(safePath.endPoints[1 - i], .0001), //TODO convert to pointmap
reversed: i != 0
};
getLinkedPathsOnConnectionPoint(safePath.endPoints[i]).push(linkedPath);
@@ -235,10 +273,130 @@ module MakerJs.model {
export function detachLoop(loopToDetach: IModel) {
for (var id in loopToDetach.paths) {
var pathDirectionalWithOriginalContext = loopToDetach.paths[id];
- var primeModel = pathDirectionalWithOriginalContext.primeModel;
- if (primeModel && primeModel.paths && pathDirectionalWithOriginalContext.primePathId) {
- delete primeModel.paths[pathDirectionalWithOriginalContext.primePathId];
+ var primeModel = pathDirectionalWithOriginalContext.modelContext;
+ if (primeModel && primeModel.paths && pathDirectionalWithOriginalContext.pathId) {
+ delete primeModel.paths[pathDirectionalWithOriginalContext.pathId];
+ }
+ }
+ }
+
+ /**
+ * @private
+ */
+ interface IRefPathEndpoints extends IRefPathIdInModel {
+ endPoints: IPoint[];
+ }
+
+ /**
+ * @private
+ */
+ class DeadEndFinder {
+
+ private pointMap: PointMap;
+
+ constructor(public pointMatchingDistance) {
+ this.pointMap = new PointMap(pointMatchingDistance);
+ }
+
+ public addPathRef(p: IPoint, pathRef: IRefPathEndpoints) {
+ var found = this.pointMap.find(p, true);
+ if (found) {
+ found.push(pathRef);
+ } else {
+ this.pointMap.add(p, [pathRef]);
}
}
+
+ private removeMatchingPathRefs(a: IRefPathEndpoints[], b: IRefPathEndpoints[]) {
+ //see if any are the same in each array
+ for (var ai = 0; ai < a.length; ai++) {
+ for (var bi = 0; bi < b.length; bi++) {
+ if (a[ai] === b[bi]) {
+ var pathRef = a[ai];
+ a.splice(ai, 1);
+ b.splice(bi, 1);
+ return pathRef;
+ }
+ }
+ }
+ return null;
+ }
+
+ private removePathRef(pathRef: IRefPathEndpoints) {
+
+ var removePath = (p: IPoint) => {
+ var pathRefs = this.pointMap.find(p, false);
+
+ for (var i = 0; i < pathRefs.length; i++) {
+ if (pathRefs[i] === pathRef) {
+ pathRefs.splice(i, 1);
+ return;
+ }
+ }
+ }
+
+ for (var i = 2; i--;) {
+ removePath(pathRef.endPoints[i]);
+ }
+ }
+
+ public removeDeadEnd(): boolean {
+ var found = false;
+ var oddPathRefs: IRefPathEndpoints[] = null;
+
+ for (var i = 0; i < this.pointMap.list.length; i++) {
+
+ var pathRefs = this.pointMap.list[i].item;
+
+ if (pathRefs.length % 2 == 0) continue;
+
+ if (pathRefs.length == 1) {
+ var pathRef = pathRefs[0];
+ this.removePathRef(pathRef);
+
+ delete pathRef.modelContext.paths[pathRef.pathId];
+ found = true;
+
+ } else {
+
+ if (!oddPathRefs) {
+ //save this for another iteration
+ oddPathRefs = pathRefs;
+ } else {
+
+ //compare with the saved
+ var pathRef = this.removeMatchingPathRefs(oddPathRefs, pathRefs);
+ if (pathRef) {
+
+ delete pathRef.modelContext.paths[pathRef.pathId];
+ found = true;
+
+ //clear the saved
+ oddPathRefs = null;
+ }
+ }
+ }
+ }
+ return found;
+ }
+ }
+
+ export function removeDeadEnds(modelContext: IModel, pointMatchingDistance = .005) {
+ var serializedPointAccuracy = .0001;
+ var deadEndFinder = new DeadEndFinder(pointMatchingDistance);
+
+ walkPaths(modelContext, function (modelContext: IModel, pathId: string, pathContext: IPath) {
+ var endPoints = point.fromPathEnds(pathContext);
+
+ if (!endPoints) return;
+
+ var pathRef: IRefPathEndpoints = { modelContext: modelContext, pathId: pathId, endPoints: endPoints };
+
+ for (var i = 2; i--;) {
+ deadEndFinder.addPathRef(endPoints[i], pathRef);
+ }
+ });
+
+ while (deadEndFinder.removeDeadEnd());
}
}
diff --git a/src/core/maker.ts b/src/core/maker.ts
index cf9b52001..354834d68 100644
--- a/src/core/maker.ts
+++ b/src/core/maker.ts
@@ -336,9 +336,25 @@ module MakerJs {
export interface IPointMatchOptions {
/**
- * Optional exemplar of number of decimal places.
+ * Max distance to consider two points as the same.
*/
- accuracy?: number;
+ pointMatchingDistance?: number;
+ }
+
+ /**
+ * Options to pass to model.combine.
+ */
+ export interface ICombineOptions extends IPointMatchOptions {
+
+ /**
+ * Flag to remove paths which are not part of a loop.
+ */
+ trimDeadEnds?: boolean;
+
+ /**
+ * Point which is known to be outside of the model.
+ */
+ farPoint?: IPoint;
}
/**
@@ -449,6 +465,20 @@ module MakerJs {
return item && (item.paths || item.models);
}
+ /**
+ * Reference to a path id within a model.
+ */
+ export interface IRefPathIdInModel {
+ modelContext: IModel;
+ pathId: string;
+ }
+
+ /**
+ * Path and its reference id within a model
+ */
+ export interface IRefPathInModel extends IRefPathIdInModel {
+ pathContext: IPath;
+ }
}
//CommonJs
diff --git a/src/core/measure.ts b/src/core/measure.ts
index 93055e9a7..998e24ade 100644
--- a/src/core/measure.ts
+++ b/src/core/measure.ts
@@ -76,6 +76,13 @@ module MakerJs.measure {
var startAngle = arc.startAngle;
var endAngle = angle.ofArcEnd(arc);
+ var span = endAngle - startAngle;
+
+ startAngle = angle.noRevolutions(startAngle);
+ endAngle = startAngle + span;
+
+ angleInQuestion = angle.noRevolutions(angleInQuestion);
+
//computed angles will not be negative, but the arc may have specified a negative angle, so check against one revolution forward and backward
return (isBetween(angleInQuestion, startAngle, endAngle, exclusive) || isBetween(angleInQuestion, startAngle + 360, endAngle + 360, exclusive) || isBetween(angleInQuestion, startAngle - 360, endAngle - 360, exclusive))
}
@@ -90,11 +97,11 @@ module MakerJs.measure {
*/
export function isBetweenPoints(pointInQuestion: IPoint, line: IPathLine, exclusive: boolean): boolean {
for (var i = 2; i--;) {
- var origin_value = round(line.origin[i]);
- var end_value = round(line.end[i]);
- if (origin_value == end_value) {
+ if (round(line.origin[i] - line.end[i], .000001) == 0) {
continue;
}
+ var origin_value = round(line.origin[i]);
+ var end_value = round(line.end[i]);
if (!isBetween(round(pointInQuestion[i]), origin_value, end_value, exclusive)) return false;
}
return true;
@@ -228,19 +235,20 @@ module MakerJs.measure {
getExtreme(totalMeasurement.high, pathMeasurement.high, Math.max);
}
- function measure(model: IModel, offsetOrigin?: IPoint) {
+ function measure(modelToMeasure: IModel, offsetOrigin?: IPoint) {
+ if (!modelToMeasure) return;
- var newOrigin = point.add(model.origin, offsetOrigin);
+ var newOrigin = point.add(modelToMeasure.origin, offsetOrigin);
- if (model.paths) {
- for (var id in model.paths) {
- lowerOrHigher(newOrigin, pathExtents(model.paths[id]));
+ if (modelToMeasure.paths) {
+ for (var id in modelToMeasure.paths) {
+ lowerOrHigher(newOrigin, pathExtents(modelToMeasure.paths[id]));
}
}
- if (model.models) {
- for (var id in model.models) {
- measure(model.models[id], newOrigin);
+ if (modelToMeasure.models) {
+ for (var id in modelToMeasure.models) {
+ measure(modelToMeasure.models[id], newOrigin);
}
}
}
diff --git a/src/core/model.ts b/src/core/model.ts
index 05b4a6e49..055986171 100644
--- a/src/core/model.ts
+++ b/src/core/model.ts
@@ -20,13 +20,33 @@ module MakerJs.model {
return count;
}
+ /**
+ * Get an unused id in the models map with the same prefix.
+ *
+ * @param modelContext The model containing the models map.
+ * @param modelId The id to use directly (if unused), or as a prefix.
+ */
+ export function getSimilarModelId(modelContext: IModel, modelId: string): string {
+ if (!modelContext.models) return modelId;
+
+ var i = 0;
+ var newModelId = modelId;
+ while (newModelId in modelContext.models) {
+ i++;
+ newModelId = modelId + '_' + i;
+ }
+ return newModelId;
+ }
+
/**
* Get an unused id in the paths map with the same prefix.
*
* @param modelContext The model containing the paths map.
- * @param pathId The pathId to use directly (if unused), or as a prefix.
+ * @param pathId The id to use directly (if unused), or as a prefix.
*/
export function getSimilarPathId(modelContext: IModel, pathId: string): string {
+ if (!modelContext.paths) return pathId;
+
var i = 0;
var newPathId = pathId;
while (newPathId in modelContext.paths) {
@@ -43,6 +63,8 @@ module MakerJs.model {
* @param origin Optional offset reference point.
*/
export function originate(modelToOriginate: IModel, origin?: IPoint) {
+ if (!modelToOriginate) return;
+
var newOrigin = point.add(modelToOriginate.origin, origin);
if (modelToOriginate.paths) {
@@ -229,12 +251,14 @@ module MakerJs.model {
if (modelContext.paths) {
for (var pathId in modelContext.paths) {
+ if (!modelContext.paths[pathId]) continue;
callback(modelContext, pathId, modelContext.paths[pathId]);
}
}
if (modelContext.models) {
for (var id in modelContext.models) {
+ if (!modelContext.models[id]) continue;
walkPaths(modelContext.models[id], callback);
}
}
diff --git a/src/core/openjscad.ts b/src/core/openjscad.ts
index 247b8909d..9cb5e29dd 100644
--- a/src/core/openjscad.ts
+++ b/src/core/openjscad.ts
@@ -152,7 +152,7 @@ module MakerJs.exporter {
var opts: IOpenJsCadOptions = {
extrusion: 1,
- accuracy: .0001
+ pointMatchingDistance: .005
};
extendObject(opts, options);
diff --git a/src/core/path.ts b/src/core/path.ts
index 09d24e3de..7e7b004cf 100644
--- a/src/core/path.ts
+++ b/src/core/path.ts
@@ -6,7 +6,7 @@ module MakerJs.path {
* @private
*/
interface IPathAreEqualMap {
- [type: string]: (path1: IPath, path2: IPath) => boolean;
+ [type: string]: (path1: IPath, path2: IPath, withinPointDistance?: number) => boolean;
}
/**
@@ -14,16 +14,17 @@ module MakerJs.path {
*/
var pathAreEqualMap: IPathAreEqualMap = {};
- pathAreEqualMap[pathType.Line] = function (line1: IPathLine, line2: IPathLine): boolean {
- return (point.areEqual(line1.origin, line2.origin) && point.areEqual(line1.end, line2.end)) || (point.areEqual(line1.origin, line2.end) && point.areEqual(line1.end, line2.origin));
+ pathAreEqualMap[pathType.Line] = function (line1: IPathLine, line2: IPathLine, withinPointDistance?: number): boolean {
+ return (point.areEqual(line1.origin, line2.origin, withinPointDistance) && point.areEqual(line1.end, line2.end, withinPointDistance))
+ || (point.areEqual(line1.origin, line2.end, withinPointDistance) && point.areEqual(line1.end, line2.origin, withinPointDistance));
};
- pathAreEqualMap[pathType.Circle] = function (circle1: IPathCircle, circle2: IPathCircle): boolean {
- return point.areEqual(circle1.origin, circle2.origin) && circle1.radius == circle2.radius;
+ pathAreEqualMap[pathType.Circle] = function (circle1: IPathCircle, circle2: IPathCircle, withinPointDistance): boolean {
+ return point.areEqual(circle1.origin, circle2.origin, withinPointDistance) && circle1.radius == circle2.radius;
};
- pathAreEqualMap[pathType.Arc] = function (arc1: IPathArc, arc2: IPathArc): boolean {
- return pathAreEqualMap[pathType.Circle](arc1, arc2) && angle.areEqual(arc1.startAngle, arc2.startAngle) && angle.areEqual(arc1.endAngle, arc2.endAngle);
+ pathAreEqualMap[pathType.Arc] = function (arc1: IPathArc, arc2: IPathArc, withinPointDistance): boolean {
+ return pathAreEqualMap[pathType.Circle](arc1, arc2, withinPointDistance) && angle.areEqual(arc1.startAngle, arc2.startAngle) && angle.areEqual(arc1.endAngle, arc2.endAngle);
};
/**
@@ -33,14 +34,14 @@ module MakerJs.path {
* @param b Second path.
* @returns true if paths are the same, false if they are not
*/
- export function areEqual(path1: IPath, path2: IPath): boolean {
+ export function areEqual(path1: IPath, path2: IPath, withinPointDistance?: number): boolean {
var result = false;
if (path1.type == path2.type) {
var fn = pathAreEqualMap[path1.type];
if (fn) {
- result = fn(path1, path2);
+ result = fn(path1, path2, withinPointDistance);
}
}
@@ -174,8 +175,8 @@ module MakerJs.path {
}
map[pathType.Arc] = function (arc: IPathArc) {
- arc.startAngle += angleInDegrees;
- arc.endAngle += angleInDegrees;
+ arc.startAngle = angle.noRevolutions(arc.startAngle + angleInDegrees);
+ arc.endAngle = angle.noRevolutions(arc.endAngle + angleInDegrees);
}
pathToRotate.origin = point.rotate(pathToRotate.origin, angleInDegrees, rotationOrigin);
diff --git a/src/core/point.ts b/src/core/point.ts
index 2f58fb682..f5198482f 100644
--- a/src/core/point.ts
+++ b/src/core/point.ts
@@ -32,8 +32,13 @@ module MakerJs.point {
* @param b Second point.
* @returns true if points are the same, false if they are not
*/
- export function areEqual(a: IPoint, b: IPoint): boolean {
- return a[0] == b[0] && a[1] == b[1];
+ export function areEqual(a: IPoint, b: IPoint, withinDistance?: number): boolean {
+ if (!withinDistance) {
+ return a[0] == b[0] && a[1] == b[1];
+ } else {
+ var distance = measure.pointDistance(a, b);
+ return distance <= withinDistance;
+ }
}
/**
@@ -48,6 +53,20 @@ module MakerJs.point {
return round(a[0], accuracy) == round(b[0], accuracy) && round(a[1], accuracy) == round(b[1], accuracy);
}
+ /**
+ * Get the average of two points.
+ *
+ * @param a First point.
+ * @param b Second point.
+ * @returns New point object which is the average of a and b.
+ */
+ export function average(a: IPoint, b: IPoint): IPoint{
+ function avg(i): number {
+ return (a[i] + b[i]) / 2;
+ }
+ return [avg(0), avg(1)];
+ }
+
/**
* Clone a point into a new point.
*
@@ -230,7 +249,7 @@ module MakerJs.point {
var d = measure.pointDistance(rotationOrigin, pointToRotate);
var rotatedPoint = fromPolar(pointAngleInRadians + angle.toRadians(angleInDegrees), d);
- return rounded(add(rotationOrigin, rotatedPoint));
+ return add(rotationOrigin, rotatedPoint);
}
/**
@@ -257,7 +276,7 @@ module MakerJs.point {
*/
export function serialize(pointContext: IPoint, accuracy?: number) {
var roundedPoint = rounded(pointContext, accuracy);
- return roundedPoint[0] + ',' + roundedPoint[1];
+ return JSON.stringify(roundedPoint);
}
/**
diff --git a/target/js/browser.maker.js b/target/js/browser.maker.js
index 38bc67c6a..f40754711 100644
--- a/target/js/browser.maker.js
+++ b/target/js/browser.maker.js
@@ -170,10 +170,12 @@ var MakerJs;
* @param b Second angle.
* @returns true if angles are the same, false if they are not
*/
- function areEqual(angle1, angle2) {
- var a1 = noRevolutions(MakerJs.round(angle1));
- var a2 = noRevolutions(MakerJs.round(angle2));
- return a1 == a2 || a1 + 360 == a2 || a1 - 360 == a2;
+ function areEqual(angle1, angle2, accuracy) {
+ if (accuracy === void 0) { accuracy = .0001; }
+ var a1 = noRevolutions(angle1);
+ var a2 = noRevolutions(angle2);
+ var d = noRevolutions(MakerJs.round(a2 - a1, accuracy));
+ return d == 0;
}
angle.areEqual = areEqual;
/**
@@ -323,8 +325,14 @@ var MakerJs;
* @param b Second point.
* @returns true if points are the same, false if they are not
*/
- function areEqual(a, b) {
- return a[0] == b[0] && a[1] == b[1];
+ function areEqual(a, b, withinDistance) {
+ if (!withinDistance) {
+ return a[0] == b[0] && a[1] == b[1];
+ }
+ else {
+ var distance = MakerJs.measure.pointDistance(a, b);
+ return distance <= withinDistance;
+ }
}
point.areEqual = areEqual;
/**
@@ -340,6 +348,20 @@ var MakerJs;
return MakerJs.round(a[0], accuracy) == MakerJs.round(b[0], accuracy) && MakerJs.round(a[1], accuracy) == MakerJs.round(b[1], accuracy);
}
point.areEqualRounded = areEqualRounded;
+ /**
+ * Get the average of two points.
+ *
+ * @param a First point.
+ * @param b Second point.
+ * @returns New point object which is the average of a and b.
+ */
+ function average(a, b) {
+ function avg(i) {
+ return (a[i] + b[i]) / 2;
+ }
+ return [avg(0), avg(1)];
+ }
+ point.average = average;
/**
* Clone a point into a new point.
*
@@ -507,7 +529,7 @@ var MakerJs;
var pointAngleInRadians = MakerJs.angle.ofPointInRadians(rotationOrigin, pointToRotate);
var d = MakerJs.measure.pointDistance(rotationOrigin, pointToRotate);
var rotatedPoint = fromPolar(pointAngleInRadians + MakerJs.angle.toRadians(angleInDegrees), d);
- return rounded(add(rotationOrigin, rotatedPoint));
+ return add(rotationOrigin, rotatedPoint);
}
point.rotate = rotate;
/**
@@ -534,7 +556,7 @@ var MakerJs;
*/
function serialize(pointContext, accuracy) {
var roundedPoint = rounded(pointContext, accuracy);
- return roundedPoint[0] + ',' + roundedPoint[1];
+ return JSON.stringify(roundedPoint);
}
point.serialize = serialize;
/**
@@ -568,14 +590,15 @@ var MakerJs;
* @private
*/
var pathAreEqualMap = {};
- pathAreEqualMap[MakerJs.pathType.Line] = function (line1, line2) {
- return (MakerJs.point.areEqual(line1.origin, line2.origin) && MakerJs.point.areEqual(line1.end, line2.end)) || (MakerJs.point.areEqual(line1.origin, line2.end) && MakerJs.point.areEqual(line1.end, line2.origin));
+ pathAreEqualMap[MakerJs.pathType.Line] = function (line1, line2, withinPointDistance) {
+ return (MakerJs.point.areEqual(line1.origin, line2.origin, withinPointDistance) && MakerJs.point.areEqual(line1.end, line2.end, withinPointDistance))
+ || (MakerJs.point.areEqual(line1.origin, line2.end, withinPointDistance) && MakerJs.point.areEqual(line1.end, line2.origin, withinPointDistance));
};
- pathAreEqualMap[MakerJs.pathType.Circle] = function (circle1, circle2) {
- return MakerJs.point.areEqual(circle1.origin, circle2.origin) && circle1.radius == circle2.radius;
+ pathAreEqualMap[MakerJs.pathType.Circle] = function (circle1, circle2, withinPointDistance) {
+ return MakerJs.point.areEqual(circle1.origin, circle2.origin, withinPointDistance) && circle1.radius == circle2.radius;
};
- pathAreEqualMap[MakerJs.pathType.Arc] = function (arc1, arc2) {
- return pathAreEqualMap[MakerJs.pathType.Circle](arc1, arc2) && MakerJs.angle.areEqual(arc1.startAngle, arc2.startAngle) && MakerJs.angle.areEqual(arc1.endAngle, arc2.endAngle);
+ pathAreEqualMap[MakerJs.pathType.Arc] = function (arc1, arc2, withinPointDistance) {
+ return pathAreEqualMap[MakerJs.pathType.Circle](arc1, arc2, withinPointDistance) && MakerJs.angle.areEqual(arc1.startAngle, arc2.startAngle) && MakerJs.angle.areEqual(arc1.endAngle, arc2.endAngle);
};
/**
* Find out if two paths are equal.
@@ -584,12 +607,12 @@ var MakerJs;
* @param b Second path.
* @returns true if paths are the same, false if they are not
*/
- function areEqual(path1, path2) {
+ function areEqual(path1, path2, withinPointDistance) {
var result = false;
if (path1.type == path2.type) {
var fn = pathAreEqualMap[path1.type];
if (fn) {
- result = fn(path1, path2);
+ result = fn(path1, path2, withinPointDistance);
}
}
return result;
@@ -688,8 +711,8 @@ var MakerJs;
line.end = MakerJs.point.rotate(line.end, angleInDegrees, rotationOrigin);
};
map[MakerJs.pathType.Arc] = function (arc) {
- arc.startAngle += angleInDegrees;
- arc.endAngle += angleInDegrees;
+ arc.startAngle = MakerJs.angle.noRevolutions(arc.startAngle + angleInDegrees);
+ arc.endAngle = MakerJs.angle.noRevolutions(arc.endAngle + angleInDegrees);
};
pathToRotate.origin = MakerJs.point.rotate(pathToRotate.origin, angleInDegrees, rotationOrigin);
var fn = map[pathToRotate.type];
@@ -741,12 +764,13 @@ var MakerJs;
return null;
}
function getAngleStrictlyBetweenArcAngles() {
- var endAngle = MakerJs.angle.ofArcEnd(arc);
+ var startAngle = MakerJs.angle.noRevolutions(arc.startAngle);
+ var endAngle = startAngle + MakerJs.angle.ofArcEnd(arc) - arc.startAngle;
var tries = [0, 1, -1];
for (var i = 0; i < tries.length; i++) {
var add = +360 * tries[i];
- if (MakerJs.measure.isBetween(angleAtBreakPoint + add, arc.startAngle, endAngle, true)) {
- return angleAtBreakPoint + add;
+ if (MakerJs.measure.isBetween(angleAtBreakPoint + add, startAngle, endAngle, true)) {
+ return arc.startAngle + angleAtBreakPoint + add - startAngle;
}
}
return null;
@@ -917,13 +941,33 @@ var MakerJs;
return count;
}
model.countChildModels = countChildModels;
+ /**
+ * Get an unused id in the models map with the same prefix.
+ *
+ * @param modelContext The model containing the models map.
+ * @param modelId The id to use directly (if unused), or as a prefix.
+ */
+ function getSimilarModelId(modelContext, modelId) {
+ if (!modelContext.models)
+ return modelId;
+ var i = 0;
+ var newModelId = modelId;
+ while (newModelId in modelContext.models) {
+ i++;
+ newModelId = modelId + '_' + i;
+ }
+ return newModelId;
+ }
+ model.getSimilarModelId = getSimilarModelId;
/**
* Get an unused id in the paths map with the same prefix.
*
* @param modelContext The model containing the paths map.
- * @param pathId The pathId to use directly (if unused), or as a prefix.
+ * @param pathId The id to use directly (if unused), or as a prefix.
*/
function getSimilarPathId(modelContext, pathId) {
+ if (!modelContext.paths)
+ return pathId;
var i = 0;
var newPathId = pathId;
while (newPathId in modelContext.paths) {
@@ -940,6 +984,8 @@ var MakerJs;
* @param origin Optional offset reference point.
*/
function originate(modelToOriginate, origin) {
+ if (!modelToOriginate)
+ return;
var newOrigin = MakerJs.point.add(modelToOriginate.origin, origin);
if (modelToOriginate.paths) {
for (var id in modelToOriginate.paths) {
@@ -1101,11 +1147,15 @@ var MakerJs;
function walkPaths(modelContext, callback) {
if (modelContext.paths) {
for (var pathId in modelContext.paths) {
+ if (!modelContext.paths[pathId])
+ continue;
callback(modelContext, pathId, modelContext.paths[pathId]);
}
}
if (modelContext.models) {
for (var id in modelContext.models) {
+ if (!modelContext.models[id])
+ continue;
walkPaths(modelContext.models[id], callback);
}
}
@@ -1116,29 +1166,33 @@ var MakerJs;
var MakerJs;
(function (MakerJs) {
var model;
- (function (model_1) {
+ (function (model) {
/**
* @private
*/
function getNonZeroSegments(pathToSegment, breakPoint) {
+ var segmentType = pathToSegment.type;
var segment1 = MakerJs.cloneObject(pathToSegment);
var segment2 = MakerJs.path.breakAtPoint(segment1, breakPoint);
if (segment2) {
var segments = [segment1, segment2];
for (var i = 2; i--;) {
- if (MakerJs.round(MakerJs.measure.pathLength(segments[i]), .00001) == 0) {
+ if (MakerJs.round(MakerJs.measure.pathLength(segments[i]), .0001) == 0) {
return null;
}
}
return segments;
}
+ else if (segmentType == MakerJs.pathType.Circle) {
+ return [segment1];
+ }
return null;
}
/**
* @private
*/
function breakAlongForeignPath(segments, overlappedSegments, foreignPath) {
- if (MakerJs.path.areEqual(segments[0].path, foreignPath)) {
+ if (MakerJs.path.areEqual(segments[0].path, foreignPath, .0001)) {
segments[0].overlapped = true;
segments[0].duplicate = true;
overlappedSegments.push(segments[0]);
@@ -1170,11 +1224,18 @@ var MakerJs;
}
if (subSegments) {
segments[i].path = subSegments[0];
- var newSegment = { path: subSegments[1], overlapped: segments[i].overlapped, uniqueForeignIntersectionPoints: [] };
- if (segments[i].overlapped) {
- overlappedSegments.push(newSegment);
+ if (subSegments[1]) {
+ var newSegment = {
+ path: subSegments[1],
+ pathId: segments[0].pathId,
+ overlapped: segments[i].overlapped,
+ uniqueForeignIntersectionPoints: []
+ };
+ if (segments[i].overlapped) {
+ overlappedSegments.push(newSegment);
+ }
+ segments.push(newSegment);
}
- segments.push(newSegment);
//re-check this segment for another deep intersection
i--;
}
@@ -1188,7 +1249,7 @@ var MakerJs;
var added = 0;
function addUniquePoint(pointToAdd) {
for (var i = 0; i < pointArray.length; i++) {
- if (MakerJs.point.areEqualRounded(pointArray[i], pointToAdd)) {
+ if (MakerJs.point.areEqual(pointArray[i], pointToAdd, .000000001)) {
return;
}
}
@@ -1203,7 +1264,7 @@ var MakerJs;
/**
* @private
*/
- function checkInsideForeignPath(segment, foreignPath, farPoint) {
+ function checkIntersectsForeignPath(segment, foreignPath, foreignPathId, farPoint) {
if (farPoint === void 0) { farPoint = [7654321, 1234567]; }
var origin = MakerJs.point.middle(segment.path);
var lineToFarPoint = new MakerJs.paths.Line(origin, farPoint);
@@ -1220,9 +1281,9 @@ var MakerJs;
* @private
*/
function checkInsideForeignModel(segment, modelToIntersect, farPoint) {
- model_1.walkPaths(modelToIntersect, function (mx, pathId2, path2) {
+ model.walkPaths(modelToIntersect, function (mx, pathId2, path2) {
if (path2) {
- checkInsideForeignPath(segment, path2, farPoint);
+ checkIntersectsForeignPath(segment, path2, pathId2, farPoint);
}
});
}
@@ -1243,19 +1304,30 @@ var MakerJs;
checkInsideForeignModel(segment, modelContext, farPoint);
return !!segment.isInside;
}
- model_1.isPathInsideModel = isPathInsideModel;
+ model.isPathInsideModel = isPathInsideModel;
+ /**
+ * Break a model's paths everywhere they intersect with another path.
+ *
+ * @param modelToBreak The model containing paths to be broken.
+ * @param modelToIntersect Optional model containing paths to look for intersection, or else the modelToBreak will be used.
+ */
+ function breakPathsAtIntersections(modelToBreak, modelToIntersect) {
+ breakAllPathsAtIntersections(modelToBreak, modelToIntersect || modelToBreak, false);
+ }
+ model.breakPathsAtIntersections = breakPathsAtIntersections;
/**
* @private
*/
- function breakAllPathsAtIntersections(modelToBreak, modelToIntersect, farPoint) {
+ function breakAllPathsAtIntersections(modelToBreak, modelToIntersect, checkIsInside, farPoint) {
var crossedPaths = [];
var overlappedSegments = [];
- model_1.walkPaths(modelToBreak, function (modelContext, pathId1, path1) {
+ model.walkPaths(modelToBreak, function (modelContext, pathId1, path1) {
if (!path1)
return;
//clone this path and make it the first segment
var segment = {
path: MakerJs.cloneObject(path1),
+ pathId: pathId1,
overlapped: false,
uniqueForeignIntersectionPoints: []
};
@@ -1265,14 +1337,16 @@ var MakerJs;
segments: [segment]
};
//keep breaking the segments anywhere they intersect with paths of the other model
- model_1.walkPaths(modelToIntersect, function (mx, pathId2, path2) {
- if (path2) {
+ model.walkPaths(modelToIntersect, function (mx, pathId2, path2) {
+ if (path2 && path1 !== path2) {
breakAlongForeignPath(thisPath.segments, overlappedSegments, path2);
}
});
- //check each segment whether it is inside or outside
- for (var i = 0; i < thisPath.segments.length; i++) {
- checkInsideForeignModel(thisPath.segments[i], modelToIntersect);
+ if (checkIsInside) {
+ //check each segment whether it is inside or outside
+ for (var i = 0; i < thisPath.segments.length; i++) {
+ checkInsideForeignModel(thisPath.segments[i], modelToIntersect, farPoint);
+ }
}
crossedPaths.push(thisPath);
});
@@ -1281,9 +1355,9 @@ var MakerJs;
/**
* @private
*/
- function checkForEqualOverlaps(crossedPathsA, crossedPathsB) {
+ function checkForEqualOverlaps(crossedPathsA, crossedPathsB, pointMatchingDistance) {
function compareSegments(segment1, segment2) {
- if (MakerJs.path.areEqual(segment1.path, segment2.path)) {
+ if (MakerJs.path.areEqual(segment1.path, segment2.path, pointMatchingDistance)) {
segment1.duplicate = segment2.duplicate = true;
}
}
@@ -1300,13 +1374,13 @@ var MakerJs;
* @private
*/
function addOrDeleteSegments(crossedPath, includeInside, includeOutside, keepDuplicates) {
- function addSegment(model, pathIdBase, segment) {
- var id = model_1.getSimilarPathId(model, pathIdBase);
- model.paths[id] = segment.path;
+ function addSegment(modelContext, pathIdBase, segment) {
+ var id = model.getSimilarPathId(modelContext, pathIdBase);
+ modelContext.paths[id] = segment.path;
}
- function checkAddSegment(model, pathIdBase, segment) {
+ function checkAddSegment(modelContext, pathIdBase, segment) {
if (segment.isInside && includeInside || !segment.isInside && includeOutside) {
- addSegment(model, pathIdBase, segment);
+ addSegment(modelContext, pathIdBase, segment);
}
}
//delete the original, its segments will be added
@@ -1323,7 +1397,7 @@ var MakerJs;
}
}
/**
- * Combine 2 models. The models should be originated.
+ * Combine 2 models. The models should be originated, and every path within each model should be part of a loop.
*
* @param modelA First model to combine.
* @param modelB Second model to combine.
@@ -1334,23 +1408,30 @@ var MakerJs;
* @param keepDuplicates Flag to include paths which are duplicate in both models.
* @param farPoint Optional point of reference which is outside the bounds of both models.
*/
- function combine(modelA, modelB, includeAInsideB, includeAOutsideB, includeBInsideA, includeBOutsideA, keepDuplicates, farPoint) {
+ function combine(modelA, modelB, includeAInsideB, includeAOutsideB, includeBInsideA, includeBOutsideA, options) {
if (includeAInsideB === void 0) { includeAInsideB = false; }
if (includeAOutsideB === void 0) { includeAOutsideB = true; }
if (includeBInsideA === void 0) { includeBInsideA = false; }
if (includeBOutsideA === void 0) { includeBOutsideA = true; }
- if (keepDuplicates === void 0) { keepDuplicates = true; }
- var pathsA = breakAllPathsAtIntersections(modelA, modelB, farPoint);
- var pathsB = breakAllPathsAtIntersections(modelB, modelA, farPoint);
- checkForEqualOverlaps(pathsA.overlappedSegments, pathsB.overlappedSegments);
+ var opts = {
+ trimDeadEnds: true,
+ pointMatchingDistance: .005
+ };
+ MakerJs.extendObject(opts, options);
+ var pathsA = breakAllPathsAtIntersections(modelA, modelB, true, opts.farPoint);
+ var pathsB = breakAllPathsAtIntersections(modelB, modelA, true, opts.farPoint);
+ checkForEqualOverlaps(pathsA.overlappedSegments, pathsB.overlappedSegments, opts.pointMatchingDistance);
for (var i = 0; i < pathsA.crossedPaths.length; i++) {
- addOrDeleteSegments(pathsA.crossedPaths[i], includeAInsideB, includeAOutsideB, keepDuplicates);
+ addOrDeleteSegments(pathsA.crossedPaths[i], includeAInsideB, includeAOutsideB, true);
}
for (var i = 0; i < pathsB.crossedPaths.length; i++) {
- addOrDeleteSegments(pathsB.crossedPaths[i], includeBInsideA, includeBOutsideA);
+ addOrDeleteSegments(pathsB.crossedPaths[i], includeBInsideA, includeBOutsideA, false);
+ }
+ if (opts.trimDeadEnds) {
+ model.removeDeadEnds({ models: { modelA: modelA, modelB: modelB } });
}
}
- model_1.combine = combine;
+ model.combine = combine;
})(model = MakerJs.model || (MakerJs.model = {}));
})(MakerJs || (MakerJs = {}));
var MakerJs;
@@ -1488,6 +1569,10 @@ var MakerJs;
function isBetweenArcAngles(angleInQuestion, arc, exclusive) {
var startAngle = arc.startAngle;
var endAngle = MakerJs.angle.ofArcEnd(arc);
+ var span = endAngle - startAngle;
+ startAngle = MakerJs.angle.noRevolutions(startAngle);
+ endAngle = startAngle + span;
+ angleInQuestion = MakerJs.angle.noRevolutions(angleInQuestion);
//computed angles will not be negative, but the arc may have specified a negative angle, so check against one revolution forward and backward
return (isBetween(angleInQuestion, startAngle, endAngle, exclusive) || isBetween(angleInQuestion, startAngle + 360, endAngle + 360, exclusive) || isBetween(angleInQuestion, startAngle - 360, endAngle - 360, exclusive));
}
@@ -1502,11 +1587,11 @@ var MakerJs;
*/
function isBetweenPoints(pointInQuestion, line, exclusive) {
for (var i = 2; i--;) {
- var origin_value = MakerJs.round(line.origin[i]);
- var end_value = MakerJs.round(line.end[i]);
- if (origin_value == end_value) {
+ if (MakerJs.round(line.origin[i] - line.end[i], .000001) == 0) {
continue;
}
+ var origin_value = MakerJs.round(line.origin[i]);
+ var end_value = MakerJs.round(line.end[i]);
if (!isBetween(MakerJs.round(pointInQuestion[i]), origin_value, end_value, exclusive))
return false;
}
@@ -1622,16 +1707,18 @@ var MakerJs;
getExtreme(totalMeasurement.low, pathMeasurement.low, Math.min);
getExtreme(totalMeasurement.high, pathMeasurement.high, Math.max);
}
- function measure(model, offsetOrigin) {
- var newOrigin = MakerJs.point.add(model.origin, offsetOrigin);
- if (model.paths) {
- for (var id in model.paths) {
- lowerOrHigher(newOrigin, pathExtents(model.paths[id]));
+ function measure(modelToMeasure, offsetOrigin) {
+ if (!modelToMeasure)
+ return;
+ var newOrigin = MakerJs.point.add(modelToMeasure.origin, offsetOrigin);
+ if (modelToMeasure.paths) {
+ for (var id in modelToMeasure.paths) {
+ lowerOrHigher(newOrigin, pathExtents(modelToMeasure.paths[id]));
}
}
- if (model.models) {
- for (var id in model.models) {
- measure(model.models[id], newOrigin);
+ if (modelToMeasure.models) {
+ for (var id in modelToMeasure.models) {
+ measure(modelToMeasure.models[id], newOrigin);
}
}
}
@@ -1700,12 +1787,18 @@ var MakerJs;
var newOffset = MakerJs.point.add((this.fixPoint ? this.fixPoint(modelToExport.origin) : modelToExport.origin), offset);
if (modelToExport.paths) {
for (var id in modelToExport.paths) {
- this.exportPath(id, modelToExport.paths[id], newOffset, modelToExport.layer);
+ var currPath = modelToExport.paths[id];
+ if (!currPath)
+ continue;
+ this.exportPath(id, currPath, newOffset, modelToExport.layer);
}
}
if (modelToExport.models) {
for (var id in modelToExport.models) {
- this.exportModel(id, modelToExport.models[id], newOffset);
+ var currModel = modelToExport.models[id];
+ if (!currModel)
+ continue;
+ this.exportModel(id, currModel, newOffset);
}
}
if (this.endModel) {
@@ -2087,14 +2180,14 @@ var MakerJs;
* @private
*/
function getSlope(line) {
- var dx = MakerJs.round(line.end[0] - line.origin[0]);
- if (dx == 0) {
+ var dx = line.end[0] - line.origin[0];
+ if (MakerJs.round(dx) == 0) {
return {
line: line,
hasSlope: false
};
}
- var dy = MakerJs.round(line.end[1] - line.origin[1]);
+ var dy = line.end[1] - line.origin[1];
var slope = dy / dx;
var yIntercept = line.origin[1] - slope * line.origin[0];
return {
@@ -2117,13 +2210,13 @@ var MakerJs;
*/
function checkAngleOverlap(arc1, arc2, options) {
var pointsOfIntersection = [];
- function checkAngles(index, a, b) {
+ function checkAngles(a, b) {
function checkAngle(n) {
return MakerJs.measure.isBetweenArcAngles(n, a, options.excludeTangents);
}
return checkAngle(b.startAngle) || checkAngle(b.endAngle);
}
- if (checkAngles(0, arc1, arc2) || checkAngles(1, arc2, arc1) || (arc1.startAngle == arc2.startAngle && arc1.endAngle == arc2.endAngle)) {
+ if (checkAngles(arc1, arc2) || checkAngles(arc2, arc1) || (arc1.startAngle == arc2.startAngle && arc1.endAngle == arc2.endAngle)) {
options.out_AreOverlapped = true;
}
}
@@ -2156,15 +2249,15 @@ var MakerJs;
var slope2 = getSlope(line2);
if (!slope1.hasSlope && !slope2.hasSlope) {
//lines are both vertical, see if x are the same
- if (slope1.line.origin[0] == slope2.line.origin[0]) {
+ if (MakerJs.round(slope1.line.origin[0] - slope2.line.origin[0]) == 0) {
//check for overlap
checkLineOverlap(line1, line2, options);
}
return null;
}
- if (slope1.hasSlope && slope2.hasSlope && (slope1.slope == slope2.slope)) {
+ if (slope1.hasSlope && slope2.hasSlope && (MakerJs.round(slope1.slope - slope2.slope, .00001) == 0)) {
//lines are parallel, but not vertical, see if y-intercept is the same
- if (slope1.yIntercept == slope2.yIntercept) {
+ if (MakerJs.round(slope1.yIntercept - slope2.yIntercept, .00001) == 0) {
//check for overlap
checkLineOverlap(line1, line2, options);
}
@@ -2206,21 +2299,22 @@ var MakerJs;
}
//line is horizontal, get the y value from any point
var lineY = MakerJs.round(clonedLine.origin[1]);
+ var lineYabs = Math.abs(lineY);
//if y is greater than radius, there is no intersection
- if (lineY > radius) {
+ if (lineYabs > radius) {
return null;
}
var anglesOfIntersection = [];
//if horizontal Y is the same as the radius, we know it's 90 degrees
- if (lineY == radius) {
+ if (lineYabs == radius) {
if (options.excludeTangents) {
return null;
}
- anglesOfIntersection.push(unRotate(90));
+ anglesOfIntersection.push(unRotate(lineY > 0 ? 90 : 270));
}
else {
function intersectionBetweenEndpoints(x, angleOfX) {
- if (MakerJs.measure.isBetween(x, clonedLine.origin[0], clonedLine.end[0], options.excludeTangents)) {
+ if (MakerJs.measure.isBetween(MakerJs.round(x), MakerJs.round(clonedLine.origin[0]), MakerJs.round(clonedLine.end[0]), options.excludeTangents)) {
anglesOfIntersection.push(unRotate(angleOfX));
}
}
@@ -2242,7 +2336,7 @@ var MakerJs;
*/
function circleToCircle(circle1, circle2, options) {
//see if circles are the same
- if (circle1.radius == circle2.radius && MakerJs.point.areEqual(circle1.origin, circle2.origin)) {
+ if (circle1.radius == circle2.radius && MakerJs.point.areEqual(circle1.origin, circle2.origin, .0001)) {
options.out_AreOverlapped = true;
return null;
}
@@ -2274,14 +2368,14 @@ var MakerJs;
return null;
}
//see if circles are tangent interior
- if (c2.radius - x == c1.radius) {
+ if (MakerJs.round(c2.radius - x - c1.radius) == 0) {
if (options.excludeTangents) {
return null;
}
return [[unRotate(180)], [unRotate(180)]];
}
//see if circles are tangent exterior
- if (x - c2.radius == c1.radius) {
+ if (MakerJs.round(x - c2.radius - c1.radius) == 0) {
if (options.excludeTangents) {
return null;
}
@@ -2328,7 +2422,7 @@ var MakerJs;
/**
* @private
*/
- function getMatchingPointProperties(path1, path2) {
+ function getMatchingPointProperties(path1, path2, options) {
var path1Properties = getPointProperties(path1);
var path2Properties = getPointProperties(path2);
var result = null;
@@ -2342,7 +2436,7 @@ var MakerJs;
};
}
function check(i1, i2) {
- if (MakerJs.point.areEqualRounded(path1Properties[i1].point, path2Properties[i2].point)) {
+ if (MakerJs.point.areEqual(path1Properties[i1].point, path2Properties[i2].point, .0001)) {
result = [
makeMatch(path1, path1Properties, i1),
makeMatch(path2, path2Properties, i2)
@@ -2366,7 +2460,7 @@ var MakerJs;
return false;
}
properties[i].shardPoint = circleIntersection.intersectionPoints[0];
- if (MakerJs.point.areEqualRounded(properties[i].point, circleIntersection.intersectionPoints[0], options.accuracy)) {
+ if (MakerJs.point.areEqual(properties[i].point, circleIntersection.intersectionPoints[0], .0001)) {
if (circleIntersection.intersectionPoints.length > 1) {
properties[i].shardPoint = circleIntersection.intersectionPoints[1];
}
@@ -2518,11 +2612,11 @@ var MakerJs;
function dogbone(line1, line2, filletRadius, options) {
if (MakerJs.isPathLine(line1) && MakerJs.isPathLine(line2) && filletRadius && filletRadius > 0) {
var opts = {
- accuracy: .0001
+ pointMatchingDistance: .005
};
MakerJs.extendObject(opts, options);
//first find the common point
- var commonProperty = getMatchingPointProperties(line1, line2);
+ var commonProperty = getMatchingPointProperties(line1, line2, options);
if (commonProperty) {
//get the ratio comparison of the two lines
var ratio = getLineRatio([line1, line2]);
@@ -2569,11 +2663,11 @@ var MakerJs;
function fillet(path1, path2, filletRadius, options) {
if (path1 && path2 && filletRadius && filletRadius > 0) {
var opts = {
- accuracy: .0001
+ pointMatchingDistance: .005
};
MakerJs.extendObject(opts, options);
//first find the common point
- var commonProperty = getMatchingPointProperties(path1, path2);
+ var commonProperty = getMatchingPointProperties(path1, path2, options);
if (commonProperty) {
//since arcs can curl beyond, we need a local reference point.
//An intersection with a circle of the same radius as the desired fillet should suffice.
@@ -2675,6 +2769,34 @@ var MakerJs;
(function (MakerJs) {
var model;
(function (model) {
+ /**
+ * @private
+ */
+ var PointMap = (function () {
+ function PointMap(matchingDistance) {
+ if (matchingDistance === void 0) { matchingDistance = .001; }
+ this.matchingDistance = matchingDistance;
+ this.list = [];
+ }
+ PointMap.prototype.add = function (pointToAdd, item) {
+ this.list.push({ averagePoint: pointToAdd, item: item });
+ };
+ PointMap.prototype.find = function (pointToFind, saveAverage) {
+ for (var i = 0; i < this.list.length; i++) {
+ var item = this.list[i];
+ var distance = MakerJs.measure.pointDistance(pointToFind, item.averagePoint);
+ if (distance <= this.matchingDistance) {
+ if (saveAverage) {
+ item.averagePoint = MakerJs.point.average(item.averagePoint, pointToFind);
+ }
+ return item.item;
+ }
+ }
+ return null;
+ };
+ return PointMap;
+ })();
+ model.PointMap = PointMap;
/**
* @private
*/
@@ -2721,7 +2843,7 @@ var MakerJs;
while (true) {
var currPath = currLink.path;
currPath.reversed = currLink.reversed;
- var id = model.getSimilarPathId(loopModel, currPath.primePathId);
+ var id = model.getSimilarPathId(loopModel, currPath.pathId);
loopModel.paths[id] = currPath;
if (!connections[currLink.nextConnection])
break;
@@ -2751,11 +2873,11 @@ var MakerJs;
var connections = {};
var result = { models: {} };
var opts = {
- accuracy: .0001
+ pointMatchingDistance: .005
};
MakerJs.extendObject(opts, options);
function getLinkedPathsOnConnectionPoint(p) {
- var serializedPoint = MakerJs.point.serialize(p, opts.accuracy);
+ var serializedPoint = MakerJs.point.serialize(p, .0001); //TODO convert to pointmap
if (!(serializedPoint in connections)) {
connections[serializedPoint] = [];
}
@@ -2774,14 +2896,15 @@ var MakerJs;
}
return result.models[id];
}
+ //todo: remove dead ends first
model.originate(modelContext);
//find loops by looking at all paths in this model
model.walkPaths(modelContext, function (modelContext, pathId, pathContext) {
if (!pathContext)
return;
var safePath = MakerJs.cloneObject(pathContext);
- safePath.primePathId = pathId;
- safePath.primeModel = modelContext;
+ safePath.pathId = pathId;
+ safePath.modelContext = modelContext;
//circles are loops by nature
if (safePath.type == MakerJs.pathType.Circle) {
var loopModel = {
@@ -2797,7 +2920,7 @@ var MakerJs;
for (var i = 2; i--;) {
var linkedPath = {
path: safePath,
- nextConnection: MakerJs.point.serialize(safePath.endPoints[1 - i], opts.accuracy),
+ nextConnection: MakerJs.point.serialize(safePath.endPoints[1 - i], .0001),
reversed: i != 0
};
getLinkedPathsOnConnectionPoint(safePath.endPoints[i]).push(linkedPath);
@@ -2837,13 +2960,110 @@ var MakerJs;
function detachLoop(loopToDetach) {
for (var id in loopToDetach.paths) {
var pathDirectionalWithOriginalContext = loopToDetach.paths[id];
- var primeModel = pathDirectionalWithOriginalContext.primeModel;
- if (primeModel && primeModel.paths && pathDirectionalWithOriginalContext.primePathId) {
- delete primeModel.paths[pathDirectionalWithOriginalContext.primePathId];
+ var primeModel = pathDirectionalWithOriginalContext.modelContext;
+ if (primeModel && primeModel.paths && pathDirectionalWithOriginalContext.pathId) {
+ delete primeModel.paths[pathDirectionalWithOriginalContext.pathId];
}
}
}
model.detachLoop = detachLoop;
+ /**
+ * @private
+ */
+ var DeadEndFinder = (function () {
+ function DeadEndFinder(pointMatchingDistance) {
+ this.pointMatchingDistance = pointMatchingDistance;
+ this.pointMap = new PointMap(pointMatchingDistance);
+ }
+ DeadEndFinder.prototype.addPathRef = function (p, pathRef) {
+ var found = this.pointMap.find(p, true);
+ if (found) {
+ found.push(pathRef);
+ }
+ else {
+ this.pointMap.add(p, [pathRef]);
+ }
+ };
+ DeadEndFinder.prototype.removeMatchingPathRefs = function (a, b) {
+ //see if any are the same in each array
+ for (var ai = 0; ai < a.length; ai++) {
+ for (var bi = 0; bi < b.length; bi++) {
+ if (a[ai] === b[bi]) {
+ var pathRef = a[ai];
+ a.splice(ai, 1);
+ b.splice(bi, 1);
+ return pathRef;
+ }
+ }
+ }
+ return null;
+ };
+ DeadEndFinder.prototype.removePathRef = function (pathRef) {
+ var _this = this;
+ var removePath = function (p) {
+ var pathRefs = _this.pointMap.find(p, false);
+ for (var i = 0; i < pathRefs.length; i++) {
+ if (pathRefs[i] === pathRef) {
+ pathRefs.splice(i, 1);
+ return;
+ }
+ }
+ };
+ for (var i = 2; i--;) {
+ removePath(pathRef.endPoints[i]);
+ }
+ };
+ DeadEndFinder.prototype.removeDeadEnd = function () {
+ var found = false;
+ var oddPathRefs = null;
+ for (var i = 0; i < this.pointMap.list.length; i++) {
+ var pathRefs = this.pointMap.list[i].item;
+ if (pathRefs.length % 2 == 0)
+ continue;
+ if (pathRefs.length == 1) {
+ var pathRef = pathRefs[0];
+ this.removePathRef(pathRef);
+ delete pathRef.modelContext.paths[pathRef.pathId];
+ found = true;
+ }
+ else {
+ if (!oddPathRefs) {
+ //save this for another iteration
+ oddPathRefs = pathRefs;
+ }
+ else {
+ //compare with the saved
+ var pathRef = this.removeMatchingPathRefs(oddPathRefs, pathRefs);
+ if (pathRef) {
+ delete pathRef.modelContext.paths[pathRef.pathId];
+ found = true;
+ //clear the saved
+ oddPathRefs = null;
+ }
+ }
+ }
+ }
+ return found;
+ };
+ return DeadEndFinder;
+ })();
+ function removeDeadEnds(modelContext, pointMatchingDistance) {
+ if (pointMatchingDistance === void 0) { pointMatchingDistance = .005; }
+ var serializedPointAccuracy = .0001;
+ var deadEndFinder = new DeadEndFinder(pointMatchingDistance);
+ model.walkPaths(modelContext, function (modelContext, pathId, pathContext) {
+ var endPoints = MakerJs.point.fromPathEnds(pathContext);
+ if (!endPoints)
+ return;
+ var pathRef = { modelContext: modelContext, pathId: pathId, endPoints: endPoints };
+ for (var i = 2; i--;) {
+ deadEndFinder.addPathRef(endPoints[i], pathRef);
+ }
+ });
+ while (deadEndFinder.removeDeadEnd())
+ ;
+ }
+ model.removeDeadEnds = removeDeadEnds;
})(model = MakerJs.model || (MakerJs.model = {}));
})(MakerJs || (MakerJs = {}));
var MakerJs;
@@ -3056,7 +3276,7 @@ var MakerJs;
var depthModel;
var opts = {
extrusion: 1,
- accuracy: .0001
+ pointMatchingDistance: .005
};
MakerJs.extendObject(opts, options);
var loops = MakerJs.model.findLoops(modelToExport, opts);
diff --git a/target/js/node.maker.js b/target/js/node.maker.js
index c2414bf35..d30624d6e 100644
--- a/target/js/node.maker.js
+++ b/target/js/node.maker.js
@@ -169,10 +169,12 @@ var MakerJs;
* @param b Second angle.
* @returns true if angles are the same, false if they are not
*/
- function areEqual(angle1, angle2) {
- var a1 = noRevolutions(MakerJs.round(angle1));
- var a2 = noRevolutions(MakerJs.round(angle2));
- return a1 == a2 || a1 + 360 == a2 || a1 - 360 == a2;
+ function areEqual(angle1, angle2, accuracy) {
+ if (accuracy === void 0) { accuracy = .0001; }
+ var a1 = noRevolutions(angle1);
+ var a2 = noRevolutions(angle2);
+ var d = noRevolutions(MakerJs.round(a2 - a1, accuracy));
+ return d == 0;
}
angle.areEqual = areEqual;
/**
@@ -322,8 +324,14 @@ var MakerJs;
* @param b Second point.
* @returns true if points are the same, false if they are not
*/
- function areEqual(a, b) {
- return a[0] == b[0] && a[1] == b[1];
+ function areEqual(a, b, withinDistance) {
+ if (!withinDistance) {
+ return a[0] == b[0] && a[1] == b[1];
+ }
+ else {
+ var distance = MakerJs.measure.pointDistance(a, b);
+ return distance <= withinDistance;
+ }
}
point.areEqual = areEqual;
/**
@@ -339,6 +347,20 @@ var MakerJs;
return MakerJs.round(a[0], accuracy) == MakerJs.round(b[0], accuracy) && MakerJs.round(a[1], accuracy) == MakerJs.round(b[1], accuracy);
}
point.areEqualRounded = areEqualRounded;
+ /**
+ * Get the average of two points.
+ *
+ * @param a First point.
+ * @param b Second point.
+ * @returns New point object which is the average of a and b.
+ */
+ function average(a, b) {
+ function avg(i) {
+ return (a[i] + b[i]) / 2;
+ }
+ return [avg(0), avg(1)];
+ }
+ point.average = average;
/**
* Clone a point into a new point.
*
@@ -506,7 +528,7 @@ var MakerJs;
var pointAngleInRadians = MakerJs.angle.ofPointInRadians(rotationOrigin, pointToRotate);
var d = MakerJs.measure.pointDistance(rotationOrigin, pointToRotate);
var rotatedPoint = fromPolar(pointAngleInRadians + MakerJs.angle.toRadians(angleInDegrees), d);
- return rounded(add(rotationOrigin, rotatedPoint));
+ return add(rotationOrigin, rotatedPoint);
}
point.rotate = rotate;
/**
@@ -533,7 +555,7 @@ var MakerJs;
*/
function serialize(pointContext, accuracy) {
var roundedPoint = rounded(pointContext, accuracy);
- return roundedPoint[0] + ',' + roundedPoint[1];
+ return JSON.stringify(roundedPoint);
}
point.serialize = serialize;
/**
@@ -567,14 +589,15 @@ var MakerJs;
* @private
*/
var pathAreEqualMap = {};
- pathAreEqualMap[MakerJs.pathType.Line] = function (line1, line2) {
- return (MakerJs.point.areEqual(line1.origin, line2.origin) && MakerJs.point.areEqual(line1.end, line2.end)) || (MakerJs.point.areEqual(line1.origin, line2.end) && MakerJs.point.areEqual(line1.end, line2.origin));
+ pathAreEqualMap[MakerJs.pathType.Line] = function (line1, line2, withinPointDistance) {
+ return (MakerJs.point.areEqual(line1.origin, line2.origin, withinPointDistance) && MakerJs.point.areEqual(line1.end, line2.end, withinPointDistance))
+ || (MakerJs.point.areEqual(line1.origin, line2.end, withinPointDistance) && MakerJs.point.areEqual(line1.end, line2.origin, withinPointDistance));
};
- pathAreEqualMap[MakerJs.pathType.Circle] = function (circle1, circle2) {
- return MakerJs.point.areEqual(circle1.origin, circle2.origin) && circle1.radius == circle2.radius;
+ pathAreEqualMap[MakerJs.pathType.Circle] = function (circle1, circle2, withinPointDistance) {
+ return MakerJs.point.areEqual(circle1.origin, circle2.origin, withinPointDistance) && circle1.radius == circle2.radius;
};
- pathAreEqualMap[MakerJs.pathType.Arc] = function (arc1, arc2) {
- return pathAreEqualMap[MakerJs.pathType.Circle](arc1, arc2) && MakerJs.angle.areEqual(arc1.startAngle, arc2.startAngle) && MakerJs.angle.areEqual(arc1.endAngle, arc2.endAngle);
+ pathAreEqualMap[MakerJs.pathType.Arc] = function (arc1, arc2, withinPointDistance) {
+ return pathAreEqualMap[MakerJs.pathType.Circle](arc1, arc2, withinPointDistance) && MakerJs.angle.areEqual(arc1.startAngle, arc2.startAngle) && MakerJs.angle.areEqual(arc1.endAngle, arc2.endAngle);
};
/**
* Find out if two paths are equal.
@@ -583,12 +606,12 @@ var MakerJs;
* @param b Second path.
* @returns true if paths are the same, false if they are not
*/
- function areEqual(path1, path2) {
+ function areEqual(path1, path2, withinPointDistance) {
var result = false;
if (path1.type == path2.type) {
var fn = pathAreEqualMap[path1.type];
if (fn) {
- result = fn(path1, path2);
+ result = fn(path1, path2, withinPointDistance);
}
}
return result;
@@ -687,8 +710,8 @@ var MakerJs;
line.end = MakerJs.point.rotate(line.end, angleInDegrees, rotationOrigin);
};
map[MakerJs.pathType.Arc] = function (arc) {
- arc.startAngle += angleInDegrees;
- arc.endAngle += angleInDegrees;
+ arc.startAngle = MakerJs.angle.noRevolutions(arc.startAngle + angleInDegrees);
+ arc.endAngle = MakerJs.angle.noRevolutions(arc.endAngle + angleInDegrees);
};
pathToRotate.origin = MakerJs.point.rotate(pathToRotate.origin, angleInDegrees, rotationOrigin);
var fn = map[pathToRotate.type];
@@ -740,12 +763,13 @@ var MakerJs;
return null;
}
function getAngleStrictlyBetweenArcAngles() {
- var endAngle = MakerJs.angle.ofArcEnd(arc);
+ var startAngle = MakerJs.angle.noRevolutions(arc.startAngle);
+ var endAngle = startAngle + MakerJs.angle.ofArcEnd(arc) - arc.startAngle;
var tries = [0, 1, -1];
for (var i = 0; i < tries.length; i++) {
var add = +360 * tries[i];
- if (MakerJs.measure.isBetween(angleAtBreakPoint + add, arc.startAngle, endAngle, true)) {
- return angleAtBreakPoint + add;
+ if (MakerJs.measure.isBetween(angleAtBreakPoint + add, startAngle, endAngle, true)) {
+ return arc.startAngle + angleAtBreakPoint + add - startAngle;
}
}
return null;
@@ -916,13 +940,33 @@ var MakerJs;
return count;
}
model.countChildModels = countChildModels;
+ /**
+ * Get an unused id in the models map with the same prefix.
+ *
+ * @param modelContext The model containing the models map.
+ * @param modelId The id to use directly (if unused), or as a prefix.
+ */
+ function getSimilarModelId(modelContext, modelId) {
+ if (!modelContext.models)
+ return modelId;
+ var i = 0;
+ var newModelId = modelId;
+ while (newModelId in modelContext.models) {
+ i++;
+ newModelId = modelId + '_' + i;
+ }
+ return newModelId;
+ }
+ model.getSimilarModelId = getSimilarModelId;
/**
* Get an unused id in the paths map with the same prefix.
*
* @param modelContext The model containing the paths map.
- * @param pathId The pathId to use directly (if unused), or as a prefix.
+ * @param pathId The id to use directly (if unused), or as a prefix.
*/
function getSimilarPathId(modelContext, pathId) {
+ if (!modelContext.paths)
+ return pathId;
var i = 0;
var newPathId = pathId;
while (newPathId in modelContext.paths) {
@@ -939,6 +983,8 @@ var MakerJs;
* @param origin Optional offset reference point.
*/
function originate(modelToOriginate, origin) {
+ if (!modelToOriginate)
+ return;
var newOrigin = MakerJs.point.add(modelToOriginate.origin, origin);
if (modelToOriginate.paths) {
for (var id in modelToOriginate.paths) {
@@ -1100,11 +1146,15 @@ var MakerJs;
function walkPaths(modelContext, callback) {
if (modelContext.paths) {
for (var pathId in modelContext.paths) {
+ if (!modelContext.paths[pathId])
+ continue;
callback(modelContext, pathId, modelContext.paths[pathId]);
}
}
if (modelContext.models) {
for (var id in modelContext.models) {
+ if (!modelContext.models[id])
+ continue;
walkPaths(modelContext.models[id], callback);
}
}
@@ -1115,29 +1165,33 @@ var MakerJs;
var MakerJs;
(function (MakerJs) {
var model;
- (function (model_1) {
+ (function (model) {
/**
* @private
*/
function getNonZeroSegments(pathToSegment, breakPoint) {
+ var segmentType = pathToSegment.type;
var segment1 = MakerJs.cloneObject(pathToSegment);
var segment2 = MakerJs.path.breakAtPoint(segment1, breakPoint);
if (segment2) {
var segments = [segment1, segment2];
for (var i = 2; i--;) {
- if (MakerJs.round(MakerJs.measure.pathLength(segments[i]), .00001) == 0) {
+ if (MakerJs.round(MakerJs.measure.pathLength(segments[i]), .0001) == 0) {
return null;
}
}
return segments;
}
+ else if (segmentType == MakerJs.pathType.Circle) {
+ return [segment1];
+ }
return null;
}
/**
* @private
*/
function breakAlongForeignPath(segments, overlappedSegments, foreignPath) {
- if (MakerJs.path.areEqual(segments[0].path, foreignPath)) {
+ if (MakerJs.path.areEqual(segments[0].path, foreignPath, .0001)) {
segments[0].overlapped = true;
segments[0].duplicate = true;
overlappedSegments.push(segments[0]);
@@ -1169,11 +1223,18 @@ var MakerJs;
}
if (subSegments) {
segments[i].path = subSegments[0];
- var newSegment = { path: subSegments[1], overlapped: segments[i].overlapped, uniqueForeignIntersectionPoints: [] };
- if (segments[i].overlapped) {
- overlappedSegments.push(newSegment);
+ if (subSegments[1]) {
+ var newSegment = {
+ path: subSegments[1],
+ pathId: segments[0].pathId,
+ overlapped: segments[i].overlapped,
+ uniqueForeignIntersectionPoints: []
+ };
+ if (segments[i].overlapped) {
+ overlappedSegments.push(newSegment);
+ }
+ segments.push(newSegment);
}
- segments.push(newSegment);
//re-check this segment for another deep intersection
i--;
}
@@ -1187,7 +1248,7 @@ var MakerJs;
var added = 0;
function addUniquePoint(pointToAdd) {
for (var i = 0; i < pointArray.length; i++) {
- if (MakerJs.point.areEqualRounded(pointArray[i], pointToAdd)) {
+ if (MakerJs.point.areEqual(pointArray[i], pointToAdd, .000000001)) {
return;
}
}
@@ -1202,7 +1263,7 @@ var MakerJs;
/**
* @private
*/
- function checkInsideForeignPath(segment, foreignPath, farPoint) {
+ function checkIntersectsForeignPath(segment, foreignPath, foreignPathId, farPoint) {
if (farPoint === void 0) { farPoint = [7654321, 1234567]; }
var origin = MakerJs.point.middle(segment.path);
var lineToFarPoint = new MakerJs.paths.Line(origin, farPoint);
@@ -1219,9 +1280,9 @@ var MakerJs;
* @private
*/
function checkInsideForeignModel(segment, modelToIntersect, farPoint) {
- model_1.walkPaths(modelToIntersect, function (mx, pathId2, path2) {
+ model.walkPaths(modelToIntersect, function (mx, pathId2, path2) {
if (path2) {
- checkInsideForeignPath(segment, path2, farPoint);
+ checkIntersectsForeignPath(segment, path2, pathId2, farPoint);
}
});
}
@@ -1242,19 +1303,30 @@ var MakerJs;
checkInsideForeignModel(segment, modelContext, farPoint);
return !!segment.isInside;
}
- model_1.isPathInsideModel = isPathInsideModel;
+ model.isPathInsideModel = isPathInsideModel;
+ /**
+ * Break a model's paths everywhere they intersect with another path.
+ *
+ * @param modelToBreak The model containing paths to be broken.
+ * @param modelToIntersect Optional model containing paths to look for intersection, or else the modelToBreak will be used.
+ */
+ function breakPathsAtIntersections(modelToBreak, modelToIntersect) {
+ breakAllPathsAtIntersections(modelToBreak, modelToIntersect || modelToBreak, false);
+ }
+ model.breakPathsAtIntersections = breakPathsAtIntersections;
/**
* @private
*/
- function breakAllPathsAtIntersections(modelToBreak, modelToIntersect, farPoint) {
+ function breakAllPathsAtIntersections(modelToBreak, modelToIntersect, checkIsInside, farPoint) {
var crossedPaths = [];
var overlappedSegments = [];
- model_1.walkPaths(modelToBreak, function (modelContext, pathId1, path1) {
+ model.walkPaths(modelToBreak, function (modelContext, pathId1, path1) {
if (!path1)
return;
//clone this path and make it the first segment
var segment = {
path: MakerJs.cloneObject(path1),
+ pathId: pathId1,
overlapped: false,
uniqueForeignIntersectionPoints: []
};
@@ -1264,14 +1336,16 @@ var MakerJs;
segments: [segment]
};
//keep breaking the segments anywhere they intersect with paths of the other model
- model_1.walkPaths(modelToIntersect, function (mx, pathId2, path2) {
- if (path2) {
+ model.walkPaths(modelToIntersect, function (mx, pathId2, path2) {
+ if (path2 && path1 !== path2) {
breakAlongForeignPath(thisPath.segments, overlappedSegments, path2);
}
});
- //check each segment whether it is inside or outside
- for (var i = 0; i < thisPath.segments.length; i++) {
- checkInsideForeignModel(thisPath.segments[i], modelToIntersect);
+ if (checkIsInside) {
+ //check each segment whether it is inside or outside
+ for (var i = 0; i < thisPath.segments.length; i++) {
+ checkInsideForeignModel(thisPath.segments[i], modelToIntersect, farPoint);
+ }
}
crossedPaths.push(thisPath);
});
@@ -1280,9 +1354,9 @@ var MakerJs;
/**
* @private
*/
- function checkForEqualOverlaps(crossedPathsA, crossedPathsB) {
+ function checkForEqualOverlaps(crossedPathsA, crossedPathsB, pointMatchingDistance) {
function compareSegments(segment1, segment2) {
- if (MakerJs.path.areEqual(segment1.path, segment2.path)) {
+ if (MakerJs.path.areEqual(segment1.path, segment2.path, pointMatchingDistance)) {
segment1.duplicate = segment2.duplicate = true;
}
}
@@ -1299,13 +1373,13 @@ var MakerJs;
* @private
*/
function addOrDeleteSegments(crossedPath, includeInside, includeOutside, keepDuplicates) {
- function addSegment(model, pathIdBase, segment) {
- var id = model_1.getSimilarPathId(model, pathIdBase);
- model.paths[id] = segment.path;
+ function addSegment(modelContext, pathIdBase, segment) {
+ var id = model.getSimilarPathId(modelContext, pathIdBase);
+ modelContext.paths[id] = segment.path;
}
- function checkAddSegment(model, pathIdBase, segment) {
+ function checkAddSegment(modelContext, pathIdBase, segment) {
if (segment.isInside && includeInside || !segment.isInside && includeOutside) {
- addSegment(model, pathIdBase, segment);
+ addSegment(modelContext, pathIdBase, segment);
}
}
//delete the original, its segments will be added
@@ -1322,7 +1396,7 @@ var MakerJs;
}
}
/**
- * Combine 2 models. The models should be originated.
+ * Combine 2 models. The models should be originated, and every path within each model should be part of a loop.
*
* @param modelA First model to combine.
* @param modelB Second model to combine.
@@ -1333,23 +1407,30 @@ var MakerJs;
* @param keepDuplicates Flag to include paths which are duplicate in both models.
* @param farPoint Optional point of reference which is outside the bounds of both models.
*/
- function combine(modelA, modelB, includeAInsideB, includeAOutsideB, includeBInsideA, includeBOutsideA, keepDuplicates, farPoint) {
+ function combine(modelA, modelB, includeAInsideB, includeAOutsideB, includeBInsideA, includeBOutsideA, options) {
if (includeAInsideB === void 0) { includeAInsideB = false; }
if (includeAOutsideB === void 0) { includeAOutsideB = true; }
if (includeBInsideA === void 0) { includeBInsideA = false; }
if (includeBOutsideA === void 0) { includeBOutsideA = true; }
- if (keepDuplicates === void 0) { keepDuplicates = true; }
- var pathsA = breakAllPathsAtIntersections(modelA, modelB, farPoint);
- var pathsB = breakAllPathsAtIntersections(modelB, modelA, farPoint);
- checkForEqualOverlaps(pathsA.overlappedSegments, pathsB.overlappedSegments);
+ var opts = {
+ trimDeadEnds: true,
+ pointMatchingDistance: .005
+ };
+ MakerJs.extendObject(opts, options);
+ var pathsA = breakAllPathsAtIntersections(modelA, modelB, true, opts.farPoint);
+ var pathsB = breakAllPathsAtIntersections(modelB, modelA, true, opts.farPoint);
+ checkForEqualOverlaps(pathsA.overlappedSegments, pathsB.overlappedSegments, opts.pointMatchingDistance);
for (var i = 0; i < pathsA.crossedPaths.length; i++) {
- addOrDeleteSegments(pathsA.crossedPaths[i], includeAInsideB, includeAOutsideB, keepDuplicates);
+ addOrDeleteSegments(pathsA.crossedPaths[i], includeAInsideB, includeAOutsideB, true);
}
for (var i = 0; i < pathsB.crossedPaths.length; i++) {
- addOrDeleteSegments(pathsB.crossedPaths[i], includeBInsideA, includeBOutsideA);
+ addOrDeleteSegments(pathsB.crossedPaths[i], includeBInsideA, includeBOutsideA, false);
+ }
+ if (opts.trimDeadEnds) {
+ model.removeDeadEnds({ models: { modelA: modelA, modelB: modelB } });
}
}
- model_1.combine = combine;
+ model.combine = combine;
})(model = MakerJs.model || (MakerJs.model = {}));
})(MakerJs || (MakerJs = {}));
var MakerJs;
@@ -1487,6 +1568,10 @@ var MakerJs;
function isBetweenArcAngles(angleInQuestion, arc, exclusive) {
var startAngle = arc.startAngle;
var endAngle = MakerJs.angle.ofArcEnd(arc);
+ var span = endAngle - startAngle;
+ startAngle = MakerJs.angle.noRevolutions(startAngle);
+ endAngle = startAngle + span;
+ angleInQuestion = MakerJs.angle.noRevolutions(angleInQuestion);
//computed angles will not be negative, but the arc may have specified a negative angle, so check against one revolution forward and backward
return (isBetween(angleInQuestion, startAngle, endAngle, exclusive) || isBetween(angleInQuestion, startAngle + 360, endAngle + 360, exclusive) || isBetween(angleInQuestion, startAngle - 360, endAngle - 360, exclusive));
}
@@ -1501,11 +1586,11 @@ var MakerJs;
*/
function isBetweenPoints(pointInQuestion, line, exclusive) {
for (var i = 2; i--;) {
- var origin_value = MakerJs.round(line.origin[i]);
- var end_value = MakerJs.round(line.end[i]);
- if (origin_value == end_value) {
+ if (MakerJs.round(line.origin[i] - line.end[i], .000001) == 0) {
continue;
}
+ var origin_value = MakerJs.round(line.origin[i]);
+ var end_value = MakerJs.round(line.end[i]);
if (!isBetween(MakerJs.round(pointInQuestion[i]), origin_value, end_value, exclusive))
return false;
}
@@ -1621,16 +1706,18 @@ var MakerJs;
getExtreme(totalMeasurement.low, pathMeasurement.low, Math.min);
getExtreme(totalMeasurement.high, pathMeasurement.high, Math.max);
}
- function measure(model, offsetOrigin) {
- var newOrigin = MakerJs.point.add(model.origin, offsetOrigin);
- if (model.paths) {
- for (var id in model.paths) {
- lowerOrHigher(newOrigin, pathExtents(model.paths[id]));
+ function measure(modelToMeasure, offsetOrigin) {
+ if (!modelToMeasure)
+ return;
+ var newOrigin = MakerJs.point.add(modelToMeasure.origin, offsetOrigin);
+ if (modelToMeasure.paths) {
+ for (var id in modelToMeasure.paths) {
+ lowerOrHigher(newOrigin, pathExtents(modelToMeasure.paths[id]));
}
}
- if (model.models) {
- for (var id in model.models) {
- measure(model.models[id], newOrigin);
+ if (modelToMeasure.models) {
+ for (var id in modelToMeasure.models) {
+ measure(modelToMeasure.models[id], newOrigin);
}
}
}
@@ -1699,12 +1786,18 @@ var MakerJs;
var newOffset = MakerJs.point.add((this.fixPoint ? this.fixPoint(modelToExport.origin) : modelToExport.origin), offset);
if (modelToExport.paths) {
for (var id in modelToExport.paths) {
- this.exportPath(id, modelToExport.paths[id], newOffset, modelToExport.layer);
+ var currPath = modelToExport.paths[id];
+ if (!currPath)
+ continue;
+ this.exportPath(id, currPath, newOffset, modelToExport.layer);
}
}
if (modelToExport.models) {
for (var id in modelToExport.models) {
- this.exportModel(id, modelToExport.models[id], newOffset);
+ var currModel = modelToExport.models[id];
+ if (!currModel)
+ continue;
+ this.exportModel(id, currModel, newOffset);
}
}
if (this.endModel) {
@@ -2086,14 +2179,14 @@ var MakerJs;
* @private
*/
function getSlope(line) {
- var dx = MakerJs.round(line.end[0] - line.origin[0]);
- if (dx == 0) {
+ var dx = line.end[0] - line.origin[0];
+ if (MakerJs.round(dx) == 0) {
return {
line: line,
hasSlope: false
};
}
- var dy = MakerJs.round(line.end[1] - line.origin[1]);
+ var dy = line.end[1] - line.origin[1];
var slope = dy / dx;
var yIntercept = line.origin[1] - slope * line.origin[0];
return {
@@ -2116,13 +2209,13 @@ var MakerJs;
*/
function checkAngleOverlap(arc1, arc2, options) {
var pointsOfIntersection = [];
- function checkAngles(index, a, b) {
+ function checkAngles(a, b) {
function checkAngle(n) {
return MakerJs.measure.isBetweenArcAngles(n, a, options.excludeTangents);
}
return checkAngle(b.startAngle) || checkAngle(b.endAngle);
}
- if (checkAngles(0, arc1, arc2) || checkAngles(1, arc2, arc1) || (arc1.startAngle == arc2.startAngle && arc1.endAngle == arc2.endAngle)) {
+ if (checkAngles(arc1, arc2) || checkAngles(arc2, arc1) || (arc1.startAngle == arc2.startAngle && arc1.endAngle == arc2.endAngle)) {
options.out_AreOverlapped = true;
}
}
@@ -2155,15 +2248,15 @@ var MakerJs;
var slope2 = getSlope(line2);
if (!slope1.hasSlope && !slope2.hasSlope) {
//lines are both vertical, see if x are the same
- if (slope1.line.origin[0] == slope2.line.origin[0]) {
+ if (MakerJs.round(slope1.line.origin[0] - slope2.line.origin[0]) == 0) {
//check for overlap
checkLineOverlap(line1, line2, options);
}
return null;
}
- if (slope1.hasSlope && slope2.hasSlope && (slope1.slope == slope2.slope)) {
+ if (slope1.hasSlope && slope2.hasSlope && (MakerJs.round(slope1.slope - slope2.slope, .00001) == 0)) {
//lines are parallel, but not vertical, see if y-intercept is the same
- if (slope1.yIntercept == slope2.yIntercept) {
+ if (MakerJs.round(slope1.yIntercept - slope2.yIntercept, .00001) == 0) {
//check for overlap
checkLineOverlap(line1, line2, options);
}
@@ -2205,21 +2298,22 @@ var MakerJs;
}
//line is horizontal, get the y value from any point
var lineY = MakerJs.round(clonedLine.origin[1]);
+ var lineYabs = Math.abs(lineY);
//if y is greater than radius, there is no intersection
- if (lineY > radius) {
+ if (lineYabs > radius) {
return null;
}
var anglesOfIntersection = [];
//if horizontal Y is the same as the radius, we know it's 90 degrees
- if (lineY == radius) {
+ if (lineYabs == radius) {
if (options.excludeTangents) {
return null;
}
- anglesOfIntersection.push(unRotate(90));
+ anglesOfIntersection.push(unRotate(lineY > 0 ? 90 : 270));
}
else {
function intersectionBetweenEndpoints(x, angleOfX) {
- if (MakerJs.measure.isBetween(x, clonedLine.origin[0], clonedLine.end[0], options.excludeTangents)) {
+ if (MakerJs.measure.isBetween(MakerJs.round(x), MakerJs.round(clonedLine.origin[0]), MakerJs.round(clonedLine.end[0]), options.excludeTangents)) {
anglesOfIntersection.push(unRotate(angleOfX));
}
}
@@ -2241,7 +2335,7 @@ var MakerJs;
*/
function circleToCircle(circle1, circle2, options) {
//see if circles are the same
- if (circle1.radius == circle2.radius && MakerJs.point.areEqual(circle1.origin, circle2.origin)) {
+ if (circle1.radius == circle2.radius && MakerJs.point.areEqual(circle1.origin, circle2.origin, .0001)) {
options.out_AreOverlapped = true;
return null;
}
@@ -2273,14 +2367,14 @@ var MakerJs;
return null;
}
//see if circles are tangent interior
- if (c2.radius - x == c1.radius) {
+ if (MakerJs.round(c2.radius - x - c1.radius) == 0) {
if (options.excludeTangents) {
return null;
}
return [[unRotate(180)], [unRotate(180)]];
}
//see if circles are tangent exterior
- if (x - c2.radius == c1.radius) {
+ if (MakerJs.round(x - c2.radius - c1.radius) == 0) {
if (options.excludeTangents) {
return null;
}
@@ -2327,7 +2421,7 @@ var MakerJs;
/**
* @private
*/
- function getMatchingPointProperties(path1, path2) {
+ function getMatchingPointProperties(path1, path2, options) {
var path1Properties = getPointProperties(path1);
var path2Properties = getPointProperties(path2);
var result = null;
@@ -2341,7 +2435,7 @@ var MakerJs;
};
}
function check(i1, i2) {
- if (MakerJs.point.areEqualRounded(path1Properties[i1].point, path2Properties[i2].point)) {
+ if (MakerJs.point.areEqual(path1Properties[i1].point, path2Properties[i2].point, .0001)) {
result = [
makeMatch(path1, path1Properties, i1),
makeMatch(path2, path2Properties, i2)
@@ -2365,7 +2459,7 @@ var MakerJs;
return false;
}
properties[i].shardPoint = circleIntersection.intersectionPoints[0];
- if (MakerJs.point.areEqualRounded(properties[i].point, circleIntersection.intersectionPoints[0], options.accuracy)) {
+ if (MakerJs.point.areEqual(properties[i].point, circleIntersection.intersectionPoints[0], .0001)) {
if (circleIntersection.intersectionPoints.length > 1) {
properties[i].shardPoint = circleIntersection.intersectionPoints[1];
}
@@ -2517,11 +2611,11 @@ var MakerJs;
function dogbone(line1, line2, filletRadius, options) {
if (MakerJs.isPathLine(line1) && MakerJs.isPathLine(line2) && filletRadius && filletRadius > 0) {
var opts = {
- accuracy: .0001
+ pointMatchingDistance: .005
};
MakerJs.extendObject(opts, options);
//first find the common point
- var commonProperty = getMatchingPointProperties(line1, line2);
+ var commonProperty = getMatchingPointProperties(line1, line2, options);
if (commonProperty) {
//get the ratio comparison of the two lines
var ratio = getLineRatio([line1, line2]);
@@ -2568,11 +2662,11 @@ var MakerJs;
function fillet(path1, path2, filletRadius, options) {
if (path1 && path2 && filletRadius && filletRadius > 0) {
var opts = {
- accuracy: .0001
+ pointMatchingDistance: .005
};
MakerJs.extendObject(opts, options);
//first find the common point
- var commonProperty = getMatchingPointProperties(path1, path2);
+ var commonProperty = getMatchingPointProperties(path1, path2, options);
if (commonProperty) {
//since arcs can curl beyond, we need a local reference point.
//An intersection with a circle of the same radius as the desired fillet should suffice.
@@ -2674,6 +2768,34 @@ var MakerJs;
(function (MakerJs) {
var model;
(function (model) {
+ /**
+ * @private
+ */
+ var PointMap = (function () {
+ function PointMap(matchingDistance) {
+ if (matchingDistance === void 0) { matchingDistance = .001; }
+ this.matchingDistance = matchingDistance;
+ this.list = [];
+ }
+ PointMap.prototype.add = function (pointToAdd, item) {
+ this.list.push({ averagePoint: pointToAdd, item: item });
+ };
+ PointMap.prototype.find = function (pointToFind, saveAverage) {
+ for (var i = 0; i < this.list.length; i++) {
+ var item = this.list[i];
+ var distance = MakerJs.measure.pointDistance(pointToFind, item.averagePoint);
+ if (distance <= this.matchingDistance) {
+ if (saveAverage) {
+ item.averagePoint = MakerJs.point.average(item.averagePoint, pointToFind);
+ }
+ return item.item;
+ }
+ }
+ return null;
+ };
+ return PointMap;
+ })();
+ model.PointMap = PointMap;
/**
* @private
*/
@@ -2720,7 +2842,7 @@ var MakerJs;
while (true) {
var currPath = currLink.path;
currPath.reversed = currLink.reversed;
- var id = model.getSimilarPathId(loopModel, currPath.primePathId);
+ var id = model.getSimilarPathId(loopModel, currPath.pathId);
loopModel.paths[id] = currPath;
if (!connections[currLink.nextConnection])
break;
@@ -2750,11 +2872,11 @@ var MakerJs;
var connections = {};
var result = { models: {} };
var opts = {
- accuracy: .0001
+ pointMatchingDistance: .005
};
MakerJs.extendObject(opts, options);
function getLinkedPathsOnConnectionPoint(p) {
- var serializedPoint = MakerJs.point.serialize(p, opts.accuracy);
+ var serializedPoint = MakerJs.point.serialize(p, .0001); //TODO convert to pointmap
if (!(serializedPoint in connections)) {
connections[serializedPoint] = [];
}
@@ -2773,14 +2895,15 @@ var MakerJs;
}
return result.models[id];
}
+ //todo: remove dead ends first
model.originate(modelContext);
//find loops by looking at all paths in this model
model.walkPaths(modelContext, function (modelContext, pathId, pathContext) {
if (!pathContext)
return;
var safePath = MakerJs.cloneObject(pathContext);
- safePath.primePathId = pathId;
- safePath.primeModel = modelContext;
+ safePath.pathId = pathId;
+ safePath.modelContext = modelContext;
//circles are loops by nature
if (safePath.type == MakerJs.pathType.Circle) {
var loopModel = {
@@ -2796,7 +2919,7 @@ var MakerJs;
for (var i = 2; i--;) {
var linkedPath = {
path: safePath,
- nextConnection: MakerJs.point.serialize(safePath.endPoints[1 - i], opts.accuracy),
+ nextConnection: MakerJs.point.serialize(safePath.endPoints[1 - i], .0001),
reversed: i != 0
};
getLinkedPathsOnConnectionPoint(safePath.endPoints[i]).push(linkedPath);
@@ -2836,13 +2959,110 @@ var MakerJs;
function detachLoop(loopToDetach) {
for (var id in loopToDetach.paths) {
var pathDirectionalWithOriginalContext = loopToDetach.paths[id];
- var primeModel = pathDirectionalWithOriginalContext.primeModel;
- if (primeModel && primeModel.paths && pathDirectionalWithOriginalContext.primePathId) {
- delete primeModel.paths[pathDirectionalWithOriginalContext.primePathId];
+ var primeModel = pathDirectionalWithOriginalContext.modelContext;
+ if (primeModel && primeModel.paths && pathDirectionalWithOriginalContext.pathId) {
+ delete primeModel.paths[pathDirectionalWithOriginalContext.pathId];
}
}
}
model.detachLoop = detachLoop;
+ /**
+ * @private
+ */
+ var DeadEndFinder = (function () {
+ function DeadEndFinder(pointMatchingDistance) {
+ this.pointMatchingDistance = pointMatchingDistance;
+ this.pointMap = new PointMap(pointMatchingDistance);
+ }
+ DeadEndFinder.prototype.addPathRef = function (p, pathRef) {
+ var found = this.pointMap.find(p, true);
+ if (found) {
+ found.push(pathRef);
+ }
+ else {
+ this.pointMap.add(p, [pathRef]);
+ }
+ };
+ DeadEndFinder.prototype.removeMatchingPathRefs = function (a, b) {
+ //see if any are the same in each array
+ for (var ai = 0; ai < a.length; ai++) {
+ for (var bi = 0; bi < b.length; bi++) {
+ if (a[ai] === b[bi]) {
+ var pathRef = a[ai];
+ a.splice(ai, 1);
+ b.splice(bi, 1);
+ return pathRef;
+ }
+ }
+ }
+ return null;
+ };
+ DeadEndFinder.prototype.removePathRef = function (pathRef) {
+ var _this = this;
+ var removePath = function (p) {
+ var pathRefs = _this.pointMap.find(p, false);
+ for (var i = 0; i < pathRefs.length; i++) {
+ if (pathRefs[i] === pathRef) {
+ pathRefs.splice(i, 1);
+ return;
+ }
+ }
+ };
+ for (var i = 2; i--;) {
+ removePath(pathRef.endPoints[i]);
+ }
+ };
+ DeadEndFinder.prototype.removeDeadEnd = function () {
+ var found = false;
+ var oddPathRefs = null;
+ for (var i = 0; i < this.pointMap.list.length; i++) {
+ var pathRefs = this.pointMap.list[i].item;
+ if (pathRefs.length % 2 == 0)
+ continue;
+ if (pathRefs.length == 1) {
+ var pathRef = pathRefs[0];
+ this.removePathRef(pathRef);
+ delete pathRef.modelContext.paths[pathRef.pathId];
+ found = true;
+ }
+ else {
+ if (!oddPathRefs) {
+ //save this for another iteration
+ oddPathRefs = pathRefs;
+ }
+ else {
+ //compare with the saved
+ var pathRef = this.removeMatchingPathRefs(oddPathRefs, pathRefs);
+ if (pathRef) {
+ delete pathRef.modelContext.paths[pathRef.pathId];
+ found = true;
+ //clear the saved
+ oddPathRefs = null;
+ }
+ }
+ }
+ }
+ return found;
+ };
+ return DeadEndFinder;
+ })();
+ function removeDeadEnds(modelContext, pointMatchingDistance) {
+ if (pointMatchingDistance === void 0) { pointMatchingDistance = .005; }
+ var serializedPointAccuracy = .0001;
+ var deadEndFinder = new DeadEndFinder(pointMatchingDistance);
+ model.walkPaths(modelContext, function (modelContext, pathId, pathContext) {
+ var endPoints = MakerJs.point.fromPathEnds(pathContext);
+ if (!endPoints)
+ return;
+ var pathRef = { modelContext: modelContext, pathId: pathId, endPoints: endPoints };
+ for (var i = 2; i--;) {
+ deadEndFinder.addPathRef(endPoints[i], pathRef);
+ }
+ });
+ while (deadEndFinder.removeDeadEnd())
+ ;
+ }
+ model.removeDeadEnds = removeDeadEnds;
})(model = MakerJs.model || (MakerJs.model = {}));
})(MakerJs || (MakerJs = {}));
var MakerJs;
@@ -3055,7 +3275,7 @@ var MakerJs;
var depthModel;
var opts = {
extrusion: 1,
- accuracy: .0001
+ pointMatchingDistance: .005
};
MakerJs.extendObject(opts, options);
var loops = MakerJs.model.findLoops(modelToExport, opts);
diff --git a/target/ts/makerjs.d.ts b/target/ts/makerjs.d.ts
index 779af0534..e18c42ef6 100644
--- a/target/ts/makerjs.d.ts
+++ b/target/ts/makerjs.d.ts
@@ -252,9 +252,22 @@ declare module MakerJs {
*/
interface IPointMatchOptions {
/**
- * Optional exemplar of number of decimal places.
+ * Max distance to consider two points as the same.
*/
- accuracy?: number;
+ pointMatchingDistance?: number;
+ }
+ /**
+ * Options to pass to model.combine.
+ */
+ interface ICombineOptions extends IPointMatchOptions {
+ /**
+ * Flag to remove paths which are not part of a loop.
+ */
+ trimDeadEnds?: boolean;
+ /**
+ * Point which is known to be outside of the model.
+ */
+ farPoint?: IPoint;
}
/**
* Options to pass to model.findLoops.
@@ -343,6 +356,19 @@ declare module MakerJs {
* Test to see if an object implements the required properties of a model.
*/
function isModel(item: any): boolean;
+ /**
+ * Reference to a path id within a model.
+ */
+ interface IRefPathIdInModel {
+ modelContext: IModel;
+ pathId: string;
+ }
+ /**
+ * Path and its reference id within a model
+ */
+ interface IRefPathInModel extends IRefPathIdInModel {
+ pathContext: IPath;
+ }
}
declare module MakerJs.angle {
/**
@@ -352,7 +378,7 @@ declare module MakerJs.angle {
* @param b Second angle.
* @returns true if angles are the same, false if they are not
*/
- function areEqual(angle1: number, angle2: number): boolean;
+ function areEqual(angle1: number, angle2: number, accuracy?: number): boolean;
/**
* Ensures an angle is not greater than 360
*
@@ -439,7 +465,7 @@ declare module MakerJs.point {
* @param b Second point.
* @returns true if points are the same, false if they are not
*/
- function areEqual(a: IPoint, b: IPoint): boolean;
+ function areEqual(a: IPoint, b: IPoint, withinDistance?: number): boolean;
/**
* Find out if two points are equal after rounding.
*
@@ -449,6 +475,14 @@ declare module MakerJs.point {
* @returns true if points are the same, false if they are not
*/
function areEqualRounded(a: IPoint, b: IPoint, accuracy?: number): boolean;
+ /**
+ * Get the average of two points.
+ *
+ * @param a First point.
+ * @param b Second point.
+ * @returns New point object which is the average of a and b.
+ */
+ function average(a: IPoint, b: IPoint): IPoint;
/**
* Clone a point into a new point.
*
@@ -567,7 +601,7 @@ declare module MakerJs.path {
* @param b Second path.
* @returns true if paths are the same, false if they are not
*/
- function areEqual(path1: IPath, path2: IPath): boolean;
+ function areEqual(path1: IPath, path2: IPath, withinPointDistance?: number): boolean;
/**
* Create a clone of a path, mirrored on either or both x and y axes.
*
@@ -698,11 +732,18 @@ declare module MakerJs.model {
* @returns Number of child models.
*/
function countChildModels(modelContext: IModel): number;
+ /**
+ * Get an unused id in the models map with the same prefix.
+ *
+ * @param modelContext The model containing the models map.
+ * @param modelId The id to use directly (if unused), or as a prefix.
+ */
+ function getSimilarModelId(modelContext: IModel, modelId: string): string;
/**
* Get an unused id in the paths map with the same prefix.
*
* @param modelContext The model containing the paths map.
- * @param pathId The pathId to use directly (if unused), or as a prefix.
+ * @param pathId The id to use directly (if unused), or as a prefix.
*/
function getSimilarPathId(modelContext: IModel, pathId: string): string;
/**
@@ -782,7 +823,14 @@ declare module MakerJs.model {
*/
function isPathInsideModel(pathContext: IPath, modelContext: IModel, farPoint?: IPoint): boolean;
/**
- * Combine 2 models. The models should be originated.
+ * Break a model's paths everywhere they intersect with another path.
+ *
+ * @param modelToBreak The model containing paths to be broken.
+ * @param modelToIntersect Optional model containing paths to look for intersection, or else the modelToBreak will be used.
+ */
+ function breakPathsAtIntersections(modelToBreak: IModel, modelToIntersect?: IModel): void;
+ /**
+ * Combine 2 models. The models should be originated, and every path within each model should be part of a loop.
*
* @param modelA First model to combine.
* @param modelB Second model to combine.
@@ -793,7 +841,7 @@ declare module MakerJs.model {
* @param keepDuplicates Flag to include paths which are duplicate in both models.
* @param farPoint Optional point of reference which is outside the bounds of both models.
*/
- function combine(modelA: IModel, modelB: IModel, includeAInsideB?: boolean, includeAOutsideB?: boolean, includeBInsideA?: boolean, includeBOutsideA?: boolean, keepDuplicates?: boolean, farPoint?: IPoint): void;
+ function combine(modelA: IModel, modelB: IModel, includeAInsideB?: boolean, includeAOutsideB?: boolean, includeBInsideA?: boolean, includeBOutsideA?: boolean, options?: ICombineOptions): void;
}
declare module MakerJs.units {
/**
@@ -1064,6 +1112,23 @@ declare module MakerJs.kit {
function getParameterValues(ctor: IKit): any[];
}
declare module MakerJs.model {
+ /**
+ * @private
+ */
+ interface IPointMappedItem {
+ averagePoint: IPoint;
+ item: T;
+ }
+ /**
+ * @private
+ */
+ class PointMap {
+ matchingDistance: number;
+ list: IPointMappedItem[];
+ constructor(matchingDistance?: number);
+ add(pointToAdd: IPoint, item: T): void;
+ find(pointToFind: IPoint, saveAverage: boolean): T;
+ }
/**
* Find paths that have common endpoints and form loops.
*
@@ -1078,6 +1143,7 @@ declare module MakerJs.model {
* @param loopToDetach The model to search for loops.
*/
function detachLoop(loopToDetach: IModel): void;
+ function removeDeadEnds(modelContext: IModel, pointMatchingDistance?: number): void;
}
declare module MakerJs.exporter {
/**