Text → 3D geometry · open-source research project

Let the LLM write the shape as code.

Vibe-CADing turns natural-language prompts into 3D geometry. An LLM writes a program for the shape (CAD code for B-rep solids, or GLSL for implicit surfaces), the program runs, and errors go back to the model until the result is valid.

Masoud Jafaripour

Turntable of a dragon written by GPT-4o as a GLSL signed distance function
“A stylized dragon…”, written by GPT-4o as 1.8 KB of GLSLSDF
Mounting plate generated with CadQuery by Qwen3-8B
Plate · Qwen3-8BB-rep
Rubber duck signed distance function turntable
Duck · smooth unionsSDF

Method

Geometry as code: generate, run, check, repair

Every harness shares one loop. The model never outputs geometry directly; it outputs a program, and the program is treated as untrusted code: run in isolation, checked, and sent back with the error if anything fails.

  1. 1Prompt

    Natural-language description, or an edit instruction for an existing shape.

  2. 2LLM

    Any text-in/text-out model: local Qwen via transformers, or an OpenAI-compatible API.

  3. 3Program

    CadQuery / build123d Python, or a GLSL function float sdf(vec3 p).

  4. 4Sandboxed run

    Executed in a subprocess with a timeout: OpenCascade kernel or headless GPU.

  5. 5Validate

    Tracebacks, compile logs with the offending lines, render checks.

  6. 6Artifact

    STEP solid for CAD code; PNG + turntable GIF for SDFs.

↺

Repair loop (5 → 2). On failure, the model gets back its own code plus the error, and tries again.

✎

Refine loop (6 → 1). A new instruction (“make the wings larger”) edits the last valid program.

CAD-code harness

B-rep
  • The LLM writes CadQuery or build123d code that assigns the part to result.
  • Runs in a subprocess and exports a STEP file through the OpenCascade kernel.
  • On failure the traceback goes back into the prompt (CADCodeVerify-style), up to max_retries.
  • Two kernel variants in separate environments, since they pull in conflicting OCP builds.
src/CAD_Harness →

SDF harness (GLSL)

SDF
  • The LLM writes float sdf(vec3 p), and optionally sdf_color, using a shared primitive library: sdSphere, sdCapsule, opSmoothUnion, rotX/Y/Z…
  • An OpenGL 3.3 sphere tracer (moderngl + EGL, no display) renders it with soft shadows and AO.
  • Rejects compile errors, a missing sdf(), empty or inside-out renders and off-frame shapes, then asks for a repair (0–3 rounds).
  • Refine an existing shape with a text instruction; outputs .glsl, .png, .gif and run metadata.
src/CAD_Harness/sdf →

LLM → JSON → pythonOCC

B-rep
  • Qwen2.5-3B-Instruct turns a prompt into a structured spec such as {"type": "cube", "size": 5}.
  • pythonOCC builds sketch → wire → face → extruded solid, exportable to STEP / IGES.
  • Simple and robust for primitives, but limited to what the JSON schema can express.
src/B-rep →

Why several representations?

SDF (GLSL)MeshB-rep (CAD code)
Storesa function (code)trianglesexact faces, edges, NURBS
Sizetiny (KB of code)largemedium
Smooth blendstrivialhardhard (fillets)
Exact dimensions / STEPnoapproximateyes
Best fororganic, sculpted formsrendering, games, printingengineering CAD

Interactive 3D

Drag to orbit. Both are real repository files.

Left: the GLSL shapes compiled live in your browser with a WebGL2 port of the harness's sphere tracer. Right: the CadQuery mounting plate, tessellated from its STEP file, with the solid's B-rep edges drawn on top.

SDFLive GLSL sphere tracing
›View the shape's GLSL: this is the whole model
float sdDragonBody(vec3 p) {
    // Main body as a series of capsules
    float body = sdCapsule(p, vec3(-0.6, -0.2, 0.0), vec3(0.6, -0.2, 0.0), 0.15);
    float neck = sdCapsule(p, vec3(-0.6, -0.2, 0.0), vec3(-0.8, 0.3, 0.0), 0.1);
    float tail = sdCapsule(p, vec3(0.6, -0.2, 0.0), vec3(0.9, -0.4, 0.0), 0.08);
    return opSmoothUnion(opSmoothUnion(body, neck, 0.1), tail, 0.1);
}

float sdDragonHead(vec3 p) {
    // Head as a rounded box
    vec3 headPos = vec3(-0.85, 0.35, 0.0);
    float head = sdRoundBox(p - headPos, vec3(0.15, 0.1, 0.1), 0.05);
    // Eyes as small spheres
    float eye1 = sdSphere(p - (headPos + vec3(0.05, 0.05, 0.1)), 0.03);
    float eye2 = sdSphere(p - (headPos + vec3(0.05, 0.05, -0.1)), 0.03);
    return opSmoothUnion(opSmoothUnion(head, eye1, 0.05), eye2, 0.05);
}

float sdDragonWings(vec3 p) {
    // Wings as larger rotated cones
    vec3 wingBase = vec3(-0.2, 0.0, 0.0);
    float wing1 = sdRoundCone(rotZ(p - wingBase, -0.5), 0.0, 0.3, 0.7);
    float wing2 = sdRoundCone(rotZ(p - wingBase, 0.5), 0.0, 0.3, 0.7);
    return opSmoothUnion(wing1, wing2, 0.1);
}

float sdDragonLegs(vec3 p) {
    // Legs as capsules
    float leg1 = sdCapsule(p, vec3(-0.3, -0.8, 0.1), vec3(-0.3, -0.5, 0.1), 0.05);
    float leg2 = sdCapsule(p, vec3(0.3, -0.8, 0.1), vec3(0.3, -0.5, 0.1), 0.05);
    return opSmoothUnion(leg1, leg2, 0.1);
}

float sdf(vec3 p) {
    // Combine all parts of the dragon
    float body = sdDragonBody(p);
    float head = sdDragonHead(p);
    float wings = sdDragonWings(p);
    float legs = sdDragonLegs(p);
    return opSmoothUnion(opSmoothUnion(opSmoothUnion(body, head, 0.1), wings, 0.1), legs, 0.1);
}

vec3 sdf_color(vec3 p) {
    // Color the dragon with a gradient from green to red
    float height = p.y + 1.0;
    return mix(vec3(0.0, 1.0, 0.0), vec3(1.0, 0.0, 0.0), height / 2.0);
}
src/CAD_Harness/sdf/examples/dragon_gpt4o.glsl · 1,851 bytes · 47 lines
B-repMounting plate from STEP

Loading…

Prompt to Qwen3-8B: “a 40x20x5mm mounting plate with two 4mm holes 15mm either side of center and 2mm filleted corners”. Scroll or pinch to zoom.

src/CAD_Harness/cadquery/plate.step

Results

What has been run so far

These are individual, qualitative runs recorded in the repository: 5 of 8 produced a valid artifact. This is not a benchmark, and the failures are listed on purpose.

HarnessModelTaskOutcomeDetail
B-repCadQueryQwen3-8Ba cube of size 5✓ validSTEP exported
B-repCadQueryQwen3-8Bmounting plate, 2 holes, filleted corners✓ validSTEP exported, render above
B-repbuild123dQwen3-8Bsame mounting plate (builder API)✓ validSTEP exported
SDFSDFGPT-4ostylized dragon✓ valid1st attempt; crude anatomy
SDFSDFGPT-4orefine: make the wings larger✓ valid1st attempt; wings visibly larger
SDFSDFQwen3-8Bstylized dragon✗ failedfailed all 3 attempts (last: compile error, missing argument)
SDFSDFQwen2.5-Coder-7Bstylized dragon✗ failedcalled undefined functions, omitted sdf()
SDFSDFQwen3-8Bsnowman of three blended spheres✗ failedfailed all 3 attempts (last: shape off-frame, caught by the framing check)

What the validators catch

Python exceptions
CAD code that raises, or never produces result, returns its traceback.
GLSL compile errors
The compiler log is returned with the offending source lines quoted.
Missing entry point
Code that never defines float sdf(vec3 p) is rejected before compiling.
Empty or inside-out
Nothing visible from any view, or a surface that fills the whole frame.
Off-frame shapes
Shapes that are not centered or exceed the [-1, 1]³ box get cut off.
Hangs
Every run has a subprocess timeout; SDF code is capped at 64 KB.

Limitations

  • The checks catch broken code, not wrong shapes: a valid program can still miss the prompt (see the dragon's floating legs).
  • Small local models (Qwen3-8B, Qwen2.5-Coder-7B) often repeat the same mistake through every repair round on SDF tasks.
  • SDF output is visual geometry, not dimensioned CAD: no STEP export or exact tolerances.
  • No quantitative benchmark yet; a vision critic that compares renders with the prompt is the natural next step.

Test suites run without any model: 24 tests for the SDF harness (prompts, compile errors, render checks, timeouts, repair and refine loops, the OpenAI backend against a stub server), plus fake-LLM tests for both CAD-code harnesses.

Implementation status

The bigger pipeline, and how much of it exists

The long-term goal is an agentic loop (Planner → Retriever → Generator → Critic) combining code generation, learned 3D generators, retrieval over CAD libraries and manufacturability feedback. This table mirrors the repository README.

Get started

Run it yourself

The SDF demo runs without any LLM; add an API key to generate new shapes.

git clone https://github.com/Masoudjafaripour/Vibe_CADing
cd Vibe_CADing/src/CAD_Harness/sdf
pip install -r requirements.txt
python demo.py --example snowman --gif

export OPENAI_API_KEY=sk-...
python demo.py --backend openai --model gpt-4o \
  --prompt "a stylized dragon" --gif

Citation

Cite this project

@misc{vibecading2025,
  author       = {Jafaripour, Masoud},
  title        = {Vibe-CADing: Conditional CAD Generation and Retrieval
                  for a Text-to-CAD Design Pipeline},
  year         = {2025},
  howpublished = {\url{https://github.com/Masoudjafaripour/Vibe_CADing}},
}