CAD Software

New CAD Software

Work on the Curta Calculator was done with OnShape While it was in an open beta test phase. Towards the end of the Curta project, OnShape finished its open beta and began offering paid and free offerings of its CAD software. The free option required that I make the Curta designs public while the paid option was priced for professionals at a level I could not afford. I did not want to lose all of the work I had done, so my only option was to make the models public.

This made me realize I didn’t really have any ownership over my models. OnShape did. So I started searching for other CAD software that was within my budget. There are other options available, but none of them really felt like it had an offering that fit my needs.

After doing some research, I discovered that there is a CAD kernel called Open Cascade which provides quite a bit of the functionality I needed to create my own CAD software. After being frustrated with the options available and having found tools to write my own solution, I started what I started a project with the working name MakerCAD in January 2018 and I have worked on it on and off since.

Architecture

The core of MakerCAD is basically a binder between multiple technologies:

An API is provided as a Node.js native module which allows creating 2D sketches, constraining them via the constraint solver, and finally extruding or revolving them into 3D shapes via Open Cascade. This allows one to define a 3D model using javascript code in a way that corresponds to how it would be done in a traditional CAD’s Graphical User Interface (GUI). A future project would utilize MakerCAD to provide that GUI.

The javascript code for a model is a program which builds the model, but it is also a series of steps that can be revised and edited to alter and improve a model. Normal source control can easily keep a history and allow branching and merging of different versions of the model’s construction steps.

Because each model is source code even if the editing is being done via a GUI, any model can easily be parameterized.native

Constraint Solver

THe constraint solver has been a particularly difficult part of MakerCAD. I initially started with the constraint solver from SolveSpace . This proved to be limiting due to difficulties getting it to solve the many different ways that a single 2D shape could be drawn and constrained. I did a lot of searching before I ultimately decided to write my own which became my DLineate project

Examples

MakerCAD is incomplete, but I do have some working examples. The way these are created may change as MakerCAD evolves.

A module to build a cube on a face or a plane

module.exports = function (face) {
    let sketch = MakerCad.Sketch(face);

    let line1 = sketch.line(sketch.point(0.0, 0.0), sketch.point(5.0, 0.0));
    line1.length(10).horizontal();

    let line2 = sketch.line(sketch.point(5.0, 0.0), sketch.point(5.0, 6.0));
    line2.length(10).vertical();

    let line3 = sketch.line(sketch.point(5.0, 5.0), sketch.point(0.0, 6.0));
    line3.horizontal();

    let line4 = sketch.line(sketch.point(0.0, 5.0), sketch.point(0.0, 1.0));
    line4.vertical();

    line1.getEnd().coincident(line2.getStart());
    line2.getEnd().coincident(line3.getStart());
    line3.getEnd().coincident(line4.getStart());
    line4.getEnd().coincident(line1.getStart());

    let line1Midpoint = sketch.point(0.0, 0.0).vertical(sketch.getOrigin());
    let line2Midpoint = sketch.point(0.0, 0.0).horizontal(sketch.getOrigin());

    line1.midpoint(line1Midpoint);
    line2.midpoint(line2Midpoint);

    let squareFace = MakerCad.Face(sketch);
    return squareFace.extrude(10);
};

A module to build a cylinder on a face or plane

module.exports = function (face, shapes = [], operation = MakerCad.MergeType.New) {
    let sketch = MakerCad.Sketch(face);

    let holeCenter = sketch.point(0, 0);
    holeCenter
        .horizontalDistance(sketch.getOrigin(), 0)
        .verticalDistance(sketch.getOrigin(), 0);

    sketch.circle(holeCenter, 9).diameter(9);

    let cylinderFace = MakerCad.Face(sketch);
    return cylinderFace.extrude(10, operation, shapes);
};

A module to build a taurus on a face or plane

module.exports = function(face) {
    let sketch = MakerCad.Sketch(face);
    let center = sketch.point(25, 5)
        .horizontalDistance(sketch.getOrigin(), 25)
        .verticalDistance(sketch.getOrigin(), 5);

    sketch.circle(center, 10).diameter(10);

    let axis = sketch.line(
        sketch.point(0.0, 0.0),
        sketch.point(0.0, 0.5)
    );

    axis.construction(true)
        .length(5.0)
        .vertical();

    axis.getStart().coincident(sketch.getOrigin());

    let taurusFace = MakerCad.Face(sketch);
    return taurusFace.revolve(axis, Math.PI * 2);
};

Using the above together

const MakerCad = require('../index.js');
const Cylinder = require('./cylinder.js');
const Cube = require('./cube.js');
const Taurus = require('./taurus.js');

let cubeOperation = Cube(MakerCad.Plane(MakerCad.Base.Top));

let topFace = MakerCad.stream([cubeOperation])
    .faces()
    .filter(f => f.isAlignedNormal(MakerCad.Plane(MakerCad.Base.Top)))
.collectList()[0];
let cylinderOperation1 = Cylinder(topFace, [cubeOperation.shape()], MakerCad.MergeType.Remove);

frontFace = MakerCad.stream([cylinderOperation1])
    .faces()
    .filter(f => f.isAlignedNormal(MakerCad.Plane(MakerCad.Base.Front)))
.collectList()[0];
let cylinderOperation2 = Cylinder(frontFace, [cylinderOperation1.shape()], MakerCad.MergeType.Remove);

rightFace = MakerCad.stream([cylinderOperation2])
    .faces()
    .filter(f => f.isAlignedNormal(MakerCad.Plane(MakerCad.Base.Right)))
.collectList()[0];
//Leak crashes here -- something about reusing operations?
let cylinderOperation3 = Cylinder(rightFace, [cylinderOperation2.shape()], MakerCad.MergeType.Remove);

let taurusOperation = Taurus(MakerCad.Plane(MakerCad.Base.Front));

let exportOperations = [cylinderOperation3, taurusOperation];

let shapes = MakerCad.streamOf(exportOperations).shapes().collectList();
console.log('exporting shapes', shapes);
MakerCad.exportStl("test.stl", shapes);

This generates this model:

Model generated by MakerCAD
Model generated by MakerCAD