zalo / CascadeStudio

A Full Live-Scripted CAD Kernel in the Browser
https://zalo.github.io/CascadeStudio/
MIT License
1.01k stars 126 forks source link

Line 18: Uncaught Null Shape detected in sceneShapes; skipping #28

Closed johnsonm closed 3 years ago

johnsonm commented 3 years ago

I'm trying our CascadeStudio after years of OpenSCAD and there are a lot of things I really like about it! A well-known language, fillets and chamfers, and so far zero Z-fighting come to mind. Thanks for working on it, it's really an attractive idea. I looked around for something geometrical to model as an exercise and saw a game, so tried modeling it as an exercise to learn CascadeStudio.

The line numbers in the error messages not being linked back to the line numbers in the source has given me a little trouble finding the occasional misplace parenthesis. But this looks like even if the model has a bug, it might also be a bug in CascadeStudio. I'm getting lots of errors of this sort:

Line 18: Uncaught Null Shape detected in sceneShapes; skipping: {\"ptr\":16073848,\"hash\":-1935030109}
...

I narrowed my model down to something relatively simple:

https://zalo.github.io/CascadeStudio/?code=hVJNa4QwEL37K%2BaoNf3QuqVg97KnFhYKpaWHsgfrjq4gsWgEbdn%2F3kliPnZ7aBCTGTPvvXljiwLKFou%2B4CXCGpI8aCn1WQy4RU6JdKUzI2%2FEQPG9DvfvzZ4ic%2B8a0qssyZZi%2FMb2iQ90XMPtkuuKXtaIA%2BU01oWtjiGVkS3LA1fziE19kECLji%2BsberOpp6rStO5zySJgD0MQq1GXoqm4%2FJaGMFPALQWiC3WVK%2F6unSW5OpKj2LsObx0ohAYftwwoCfZMchWDDbdFOp6Bt6uSRmIfsQoyoOjRy8b30hlJyKUVtLwSsxD6zH5RlBbO83pLGXw53zOLjmqrodQEk1q0LQ96FHQMY6NEqNmsp6GEzmiZ0bskRtcbgss9KyhZwc9n0Ib%2BNnBz%2F%2FCy2XceePkoG6e%2BVYteplBZu6%2FIMP0xI0Tch0D917mq0DVpLwB5b8%3D&gui=Pc0xC8IwEAXgvyI3h3AmLmYR0VWHDC7icCanDTZp6bU4iP%2FdKNTxPd7He0Hoct8VLuORMoODQBIo8inxExQcWBrPAg61sf%2Foqdzr9owal8pcFOwoNLwBNw4TK%2FAU01SNtXo9p5kYVCusYivC%2BdpyBHejVqra0%2FBY9InD9%2B7XvT8%3D

Thanks!

cpu-1 commented 3 years ago

I changed to an array for:

function Union(objectsToJoin: oc.TopoDS_Shape[], keepObjects?: boolean, fuzzValue?: number, keepEdges?: boolean): oc.TopoDS_Shape

Also nudged the pegs into the board by 1 unit as co-planar faces can slow down boolean operations.

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

let boardHeight = 5; let pegHeight = 6; let pegOffset = pegHeight / 2 + boardHeight;

function peg() { let pegLeg = dWid - clearance; return Rotate([0, 0, 1], 45, Box(pegLeg, pegLeg, pegHeight, true)); } var a=[] 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 = Union(board, Translate([xOffset, yOffset, pegOffset], peg())); a.push( Translate([xOffset, yOffset, pegOffset-1], peg()) ) } } a.push(board); board=Union(a); return board; }

baseBoard();

cpu-1 commented 3 years ago

With formatting:

https://zalo.github.io/CascadeStudio/?code=jZJBS8MwFMfv%2BRTv2Lhss3MTofayk8JAEMXD6CFu6VYo6WhTaZV9d1%2BSJun0oKG0yWve7%2F3zfymFgl0peM3lTkAKcUJKDL3zRmyExMBiZSOtLFSD6zu73L8Ve1y5fXNYzJbxckgWn6J8lA1OU7gZYhWvdY46Ysyyrnz2BBZ65dMSEnIeRHE4atCg4yQOPnTrQ095bsuF3ygJwSMGUvNW7lRRSb0tovBFAMeA2IgD5ptzTYMlidlSC9XWEp4rxZWIttcM8IkzBssVg3XVRTafwehrizJQdSsoTciZfPAaeLrNggxtwForjGjQYiSjlBcU0JSjgmM%2F8HSZLR2cZfBr%2FlOErpFXNUS6UGf6jZ972xGcTibOFaem89ZGHRpjW4fVaehf4hM8urfoPqD7S7TD9wHf%2F4nXYz53%2FrxKtNAen43NGhQzx2bhgqBltvXOCzf47NQ2xwj%2ByZnGgQRAPepMwntAGn000REzTa1sTi9ulvml74gJktG9SMg3&gui=Pc0xC8IwEAXgvyI3h3AmLmYR0VWHDC7icCanDTZp6bU4iP%2FdKNTxPd7He0Hoct8VLuORMoODQBIo8inxExQcWBrPAg61sf%2Foqdzr9owal8pcFOwoNLwBNw4TK%2FAU01SNtXo9p5kYVCusYivC%2BdpyBHejVqra0%2FBY9InD9%2B7XvT8%3D#

johnsonm commented 3 years ago

Thank you! This was excerpted from a larger model. I feel silly about missing the list on the Union invocation; every other Union in my surrounding code not copied when I made this simplified model did that right.

All of my OpenSCAD models have explicit overlap to avoid co-planar surfaces; I just was noticing that you managed to avoid Z-fighting in some trivial examples, and I was playing with co-planar surfaces on purpose. ☺

Here's my version — which, since I sometimes have thin parts, makes me ask how much overlap is useful for avoiding co-planar surfaces. Do you have a heuristic similarity value which should be a minimum overlap for computational efficiency?

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);
}
johnsonm commented 3 years ago

Here's my completed test model just in case it's interesting as an example. It's a representation of the "Pathagon" game that I saw when I looked around for something geometric to try to model as a real-life example.

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());
}
cpu-1 commented 3 years ago

Thanks Michael!

It's an interesting example.