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

Model trees

Model tree structures

A model is a tree structure which may contain paths, and it may also contain other models in a heirachy. Let's look at an example:

//mounting plate

var makerjs = require('makerjs');

var plate = {
    models: {
        outer: makerjs.model.center(new makerjs.models.RoundRectangle(120, 100, 10)),
        bolts: makerjs.model.center(new makerjs.models.BoltRectangle(100, 80, 5))
    },
    paths: {
        hole: new makerjs.paths.Circle(25)
    }
};

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

document.write(svg);

If we represent this drawing as a conceptual tree structure, it would look like this:

plate
+-models
| +-outer
| | +-paths
| |   +-TopLeft
| |   +-Top
| |   +-TopRight
| |   +-Left
| |   +-Right
| |   +-BottomLeft
| |   +-Bottom
| |   +-BottomRight
| +-bolts
|   +-paths
|     +-TopLeft_bolt
|     +-TopRight_bolt
|     +-BottomLeft_bolt
|     +-BottomRight_bolt
+paths
 +-hole

(You may notice that this structure is reminiscent of a folder system on your computer.) We can easily traverse the tree when starting at the root. For example, let's change the radius of the BottomRight_bolt hole:

//mounting plate

var makerjs = require('makerjs');

var plate = {
    models: {
        outer: makerjs.model.center(new makerjs.models.RoundRectangle(120, 100, 10)),
        bolts: makerjs.model.center(new makerjs.models.BoltRectangle(100, 80, 5))
    },
    paths: {
        hole: new makerjs.paths.Circle(25)
    }
};

//change radius of BottomRight bolt hole
plate.models.bolts.paths.BottomRight_bolt.radius = 2;

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

document.write(svg);

We can access the BottomRight_bolt circle from the plate object downward. Notice that in this tree structure, you cannot access upwardly. The plate object contains the bolt model which contains the BottomRight_bolt path, but BottomRight_bolt does not have a reference to its container. There are no properties of the bottomRight_bolt circle object which access anything up the tree. It also does not have any references to its sibling circles within bolt.paths.

Downward-only access is the nature of a simple object tree structure. We can overcome this using routes.

Routes

We know that we are able to refer to deep objects by using the dot notation:

var bottomRight_bolt = plate.models.bolts.paths.BottomRight_bolt;

The reference from plate to BottomRight_bolt is hard-coded. Suppose that we had a model plate2 which was a duplicate of plate - we would need to have to hard-code the reference to its BottomRight_bolt:

var bottomRight_bolt2 = plate2.models.bolts.paths.BottomRight_bolt;

Instead of hard-coded dot notation, we can have an abstract way of referencing deep objects by using a route. It is simply an array of strings that represent the segments names between the dots. We do not put the root object in a route. A route that we can apply to both plate and plate2 would be:

var route = ["models", "bolts", "paths", "BottomRight_bolt"];

Travel a route

Use makerjs.travel(rootModel, route) to get to a child object in rootModel via a route. This function will return an object with these 2 properties:

  • result: model or path - the object referenced by the route
  • offset: point - the offset of the result object's origin from the rootModel's origin

//mounting plate
var makerjs = require('makerjs');

var plate = {
    models: {
        outer: makerjs.model.center(new makerjs.models.RoundRectangle(120, 100, 10)),
        bolts: makerjs.model.center(new makerjs.models.BoltRectangle(100, 80, 5))
    },
    paths: {
        hole: new makerjs.paths.Circle(25)
    }
};

var plate2 = makerjs.cloneObject(plate);
plate2.origin = [130, 0];

//route to the BottomRight_bolt circle
var route = ["models", "bolts", "paths", "BottomRight_bolt"];

//create a local variables for BottomRight_bolt holes
var bottomRight_bolt = makerjs.travel(plate, route).result;
bottomRight_bolt.radius = 2;

var bottomRight_bolt2 = makerjs.travel(plate2, route).result;
bottomRight_bolt2.radius = 3;

var plates = {
    models: {
        plate: plate,
        plate2: plate2
    }
};

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

document.write(svg);

Patterns in Routes

Notice that the schema for Maker.js models has a pattern of models.modelName and paths.pathName. There are always 2 segments between model and/or path objects. So, in any given route to an object, you can always get to its parent by subtracting the last 2 array elements of the route. We will use Array.slice(0, -2) to make a copy of the route array without the last 2 elements:

//mounting plate
var makerjs = require('makerjs');

var plate = {
    models: {
        outer: makerjs.model.center(new makerjs.models.RoundRectangle(120, 100, 10)),
        bolts: makerjs.model.center(new makerjs.models.BoltRectangle(100, 80, 5))
    },
    paths: {
        hole: new makerjs.paths.Circle(25)
    }
};

var plate2 = makerjs.cloneObject(plate);
plate2.origin = [130, 0];

//route to the BottomRight_bolt circle
var route = ["models", "bolts", "paths", "BottomRight_bolt"];

//create a local variables for BottomRight_bolt holes
var bottomRight_bolt = makerjs.travel(plate, route).result;
bottomRight_bolt.radius = 2;

//subtract 2 elements to get the parent
var parentRoute = route.slice(0, -2);
var bolts = makerjs.travel(plate2, parentRoute).result;

//modify children
delete bolts.paths.TopLeft_bolt;
delete bolts.paths.BottomRight_bolt;

var plates = {
    models: {
        plate: plate,
        plate2: plate2
    }
};

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

document.write(svg);

Route Keys

Additionally, we can "flatten" a route array into a string, known as a route key, by calling makerjs.createRouteKey(route) and passing a route. Every route key is of course unique in the scope of the root object. It may used as a unique id of a child path or model.

Walking a model tree

You can traverse a model tree by calling makerjs.model.walk with your model and an object with these optional properties:

property name property type description
onPath function(walkPath object) called for every path (in every model) in your tree.
beforeChildWalk function(walkModel) called for every model in your tree, prior to diving deeper down the tree. Return false if you wish to not dive deeper.
afterChildWalk function(walkModel) called for every model in your tree, after returning from a deep dive down the tree.

walkPath object

A walkPath object has these properties:

  • layer: the layer name (if any) containing this path.
  • modelContext: the model containing this path.
  • offset: the absolute coordinates from [0, 0] where this path is located.
  • pathContext: the path itself.
  • pathId: the id of this path in its parent model.paths container.
  • route: array of property names to locate this path from the root of the tree.
  • routeKey: a string representation of the route which may safely be used as a unique key identifier for this path.

walkModel object

A walkModel object has these properties:

  • childId: the id of this model in its parent model.models container.
  • childModel: the model itself
  • layer: the layer name (if any) containing this path.
  • offset: the absolute coordinates from [0, 0] where this model is located.
  • parentModel: the model containing this model.
  • route: array of property names to locate this model from the root of the tree.
  • routeKey: a string representation of the route which may safely be used as a unique key identifier for this model.

Example

In this example we will create a RoundRectangle and walk its tree. We have an onPath function that will get called for every path in the model. If the path is an arc, we will invert it:

//render a model using mixed units
var makerjs = require('makerjs');

function invertArc(arc) {
  var chord = new makerjs.paths.Chord(arc);
  var midPoint = makerjs.point.middle(chord);
  makerjs.path.rotate(arc, 180, midPoint);
}

var shape = new makerjs.models.RoundRectangle(100, 50, 10);

var walkOptions = {
  onPath: function (wp) {
    if (wp.pathContext.type === 'arc') {
      invertArc(wp.pathContext);
    }
  }
};

makerjs.model.walk(shape, walkOptions);
var svg = makerjs.exporter.toSVG(shape);

document.write(svg);

Next: Working with chains.