Skip to content

Commit

Permalink
Merge pull request #65 from Microsoft/dev
Browse files Browse the repository at this point in the history
Major accuracy upgrade for Combine operations
  • Loading branch information
danmarshall committed Dec 15, 2015
2 parents 02f8306 + 61f0101 commit 0289c5d
Show file tree
Hide file tree
Showing 20 changed files with 1,432 additions and 422 deletions.
4 changes: 4 additions & 0 deletions debug/viewer.css
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,8 @@ svg text {
.selectModelCode * {
font-size: 18px;
font-weight: 200;
}

#crosshairs-horizontal_text, #crosshairs-vertical_text {
display: none;
}
1 change: 1 addition & 0 deletions debug/viewer.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
<script src="../src/models/OvalArc.js"></script>
<script src="../src/models/ConnectTheDots.js"></script>
<script src="../src/models/Rectangle.js"></script>
<script src="../src/models/Ring.js"></script>
<script src="../src/models/Polygon.js"></script>
<script src="../src/models/Square.js"></script>
<script src="../src/models/SCurve.js"></script>
Expand Down
424 changes: 322 additions & 102 deletions index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
10 changes: 5 additions & 5 deletions src/core/angle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/**
Expand Down
8 changes: 5 additions & 3 deletions src/core/break.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
98 changes: 65 additions & 33 deletions src/core/combine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,20 @@ module MakerJs.model {
* @private
*/
function getNonZeroSegments(pathToSegment: IPath, breakPoint: IPoint): IPath[] {
var segmentType = pathToSegment.type;
var segment1 = cloneObject<IPath>(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;
}
Expand All @@ -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;

Expand Down Expand Up @@ -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--;
Expand All @@ -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;
}
}
Expand All @@ -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);
Expand All @@ -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);
}
});
}
Expand Down Expand Up @@ -171,16 +181,15 @@ module MakerJs.model {
* @private
*/
interface ICrossedPathSegment extends IPathInside {
pathId: string;
overlapped: boolean;
duplicate?: boolean;
}

/**
* @private
*/
interface ICrossedPath {
modelContext: IModel;
pathId: string;
interface ICrossedPath extends IRefPathIdInModel {
segments: ICrossedPathSegment[];
}

Expand All @@ -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[] = [];
Expand All @@ -207,6 +226,7 @@ module MakerJs.model {
//clone this path and make it the first segment
var segment: ICrossedPathSegment = {
path: cloneObject<IPath>(path1),
pathId: pathId1,
overlapped: false,
uniqueForeignIntersectionPoints: []
};
Expand All @@ -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);
Expand All @@ -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;
}
}
Expand All @@ -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);
}
}

Expand All @@ -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.
Expand All @@ -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(<IModel>{ models: { modelA: modelA, modelB: modelB } });
}
}

Expand Down
8 changes: 6 additions & 2 deletions src/core/exporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand Down
14 changes: 7 additions & 7 deletions src/core/fillet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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)
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down

0 comments on commit 0289c5d

Please sign in to comment.