Basic Drawing
Points
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
};
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);
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
Maker.js provides these fundamental 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:
- Specify units during export. [See exporting for details per format.]
- 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:
-
angle.toDegrees
Convert an angle from radians to degrees.
-
angle.toRadians
Convert an angle from degrees to radians.
-
angle.ofLineInDegrees
Given a line, returns its angle in degrees.
-
angle.ofPointInDegrees
Given two points, returns the angle of the line through them, in degrees.
-
angle.ofPointInRadians
Given two points, returns the angle of the line through them, in radians.
-
angle.noRevolutions
Given a polar angle in degrees, returns the same angle cast between -360 and 360. For example, 725 degrees = 5 degrees.
-
angle.ofArcSpan
Given an arc, returns total angle span between its start and end angles.
Functions for working with measurements in the makerjs.measure module:
-
measure.pointDistance
Calculates the distance between two points using the Pythagorean theorem.
-
measure.pathLength
Measures the length of a path.
-
measure.isPointEqual
Given two points, determine if they are equal within a distance of accuracy.
-
measure.isMeasurementOverlapping
Given two measurements, determine if they are overlapping. Also known as "bounding box overlap".
-
measure.isAngleEqual
Given two angles, determine if they are equal within a margin of accuracy.
Next: learn more in Intermediate drawing.
A point is represented by an array with 2 elements. The first element is x, the second element is y.
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.