Hacker News new | ask | show | jobs
by ajross 744 days ago
FWIW, those who've played with OpenSCAD and been annoyed at the holes in capability should take a look at CadQuery, which fills a very similar space but with a lot more expressivity:

* It's imperative python instead of a purely declarative custom language. Declarative DSLs are great when your problem is simple, but don't scale.

* The API design is sorta/kinda inspired by jQuery, operating on "selected sets" of subgeometry in a symmetric way, which recovers a lot of the "declarivity" for simple constructions.

* The underlying data model is a B-Rep thing built around OpenCascade. It has the same CSG primitives you're used to from OpenSCAD, but retains the ability to operate on faces and edges directly.

I'm no expert, but it's been a blast to play with. Definitely something to consider; OpenSCAD was sort of a mess back in the days of the RepRap Mendel, and frankly it hasn't improved much since.

2 comments

> Declarative DSLs are great when your problem is simple, but don't scale.

Can you define "don't scale"? I keep hearing that but I feel like I've made relatively complex models with declarative DSLs and from a "writing code" perspective I do think they scale perfectly fine. I'll grant that performance gets a little bad, generally I end up having greatly reduce the face number when I'm working and bump it up when doing a final render.

Inevitably you want to do something that the declarators in your language don't support. Like, in this space you might have a parametrization function for a model (like... "swiss cheese" maybe) which needs to decide on a number of CSG child nodes ("holes") that is randomly generated. Your DSL (OpenSCAD) can't do that because it has a fixed graph. So now you need to write python to generate your DSL code in two separate layers. It would be simpler to have just started with python to begin with.

This pattern repeats again and again[1] every time someone tries to push a DSL for complicated problems. DSLs are a great way to address the boilerplate and copying and tedium of working with bad frameworks in imperative languages. But they don't work once you really get rolling, and you end up in some kind of imperative hell anyway.

Declarative programming as an API design style is hugely valuable and great. Declarative programming languages as an implementation choice are IMHO a bad smell.

[1] If you think about it, this is really just another corrolary of Greenspun's Tenth Rule.

Fair enough; it breaks some functional purity but in Bowler Studio you can have a mostly-scad-like DSL but you have access to the typical Clojure `rand` function. I'm pretty sure I could make that Swiss cheese pattern you described.
Or better, Build123D, which is CadQuery-compatible but does not use the frankly rather overcooked fluent API approach.
Meh. I mean, really both of these are semanticaly equivalent: they're python wrappers around a B-Rep toolkit (the same one, actually). On top of that they add, inevitably, a slightly opinionated API framework[1].

You don't like fluent APIs, which is your right. I think they have a place, and are a good DRY trick for grouping a bunch of things that would normally be "sequential" and making them look "declarative".

On the other hand I don't much like voodoo, and in my very limited experience with build123d it's sort of full of it. The very first "box" example looks like this:

    with BuildPart() as ex1:
        Box(length, width, thickness)
Why do we need a with expression here? What's the resource being allocated that must be freed? In particular why bother with "with" since the context appears to be implicit. The allocation gets bound to an "ex1" variable that is never used! How does Box() know where to put itself? It doesn't say, you just need to know what magic the toolkit is doing.

I mean, is that fatal? No. I'm sure it's reasonably easy to explain. But everyone has tastes, and mine (informed by decades of scars from APIs like this) run hard toward the "make things explicit" side of the theater.

[1] Because if they didn't they wouldn't have much value. There are OpenCascade python bindings already, after all. But they don't match the use case of "write a simple script to emit your object" and are more aimed at "build a CAD tool".

Build123D and CadQuery are actually compatible at the library level, I think? Certainly at the editor/viewer level.

In principle I think this means Build123D could even be given a FreeCAD workbench, but then there is no properly-supported CadQuery2 workbench at the moment (there's a fork that works but I don't think it has a real maintainer, and it's entirely possible that recent changes have broken it).

https://github.com/jpmlt/freecad-cadquery2-workbench

I definitely agree the implicit variable (in fact I don't think there is really an implicit variable, so much as dynamic scope detection, but we can imagine there is one) set up by the with statment is not particularly "Pythonic" but it is documented precisely here:

https://build123d.readthedocs.io/en/latest/key_concepts.html...

It's not ideal but it is still clearer than the CadQuery fluent API, which I don't really like, not because I think fluent APIs are bad at all (quite the opposite, actually) but because I don't believe the one in CadQuery makes a particularly good case for its existence (it's more the selector grammar I am unconvinced by).

Have you taken a look at the build123d "algebra API" yet? It is intended to be near zero magic without any of the with blocks from the "builder API". There are algebra and builder API versions of all of the introductory examples on this page https://build123d.readthedocs.io/en/latest/introductory_exam...
I have not, and I will. Thanks for the link.