Maker.js, a Microsoft Garage project, is a JavaScript library for creating and sharing modular line drawings for CNC and laser cutters.

View project on GitHub Star

Intermediate drawing

Zeroing and Centering

To move a model so that its bottom and/or left edges are on the x & y axes, use model.zero. This function accepts 2 boolean parameters: zeroOnXAxis, zeroOnYAxis. If you do not pass any parameters, it will zero on both axes.

//zero a model

var makerjs = require('makerjs');

var model = {
  models: {

    crosshairs: {
      paths: {
        h: new makerjs.paths.Line([-5, 0], [5, 0]),
        v: new makerjs.paths.Line([0, -5], [0, 5])
      }
    },

    nut: {
      models: {
        polygon: new makerjs.models.Polygon(6, 40)
      },
      paths: {
        inner: new makerjs.paths.Circle(20)
      }
    }

  }
};

makerjs.model.zero(model.models.nut);

var svg = makerjs.exporter.toSVG(model);

document.write(svg);

To move a model so that it is centered on on the x & y axes, use model.center. This function accepts 2 boolean parameters: centerOnXAxis, centerOnYAxis. If you do not pass any parameters, it will center on both axes.

//center a couple of models

var makerjs = require('makerjs');

var model = {
  models: {

    crosshairs: {
      paths: {
        h: new makerjs.paths.Line([-5, 0], [5, 0]),
        v: new makerjs.paths.Line([0, -5], [0, 5])
      }
    },

    box: {
      models: {
        outer: new makerjs.models.Rectangle(60, 30),
        inner: new makerjs.models.Oval(45, 15)
      }
    }

  }
};

var shortcut = model.models.box.models;

makerjs.model.center(shortcut.outer);
makerjs.model.center(shortcut.inner);

var svg = makerjs.exporter.toSVG(model);

document.write(svg);

Originating

A path within a model is referenced relatively to its parent model. There may be times when you want all objects to be within the same coordinate space. Let's create a simple demonstration model:

//render a couple boxes in their own coordinate space

var makerjs = require('makerjs');

function box(origin) {
    this.models = {
        outer: new makerjs.models.RoundRectangle(100, 100, 1)
    };
    this.paths = {
      inner: new makerjs.paths.Circle([50, 50], 25)
    };

    this.origin = origin;
}

var box1 = new box([0, 0]);
var box2 = new box([150, 0]);

var model = {
    models: {
        box1: box1,
        box2: box2
    }
};

var svg = makerjs.exporter.toSVG(model);

document.write(svg);

console.log(box1.paths.inner.origin);
console.log(box2.paths.inner.origin);

In this example, both box1.paths.inner.origin and box2.paths.inner.origin have an origin of [50, 50] even though they are not in the same place, because they are located relative to the model that contains them. To make all models and paths occupy a singular coordinate space, we can use makerjs.model.originate:

//render a couple boxes in the same coordinate space

var makerjs = require('makerjs');

function box(origin) {
    this.models = {
        outer: new makerjs.models.RoundRectangle(100, 100, 1)
    };
    this.paths = {
      inner: new makerjs.paths.Circle([50, 50], 25)
    };

    this.origin = origin;
}

var box1 = new box([0, 0]);
var box2 = new box([150, 0]);

var model = {
    models: {
        box1: box1,
        box2: box2
    }
};

//move all path origins into the same space
makerjs.model.originate(model);

var svg = makerjs.exporter.toSVG(model);

document.write(svg);

console.log(box1.paths.inner.origin);
console.log(box2.paths.inner.origin);

Now box1.paths.inner.origin and box2.paths.inner.origin have the origins [50, 50] and [200, 50].

Scaling

To proportionately scale a simple point, use makerjs.point.scale. To proportionately scale paths and models, use these functions:

Each of these functions return the original object, so that we can "chain" on the same line of code.

Scale path example:

//render a scaled arc

var makerjs = require('makerjs');

var arc1 = new makerjs.paths.Arc([0, 0], 25, 0, 90);
var arc2 = new makerjs.paths.Arc([0, 0], 25, 0, 90);

arc2 = makerjs.path.scale(arc2, 2);

var svg = makerjs.exporter.toSVG({ paths: { arc1: arc1, arc2: arc2 }});

document.write(svg);

Scale model example:

//render a scaled polygon

var makerjs = require('makerjs');

var model = {
  models: {
    inner: new makerjs.models.Polygon(6, 40),
    outer: makerjs.model.scale(new makerjs.models.Polygon(6, 40), 1.7)
  }
};

var svg = makerjs.exporter.toSVG(model);

document.write(svg);

Distorting

To disproportionately scale a simple point, use makerjs.point.distort.

To disproportionately scale a path, use makerjs.path.distort(path: object, scaleX: number, scaleY: number) which returns a new object and does not modify the original. The type of returned object is dependent on the type of path being distorted:

  • A line will return a line IPath object, since the distortion can be represented with another line.
  • An arc will return a BezierCurve IModel object, since the distortion is not circular.
  • A circle will return an Ellipse IModel object, since the distortion is not circular.

Distort path example:

//render distorted paths

var makerjs = require('makerjs');

var circle = new makerjs.paths.Circle(50);
var line = new makerjs.paths.Line([-50,-50], [50, 50]);

//a distorted line is a path, so it should be added to paths
var distortedLine = makerjs.path.distort(line, 4, 1.5);

//a distorted circle is a model, so it should be added to models
var ellipse = makerjs.path.distort(circle, 4, 1.5);

var model = {
  paths: {
    circle: circle,
    line: line,
    distortedLine: distortedLine
  },
  models: {
    ellipse: ellipse
  }
};

var svg = makerjs.exporter.toSVG(model);
  
document.write(svg);

To disproportionately scale a model, use makerjs.model.distort(model: object, scaleX: number, scaleY: number) which returns a new IModel object and does not modify the original.

Distort model example:

//render a distorted star

var makerjs = require('makerjs');

var star = new makerjs.models.Star(5, 100);
makerjs.model.rotate(star, 18);

//make the star 4 times wider, and 2 times taller
var wideStar = makerjs.model.distort(star, 4, 2);

var model = {
  models: {
    star: star,
    wideStar: wideStar
  }
};

var svg = makerjs.exporter.toSVG(model);

document.write(svg);

Rotating

To rotate a single point, see makerjs.point.fromPolar and makerjs.point.rotate depending on what you are trying to achieve.

You can rotate paths and models with these functions:

Each of these functions return the original object, so that we can "chain" on the same line of code.

Rotate path example:

//render a rotated line

var makerjs = require('makerjs');

var line1 = new makerjs.paths.Line([0, 0], [100, 0]);
var line2 = new makerjs.paths.Line([0, 0], [100, 0]);

var paths = [line1, makerjs.path.rotate(line2, -30, [100, 0])];

var svg = makerjs.exporter.toSVG(paths);

document.write(svg);

Rotate model example:

//render a rotated rectangle

var makerjs = require('makerjs');

var rect1 = new makerjs.models.Rectangle(40, 80);

makerjs.model.rotate(rect1, 45, [0, 0]);

var svg = makerjs.exporter.toSVG(rect1);

document.write(svg);

Cloning

Models and paths are simple JavaScript objects, so they are easy to clone in a way that is standard to JavaScript. Maker.js provides a few functions for cloning:

Cloning is useful in many situations. For example, if you need many copies of a model for rotation:

//clone and rotate

var makerjs = require('makerjs');

function sawtooth(numTeeth, r1, rd, offset) {
    var a = 360 / numTeeth;
    var a1 = 90 - a / 2;
    var r2 = r1 + rd;
    var p1 = makerjs.point.fromPolar(makerjs.angle.toRadians(a1), r1);
    var p2 = makerjs.point.rotate(p1, a, [0, 0]);

    var p3 = [-offset, r2];

    this.paths = {
        outer: new makerjs.paths.Arc(p1, p3, r2 / 4, false, false),
        inner: new makerjs.paths.Arc(p2, p3, r1 / 4, false, false)
    };
}

var wheel = { models: {} };
var numTeeth = 30;
var tooth = new sawtooth(numTeeth, 100, 20, 10);

for (var i = 0; i < numTeeth; i++ ) {
    var clone = makerjs.cloneObject(tooth);
    var a = 360 / numTeeth;
    makerjs.model.rotate(clone, a * i, [0, 0]);
    wheel.models[i] = clone;
}

var svg = makerjs.exporter.toSVG(wheel);

document.write(svg);

Mirroring

Use makerjs.angle.mirror to get a mirror of an angle, and makerjs.point.mirror to get a mirror of a simple point.

You can create a mirrored copy of paths and models with the following functions. The mirroring can occur on the x axis, the y axis, or both.

Each of these functions returns a new object and does not modify the original.

Mirror path example:

//render a line mirrored in the x dimension

var makerjs = require('makerjs');

var line1 = new makerjs.paths.Line([0, 0], [100, 100]);
var line2 = makerjs.path.mirror(line1, true, false);

var paths = [line1, line2];

var svg = makerjs.exporter.toSVG(paths);

document.write(svg);

Mirror model example:

//render a model mirrored in the y dimension

var makerjs = require('makerjs');

var ovalArc1 = new makerjs.models.OvalArc(45, 135, 50, 10);

var model = {
    models: {
        ovalArc1: ovalArc1,
        ovalArc2: makerjs.model.mirror(ovalArc1, false, true)
    }
};

var svg = makerjs.exporter.toSVG(model);

document.write(svg);

Hint: When creating symmetrical models, it may be easier to create one half, and then use mirror to generate the other half.

Repeating layouts

Maker.js provides several functions which will clone your paths or models and repeat them in various layouts.

Columns

Call makerjs.layout.cloneToColumn(path or model, count, [optional] margin) to repeatedly clone and layout in a column. The interval will be the height of the path's or model's bounding box. Extra vertical margin is optional.
//Grooves for a finger joint

var m = require('makerjs');

var dogbone = new m.models.Dogbone(50, 20, 2, -1, false);

var grooves = m.layout.cloneToColumn(dogbone, 5, 20);

document.write(m.exporter.toSVG(grooves));

Rows

Call makerjs.layout.cloneToRow(path or model, count, [optional] margin) to repeatedly clone and layout in a row. The interval will be the width of the path's or model's bounding box. Extra horizontal margin is optional.
//grill of ovals

var m = require('makerjs');

var oval = new m.models.Oval(20, 150);

var grill = m.layout.cloneToRow(oval, 12, 20);

document.write(m.exporter.toSVG(grill));

Grid

Call makerjs.layout.cloneToGrid(path or model, xcount, ycount, [optional] margin) to repeatedly clone and layout in a grid. The interval will be the path's or model's bounding box. Extra margin is optional.
//grill of rounded squares

var m = require('makerjs');

var roundSquare = new m.models.RoundRectangle(20, 20, 4);

var grid = m.layout.cloneToGrid(roundSquare, 11, 5, 5);

document.write(m.exporter.toSVG(grid));

Brick

Call makerjs.layout.cloneToBrick(path or model, xcount, ycount, [optional] margin) to repeatedly clone and layout in a brick wall format. The interval will be the path's or model's bounding box. Extra margin is optional.
//brick wall

var m = require('makerjs');

var brick = new m.models.Rectangle(20, 8);

var wall = m.layout.cloneToBrick(brick, 8, 7, 2);

document.write(m.exporter.toSVG(wall));

Honeycomb

Call makerjs.layout.cloneToHoneycomb(path or model, xcount, ycount, [optional] margin) to repeatedly clone and layout in a honeycomb format. The interval will be the path's or model's bounding hexagon. Extra margin is optional.
//Honeycomb

var m = require('makerjs');

var star = m.model.rotate(new m.models.Star(6, 50, 0, 2), 30);

var pattern = m.layout.cloneToHoneycomb(star, 8, 5, 30);

document.write(m.exporter.toSVG(pattern));

Radial

Call makerjs.layout.cloneToRadial(path or model, count, angleInDegrees, [optional] rotationOrigin) to repeatedly clone and layout in a radial format.
//spinner

var m = require('makerjs');

var rect = m.model.move(new m.models.Rectangle(30, 10), [40, -5]);

var spinner = m.layout.cloneToRadial(rect, 16, 22.5);

document.write(m.exporter.toSVG(spinner));

Intersection

You can find the point(s) of intersection between two paths using makerjs.path.intersection. If the paths do not intersect, this function will return null. Otherwise, it will return an object with a property named intersectionPoints which is an array of points. Additionally, if either path was an arc or circle, this object will contain the angles at which an intersection occurred.

Intersection examples:

//line-line intersection

var makerjs = require('makerjs');

var model = {
    paths: {
        line1: new makerjs.paths.Line([0, 0], [20, 10]),
        line2: new makerjs.paths.Line([2, 10], [50, 2])
    }
};

var int = makerjs.path.intersection(model.paths.line1, model.paths.line2);

if (int) {
    var p = int.intersectionPoints[0];
    var id = JSON.stringify(makerjs.point.rounded(p, 0.01));
    model.paths[id] = new makerjs.paths.Circle(p, 1);
}

var svg = makerjs.exporter.toSVG(model);

document.write(svg);
//circle-circle intersection

var makerjs = require('makerjs');

var model = {
    paths: {
        circle1: new makerjs.paths.Circle([0, 10], 20),
        circle2: new makerjs.paths.Circle([20, 0], 20)
    }
};

var int = makerjs.path.intersection(model.paths.circle1, model.paths.circle2);

if (int) {
    int.intersectionPoints.forEach(
        function(p, i) {
            var id = JSON.stringify(makerjs.point.rounded(p, 0.01)) + ' intersects circle1 at ' + makerjs.round(int.path1Angles[i], .01) + ' circle2 at ' + makerjs.round(int.path2Angles[i], .01);
            model.paths[id] = new makerjs.paths.Circle(p, 1);
        }
    );
}

var svg = makerjs.exporter.toSVG(model);

document.write(svg);
//line-arc intersection

var makerjs = require('makerjs');

var model = {
    paths: {
        line1: new makerjs.paths.Line([0, 0], [20, 10]),
        arc1: new makerjs.paths.Arc([12, 0], 10, 45,215)
    }
};

var int = makerjs.path.intersection(model.paths.line1, model.paths.arc1);

if (int) {
    int.intersectionPoints.forEach(
        function(p, i) {
            var id = JSON.stringify(makerjs.point.rounded(p, 0.01)) + ' arc1 at ' + makerjs.round(int.path2Angles[i], .01);
            model.paths[id] = new makerjs.paths.Circle(p, 1);
        }
    );
}

var svg = makerjs.exporter.toSVG(model);

document.write(svg);

Converging lines

To make lines meet at their slope intersection point, use makerjs.path.converge. This function will only work with lines, it will not work with arcs.

The converge function will try to use the end of the line that is closest to the convergence point. If you need to specify which ends of your lines should be converged, pass two additional boolean values. The boolean value is true to use the line's origin, false to use the end.

Converge example:

//converge lines

var makerjs = require('makerjs');

var model = {
  origin: [0, 0],
  paths: {
    line1: new makerjs.paths.Line([0, 0], [10, 5]),
    line2: new makerjs.paths.Line([0, 10], [10, 4]),
    line3: new makerjs.paths.Line([1, 0], [5, -2])
  }
};

var clone1 = makerjs.cloneObject(model);
clone1.origin = [10, 0];

var clone2 = makerjs.cloneObject(model);
clone2.origin = [20, 0];

makerjs.path.converge(clone1.paths.line1, clone1.paths.line2);
makerjs.path.converge(clone1.paths.line1, clone1.paths.line3);

makerjs.path.converge(clone2.paths.line1, clone2.paths.line2, false, true);
makerjs.path.converge(clone2.paths.line1, clone2.paths.line3, true, false);

var svg = makerjs.exporter.toSVG({ models: { before: clone1, after: model, x: clone2 } });

document.write(svg);

Modifying models

We know that models are relatively simple objects with a well known recursive structure. This allows us to modify them for different purposes. Let's modify and combine two different models in one drawing.

For this example we will use ovals to make an oval L shape. We begin by creating a model function that has two ovals:

//render two ovals which overlap

var makerjs = require('makerjs');

function ovalL(width, height, thickness) {
    var ovalH = new makerjs.models.Oval(width, thickness);
    var ovalV = new makerjs.models.Oval(thickness, height);

    this.models = {
        h: ovalH, v: ovalV
    };
}

var svg = makerjs.exporter.toSVG(new ovalL(100, 100, 37));

document.write(svg);

There are overlapping arcs in the lower left corner. We can remove them if we know their id and position in the heirarchy. There are several ways we can inspect this model, here are a few:

  • Look at the code which created it. This may involve deep lookups. For example, the Oval source code references the RoundRectangle source code.
  • Use the browser's console, or JavaScript debugger to set a breakpoint in your model.
  • Use the browser's DOM inspector to traverse the rendered SVG.
  • Output the raw JSON or SVG on screen.
  • Use the Playground app and click "show path names".

By looking at the source code we know that an Oval is a RoundRectangle and that the ids for arcs are BottomLeft, BottomRight, TopLeft and TopRight. The ids for the sides are Left, Right, Top and Bottom. Also, we need to note the orientation of these lines so we know which are origin and end points.

To remove a path we use the JavaScript delete keyword:

//render two ovals which overlap

var makerjs = require('makerjs');

function ovalL(width, height, thickness) {
    var ovalH = new makerjs.models.Oval(width, thickness);
    var ovalV = new makerjs.models.Oval(thickness, height);

    //delete the lower arcs from the vertical oval
    delete ovalV.paths.BottomLeft;
    delete ovalV.paths.BottomRight;

    //delete the inside arc of the horizontal
    delete ovalH.paths.TopLeft;

    this.models = {
        h: ovalH, v: ovalV
    };
}

var svg = makerjs.exporter.toSVG(new ovalL(100, 100, 37));

document.write(svg);

The next step is to eliminate the overlap in the lines. Here are two approaches to do this:

Adjust only the x or y component of the point:

//render an L shape, modifying points by their x and y

var makerjs = require('makerjs');

function ovalL(width, height, thickness) {
    var ovalH = new makerjs.models.Oval(width, thickness);
    var ovalV = new makerjs.models.Oval(thickness, height);

    delete ovalV.paths.BottomLeft;
    delete ovalV.paths.BottomRight;
    delete ovalH.paths.TopLeft;

    //move the x of the horizontal's top
    ovalH.paths.Top.end[0] = thickness;

    //move the y of the vertical's right
    ovalV.paths.Right.origin[1] = thickness;

    this.models = {
        h: ovalH, v: ovalV
    };
}

var svg = makerjs.exporter.toSVG(new ovalL(100, 100, 37));

document.write(svg);

Share a point on both lines:

//render an L shape, sharing a point

var makerjs = require('makerjs');

function ovalL(width, height, thickness) {
    var ovalH = new makerjs.models.Oval(width, thickness);
    var ovalV = new makerjs.models.Oval(thickness, height);

    delete ovalV.paths.BottomLeft;
    delete ovalV.paths.BottomRight;
    delete ovalH.paths.TopLeft;

    //set to the same point
    ovalH.paths.Top.end =
        ovalV.paths.Right.origin =
            [thickness, thickness];

    this.models = {
        h: ovalH, v: ovalV
    };
}

var svg = makerjs.exporter.toSVG(new ovalL(100, 100, 37));

document.write(svg);

Let's progress this example further, by modifying an L shape into a C shape. Create a new model function for C, and immediately create an L within it. The C may create a new models object for itself, and nest the L inside; alternatively, C can just assume L's models object:

//render an L with an oval over it

var makerjs = require('makerjs');

function ovalL(width, height, thickness) {
    var ovalH = new makerjs.models.Oval(width, thickness);
    var ovalV = new makerjs.models.Oval(thickness, height);
    delete ovalV.paths.BottomLeft;
    delete ovalV.paths.BottomRight;
    delete ovalH.paths.TopLeft;
    ovalH.paths.Top.end =
        ovalV.paths.Right.origin =
            [thickness, thickness];
    this.models = { h: ovalH, v: ovalV };
}

function ovalC(width, height, thickness) {

    //assume the same models as L
    this.models = new ovalL(width, height, thickness).models;

    //add another oval
    this.models.h2 = new makerjs.models.Oval(width, thickness);

    //move it to the top
    this.models.h2.origin = [0, height - thickness];
}

//using C instead of L
var svg = makerjs.exporter.toSVG(new ovalC(100, 100, 37));

document.write(svg);

Just as before, we need to delete the overlapping paths using the delete keyword. Let us also make a short alias for this.models to save us some keystrokes:

//render an L and form a C

var makerjs = require('makerjs');

function ovalL(width, height, thickness) {
    var ovalH = new makerjs.models.Oval(width, thickness);
    var ovalV = new makerjs.models.Oval(thickness, height);
    delete ovalV.paths.BottomLeft;
    delete ovalV.paths.BottomRight;
    delete ovalH.paths.TopLeft;
    ovalH.paths.Top.end =
        ovalV.paths.Right.origin =
            [thickness, thickness];
    this.models = { h: ovalH, v: ovalV };
}

function ovalC(width, height, thickness) {

    //set local var m for easy typing
    var m =
        this.models =
            new ovalL(width, height, thickness).models;

    m.h2 = new makerjs.models.Oval(width, thickness);
    m.h2.origin = [0, height - thickness];

    //delete overlapping arcs again
    delete m.h2.paths.TopLeft;
    delete m.h2.paths.BottomLeft;
    delete m.v.paths.TopRight;
}

var svg = makerjs.exporter.toSVG(new ovalC(100, 100, 37));

document.write(svg);

Lastly, we need our overlapping lines to meet at a common point. Notice that the new oval h2 has a different origin the the previous ovals. So, we must originate for all of the ovals to share the same coordinate space. Afterwards, we can assign the common point to both lines.

In the Play editor, try removing the call to originate to see the results without it.

//render a C shape

var makerjs = require('makerjs');

function ovalL(width, height, thickness) {
    var ovalH = new makerjs.models.Oval(width, thickness);
    var ovalV = new makerjs.models.Oval(thickness, height);
    delete ovalV.paths.BottomLeft;
    delete ovalV.paths.BottomRight;
    delete ovalH.paths.TopLeft;
    ovalH.paths.Top.end = 
        ovalV.paths.Right.origin = 
            [thickness, thickness];
    this.models = { h: ovalH, v: ovalV };
}

function ovalC(width, height, thickness) {
    var m = 
        this.models = 
            new ovalL(width, height, thickness).models;
    m.h2 = new makerjs.models.Oval(width, thickness);
    m.h2.origin = [0, height - thickness];
    delete m.h2.paths.TopLeft;
    delete m.h2.paths.BottomLeft;
    delete m.v.paths.TopRight;
    
    //h2 has paths relative to h2 origin, 
    //we need to originate to share the point
    makerjs.model.originate(this);
    
    //share the point
    m.h2.paths.Bottom.origin = 
        m.v.paths.Right.end =
            [thickness, height - thickness];
}

var svg = makerjs.exporter.toSVG(new ovalC(100, 100, 37));

document.write(svg);

Breaking paths

You can break paths into two pieces if you have a point that lies on the path (from an intersection, for example) by using makerjs.path.breakAtPoint. This function will change the path that you pass it, so that it is broken at that point, and it will return a new path object which is the other broken piece:
//break a path in two

var makerjs = require('makerjs');

var model = {
  paths: {
    arc: new makerjs.paths.Arc([0, 0], 50, 0, 180)
  }
};

var arc2 = makerjs.path.breakAtPoint(model.paths.arc, [0, 50]);
makerjs.model.moveRelative(arc2, [-10, 0]);

model.paths.arc2 = arc2;

var svg = makerjs.exporter.toSVG(model);

document.write(svg);
For Circle, the original path will be converted in place to an Arc, and null is returned.

Fillets

Fillets are round corners where two paths meet. Maker.js provides two types of fillets: traditional fillets and dogbone fillets.

Traditional fillet

Rounding a corner can add strength to your part, as well as make it faster to print. Using makerjs.path.fillet you can round a corner at the junction between two lines, two arcs, or a line and an arc. This function will clip the two paths that you pass it, and will return a new arc path which fits between the clipped ends. The paths must meet at one point, this is how it determines which ends of the paths to clip. You also provide a radius of the fillet. If the fillet cannot be created this function will return null.

//fillet between lines

var makerjs = require('makerjs');

var model = {
  paths: {
    line1: new makerjs.paths.Line([0, 20], [30, 10]),
    line2: new makerjs.paths.Line([10, 0], [30, 10])
  }
};

//create a fillet
var arc = makerjs.path.fillet(model.paths.line1, model.paths.line2, 2);

//add the fillet to the model
model.paths.arc = arc;

var svg = makerjs.exporter.toSVG(model);

document.write(svg);
//fillet between arcs

var makerjs = require('makerjs');

var model = {
  paths: {
    arc1: new makerjs.paths.Arc([0, 50], 50, 270, 0),
    arc2: new makerjs.paths.Arc([100, 50], 50, 180, 270)
  }
};

//create a fillet
var arc = makerjs.path.fillet(model.paths.arc1, model.paths.arc2, 2);

//add the fillet to the model
model.paths.arc = arc;

var svg = makerjs.exporter.toSVG(model);

document.write(svg);
//fillet between line and arc (or arc and line!)

var makerjs = require('makerjs');

var model = {
  paths: {
    arc: new makerjs.paths.Arc([0, 50], 50, 270, 0),
    line: new makerjs.paths.Line([50, 50], [50, 0])
  }
};

//create a fillet
var arc2 = makerjs.path.fillet(model.paths.arc, model.paths.line, 2);

//add the fillet to the model
model.paths.arc2 = arc2;

var svg = makerjs.exporter.toSVG(model);

document.write(svg);

Dogbone Fillets

Many CNC tools are not able to cut a sharp interior corner. The way to clear the apex of an interior corner is by encompassing the corner with a circular cut known as a dogbone fillet. Use makerjs.path.dogbone to round a corner at the junction between two lines. This function will only work for two lines which must meet at one point. It will clip the two lines that you pass it, and will return a new arc path which clears the corner where the lines meet. It will return null if a dogbone fillet cannot be created at the radius you specify.

//dogbone fillet between lines.

var makerjs = require('makerjs');

var model = {
  paths: {
    line1: new makerjs.paths.Line([0, 0], [0, 5]),
    line2: new makerjs.paths.Line([0, 5], [10, 5])
  }
};

//create dogbone fillet
var arc1 = makerjs.path.dogbone(model.paths.line1, model.paths.line2, 1);

//add the fillet to the model
model.paths.arc1 = arc1;

var svg = makerjs.exporter.toSVG(model);

document.write(svg);

Dogbone models

If you need a rectangle with dogbones at each corner, you can use makerjs.models.Dogbone(width, height, radius, optional style, optional bottomless). There are a 3 styles of a Dogbone model:

  • 0 : (default) rounded
  • -1 : horizontal
  • -1 : vertical

Dogbone model corner styles:

//dogbone corner styles.

var makerjs = require('makerjs');

var dogbones = {
  models: {
    round: new makerjs.models.Dogbone(100,50, 5, 0),
    horizontal: new makerjs.models.Dogbone(100,50, 5, -1),
    vertical: new makerjs.models.Dogbone(100,50, 5, 1)
  }
};

dogbones.models.horizontal.origin = [115, 0];
dogbones.models.vertical.origin = [230, 0];

var svg = makerjs.exporter.toSVG(dogbones);

document.write(svg);

Making them bottomless is useful for creating tongue-and-groove shapes:

//bottomless dogbones.

var makerjs = require('makerjs');

var dogbones = {
  models: {
    O: new makerjs.models.Dogbone(100,50, 5, 0, true),
    horizontal: new makerjs.models.Dogbone(100,50, 5, -1, true),
    vertical: new makerjs.models.Dogbone(100,50, 5, 1, true)
  }
};

dogbones.models.horizontal.origin = [115, 0];
dogbones.models.vertical.origin = [230, 0];

var svg = makerjs.exporter.toSVG(dogbones);

document.write(svg);

Layers

Layers are a way of logically grouping your paths or models as you see fit. Simply add a layer property to any path or model object, with the name of the layer. Every path within a model will automatically inherit its parent model's layer, unless it has its own layer property. As you can see in this example, a layer can transcend the logical grouping boundaries of models:

//render a round rectangle with arcs in their own layer

var makerjs = require('makerjs');

var roundRect = new makerjs.models.RoundRectangle(100, 50, 10);
roundRect.layer = "layer1";

roundRect.paths.BottomLeft.layer = "layer2";
roundRect.paths.BottomRight.layer = "layer2";
roundRect.paths.TopRight.layer = "layer2";
roundRect.paths.TopLeft.layer = "layer2";

var svg = makerjs.exporter.toSVG(roundRect);

document.write(svg);
Layers are not visible in this example but they logically exist to separate arcs from straight lines.

A layer name can be any string. Furthermore, you can use a reserved color name from this list to get an automatic stroke color when your model is exported in DXF or SVG:
aqua, black, blue, fuchsia, green, gray, lime, maroon, navy, olive, orange, purple, red, silver, teal, white, yellow

//render a round rectangle with arcs in their own color layer

var makerjs = require('makerjs');

var roundRect = new makerjs.models.RoundRectangle(100, 50, 10);
roundRect.layer = "layer1";

roundRect.paths.BottomLeft.layer = "red";
roundRect.paths.BottomRight.layer = "red";
roundRect.paths.TopRight.layer = "blue";
roundRect.paths.TopLeft.layer = "blue";

var svg = makerjs.exporter.toSVG(roundRect);

document.write(svg);

Layers will be output during the export process in these formats:

  • DXF - paths will be assigned to a DXF layer.
  • SVG - in continuous mode, a new <path> element will be created for each layer.

Cascading functions

When calling a function, you can pass its output directly into another function. This is called cascading. This lets you do multiple operations in one statement. Here we will center, rotate and move a square:
//cascade functions

var makerjs = require('makerjs');

//many operations in this one statement
var square = 
        makerjs.model.moveRelative(
            makerjs.model.rotate(
                makerjs.model.center(
                    new makerjs.models.Square(10)
                ),
            45),
        [0, 15])
    ;

var drawing = {
    models: {
        dome: new makerjs.models.Dome(30, 30),
        square: square
    }
};

var svg = makerjs.exporter.toSVG(drawing);

document.write(svg);
This is convenient, but it also has the drawback of making the code less readable. As more function calls are added, the parameters associated with the call are separated outward. Also notice that the final operation (moveRelative) appears at the beginning of the statement.

The $ function

As an alternative to cascading functions, Maker.js offers a handy way to modify your drawing in an object-oriented style, inspired by the jQuery library.

Call makerjs.$(x) to get a cascade container object returned. You can then invoke a series of cascading functions upon x. The output of each function becomes the input of the next. A cascade container will only work with functions that output the same type of object that they input as their first parameter, which must be one of these types:
  • Model
  • Path
  • Point

Container operators

A cascade container will have special properties that operate the container itself (as opposed to operating upon x). These are prefixed with $:
  • $initial: object Gets the original x object that you passed in.
  • $result: object Gets the final result of all cascaded function calls.
  • $reset(): function() - Resets the container to $initial.

Cascadable functions

Depending on the type of x, a cascade container will provide all of the functions from one of the corresponding modules. These are the same functions that we've covered in previous lessons. One difference is that you do not need to provide the first parameter, since it will either be x or the cascaded result of the previous function call.

Example

Let's rewrite the example from above to compare the readability of the code:
//cascade functions

var makerjs = require('makerjs');

//many operations in this one statement
var square = makerjs.$(new makerjs.models.Square(10))
    .center()
    .rotate(45)
    .moveRelative([0, 15])
    .$result;

var drawing = {
    models: {
        dome: new makerjs.models.Dome(30, 30),
        square: square
    }
};

var svg = makerjs.exporter.toSVG(drawing);

document.write(svg);
This has saved us some typing - we didnt need to use makerjs.model... to access any functions. The order of operations makes more sense too: the first operation (center()) is at the top, the final operation (moveRelative([0, 15])) is at the bottom, and the function parameters are together with their call.

Using addTo() instead of .$result

In some cases, you can avoid using .$result and just add a path or add a model to a parent model by calling addTo(model, id). This is particularly useful prior to a call that creates a clone (such as mirror):
//use addTo with mirror

var makerjs = require('makerjs');

var starburst = {};

makerjs.$(new makerjs.paths.Arc([5, 5], 5, 180, 270))
.addTo(starburst, 'arc1')
.mirror(true, false)
.addTo(starburst, 'arc2')
.mirror(false, true)
.addTo(starburst, 'arc3')
.mirror(true, false)
.addTo(starburst, 'arc4');

var svg = makerjs.exporter.toSVG(starburst);

document.write(svg);

Captions

Captions are fragments of text that can be positioned anywhere in your model, useful for adding documentation within your drawing. Captions are unlike the Text model, which is a line drawing of glyphs in a given font.

A caption is aligned to an invisible line called an anchor. The caption text is centered both horizontally and vertically at the center point of the anchor line. The text in a caption will not wrap, it is a single line of text. The text and anchor line do not need to be the same length, the anchor line is only used to determine the center point and the slope. The anchor line may be rotated to angle the caption text. Anchor lines are moved, originated, scaled, distorted and rotated accoordingly within a model. The font size of caption text is determined when you export your model. A caption can be put on a different layer from it's model by setting the layer of it's anchor. Note: In the Playground, caption text does not scale when you zoom in or out.

Creating a caption object

A caption is an object with these two properties:

  • text - String
  • anchor - Line object
Add this to a model via the caption property:

//add a caption to a model

var makerjs = require('makerjs');

var square = new makerjs.models.Square(100);

square.caption = {
    text: "a square",
    anchor: new makerjs.paths.Line([0, 50], [100, 50])
};

var svg = makerjs.exporter.toSVG(square);

document.write(svg);

There is a helper function makerjs.model.addCaption(text, [optional] leftAnchorPoint, [optional] rightAnchorPoint) which lets you add a caption on one line of code:

//add a caption to a model with the helper

var makerjs = require('makerjs');

var square = new makerjs.models.Square(100);

makerjs.model.addPath(square, new makerjs.paths.Line([10, 10], [90, 90]));

makerjs.model.addCaption(square, 'fold here', [10, 20], [80, 90]);

var svg = makerjs.exporter.toSVG(square);

document.write(svg);

If the anchor line is degenerate (meaning its origin and end point are the same), you can achieve text which will remain horizontally aligned regardless of model rotation:

//add a caption that will not rotate

var makerjs = require('makerjs');

var square = makerjs.$(new makerjs.models.Square(100))
    .addCaption('always aligned', [50, 50], [50, 50])
    .rotate(22)
	.$result;

var svg = makerjs.exporter.toSVG(square);

document.write(svg);

Next: learn more in Advanced drawing.