CascadeStudio vs. OpenSCAD

Hack-a-Day mentioned CascadeStudio a few days ago, but it really didn’t go into much detail. I just played with it today, and overall I liked it. It’s been barely more than 2 months since its first commit, but I wouldn’t have expected that from playing with it so far.

I don’t see much in the way of documentation, just a few examples. But it has a lot of good points.

  • The language syntax is just JavaScript, and the examples are written in modern javascript. It’s not an ideosyncratic language like OpenSCAD. JavaScript isn’t my favorite language, but a lot more people know it than know OpenSCAD, and the modern dialect mostly isn’t terrible.
  • It supports fillets and chamfers!
  • At least some operations seem faster than OpenSCAD, and I didn’t see any of the Z-fighting issues that plague fast rendering in OpenSCAD. There’s no need to re-render slowly to export to STL. Instead of F5/F6/F7, F5 is a full render and then you can export the rendered object. My fingers have been trained to use F5 so I’m grateful for that key mapping.
  • It exports to STL, STEP, and even IGES (if you really want to) and also imports them.
  • It has some direct manipulation features, as well as interactions that can give the same value as with OpenSCAD customizer. I had mixed luck with the direct manipulation so far.
  • Every time you render, entire editing buffer is represented in the URL, so you can just share the URL with someone else and they can drop it in their browser and see what you were working on. This isn’t storing it on the server; the server is just a static page.
  • It supports fillets and chamfers! I know I said this before, but this is one of the things that I really wish OpenSCAD had.

As far as I can tell, it doesn’t have a way to assign colors to elements, nor a way to get the value provided by the special characters in OpenSCAD for debugging models.

I’ve written a lot of Python code, but SolidPython feels like a layer of indirection around OpenSCAD that doesn’t bring me a lot of extra value, and doesn’t have the immediacy of an integrated interface, so even though it’s a language I know well I haven’t been motivated to use it so far. CascadeStudio has the same immediacy as OpenSCAD but a cleaner language. And since sharing models is just sending a URL with the code for the model embedded, it’s very easy to share.

Has anyone else here played with it?

3 Likes

Thanks for the review. I also saw the article but have not played with it.

2 Likes

I had a “bug report” that I sent in with something like “this is probably a bug in my model but the error messages aren’t pointing in the right place.” The author found my bug, suggested a more efficient way of modeling, and explained that it’s still worth doing overlapping parts to join together, even though it doesn’t display Z-fighting artifacts, because it’s computationally more efficient to do so.

2 Likes

Here’s what my example code looks like to represent the playing board and pieces for the game “Pathagon” (the first complex geometric construction my eyes fell on looking for inspiration for something real to try to model):

let clearance = 1;
let partClearance = 5;
let baseLen = 25;
let units = 8;
let dWid = baseLen / 2.414;
let bezelInset = 3;
let boardWidth = units * baseLen + 2 * bezelInset;

let bezelWidth = 14;
let bezelHeight = 12;
let bezelLength = boardWidth + 2 * bezelWidth - 2 * bezelInset;
let bezelInsetOffset = 2;

let boardHeight = 3;
let pegHeight = 6;
let pegOffset = pegHeight / 2 + boardHeight;
let pawnHeight = pegHeight + 4;

let pawnsPerSide = 14;

let assembled = Checkbox("Assembled", false);
let darkPieces = Checkbox("Dark pieces", false);

function pawn() {
    let pawnLeg = baseLen - clearance;
    let basePawn = Box(pawnLeg, pawnLeg, pawnHeight, true);
    return Intersection([
        basePawn,
        Rotate([0, 0, 1], 45, basePawn)
    ]);
}

function peg() {
    let pegLeg = dWid - clearance;
    return Rotate([0, 0, 1], 45, Box(pegLeg, pegLeg, pegHeight+0.2, true));
}

function baseBoard() {
    let board = [Translate([0, 0, boardHeight / 2], Box(boardWidth, boardWidth, boardHeight, true))];
    for (let x = 1; x < units; x++) {
        let xOffset = (x - units / 2) * baseLen;
        for (let y = 1; y < units; y++) {
            let yOffset = (y - units / 2) * baseLen;
            board.push(Translate([xOffset, yOffset, pegOffset-0.1],  peg()));
        }
    }
    return Union(board);
}

function bezel() {
    return Translate([0, 0, bezelWidth / 2],
        FilletEdges(
            Difference(
                ChamferEdges(Box(bezelLength, bezelHeight, bezelWidth, true), bezelWidth - 0.01, [1, 5]),
                [Translate([0, boardHeight-bezelHeight/2, bezelWidth / 2 - bezelInset / 2], Box(bezelLength, boardHeight, bezelInset, true))]
            ),
            1.5, [15, 19]
        )
    );
}

function bezelSet() {
    return Rotate([0, 0, 1], 90, Union([
        Translate([0, -(bezelHeight / 2 + partClearance/2), 0], bezel()),
        Translate([0, bezelHeight / 2 + partClearance/2, 0], bezel())
    ]));
}

function pawnSet() {
    let pawns = [];
    for (let y = 0; y < pawnsPerSide/2; y++) {
        pawns.push(Translate([0, boardWidth/2 - baseLen/2 - y * (baseLen + partClearance), 0], pawn()));
        pawns.push(Translate([baseLen + partClearance, boardWidth/2 - baseLen/2 -y * (baseLen + partClearance), 0], pawn()));
    }
    return Union(pawns);
}

if (assembled) {
    Translate([0, 0, bezelInsetOffset], baseBoard());
    let b = Translate([0, boardWidth/2 + bezelWidth - bezelInset, bezelHeight/2], Rotate([1, 0, 0], 90, bezel()));
    for (let n=0; n < 4; n++) {
        b = Rotate([0, 0, 1], 90, b, true);
    }
    for (let x=0; x < units; x++) {
        for (let y=0; y < units; y++) {
            // show that every position could be filled with one of the 28 actual pawns
            Translate([-baseLen*units/2 + baseLen/2 + baseLen*x, -baseLen*units/2 + baseLen/2 + baseLen*y, bezelInsetOffset+boardHeight], pawn());
        }
    }
} else if (darkPieces) {
    Translate([baseLen/2 + partClearance, 0, 0], pawnSet());
    Translate([-(bezelWidth + partClearance), 0, 0], bezelSet());
} else {
    baseBoard();
    Translate([boardWidth/2 + baseLen/2 + partClearance, 0, 0], pawnSet());
    Translate([-(boardWidth/2 + bezelWidth + partClearance), 0, 0], bezelSet());
}

It’s kind of cool that you can play with this model just by following this link that includes the entire text above, compressed and encoded!

2 Likes