{"slug": "show-hn-autonomous-cad-design-and-openfoam-optimization-loop-using-local-llms", "title": "Show HN: Autonomous CAD design and OpenFOAM optimization loop using local LLMs", "summary": "A developer created an autonomous multi-agent system that uses local LLMs, CadQuery, and OpenFOAM to design and optimize 3D-printable quadcopter propellers. The system employs a team of AI agents to propose designs and a physics-based referee to score them, enabling multi-objective optimization for quiet, efficient, and strong propellers. The project demonstrates a trustworthy approach where AI suggests but never scores, relying on trusted code for evaluation.", "body_md": "*An autonomous multi-agent system for parametric design, simulation, and multi-objective optimization of 3D-printable quadcopter propellers — powered by free, local LLMs (Ollama), CadQuery, and OpenFOAM CFD.*\n\nThis project tries to answer a simple question: instead of an engineer hand-tweaking a propeller and testing it over and over, can we let a group of AI models do that loop themselves — and end up with a better propeller than a person would patiently grind out by hand?\n\nThe target propeller should be three things at once: **quiet**, **efficient**\n(it doesn't waste battery), and **strong** (it pushes a lot of air). Those goals\nfight each other — a bigger, grippier blade gives you more thrust but more noise,\nfor example — so there's no single \"best\" answer. There's a set of good\ntrade-offs, and the job is to find them.\n\nIf you're new to AI agents, this is a nice thing to learn from, because it's not a\nchatbot. It's AI used as a worker that actually *does* something and checks its own\nresults.\n\nPicture a small research team where every member is a program:\n\n- A few\n**junior members** are fast and cheap. Their job is to brainstorm — throw out lots of propeller designs, some sensible, some weird. They don't need to be smart, they need to be prolific. These run on your own computer with free local models (via Ollama), so brainstorming costs nothing. - A\n**lead researcher** is the expensive, smart one. It doesn't generate the grunt work; it reads the results, notices patterns (\"the 5-blade designs are getting quieter — push harder there\"), and decides what the team tries next. That role is**Antigravity**, the agent running the show. - A\n**referee** that never lies: plain, trusted math and physics code. The AIs only ever*propose*designs. They're never allowed to*score*their own work. The referee does the scoring, so a confident-but-wrong model can't fool the system.\n\nThat last point is the whole trick, and it's worth remembering as a general lesson\nabout AI: **let models suggest, let trusted code decide.** Models are great at\ncoming up with options and terrible at being a reliable judge of truth. So we use\nthem only for the part they're good at.\n\nThe team works in a loop. One lap looks like this:\n\n**Propose**— the cheap models spit out a batch of new propeller designs.** Build**— code turns each design into actual 3D geometry (a real CAD file).** Score**— the physics code estimates how much thrust, how much noise, and how efficient each one is. This first pass is fast approximate math, not a full simulation.**Shortlist**— the system keeps the designs that aren't beaten on every goal at once. That surviving set is called the** Pareto front**— the current \"menu\" of best trade-offs.** Reflect**— Antigravity looks at the front and steers: what's working, what to explore next.** Write it down**— a one-line summary of the round gets logged, and the loop starts again.\n\nEvery so often, the most promising designs get the expensive treatment: a real\nfluid-dynamics simulation (**CFD**, run locally with OpenFOAM) that models the air\nactually flowing over the blade. That's slow, so we only spend it on candidates\nthat already look good on the cheap math.\n\nThere's one more helper worth naming: a **surrogate model** (a Gaussian Process,\nfrom scikit-learn). Think of it as the team learning to *guess the simulation's\nanswer* from the designs it has already simulated — so it can skip a lot of slow\nruns and spend them only where it's genuinely unsure. It's the system getting\nsmarter about where to look as it goes.\n\nThe one rule that makes this trustworthy: **the AI agents only ever propose\ndesigns — they never score them.** Scoring is done by plain physics code that\ncan't be talked into a wrong answer. Here's the whole loop:\n\n```\nflowchart TD\n    OBJ[\"Antigravity sets the objective<br/>quiet · efficient · high thrust\"]\n\n    subgraph propose[\"PROPOSE — agents only suggest (never score)\"]\n        P[\"Proposer<br/>local LLM\"]\n        M[\"Mutator<br/>local LLM\"]\n        C[\"Coder<br/>writes a search operator\"]\n        GA[\"Deterministic GA<br/>always runs, guarantees progress\"]\n    end\n\n    CAND[\"candidate designs<br/>(7 parameters each)\"]\n\n    subgraph score[\"SCORE — trusted physics, no LLM (the ground truth)\"]\n        PERF[\"performance.py<br/>BEMT hover → thrust, Figure of Merit\"]\n        TUB[\"tubercle_analysis.py<br/>→ noise reduction (dB)\"]\n        STR[\"propeller_physics.py<br/>→ stress, resonance\"]\n        V[\"evaluate.py<br/>objectives + constraints → feasible?\"]\n        PERF --> V\n        TUB --> V\n        STR --> V\n    end\n\n    SEL[\"pareto.py<br/>keep the non-dominated designs\"]\n    DB[(\"SQLite research.db<br/>every design + score\")]\n    REF[\"Antigravity reflects<br/>steers the next generation\"]\n    CAD[\"generate_propeller.py<br/>STEP / STL + watertight check\"]\n    CFD[\"cfd_verify.py<br/>OpenFOAM truth check\"]\n\n    OBJ --> P & M & C & GA\n    P & M & C & GA --> CAND\n    CAND --> PERF & TUB & STR\n    V --> SEL\n    SEL --> DB --> REF\n    REF -->|next generation| P\n    SEL -->|best designs| CAD --> CFD\n```\n\nRead it left-to-right, top-to-bottom: the agents (and a deterministic genetic algorithm that always runs as a safety net) throw out candidate designs → the physics scripts score each one → the non-dominated winners are kept and saved → Antigravity looks at the winners and steers the next round → the loop repeats. The best designs eventually drop out the bottom into CAD and CFD verification.\n\nThis is the part most transferable to your own projects. Every prompt is readable plain English, and you can copy the pattern for any domain where you want a small local model to generate structured, validated output.\n\nThe secret to making a 7B model reliably produce working CAD code and valid design\nparameters is **role separation + sandboxed execution + self-correction**. Each\nworker gets a single, constrained job with a strict output schema.\n\n| Worker | Model | What it does | Output |\n|---|---|---|---|\nProposer |\n`qwen2.5-coder:7b` |\nBrainstorms brand-new designs from scratch | JSON array of 7-parameter design vectors |\nMutator |\n`qwen2.5-coder:7b` |\nTakes a good design and creates small variations | JSON array of tweaked vectors |\nCoder |\n`qwen2.5-coder:7b` |\nWrites a Python search operator (mutation function) | JSON `{\"code\": \"...\"}` |\nCFD Analyst |\n`phi4-mini` |\nReads OpenFOAM logs and diagnoses solver failures | JSON `{\"status\": \"...\", \"fix\": \"...\"}` |\nScribe |\n`phi4-mini` |\nWrites one-line journal entries | Plain text |\n\nThis is `src/autoresearch/skills/proposer.md`\n\n— the full prompt that a 7B model\nreceives. Notice: no vague instructions, just hard bounds and domain knowledge:\n\n```\nYou are a PROPELLER DESIGN PROPOSER in an automated research swarm.\n\nYour job: propose NEW candidate propeller designs that might improve hover\nefficiency, increase tubercle noise reduction, or reduce blade mass.\n\nYou output ONLY JSON. No prose, no markdown. The schema is:\n{\"designs\": [\n  {\"chord_root_m\": <float>, \"chord_tip_m\": <float>,\n   \"twist_root_deg\": <float>, \"twist_tip_deg\": <float>,\n   \"tubercle_amp_m\": <float>, \"tubercle_wl_m\": <float>,\n   \"n_blades\": <int>},\n  ...\n]}\n\nHard bounds (stay inside these; values outside are clamped):\n  chord_root_m   : 0.020 .. 0.034\n  chord_tip_m    : 0.006 .. 0.014\n  twist_root_deg : 25 .. 45\n  twist_tip_deg  : 6 .. 20\n  tubercle_amp_m : 0.0 .. 0.005\n  tubercle_wl_m  : 0.020 .. 0.060\n  n_blades       : 2 .. 6 (integer)\n```\n\nThe Coder writes arbitrary Python, which is dangerous. The\n[sandbox](/ostenjap/LLM-Agent-generated-Quadcopter-Prop/blob/main/src/autoresearch/sandbox.py) handles it in two layers:\n\n**AST allowlist**— before execution, an AST walker rejects any`import`\n\noutside`{math, numpy, random}`\n\n, any dunder access, and any dangerous builtin (`open`\n\n,`exec`\n\n,`eval`\n\n,`os`\n\n,`subprocess`\n\n, etc.)**Subprocess isolation**— the screened code runs in a fresh Python process with a hard timeout and a scratch working directory. Only a JSON line on stdout is accepted back.\n\nIf the code is invalid, times out, or returns garbage, it's silently discarded — worst case is a wasted generation slot, never a corrupted archive:\n\n``` python\n# The sandbox contract (from sandbox.py):\ndef mutate(parents, bounds, rng):\n    # parents : list of design vectors\n    # bounds  : list of [lo, hi] for each variable\n    # rng     : random.Random instance (for reproducibility)\n    # returns : list of NEW design vectors\n    ...\n\n# STRICT sandbox rules (violations → operator discarded):\n# - Import ONLY: math, numpy, random. Nothing else.\n# - No file/network/system access, no open/exec/eval.\n# - Must return within 5 seconds.\n# - Every value clamped into [lo, hi] bounds.\n```\n\nWhen something fails — a bad mesh, a diverging CFD solver, malformed JSON — the\nerror is fed back to the responsible worker with the diagnostic context. The\nCFD Analyst, for example, gets the tail of the solver log and the residual values,\nand must return exactly *one concrete fix* to try next:\n\n```\n{\"status\": \"diverging\",\n \"diagnosis\": \"U residuals climbing after iteration 200, likely Courant violation\",\n \"fix\": \"reduce deltaT from 1e-3 to 5e-4\",\n \"fields\": {\"deltaT\": \"5e-4\"}}\n```\n\nThis is the pattern: **structured output → validation → auto-retry**. It works\nbecause the model never has to be right on the first try — it just has to be right\n*eventually*, within a budget of retries.\n\n📂 All six worker prompts are in\n\n[— read them directly, they're short and self-contained.]`src/autoresearch/skills/`\n\nIf you work with OpenFOAM, CadQuery, or parametric design tools, this project is also a working reference for automating the design-simulate-optimize loop. Here's what's under the hood that you can reuse or learn from:\n\nEvery propeller is defined by 7 parameters — chord at root and tip, twist\ndistribution, tubercle amplitude and wavelength, and blade count. The\n[ generate_propeller.py](/ostenjap/LLM-Agent-generated-Quadcopter-Prop/blob/main/src/generate_propeller.py) script takes these 7 numbers\nand produces a watertight STEP/STL via CadQuery, with:\n\n**Airfoil cross-sections** lofted along the span with linear twist**Leading-edge tubercles**(sinusoidal bumps inspired by humpback whale fins) for noise reduction** Automatic watertightness checking**before any design enters the CFD pipeline** STEP + STL export**ready for meshing, printing, or further CAD work\n\nThis is fully programmatic — no GUI, no manual steps. If you want to adapt it for a different part (turbine blade, heat exchanger fin, any swept surface), the parametric structure is designed to be swapped in.\n\nThe physics scoring stack is hand-written, not an LLM:\n\n**BEMT hover analysis**() — Blade Element Momentum Theory for thrust and Figure of Merit at a fixed RPM and diameter`src/optimization/`\n\n**Tubercle noise model**() — analytical estimate of noise reduction from leading-edge serrations`src/tubercle_analysis.py`\n\n**Structural checks**() — centrifugal stress, resonance frequency clearance, tip Mach constraint`src/propeller_physics.py`\n\n**Non-dominated sorting**— true Pareto front over three objectives (efficiency, noise, thrust), not a weighted sum\n\nThe surrogate (Gaussian Process, scikit-learn) learns from evaluated designs and proposes infill points via Expected Improvement, reducing how many full evaluations you need.\n\nThe [ setup_openfoam_case.py](/ostenjap/LLM-Agent-generated-Quadcopter-Prop/blob/main/src/setup_openfoam_case.py) script generates a\ncomplete OpenFOAM case directory from a STEP file:\n\n**snappyHexMesh** dictionary with castellated/snap/layer settings tuned for propeller geometry**simpleFoam** with k-ω SST turbulence and appropriate boundary conditions**Force coefficient extraction** from`postProcessing/forces/`\n\n**Automated convergence checking**— the CFD Analyst agent reads residuals and applies one fix at a time (relaxation factors, time step, mesh quality) when the solver diverges\n\nThe CFD step is optional — the analytical loop runs standalone and fast. But when you want ground-truth validation, the pipeline is ready.\n\nThe loop can run for hours, so it can't keep everything in its head. It writes\neverything to a single local database file, `data/research.db`\n\n(SQLite).\n\nThis isn't just bookkeeping. Because that database saves each result the instant\nit's final, the loop can be killed — power cut, crash, you closing the laptop — and\npick up exactly where it left off instead of starting over. A human-readable diary\nof the run also lands in `data/journal.md`\n\nif you just want to skim what happened.\n\nHonest version: don't start this and immediately walk away the first time. The first run has setup to get through, and you'll want to see it work once.\n\nYou need a few things installed first:\n\n**Ollama** with two local models:`ollama pull qwen2.5-coder:7b`\n\nand`ollama pull phi4-mini`\n\n**OpenFOAM**(through WSL or Docker) — only needed once you reach the simulation step- Python packages:\n`pip install cadquery numpy scikit-learn matplotlib`\n\nThen, the way you actually use it: open this folder in **Antigravity** and tell it\n**\"go to work.\"** It reads [ AGENTS.md](/ostenjap/LLM-Agent-generated-Quadcopter-Prop/blob/main/AGENTS.md) — its instruction sheet — and\nstarts working through the plan on its own, stopping to check in with you at the\npoints that matter.\n\nTo run the core loop by hand:\n\n```\ncd src\npython -m autoresearch.researcher --no-llm --budget 30   # quick, no AI — sanity check\npython -m autoresearch.researcher --budget 1800          # the full team\n```\n\nOnce you've watched it work once and you trust it, the long runs are the part you\n*can* sleep through. There's a babysitter script for exactly that:\n\n```\npowershell -ExecutionPolicy Bypass -File .\\run_overnight.ps1\n```\n\nIt restarts the loop if it crashes, refuses to run forever (there are time and\nrestart caps), logs everything, and leaves a one-line verdict in\n`data/RUN_STATUS.txt`\n\nfor you to read with your coffee. Drop a file named `STOP`\n\nin this folder to stop it cleanly. If you wire in a Telegram token, it'll message\nyou when it's done.\n\nFirst full run of the pipeline. The optimizer explored a few hundred feasible designs and mapped the trade-off surface between the three goals:\n\nThe red rings are the **Pareto front** — designs that aren't beaten on all three\ngoals at once, i.e. the current menu of best trade-offs. The bottom-right panel\nshows the search improving generation over generation.\n\nThe best efficiency pick (Figure of Merit 0.867, 38 N thrust, 6 blades) was exported to CAD and passed the watertightness check:\n\n*Actual 3D CAD model rendering of the generated 6-blade propeller:*\n\nHonest caveat: these scores come from the fast analytical physics, and this winner sits against the edges of the allowed design range — so treat V1 as a working pipeline and a first map, not a final answer. CFD verification and a re-run with reviewed bounds come next.\n\nRegenerate these anytime:\n\n```\ncd src\npython plot_results.py        # docs/v1_results.png\npython export_best.py         # cad/best_fm.* + validity report\n```\n\n| Path | What's there |\n|---|---|\n`implementation_plan.md` |\n\n`AGENTS.md`\n\n`src/autoresearch/skills/`\n\n`src/optimization/`\n\n`src/generate_propeller.py`\n\n`src/setup_openfoam_case.py`\n\n`src/propeller_physics.py`\n\n`src/tubercle_analysis.py`\n\n`data/research.db`\n\n`cad/`\n\n**If you're an LLM developer:** start with `src/autoresearch/skills/`\n\n— those are\nthe plain-English prompts the AI workers run on.\n\n**If you're a CAD/CFD engineer:** start with `src/generate_propeller.py`\n\nand\n`src/setup_openfoam_case.py`\n\n— those are the parametric geometry and simulation\npipelines you can adapt for your own parts.\n\n[MIT](/ostenjap/LLM-Agent-generated-Quadcopter-Prop/blob/main/LICENSE) — use it, fork it, build on it.", "url": "https://wpnews.pro/news/show-hn-autonomous-cad-design-and-openfoam-optimization-loop-using-local-llms", "canonical_source": "https://github.com/ostenjap/LLM-Agent-generated-Quadcopter-Prop", "published_at": "2026-06-27 21:28:55+00:00", "updated_at": "2026-06-27 22:05:16.778211+00:00", "lang": "en", "topics": ["ai-agents", "ai-research", "ai-tools", "generative-ai"], "entities": ["Ollama", "CadQuery", "OpenFOAM", "scikit-learn", "Antigravity"], "alternates": {"html": "https://wpnews.pro/news/show-hn-autonomous-cad-design-and-openfoam-optimization-loop-using-local-llms", "markdown": "https://wpnews.pro/news/show-hn-autonomous-cad-design-and-openfoam-optimization-loop-using-local-llms.md", "text": "https://wpnews.pro/news/show-hn-autonomous-cad-design-and-openfoam-optimization-loop-using-local-llms.txt", "jsonld": "https://wpnews.pro/news/show-hn-autonomous-cad-design-and-openfoam-optimization-loop-using-local-llms.jsonld"}}