FreeCAD + AI: automating CAD with Python and LLMs
FreeCAD runs on Python. LLMs write Python. The combination works better than you'd expect for simple parametric parts, and worse than you'd hope for anything complex.
Quick answer
FreeCAD's Python API can be combined with LLMs (ChatGPT, Claude) to generate parametric CAD models through AI-written macros. The workflow: describe a part to the LLM, get FreeCAD Python code, run it in FreeCAD's console. Works for simple prismatic parts; fails on complex geometry, constraints, and assemblies.
I needed a mounting plate for a small stepper motor, the kind with four M3 holes on a 31mm square pattern and a central bore for the shaft. Simple part. Ten minutes in FreeCAD, five if the sketcher was feeling cooperative. Instead I opened Claude, typed out the dimensions, and asked for a FreeCAD Python macro. Forty seconds later I had a script. I pasted it into FreeCAD's Python console, hit Enter, and watched it build the part step by step: new document, new body, sketch on XY plane, rectangle, holes, pad, central bore. The dimensions were correct. The hole pattern was correct. The central bore was the right diameter. It even added fillets on the outer corners, which I hadn't asked for but didn't mind.
Then I asked for a version with countersunk holes instead of through-holes, and the script crashed on the second sketch because Claude generated a Part.makeCone call with arguments in the wrong order. Welcome to AI-assisted FreeCAD.
The basic workflow#
The concept is straightforward. FreeCAD exposes nearly everything through a Python API. You can create documents, build sketches, apply features, set constraints, modify parameters, export files, and run simulations, all from Python. LLMs are good at writing Python. The connection writes itself.
The workflow:
- Describe the part to an LLM with specific dimensions, features, and positions.
- Ask for a FreeCAD Python macro.
- Copy the script.
- Open FreeCAD, go to View > Panels > Python Console.
- Paste and run.
- Inspect the result. Fix what's wrong, either in the script or in the GUI.
For simple parts, this works more often than it fails. A rectangular plate with holes. An L-bracket with mounting features. A cylindrical standoff. A simple enclosure. Parts built from extrusions, cuts, and basic features on flat sketches generate reliably because the API calls are straightforward and well-documented enough that LLMs have seen plenty of examples.
What to tell the LLM#
FreeCAD's Python API has multiple ways to do the same thing, and LLMs don't always pick the right one. Being specific about which approach you want saves debugging time.
Tell the LLM to use the Part Design workbench, not the Part workbench, for parametric modeling. Part Design creates features with history (Pad, Pocket, Fillet, Chamfer) that you can edit later. The Part workbench creates shapes directly, which is simpler to script but produces non-parametric geometry. If you want to modify the model after generation, you need Part Design.
Ask for fully constrained sketches. This is the most important instruction and the one LLMs most often ignore. A FreeCAD sketch needs geometric constraints (coincident points, horizontal/vertical lines, fixed positions) and dimensional constraints (lengths, angles, radii) to be fully determined. Under-constrained sketches produce geometry that looks right but shifts unpredictably when you modify anything. The LLM needs to add constraints, not just draw geometry.
A prompt that works for me:
"Write a FreeCAD Python macro using Part Design workbench. Create a new document and body. Sketch on the XY plane. Draw a 60mm x 40mm rectangle centered on the origin. Fully constrain the sketch with dimensional and positional constraints. Pad the sketch 5mm. Add four 3.5mm through-holes at positions (20, 12), (-20, 12), (20, -12), (-20, -12) relative to center, each as a separate sketch on the top face with a Pocket through all. Add 2mm fillets on all outer vertical edges. Call recompute after each operation."
That last part matters. FreeCAD needs explicit recompute() calls to update the model after scripted operations. LLMs forget this about a third of the time, and the result is a script that runs without errors but produces an empty or partially-built model because the features never actually computed.
The API problems LLMs stumble on#
FreeCAD's scripting API is powerful but inconsistent, and the inconsistencies are exactly where LLMs fail.
Sketch constraint syntax trips up every model I've tested. FreeCAD uses index-based references for constraint targets: Sketch.addConstraint(Sketcher.Constraint('Coincident', 0, 3, 1, 1)) means "make the endpoint of the first line coincident with the start point of the second line." Those numeric indices depend on the order geometry was added to the sketch. LLMs generate constraints with wrong indices constantly, because the correct indices depend on the runtime state of the sketch, which the LLM can't see.
The coordinate system differences between workbenches cause silent failures. Part Design sketches use local coordinates on the sketch plane. The Part module uses global coordinates. FreeCAD.Vector(10, 0, 0) means different things in different contexts, and LLMs mix them up.
Method names change between FreeCAD versions. A script generated for FreeCAD 0.21 might use deprecated methods that fail in FreeCAD 1.0. LLMs train on data from multiple versions and don't always generate code for the version you're running. If a script fails on a method call, check whether the method name is current. Part.show() vs FreeCADGui.ActiveDocument.ActiveView.fitAll() is the kind of thing that changes between versions and breaks silently.
Topological naming is the deep problem. FreeCAD has a long-standing issue where feature references (faces, edges, vertices) can change identity when earlier features are modified. This affects scripted operations that reference specific faces: "add a sketch on Face6 of the Pad" works until you modify the Pad, at which point Face6 might be a different face. LLMs generate face references based on expected topology, and those references are fragile. FreeCAD 1.0 has made significant progress on this issue with the toponaming fix, but the problem isn't fully solved and AI-generated scripts still hit it.
Claude vs ChatGPT for FreeCAD macros#
I've generated hundreds of FreeCAD macros with both, and the differences are noticeable.
Claude produces cleaner code structure. It breaks operations into functions, names variables descriptively, and adds comments that are actually helpful. It handles the Part Design workflow more consistently, creating proper Body > Sketch > Feature sequences. It's also better at generating constrained sketches, though still not reliable enough to skip checking.
ChatGPT (GPT-4 and later) generates code that works on the first run more often for simple parts. It seems to have more FreeCAD-specific training data, or at least more recent examples. For basic Pad/Pocket operations, ChatGPT's success rate is slightly higher. For more complex operations involving multiple sketches, datum planes, or patterns, Claude handles the complexity better.
Both fail on the same categories of problems: sketch constraints, topological references, and complex boolean operations. The failure modes differ (Claude tends to over-specify constraints in ways that conflict, ChatGPT tends to under-specify them), but the end result is the same: you're debugging a Python script against FreeCAD's API documentation.
Local models through Ollama are usable for trivial geometry and unreliable for anything else. I've had success with DeepSeek Coder for basic shapes, but the moment the script needs to reference specific faces or add non-trivial constraints, local models produce code that doesn't survive contact with FreeCAD's runtime.
Making the output actually useful#
The scripts LLMs generate are starting points. Here's how I turn them into useful parts.
Run in Plan mode if using the FreeCAD AI Workbench. If pasting manually, read the script before running it. Look for obvious problems: missing recompute() calls, hard-coded face references, unconstrained sketches, operations on the wrong plane.
After running, check the model tree. Every feature should show a green checkmark. Yellow warnings mean something computed but not as expected. Red means failure. If a feature is red, check the Python report view at the bottom for the error message.
Measure critical dimensions. Use FreeCAD's measurement tool to verify that holes are the right diameter, walls are the right thickness, and features are positioned correctly. LLMs get dimensions wrong often enough that this step isn't optional.
If the script needs modification, edit it and re-run on a new document rather than trying to modify the generated model in the GUI. The parametric relationships in a scripted model are often fragile, and manually editing a feature can break downstream references in ways that are harder to fix than just adjusting the script and regenerating.
Save working scripts as macros. FreeCAD stores macros in its macros directory, and you can re-run them anytime. I keep a small library of AI-generated macros for common parts (standoffs, mounting plates, simple enclosures) that I've debugged and parameterized. Changing a dimension at the top of the script and re-running is faster than regenerating from the LLM, and the output is predictable because the script is known to work.
When to use this instead of just modeling#
The honest calculation: if you can model the part in FreeCAD in under fifteen minutes, just model it. The time you spend writing a prompt, waiting for the LLM, debugging the script, and verifying the output usually exceeds fifteen minutes for a first-time generation. The value comes from reuse and iteration.
If you need ten variations of a mounting plate with different hole patterns, generating a parametric script once and modifying the variables for each variation is faster than modeling each plate individually. If you're exploring design options and want to see a bracket at 40mm, 50mm, and 60mm heights quickly, a parametric script with a single variable change is faster than three manual models.
The other use case is learning. If you're new to FreeCAD's Python API, asking an LLM to generate a script for a part you understand, then reading the script to see how the API calls work, is a surprisingly effective tutorial. I learned more about FreeCAD's Sketcher constraint API from reading AI-generated scripts than from the official documentation, partly because the scripts show complete working examples while the docs show isolated function signatures.
The practical state of things#
FreeCAD + LLMs for macro generation works. Not seamlessly, not reliably for complex parts, not without debugging. But for simple parametric geometry, the workflow produces usable results faster than I expected when I first tried it.
The FreeCAD AI plugins are trying to make this smoother by handling the integration inside FreeCAD itself. They're getting better. For now, the manual workflow of prompting an LLM, pasting into the console, and fixing the errors is the most reliable approach because you stay in control of every step.
If you're coming from OpenSCAD + AI, expect a rougher experience. OpenSCAD's small language makes AI generation more predictable. FreeCAD's large API makes it more capable but more fragile. The text-to-CAD open source landscape has room for both approaches: OpenSCAD for quick parametric parts you'll 3D print, FreeCAD for parts that need STEP export, proper B-Rep geometry, or features that OpenSCAD can't express.
The combination of FreeCAD and LLMs isn't magic. It's a moderately capable junior colleague who knows the API vocabulary, works fast, and needs supervision. I've worked with actual junior colleagues like that. You give them clear instructions, check their work, fix their mistakes, and appreciate that they saved you some time even if the result needed polishing. Same thing here, except this colleague doesn't drink the last of the coffee.
Newsletter
Get new TexoCAD thoughts in your inbox
New articles, product updates, and practical ideas on Text-to-CAD, AI CAD, and CAD workflows.