Benchmark: CadQuery vs. OpenSCAD for agentic CAD work

Sep 13, 2026 02:57 AM - 8 hours ago 4

ModelRift generates OpenSCAD for each exemplary connected the platform. That prime is worthy re-testing occasionally, truthful we ran a controlled comparison against the astir reliable replacement for code-first CAD: CadQuery, a Python room connected apical of the OpenCascade B-rep kernel.

The mobility was narrow: which 1 tin an AI supplier thrust to a correct, printable, functional portion pinch cipher watching? How pleasant each is to constitute by manus did not travel into it.

Six agents, 3 tasks, 2 tools. Every resulting STL was past checked by a parser that trusts neither tool.

All six parts came retired printable. Capability turned retired to beryllium the boring portion of the answer. Where the 2 diverge is successful really they fail.

Two threaded hose adapters broadside by side, 1 built successful OpenSCAD and 1 successful CadQuery, astir identical

The hardest task successful the set, solved by some tools. OpenSCAD connected the left, CadQuery connected the right.

Setup

All six runs were driven by Claude Opus 5 (1M context) done Claude Code. Each compartment ran arsenic a abstracted general-purpose subagent inheriting that aforesaid model, pinch nary cross-talk betwixt them. One supplier per cell, truthful portion of the dispersed beneath is supplier variance alternatively than instrumentality difference. Tool versions were CadQuery 2.8.0 connected Python 3.14 and OpenSCAD 2026.06.12, some connected an M-series Mac.

The OpenSCAD broadside ran connected our ain openscad-skill, the supplier accomplishment we people and usage successful house. It covers record and type naming, the render-inspect-fix QA loop, camera presets for CLI previews, cross-section debugging, and Customizer syntax.

For CadQuery we ported that accomplishment cognition by operation, keeping the aforesaid building and replacing only what has nary OpenSCAD equivalent. CadQuery has nary CLI renderer, truthful the larboard needed a mini offscreen renderer written for it, positive B-rep validity metrics successful spot of the CSG position line. Both files past sewage the aforesaid 3D-printing creation rules, wall thickness, clearances and overhangs, truthful neither broadside was handed proposal the different lacked. Sizes landed astatine 12.4 KB for OpenSCAD and 13.1 KB for CadQuery.

One asymmetry survived: the CadQuery record carries an API cheatsheet the OpenSCAD record does not need, because the exemplary already knows OpenSCAD syntax well. That helps CadQuery connected syntax and does thing for it connected geometry.

Each supplier was capped astatine 12 versions, told ne'er to clone success, and required to study each nonaccomplishment pinch its verbatim correction text. They ran unattended.

We did not return the agents’ connection for anything. Every last STL went done a parser that sounds the record straight and reports triangle count, bounding box, volume, watertightness, non-manifold and bound edges, flipped faces, and connected components. That turned retired to matter much than we expected, for reasons further down.

The 3 tasks

Both agents connected a task sewage the aforesaid text, pinch nary tool-specific hints.

The elemental one, T1, was a wall-mounted support L-bracket: 2 plates astatine 90 degrees, 4 thick, 2 triangular gussets, 2 countersunk holes for flat-head screws astatine 90 degrees and 9 caput diameter, 2 plain holes, R3 connected the outer vertical corners, R4 fillet connected the soul corner, printable without supports.

T2 raised the stakes to a two-part snap-fit enclosure for a 50 x 26 PCB. Walls 2, cavity clearance 0.4 per side, 4 M2 posts, a 9.5 x 3.5 USB-C cutout, a abstracted lid pinch a peripheral articulator astatine 0.2 clearance and moving snaps, 5 vent slots. The 2 parts had to really fit.

T3 was the difficult one: an M24x2 threaded hose-barb adapter pinch a hex flange 30 crossed flats, a existent helical thread 12 long, a 25 agelong barb carrying 3 barbs for 12 ID hose, and an 8 through-channel. The spec explicitly banned stacked rings. The thread had to beryllium existent helical geometry.

Results

T1 OpenSCADT1 CadQueryT2 OpenSCADT2 CadQueryT3 OpenSCADT3 CadQuery
Versions238513
Lines of code103135220266150172
Errors the instrumentality raised140011
Silent incorrect geometry005301
Agent wall-clock461 s562 s1058 s909 s542 s935 s
Agent tokens77 k89 k138 k139 k82 k119 k
Geometry recompute12 ms1.56 s16 ms1.93 s43 ms1.95 s
Final STL verdictcleancleancleancleancleanclean

Summed crossed the 3 tasks:

OpenSCADCadQuery
Versions1111
Lines of code473573
Errors the instrumentality raised25
Silent incorrect geometry54
Agent wall-clock2061 s2406 s
Agent tokens297 k347 k

“Clean” intends watertight, 1 connected component, zero non-manifold aliases bound edges, sitting connected z = 0, measured from the record alternatively than reported by the instrumentality that wrote it.

Iteration count came retired identical astatine 11 versions each. So did the wide style of the effort. What differs is the characteristic of the problems each supplier hit.

T1: the elemental bracket

Two L-brackets viewed from the soul area showing gussets, 4 holes and the soul fillet

Both correct, shown from the soul area truthful the gussets are visible. The visible quality is interpretation: the spec near gusset size and inset open.

Rounding the 4 outer corners separates the 2 models of the world cleanly. OpenSCAD rounds a 2D floor plan and extrudes it, truthful the cognition does not attraction really the coagulated was assembled:

module corner_mask() { translate([0, 0, -eps]) linear_extrude(vert_h + 2*eps) offset(r = corner_r) offset(delta = -corner_r) square([plate_w, horiz_d]); }

CadQuery has to sanction the 4 edges first, and naming is the difficult part:

result = result.edges("|Z").edges( BoxSelector((-WIDTH, -EPS, -EPS), (WIDTH, EPS, V_HEIGHT + EPS)) + BoxSelector((-WIDTH, H_DEPTH - EPS, -EPS), (WIDTH, H_DEPTH + EPS, V_HEIGHT + EPS)) ).fillet(R_OUTER)

OpenSCAD compiled correct geometry connected the first effort and vanished successful 2 versions. CadQuery spent astir a 3rd of its tally connected a azygous error: .fillet(3.0) grounded pinch BRep_API: bid not done, a connection that names neither the separator nor the radius. The supplier had to bisect by manus to observe the cause, which turned retired to beryllium arithmetic: 2 R3 fillets do not fresh successful a 4 mm wall.

T2: 2 parts that must fit

Two enclosure boxes broadside by broadside pinch mounting posts and alleviation slots

Both agents derived the aforesaid 54.8 x 30.8 x 25.0 mm container from the clearance stack-up.

This is wherever CadQuery’s parameter concatenation earned its keep. Changing the articulator extent moved rim, plate, slots, groove and barb together, because each magnitude is derived alternatively than typed twice. That benignant of magnitude concatenation is the aforesaid point a bully parametric UI exposes to the user, which we wrote astir successful Building a amended OpenSCAD customizer. CadQuery has nary Customizer equivalent, truthful the constants artifact is the full parameter interface.

More absorbing is really each broadside checked the fit. OpenSCAD prints numbers for personification to read:

echo(str("cavity LxW = ", cav_l, " x ", cav_w, " (clearance/side ", pcb_clr, ")"));

CadQuery asserts, and the build stops erstwhile the assertion is false:

assert BOX.val().intersect(LID.val()).Volume() < 1e-6, "box and lid interfere"

An echo only helps if personification sounds it. An asseverate fails the build connected its own. For unattended procreation that spread is astir of the story.

OpenSCAD needed 8 versions to CadQuery’s five, and 3 of those 8 went to boolean hygiene alternatively than design: cleaning up slivers thrown disconnected by tangent and coincident faces.

T3: the existent thread

The difficult task produced the biggest upset. OpenSCAD vanished successful 1 version, correct connected the first compile, successful 43 ms, pinch nary library.

OpenSCAD has nary expanse operation, truthful the supplier wrote the helix arsenic earthy vertex and look arithmetic: 1 four-point ISO profile, 96 sections per turn, emitted arsenic a azygous polyhedron. There are nary guardrails successful this codification astatine all. Get the winding bid incorrect and the instrumentality says nothing.

module helical_thread(turns, z0) { prof = [ [r_in, -flank_hz], [r_maj, -crest_hz], [r_maj, crest_hz], [r_in, flank_hz] ]; n = round(turns * STEPS); pts = [ for (i = [0:n]) let(a = one * 360 / STEPS, zo = z0 + one * thread_pitch / STEPS) for (j = [0:3]) [ prof[j][0] * cos(a), prof[j][0] * sin(a), prof[j][1] + zo ] ]; fcs = concat( [ [3, 2, 1, 0] ], [ for (i = [0:n-1]) for (j = [0:3]) let(k = (j + 1) % 4) [ 4*i + j, 4*i + k, 4*(i+1) + k, 4*(i+1) + j ] ], [ [4*n + 0, 4*n + 1, 4*n + 2, 4*n + 3] ] ); polyhedron(points = pts, faces = fcs, convexity = 8); }

The supplier besides pre-empted the classical thread trap earlier penning a line: an ISO bony spans precisely 1 pitch, truthful consecutive guidelines flats onshore coplanar and the national goes bad. It sank the swept floor plan beneath the insignificant radius truthful the helix crosses the halfway alternatively of rubbing it. That is why the boolean was a non-event.

CadQuery expresses the aforesaid geometry successful six readable lines, and that portion worked first try:

helix = cq.Wire.makeHelix(pitch=THREAD_P, height=h, radius=R_ROOT, center=(0, 0, z0)) prof = (cq.Workplane("XZ", origin=(0, 0, z0)).center(R_ROOT, 0) .polyline(thread_profile_points()).close()) ridge = prof.sweep(path, isFrenet=True) core = cq.Workplane("XY", origin=(0, 0, z0)).circle(R_ROOT).extrude(h) rod = core.union(ridge)

The nonaccomplishment came connected the past line, and it is the 1 consequence that changed really we deliberation astir the QA loop.

 the near shows thread turns floating pinch nary halfway cylinder, the correct shows a coagulated body

CadQuery T3 type 1 connected the left. The threaded conception is thing but floating helical turns.

union() silently discarded the halfway cylinder because the thread guidelines sat precisely connected the halfway radius. Exact tangency on a helical curve, and OCCT dropped a coagulated without a word. From the extracurricular the portion looked perfect. The supplier publication 4 renders without noticing. What caught it was a measurement measurement: 7065 mm³ wherever 10323 was expected.

Worse, the surgery portion reported valid=True and solids=1. Raising the boolean tolerance to 1e-3 produced a coagulated of antagonistic measurement that besides reported valid=True.

OpenSCAD is not guiltless present either. In T2 its Manifold backend certified Status: NoError for an STL carrying 4 non-manifold edges and 60 zero-area triangles. Neither tool’s self-report is the past word, which is why we parsed each mesh ourselves.

Axial cross-sections of some vanished adapters showing thread crests staggered by half a pitch

Both vanished threads. Crests connected the near flank beryllium half a transportation disconnected those connected the right, which is the signature of a existent single-start helix and the cheque that tells a existent thread from stacked rings.

Download the parts

Here are the 8 last meshes, precisely arsenic the agents exported them, pinch nary cleanup aliases repair from us. Binary STL, millimetres, oriented for printing pinch the portion sitting connected z = 0.

Every 1 of the 8 is watertight, a azygous connected component, pinch zero non-manifold edges, zero bound edges and zero flipped faces. Clearances presume FDM pinch a 0.4 nozzle, truthful each T2 brace should threat together arsenic printed.

The triangle counts are worthy a glance, and they do not favour 1 instrumentality consistently. CadQuery’s container carries almost 5 times the triangles of the OpenSCAD box, while its lid has fewer. Mesh density present follows really overmuch rounded item each supplier chose to add, not the kernel.

What we found

Renders caught thing that mattered

This is the consequence we did not expect. Across six runs, images caught coarse blunders, for illustration 4 mounting posts deleted by a cavity subtraction, and thing subtle. Every defect that would person ruined a people was recovered by a number instead: a volume, an angle, an interference test, a strain calculation. In T3 the OpenSCAD supplier had the other problem and astir rejected correct geometry, because a thread close-up rendered successful a measurement that looked wrong. Its ain statement was that the render was not capable grounds successful either direction.

The nonaccomplishment modes are reflector images

CadQuery fails loudly and early. Its messages are poor, but an objection stops the run, and a stopped exemplary cannot vessel by accident. OpenSCAD fails silently and late: successful T2 it reported nary errors and nary warnings crossed astir 45 invocations while producing deleted posts, misplaced slots, and a corrupted export it had conscionable certified arsenic clean. For unattended generation, silent occurrence is the much costly failure.

CadQuery tin beryllium interrogated, OpenSCAD cannot

CadQuery answers questions astir its ain geometry. The T1 supplier proved its countersink was 90 degrees and 9 crossed by reference the cone’s half-angle disconnected the B-rep, past wired the spec into asserts that re-ran connected each build. OpenSCAD has nary measurement to query geometry, truthful some OpenSCAD agents independently wrote binary STL parsers, astir arsenic overmuch codification arsenic the models themselves, to measurement what they had built. It works, but it only sees the mesh aft export, ne'er the design.

Speed favours OpenSCAD, and it hardly matters

Geometry recompute is 30 to 100 times faster, 16 sclerosis against 1.9 s. Inside an supplier loop dominated by exemplary conclusion that is noise. It would matter for a unrecorded customizer aliases a ample parameter sweep.

OpenSCAD renders person nary conception of a portion edge

After CSG location is only a triangle soup, truthful --view=edges draws the triangulation alternatively than an outline. CadQuery’s B-rep knows wherever 2 faces really meet. Since images are the agent’s feedback channel, this was a existent asymmetry successful our setup, truthful we fixed it by rendering the exported STL and reconstructing outlines from the perspective betwixt adjacent faces.

 level default, cluttered triangulation edges, and cleanable outlines

Same part, 3 renderers. Left: the CLI default, nary outlines. Middle: --view=edges, which draws each facet. Right: feature-edge reconstruction from the STL, which besides paints mesh defects successful red. All six runs predate this fix.

Takeaways

Neither instrumentality wins connected output. Six cleanable printable parts, 3 from each.

What decides it is verifiability alternatively than expressiveness, and CadQuery leads location by a wider separator than the syntax quality suggests. Being capable to inquire the kernel what you conscionable built, and to asseverate connected the answer, is worthy much successful an supplier loop than immoderate magnitude of convenient syntax.

Task style decides much than the instrumentality does. Simple prismatic activity went to OpenSCAD. Two parts that must mate went to CadQuery. The helical thread, the task that looked tailor-made for a B-rep kernel, went to OpenSCAD outright.

If a procreation pipeline leans connected screenshots, it leans connected the 1 transmission that caught thing here. Numeric assertions are the point to force: wall thickness, clearance, interference volume, overhang angle.

Verify the mesh independently, because some toolchains certified geometry that was wrong. This is the aforesaid instruction the Pantheon benchmark produced from a different angle, wherever Codex looked beardown successful the preview loop but shipped an STL pinch geometry problems astir the portico roof. Preview and export are not the aforesaid thing. For thing going to print, the exported mesh needs its ain inspection pass.

Caveats

One supplier per cell, truthful immoderate of the dispersed is supplier variance alternatively than instrumentality difference.

BOSL2 was deliberately not used. A library-assisted OpenSCAD tally would apt alteration T2 and T3 substantially.

All six runs predate the outline renderer, truthful the ocular transmission was uneven while they ran.

This measured building only. Nobody measured the harder skill, which is repairing a exemplary personification other broke.

What this intends for ModelRift

We are staying connected OpenSCAD, and this benchmark sharpened why alternatively than changing the answer. The original reasons still hold: a compact matter format an LLM tin constitute directly, safe sandboxing, and accelerated rendering, each covered successful Why we built ModelRift connected OpenSCAD. CadQuery’s advantage is existent but sits mostly successful verification, and verification is thing we tin adhd astir OpenSCAD. Its disadvantages are structural for our case: a Python runtime per convention alternatively of a WASM sandbox, two-second rebuilds alternatively of 16 ms, and nary cleanable multi-object colour export of the benignant we built for multicolor 3MF.

The much useful uncovering is astir feedback. The Pantheon benchmark ended connected the study that autonomous procreation is not the correct workflow yet, and that Annotation Mode, wherever you tie connected a render and manus it backmost to the AI, is what makes spatial loop work. This tally adds a bound to that. Visual feedback is the correct instrumentality for massing, proportion, and “the file is successful the incorrect place”. It is the incorrect instrumentality for wall thickness, clearance, and whether a boolean softly deleted half your part. Those request numbers, and the supplier has to beryllium made to look astatine them.

Two changes are already going backmost into the published OpenSCAD skill. The feature-edge renderer above, truthful agents get previews pinch existent portion outlines alternatively of triangulation. And a numeric cheque walk the supplier has to tally earlier it tin telephone a exemplary finished: wall thickness, clearances, interference, and a mesh audit that does not inquire the instrumentality whether it did a bully job.

More