Skip to content

Commit

Permalink
Merge pull request #64 from Microsoft/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
danmarshall committed Nov 14, 2015
2 parents 5001f4c + 1bd9dad commit 02f8306
Show file tree
Hide file tree
Showing 14 changed files with 379 additions and 117 deletions.
50 changes: 48 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,54 @@
# Maker.js

Maker.js, a Microsoft Garage project, is a JavaScript library for creating and sharing modular line drawings for CNC and laser cutters. Maker.js runs in both Node.js and web browsers.
Maker.js, a Microsoft Garage project, is a JavaScript library for creating line drawings for CNC and laser cutters. It runs in both Node.js and web browsers.

[About](http://microsoft.github.io/maker.js/about/) - [Blog](http://microsoft.github.io/maker.js/) - [Demos](http://microsoft.github.io/maker.js/demos/) - [Documentation](http://microsoft.github.io/maker.js/docs/) - [Discussion](https://gitter.im/Microsoft/maker.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[Demos](http://microsoft.github.io/maker.js/demos/) - [Documentation](http://microsoft.github.io/maker.js/docs/)

## Features

### Drawing with JavaScript code

Draw using three primitive paths: [Line, Circle, and Arc](http://microsoft.github.io/maker.js/docs/basic-drawing/#Paths).

Paths can be [grouped into Models](http://microsoft.github.io/maker.js/docs/basic-drawing/#Models) to form more complex drawings.

Behind the scenes, drawings are a [simple Javascript object](http://microsoft.github.io/maker.js/docs/basic-drawing/#It%27s%20Just%20JSON) which can be serialized / deserialized conventionally with JSON.

Other people's Models can be imported, [modified](http://microsoft.github.io/maker.js/docs/intermediate-drawing/#Modifying%20models), and re-exported.

Models can be [scaled](http://microsoft.github.io/maker.js/docs/intermediate-drawing/#Scaling), [measured](http://microsoft.github.io/maker.js/docs/api/modules/makerjs.measure.html#modelextents), and [converted to different unit systems](http://microsoft.github.io/maker.js/docs/basic-drawing/#Units).

Models can be [rotated](http://microsoft.github.io/maker.js/docs/intermediate-drawing/#Rotating) or [mirrored](http://microsoft.github.io/maker.js/docs/intermediate-drawing/#Mirroring).

Find [intersection points or intersection angles](http://microsoft.github.io/maker.js/docs/api/modules/makerjs.path.html#intersection) of paths.

Easily add a curvature at the joint between any 2 paths, using a [traditional fillet](http://microsoft.github.io/maker.js/docs/api/modules/makerjs.path.html#fillet) or a [dogbone fillet](http://microsoft.github.io/maker.js/docs/api/modules/makerjs.path.html#dogbone).

[Combine models](http://microsoft.github.io/maker.js/docs/api/modules/makerjs.model.html#combine) with boolean operations to get unions, intersections, or punches.

[Detect loops](http://microsoft.github.io/maker.js/docs/api/modules/makerjs.model.html#findloops) formed by paths connecting end to end.

### Output formats

2D: [DXF](http://microsoft.github.io/maker.js/docs/api/modules/makerjs.exporter.html#todxf), [SVG](http://microsoft.github.io/maker.js/docs/api/modules/makerjs.exporter.html#tosvg)

3D: [OpenJsCad script](http://microsoft.github.io/maker.js/docs/api/modules/makerjs.exporter.html#toopenjscad), [STL](http://microsoft.github.io/maker.js/docs/api/modules/makerjs.exporter.html#tostl) (Must include [OpenJsCad](http://joostn.github.io/OpenJsCad/) or [openjscad-csg](https://www.npmjs.com/package/openjscad-csg))

### Built-in models

* Bolt Circle
* Bolt Rectangle
* Connect the dots
* Dome
* Oval
* OvalArc
* Polygon
* Rectangle
* Ring
* RoundRectangle
* S curve
* Square
* Star

## Getting Started

Expand Down
12 changes: 9 additions & 3 deletions debug/viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,12 +168,18 @@ var Viewer = {
break;

case 'bool':
input = new makerjs.exporter.XmlTag('input', {

var checkboxAttrs = {
id: id,
type: 'checkbox',
checked: attrs.value ? 'checked' : '',
onchange: 'Viewer.Refresh(' + i + ', this.checked)'
});
};

if (attrs.value) {
checkboxAttrs['checked'] = true;
}

input = new makerjs.exporter.XmlTag('input', checkboxAttrs);

break;
}
Expand Down
79 changes: 60 additions & 19 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,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 add(rotationOrigin, rotatedPoint);
return rounded(add(rotationOrigin, rotatedPoint));
}
point.rotate = rotate;
/**
Expand Down Expand Up @@ -1334,6 +1334,10 @@ var MakerJs;
* @param farPoint Optional point of reference which is outside the bounds of both models.
*/
function combine(modelA, modelB, includeAInsideB, includeAOutsideB, includeBInsideA, includeBOutsideA, keepDuplicates, farPoint) {
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);
Expand Down Expand Up @@ -2352,7 +2356,7 @@ var MakerJs;
/**
* @private
*/
function populateShardPointsFromReferenceCircle(filletRadius, center, properties) {
function populateShardPointsFromReferenceCircle(filletRadius, center, properties, options) {
var referenceCircle = new MakerJs.paths.Circle(center, filletRadius);
//get reference circle intersection points
for (var i = 0; i < 2; i++) {
Expand All @@ -2361,7 +2365,7 @@ var MakerJs;
return false;
}
properties[i].shardPoint = circleIntersection.intersectionPoints[0];
if (MakerJs.point.areEqualRounded(properties[i].point, circleIntersection.intersectionPoints[0], .0001)) {
if (MakerJs.point.areEqualRounded(properties[i].point, circleIntersection.intersectionPoints[0], options.accuracy)) {
if (circleIntersection.intersectionPoints.length > 1) {
properties[i].shardPoint = circleIntersection.intersectionPoints[1];
}
Expand Down Expand Up @@ -2510,8 +2514,12 @@ var MakerJs;
* @param line2 Second line to fillet, which will be modified to fit the fillet.
* @returns Arc path object of the new fillet.
*/
function dogbone(line1, line2, filletRadius) {
function dogbone(line1, line2, filletRadius, options) {
if (MakerJs.isPathLine(line1) && MakerJs.isPathLine(line2) && filletRadius && filletRadius > 0) {
var opts = {
accuracy: .0001
};
MakerJs.extendObject(opts, options);
//first find the common point
var commonProperty = getMatchingPointProperties(line1, line2);
if (commonProperty) {
Expand All @@ -2523,7 +2531,7 @@ var MakerJs;
//use the bisection theorem to get the angle bisecting the lines
var bisectionAngle = MakerJs.angle.ofPointInDegrees(commonProperty[0].point, midRatioPoint);
var center = MakerJs.point.add(commonProperty[0].point, MakerJs.point.fromPolar(MakerJs.angle.toRadians(bisectionAngle), filletRadius));
if (!populateShardPointsFromReferenceCircle(filletRadius, center, commonProperty)) {
if (!populateShardPointsFromReferenceCircle(filletRadius, center, commonProperty, opts)) {
return null;
}
//get the angles of the fillet and a function which clips the path to the fillet.
Expand Down Expand Up @@ -2557,14 +2565,18 @@ var MakerJs;
* @param path2 Second path to fillet, which will be modified to fit the fillet.
* @returns Arc path object of the new fillet.
*/
function fillet(path1, path2, filletRadius) {
function fillet(path1, path2, filletRadius, options) {
if (path1 && path2 && filletRadius && filletRadius > 0) {
var opts = {
accuracy: .0001
};
MakerJs.extendObject(opts, options);
//first find the common point
var commonProperty = getMatchingPointProperties(path1, path2);
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.
if (!populateShardPointsFromReferenceCircle(filletRadius, commonProperty[0].point, commonProperty)) {
if (!populateShardPointsFromReferenceCircle(filletRadius, commonProperty[0].point, commonProperty, opts)) {
return null;
}
//get "parallel" guidelines
Expand Down Expand Up @@ -2685,7 +2697,16 @@ var MakerJs;
/**
* @private
*/
function follow(connections, loops) {
function collectLoop(loop, loops, detach) {
loops.push(loop);
if (detach) {
detachLoop(loop);
}
}
/**
* @private
*/
function follow(connections, loops, detach) {
//for a given point, follow the paths that connect to each other to form loops
for (var p in connections) {
var linkedPaths = connections[p];
Expand All @@ -2699,7 +2720,7 @@ var MakerJs;
while (true) {
var currPath = currLink.path;
currPath.reversed = currLink.reversed;
var id = model.getSimilarPathId(loopModel, currLink.id);
var id = model.getSimilarPathId(loopModel, currPath.primePathId);
loopModel.paths[id] = currPath;
if (!connections[currLink.nextConnection])
break;
Expand All @@ -2710,7 +2731,7 @@ var MakerJs;
currLink = nextLink;
if (currLink.path === firstLink.path) {
//loop is closed
loops.push(loopModel);
collectLoop(loopModel, loops, detach);
break;
}
}
Expand All @@ -2721,15 +2742,19 @@ var MakerJs;
* Find paths that have common endpoints and form loops.
*
* @param modelContext The model to search for loops.
* @param accuracy Optional exemplar of number of decimal places.
* @returns A new model with child models ranked according to their containment within other found loops. The paths of models will be IPathDirectional.
* @param options Optional options object.
* @returns A new model with child models ranked according to their containment within other found loops. The paths of models will be IPathDirectionalWithPrimeContext.
*/
function findLoops(modelContext, accuracy) {
function findLoops(modelContext, options) {
var loops = [];
var connections = {};
var result = { models: {} };
var opts = {
accuracy: .0001
};
MakerJs.extendObject(opts, options);
function getLinkedPathsOnConnectionPoint(p) {
var serializedPoint = MakerJs.point.serialize(p, accuracy);
var serializedPoint = MakerJs.point.serialize(p, opts.accuracy);
if (!(serializedPoint in connections)) {
connections[serializedPoint] = [];
}
Expand All @@ -2754,31 +2779,32 @@ var MakerJs;
if (!pathContext)
return;
var safePath = MakerJs.cloneObject(pathContext);
safePath.primePathId = pathId;
safePath.primeModel = modelContext;
//circles are loops by nature
if (safePath.type == MakerJs.pathType.Circle) {
var loopModel = {
paths: {},
insideCount: 0
};
loopModel.paths[pathId] = safePath;
loops.push(loopModel);
collectLoop(loopModel, loops, opts.removeFromOriginal);
}
else {
//gather both endpoints from all non-circle segments
safePath.endPoints = MakerJs.point.fromPathEnds(safePath);
for (var i = 2; i--;) {
var linkedPath = {
id: pathId,
path: safePath,
nextConnection: MakerJs.point.serialize(safePath.endPoints[1 - i], accuracy),
nextConnection: MakerJs.point.serialize(safePath.endPoints[1 - i], opts.accuracy),
reversed: i != 0
};
getLinkedPathsOnConnectionPoint(safePath.endPoints[i]).push(linkedPath);
}
}
});
//follow paths to find loops
follow(connections, loops);
follow(connections, loops, opts.removeFromOriginal);
//now we have all loops, we need to see which are inside of each other
spin(function (firstLoop) {
var firstPath = getFirstPathFromModel(firstLoop);
Expand All @@ -2802,6 +2828,21 @@ var MakerJs;
return result;
}
model.findLoops = findLoops;
/**
* Remove all paths in a loop model from the model(s) which contained them.
*
* @param loopToDetach The model to search for loops.
*/
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];
}
}
}
model.detachLoop = detachLoop;
})(model = MakerJs.model || (MakerJs.model = {}));
})(MakerJs || (MakerJs = {}));
var MakerJs;
Expand Down Expand Up @@ -3017,7 +3058,7 @@ var MakerJs;
accuracy: .0001
};
MakerJs.extendObject(opts, options);
var loops = MakerJs.model.findLoops(modelToExport, opts.accuracy);
var loops = MakerJs.model.findLoops(modelToExport, opts);
while (depthModel = loops.models[depth]) {
var union = '';
for (var modelId in depthModel.models) {
Expand Down
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.2",
"version": "0.5.3",
"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
2 changes: 1 addition & 1 deletion src/core/combine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ 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, includeAOutsideB: boolean, includeBInsideA: boolean, includeBOutsideA: boolean, keepDuplicates: boolean = true, farPoint?: IPoint) {
export function combine(modelA: IModel, modelB: IModel, includeAInsideB: boolean = false, includeAOutsideB: boolean = true, includeBInsideA: boolean = false, includeBOutsideA: boolean = true, keepDuplicates: boolean = true, farPoint?: IPoint) {

var pathsA = breakAllPathsAtIntersections(modelA, modelB, farPoint);
var pathsB = breakAllPathsAtIntersections(modelB, modelA, farPoint);
Expand Down
22 changes: 16 additions & 6 deletions src/core/fillet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ module MakerJs.path {
/**
* @private
*/
function populateShardPointsFromReferenceCircle(filletRadius: number, center: IPoint, properties: IMatchPointProperty[]): boolean {
function populateShardPointsFromReferenceCircle(filletRadius: number, center: IPoint, properties: IMatchPointProperty[], options: IPointMatchOptions): boolean {
var referenceCircle = new paths.Circle(center, filletRadius);

//get reference circle intersection points
Expand All @@ -110,7 +110,7 @@ module MakerJs.path {

properties[i].shardPoint = circleIntersection.intersectionPoints[0];

if (point.areEqualRounded(properties[i].point, circleIntersection.intersectionPoints[0], .0001)) {
if (point.areEqualRounded(properties[i].point, circleIntersection.intersectionPoints[0], options.accuracy)) {
if (circleIntersection.intersectionPoints.length > 1) {
properties[i].shardPoint = circleIntersection.intersectionPoints[1];
} else {
Expand Down Expand Up @@ -291,10 +291,15 @@ module MakerJs.path {
* @param line2 Second line to fillet, which will be modified to fit the fillet.
* @returns Arc path object of the new fillet.
*/
export function dogbone(line1: IPathLine, line2: IPathLine, filletRadius: number): IPathArc {
export function dogbone(line1: IPathLine, line2: IPathLine, filletRadius: number, options?: IPointMatchOptions): IPathArc {

if (isPathLine(line1) && isPathLine(line2) && filletRadius && filletRadius > 0) {

var opts: IPointMatchOptions = {
accuracy: .0001
};
extendObject(opts, options);

//first find the common point
var commonProperty = getMatchingPointProperties(line1, line2);
if (commonProperty) {
Expand All @@ -311,7 +316,7 @@ module MakerJs.path {

var center = point.add(commonProperty[0].point, point.fromPolar(angle.toRadians(bisectionAngle), filletRadius));

if (!populateShardPointsFromReferenceCircle(filletRadius, center, commonProperty)) {
if (!populateShardPointsFromReferenceCircle(filletRadius, center, commonProperty, opts)) {
return null;
}

Expand Down Expand Up @@ -350,17 +355,22 @@ module MakerJs.path {
* @param path2 Second path to fillet, which will be modified to fit the fillet.
* @returns Arc path object of the new fillet.
*/
export function fillet(path1: IPath, path2: IPath, filletRadius: number): IPathArc {
export function fillet(path1: IPath, path2: IPath, filletRadius: number, options?: IPointMatchOptions): IPathArc {

if (path1 && path2 && filletRadius && filletRadius > 0) {

var opts: IPointMatchOptions = {
accuracy: .0001
};
extendObject(opts, options);

//first find the common point
var commonProperty = getMatchingPointProperties(path1, path2);
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.
if (!populateShardPointsFromReferenceCircle(filletRadius, commonProperty[0].point, commonProperty)) {
if (!populateShardPointsFromReferenceCircle(filletRadius, commonProperty[0].point, commonProperty, opts)) {
return null;
}

Expand Down

0 comments on commit 02f8306

Please sign in to comment.