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

Basic Drawing

Points

A point is represented by an array with 2 elements. The first element is x, the second element is y.

var p = [0, 0];

Coordinates

Maker.js uses the same coordinate system from basic mathematics and traditional drafting, where x values increase from left to right, and y values increase from bottom to top. Negative values are allowed.

Note that the SVG coordinate system is slightly different (Y values increase from top to bottom, and negative values do not appear), but Maker.js will handle that for us automatically.

Paths

A path is represented by an object with these mandatory properties:

  • type: string - "line", "circle", or "arc"
  • origin: point

Line

A line is a path with the type "line" and this additional property:

  • end: point
var line = { 
  type: 'line', 
  origin: [0, 0], 
  end: [1, 1] 
 };

Circle

A circle is a path with the type "circle" and this additional property:

  • radius: number
var circle = { 
  type: 'circle', 
  origin: [0, 0],
  radius: 1
 };

Arc

An arc is a path with the type "arc" and these additional properties:

  • radius: number
  • startAngle: number
  • endAngle: number

Note: Property names are case-sensitive.

var arc = { 
  type: 'arc', 
  origin: [0, 0],
  radius: 1,
  startAngle: 0,
  endAngle: 45
 };
(additional optional properties covered in advanced lessons)

Basic rendering in SVG

Call the makerjs.exporter.toSVG function and pass your path as a parameter:

//renders a line

var makerjs = require('makerjs');

var line = { 
  type: 'line', 
  origin: [0, 0], 
  end: [50, 50] 
 };
 
var svg = makerjs.exporter.toSVG(line);

document.write(svg);

You may also call makerjs.exporter.toSVG with an array of paths as a parameter:

//renders a line and a circle

var makerjs = require('makerjs');

var line = { 
  type: 'line', 
  origin: [0, 0], 
  end: [50, 50] 
 };

var circle = { 
  type: 'circle', 
  origin: [0, 0],
  radius: 50
 };

var pathArray = [ line, circle ];

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

document.write(svg);

Models

Models are the heart of Maker.js. A model is represented by an object with these optional properties:

  • origin: point
  • paths: object map of paths
  • models: object map of models

Let's look at paths first, using the example above.

//render a line and circle in a model

var makerjs = require('makerjs');

var line = { 
  type: 'line', 
  origin: [0, 0], 
  end: [50, 50] 
 };

var circle = { 
  type: 'circle', 
  origin: [0, 0],
  radius: 50
 };

var pathObject = { myLine: line, myCircle: circle };

var model = { paths: pathObject };

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

document.write(svg);

Note that we can also pass a model to makerjs.exporter.toSVG.

If we wrap our model code in a function we can call it multiple times. There are several ways to do this. First we will leave the code as is, and return the model variable.

//render a model created by a function

var makerjs = require('makerjs');

function myModel() {

 var line = { 
   type: 'line', 
   origin: [0, 0], 
   end: [50, 50] 
  };

 var circle = { 
   type: 'circle', 
   origin: [0, 0],
   radius: 50
  };

 var pathObject = { myLine: line, myCircle: circle };

 var model = { paths: pathObject };
 
 return model;
}

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

document.write(svg);

Alternatively, we can change our function to be usable with the new operator, and our model properties are set using the this keyword:

//render a model created by a function, using the 'this' keyword

var makerjs = require('makerjs');

function myModel() {

 var line = { 
   type: 'line', 
   origin: [0, 0], 
   end: [50, 50] 
  };

 var circle = { 
   type: 'circle', 
   origin: [0, 0],
   radius: 50
  };

 var pathObject = { myLine: line, myCircle: circle };

//set properties using the "this" keyword
 this.paths = pathObject;
}

//note we are using the "new" operator
var svg = makerjs.exporter.toSVG(new myModel());

document.write(svg);

The example output should be the same as above. While we changed the way we defined our model, we haven't yet changed the functionality:

Now we are better set up to look at models and origin. We will create a new model which has 2 instances of myModel:

//render 2 instances of the same model

var makerjs = require('makerjs');

function myModel() {

 var line = { 
   type: 'line', 
   origin: [0, 0], 
   end: [50, 50] 
  };

 var circle = { 
   type: 'circle', 
   origin: [0, 0],
   radius: 50
  };

 var pathObject = { myLine: line, myCircle: circle };
 
//set properties using the "this" keyword
 this.paths = pathObject;
}

var model1 = new myModel();
var model2 = new myModel();

//they will be on top of each other, so let's move the origin
model2.origin = [100, 0];

var model = { 
  models: { "myModel1": model1, "myModel2": model2 }
};

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

document.write(svg);
(additional optional properties covered in advanced lessons)

Path constructors

In the example code above we used plain old JavaScript objects to create paths and models. Notice that we didn't need to use a special constructor provided by Maker.js to create either a path or a model. This is an intentional aspect of Maker.js, that you can decide how to create your objects. To make these plain objects work with Maker.js, they needed to use the property names specified above.

We also illustrated 3 ways of defining an object: using a var, using a function that returns a var, and using a constructor function (for use by the new keyword). Let's revisit our simple line path example, and convert it to a constructor function.

//render a line

var makerjs = require('makerjs');

var line = { 
  type: 'line', 
  origin: [0, 0], 
  end: [50, 50] 
 };
 
var svg = makerjs.exporter.toSVG(line);

document.write(svg);
//render a line created by a function

var makerjs = require('makerjs');

function line() { 
  this.type = 'line', 
  this.origin = [0, 0], 
  this.end = [50, 50] 
 };
 
var svg = makerjs.exporter.toSVG(new line());

document.write(svg);

Of course this example is not very useful because it only produces a line with the same origin and end every time. Instead, these should be passed as parameters.

Since this is a common scenario, Maker.js provides constructors for all primitive paths: line, circle and arc:

//render the basic paths

var makerjs = require('makerjs');

var line = new makerjs.paths.Line([0, 0], [50, 50]);
var circle = new makerjs.paths.Circle([0, 0], 50);
var arc = new makerjs.paths.Arc([0, 0], 25, 0, 90);
 
var svg = makerjs.exporter.toSVG([line, circle, arc]);

document.write(svg);

Path independence and Chains

All paths in a drawing are atomic elements of either line, arc, or circle. Paths may happen to touch each other or they may not. When any two paths have the same endpoint, this is called a chain. A chain can continue with any number of paths that meet end to end. If the chain begins and ends at the same point, this is called an endless chain.

Chains are an important concept that we will build upon, yet they are not a thing that you specify in your code. Rather, chains are "found" by Maker.js when it processes your drawing model. Paths in your drawing model are independent elements which may be added, modified or deleted by you or another developer. As you work with paths, bear in mind that you are also implicitly working with chains.

//render a model that nas no chains

var makerjs = require('makerjs');

var model = {

  paths: {
    "h1": new makerjs.paths.Line([0, 10], [30, 10]),
    "h2": new makerjs.paths.Line([0, 20], [30, 20]),
    "v1": new makerjs.paths.Line([10, 0], [10, 30]),
    "v2": new makerjs.paths.Line([20, 0], [20, 30])
  }

};

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

document.write(svg);
//render a model with paths that form a chain

var makerjs = require('makerjs');

var model = {

  paths: {
    "0": new makerjs.paths.Line([0, 0], [100, 0]),
    "1": new makerjs.paths.Line([100, 0], [100, 100]),
    "2": new makerjs.paths.Line([100, 100], [200, 100])
  }

};

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

document.write(svg);
//render a model with paths that form an endless chain

var makerjs = require('makerjs');

var model = {

  paths: {
    "v": new makerjs.paths.Line([0, 0], [0, 100]),
    "h": new makerjs.paths.Line([0, 0], [100, 0]),
    "arc":new makerjs.paths.Arc([0, 0], 100, 0, 90)
  }

};

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

document.write(svg);

Built-in models

Moving

Models and paths can be moved to an absolute location, or moved by an [x, y] amount relative to their current location. Keep in mind that since paths are contained within models, and models may be contained within models, that their coordinates will be relative to the containing model.

To illustrate this, let's create a model that has a few squares:

//create some squares side by side

function Squares() {

    this.models = {

        s1: new makerjs.models.Square(100),

        //calling makerjs.model.move and creating a model all on one line of code.
        s2: makerjs.model.move(new makerjs.models.Square(100), [120, 0]),

        s3: new makerjs.models.Square(100)

    };

    //move the third square by setting its origin property.
    this.models.s3.origin = [240, 0];

}

var makerjs = require('makerjs');
var squares = new Squares();

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

document.write(svg);

The way to move a model to an absolute position is to set its origin property. The makerjs.model.move function does just that, but it also lets you do more operations on one line of code.

To move a model by a relative amount, use makerjs.model.moveRelative:

//move some squares by a relative distance

function Squares() {
    this.models = {
        s1: new makerjs.models.Square(100),
        s2: makerjs.model.move(new makerjs.models.Square(100), [120, 0]),
        s3: new makerjs.models.Square(100)
    };
    this.models.s3.origin = [240, 0];
}

var makerjs = require('makerjs');
var squares = new Squares();

//move some squares by a relative distance
makerjs.model.moveRelative(squares.models.s2, [-10, 10]);
makerjs.model.moveRelative(squares.models.s3, [-20, 20]);

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

document.write(svg);

Likewise, paths can be moved absolutely with makerjs.path.move or relatively with makerjs.path.moveRelative:

//move some paths within the squares

function Squares() {
    this.models = {
        s1: new makerjs.models.Square(100),
        s2: makerjs.model.move(new makerjs.models.Square(100), [120, 0]),
        s3: new makerjs.models.Square(100)
    };
    this.models.s3.origin = [240, 0];
}

var makerjs = require('makerjs');
var squares = new Squares();

//move a path by a relative distance
makerjs.path.moveRelative(squares.models.s3.paths.ShapeLine3, [0, 20]);

//move a path to an absolute point
makerjs.path.move(squares.models.s2.paths.ShapeLine1, [30, 20]);

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


document.write(svg);

Notice that the 2nd square had an origin of [120, 0] but we moved a line within the square to an absolute point [30, 20]. Since the line is contained within the square model, its coordinates are in terms of the square, which is why it appears to be at [150, 20].

Basic modeling

Given the fundamental models and ability to move instances of them, we can now start modeling. Here are a few examples to illustrate how you might use these:

House:

//render a simple house using ConnectTheDots and Square

var makerjs = require('makerjs');

var points = [
  [100, 0], [100, 100], [0, 175], [-100, 100], [-100, 0],
  [-20, 0], [-20, 80], [20, 80], [20, 0]
];

var house = new makerjs.models.ConnectTheDots(true, points);

var window1 = new makerjs.models.Square(40);
window1.origin = [40, 40];

var window2 = new makerjs.models.Square(40);
window2.origin = [-80, 40];

var houseWithWindows = {
  models: { "myHouse": house, "window1": window1, "window2": window2 }
};

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

document.write(svg);

Tablet mount:

//render a tablet frame using BoltRectangle and RoundRectangle

var makerjs = require('makerjs');

var outer = new makerjs.models.RoundRectangle(200, 280, 8);

var inner = new makerjs.models.RoundRectangle(160, 230, 8);
inner.origin = [20, 30];

var buttonhole = new makerjs.paths.Circle([100, 15], 8);

var bolts = new makerjs.models.BoltRectangle(180, 260, 2);
bolts.origin = [10, 10];

var tabletFaceMount = {
  paths: { buttonhole: buttonhole },
  models: { inner: inner, outer: outer, bolts: bolts }
};

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

document.write(svg);

Circular adapter plate:

//render an adapter using Ring and BoltCircle

var makerjs = require('makerjs');

var model = {
  models: {
    ring1: new makerjs.models.Ring(40, 100),
    bc1: new makerjs.models.BoltCircle(90, 4, 10),
    bc2: new makerjs.models.BoltCircle(55, 7, 6, 30)
  }
};

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

document.write(svg);

Skateboard deck:

//render a skateboard deck using BoltRectangle and Oval

var makerjs = require('makerjs');

function truckBolts() {
  var tx = 1 + 5/8;
  var ty = 1 + 1/8;
  var bolts = new makerjs.models.BoltRectangle(tx, ty, 7/32 / 2);
  bolts.origin = [tx / -2, ty / -2];

  this.models = [bolts];
}

function deck(width, length, truckOffset) {

  var board = new makerjs.models.Oval(length, width);
  board.origin = [0, width / -2];

  var truck1 = new truckBolts();
  truck1.origin = [truckOffset, 0];

  var truck2 = new truckBolts();
  truck2.origin = [length - truckOffset, 0];

  this.models = { board: board, truck1: truck1, truck2: truck2 };
}

var svg = makerjs.exporter.toSVG(new deck(8, 32, 7));

document.write(svg);

It's Just JSON

Remember that your models are plain old JavaScript objects. This is also true for the basic models included with Maker.js we've seen above. To illustrate this, we will export a model using JSON.stringify. Let's use the Tablet Mount again as our example:

var makerjs = require('makerjs');

var outer = new makerjs.models.RoundRectangle(200, 280, 8);

var inner = new makerjs.models.RoundRectangle(160, 230, 8);
inner.origin = [20, 30];

var buttonhole = new makerjs.paths.Circle([100, 15], 8);

var bolts = new makerjs.models.BoltRectangle(180, 260, 2);
bolts.origin = [10, 10];

var tabletFaceMount = {
  paths: { buttonhole: buttonhole },
  models: { inner: inner, outer: outer, bolts: bolts }
};

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

document.write(svg);

Now let's pass tabletFaceMount through JSON.stringify and look at the result:

var makerjs = require('makerjs');

var outer = new makerjs.models.RoundRectangle(200, 280, 8);

var inner = new makerjs.models.RoundRectangle(160, 230, 8);
inner.origin = [20, 30];

var buttonhole = new makerjs.paths.Circle([100, 15], 8);

var bolts = new makerjs.models.BoltRectangle(180, 260, 2);
bolts.origin = [10, 10];

var tabletFaceMount = {
  paths: { buttonhole: buttonhole },
  models: { inner: inner, outer: outer, bolts: bolts }
};

var json = JSON.stringify(tabletFaceMount);

document.write('<code>' + json + '</code><hr />');

We can copy and paste this same JSON and re-use it directly as a model:

//render from a blob of JSON

var makerjs = require('makerjs');

var tabletFaceMount = {"paths":{"buttonhole":{"origin":[100,15],"radius":8,"type":"circle"}},"models":{"inner":{"paths":{"BottomLeft":{"origin":[8,8],"radius":8,"startAngle":180,"endAngle":270,"type":"arc"},"BottomRight":{"origin":[152,8],"radius":8,"startAngle":270,"endAngle":0,"type":"arc"},"TopRight":{"origin":[152,222],"radius":8,"startAngle":0,"endAngle":90,"type":"arc"},"TopLeft":{"origin":[8,222],"radius":8,"startAngle":90,"endAngle":180,"type":"arc"},"Bottom":{"origin":[8,0],"end":[152,0],"type":"line"},"Top":{"origin":[152,230],"end":[8,230],"type":"line"},"Right":{"origin":[160,8],"end":[160,222],"type":"line"},"Left":{"origin":[0,222],"end":[0,8],"type":"line"}},"origin":[20,30]},"outer":{"paths":{"BottomLeft":{"origin":[8,8],"radius":8,"startAngle":180,"endAngle":270,"type":"arc"},"BottomRight":{"origin":[192,8],"radius":8,"startAngle":270,"endAngle":0,"type":"arc"},"TopRight":{"origin":[192,272],"radius":8,"startAngle":0,"endAngle":90,"type":"arc"},"TopLeft":{"origin":[8,272],"radius":8,"startAngle":90,"endAngle":180,"type":"arc"},"Bottom":{"origin":[8,0],"end":[192,0],"type":"line"},"Top":{"origin":[192,280],"end":[8,280],"type":"line"},"Right":{"origin":[200,8],"end":[200,272],"type":"line"},"Left":{"origin":[0,272],"end":[0,8],"type":"line"}}},"bolts":{"paths":{"BottomLeft_bolt":{"origin":[0,0],"radius":2,"type":"circle"},"BottomRight_bolt":{"origin":[180,0],"radius":2,"type":"circle"},"TopRight_bolt":{"origin":[180,260],"radius":2,"type":"circle"},"TopLeft_bolt":{"origin":[0,260],"radius":2,"type":"circle"}},"origin":[10,10]}}};

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

document.write(svg);

Note that you might obtain JSON from some other source, perhaps generated by a tool. The only requirement for it to work with Maker.js is it must have the properties as described above.

Units

Paths and points are unitless. Models may also be unitless, or they may specify a unit system. When it comes time to make your model on a laser cutter or waterjet etc., you will probably want to specify units. You can do this two different ways:

  1. Specify units during export. [See exporting for details per format.]
  2. Specify units on your model.

To specify units on your model, add a units property to it with a value from the makerjs.unitType object:

  • Centimeter
  • Foot
  • Inch
  • Meter
  • Millimeter

These properties are case sensitive. They contain the values "cm", "foot", "inch", "m" and "mm" respectively. It is your choice whether to use the named property or the string value directly.

If a model that you wish to use has a different unit system than your own model, you can call makerjs.model.convertUnits(modeltoScale: model, units: string). to convert it.

Let's use our skateboard example above and mix Inch and Centimeter units:

//render a model using mixed units

var makerjs = require('makerjs');

function truckBolts() {
  var tx = 1 + 5/8;
  var ty = 1 + 1/8;
  var bolts = new makerjs.models.BoltRectangle(tx, ty, 7/32 / 2);
  bolts.origin = [tx / -2, ty / -2];

  this.units = makerjs.unitType.Inch;
  this.models = [bolts];
}

function deck(width, length, truckOffset) {
  
  this.units = makerjs.unitType.Centimeter;
  
  var board = new makerjs.models.Oval(length, width);
  board.origin = [0, width / -2];
    
  var truck1 = makerjs.model.convertUnits(new truckBolts(), this.units);
  truck1.origin = [truckOffset, 0];
    
  var truck2 = makerjs.model.convertUnits(new truckBolts(), this.units);
  truck2.origin = [length - truckOffset, 0];

  this.models = { board: board, truck1: truck1, truck2: truck2 };
}

var svg = makerjs.exporter.toSVG(new deck(20, 80, 18));

document.write(svg);

Measuring

Browse to the makerjs.measure module documentation to see all functions related to measuring.

To get the bounding rectangle of a path or a model, use:

  • makerjs.measure.pathExtents(path: object)
  • makerjs.model.modelExtents(model: object)

These functions return a measurement object with high and low points.

Measure path example:

//render an arc, and a measurement reactangle around it

var makerjs = require('makerjs');

var arc = new makerjs.paths.Arc([0, 0], 100, 45, 135);
var m = makerjs.measure.pathExtents(arc);

console.log('measurement:');
console.log(m);

var totalWidth = m.high[0] - m.low[0];
var totalHeight = m.high[1] - m.low[1];

var measureRect = new makerjs.models.Rectangle(totalWidth, totalHeight);
measureRect.origin = m.low;

var model = {
    paths: {
        arc: arc
    },
    models: {
        measureRect: measureRect
    }
};

var svg = makerjs.exporter.toSVG(model, {useSvgPathOnly: false});

document.write(svg);

Measure model example:

//render an oval, and a measurement reactangle around it

var makerjs = require('makerjs');

var oval = new makerjs.models.Oval(100, 20);
makerjs.model.rotate(oval, 30);
var m = makerjs.measure.modelExtents(oval);

console.log('measurement:');
console.log(m);

var totalWidth = m.high[0] - m.low[0];
var totalHeight = m.high[1] - m.low[1];

var measureRect = new makerjs.models.Rectangle(totalWidth, totalHeight);
measureRect.origin = m.low;

var model = {
    models: {
        measureRect: measureRect,
        oval: oval
    }
};

var svg = makerjs.exporter.toSVG(model, {useSvgPathOnly: false});

document.write(svg);

Frequently used functions

It's good to be aware of these functions which apply to many drawing scenarios. Also, browse the APIs of each module for lesser used specialized functions.

Functions for working with points in the makerjs.point module:

  • point.add

    Add two points together and return the result as a new point.

  • point.subtract

    Subtract a point from another point and return the result as a new point.

  • point.average

    Get the average of two points and return the result as a new point.

  • point.fromPolar

    Get a point from its polar coordinates: angle (in radians) and radius.

  • point.closest

    Given a reference point and an array of points, find the closest point in the array to the reference point.

  • point.scale

    Proportionately scale a point and return the result as a new point.

  • point.distort

    Disproportionately scale a point and return the result as a new point.

  • point.rotate

    Rotate a point and return the result as a new point.

  • point.fromPathEnds

    Return the two end points of a given path (null if path is a circle).

Functions for working with angles in the makerjs.angle module:

Functions for working with measurements in the makerjs.measure module:

Next: learn more in Intermediate drawing.