noahclark556/mesh-to-cad-14b-v2
Mesh-to-CAD 14B v2
An experimental model that turns an STL mesh or point cloud into editable CadQuery Python.
The goal is a program you can read and change, not just another mesh that looks similar. Multiple scripts can describe the same shape, so exact source recovery is not the bar.
License: CC BY-NC 4.0. Non-commercial use with attribution is welcome. Commercial use needs a separate license.
This is not a normal text chat model. Geometry is sampled, encoded, and passed through a point projector into inputs_embeds. The language model then writes CadQuery after a <|gen|> boundary token.
Successor to `noahclark556/mesh-to-cad-14b` (v1).
Why I built this
My initial reason for building this model was to use it within my web platform SliceFoundry (SliceFoundry.com), as well as within my desktop application Draftr (draftrcad.com). A lot of users come to these platforms trying to "remix" an existing STL, which is a supported feature in both applications. However, the system currently in place hits a wall that cannot be crossed without a custom trained model. The idea moving forward, which is currently working well with model versions that have not yet been released, is to have the mesh-to-cad model produce the geometry from the uploaded mesh, then run that geometry through the existing parametric fine-tune pipeline alongside a detailed analysis of the mesh.
This system has proven to be far more accurate than I initially anticipated, and should save a lot of time and spending for me as development continues. The process of training this model has been a relatively large (but informative) undertaking, as the training setup had to be almost entirely custom to support a project built in this way. Over the course of multiple months, I have built a massive and streamlined local pipeline for this training process.
From dataset generation modules to training and deployment scripts for both GCP and RunPod, as well as full support for training the model entirely locally. A lot of the training that occurred was done locally, and I bought a new MacBook partly so I could do this. Over the course of 12 SFT sessions, 4-5 GRPO sessions, 16 ORPO sessions, and an absurdly large amount of training data built using data extracted from the aforementioned platforms and synthetically produced, mesh-to-cad was born through the forge of trial and error.
What's different in v2
v1 was a clean train after I found a fatal training bug. Before that, I had already stacked six SFT runs on a ton of good CadQuery data. That stack had real skill in it, but the early runs used 512-point FPS without split sampling, and a stop-token masking bug meant the model never properly learned when to stop generating. Those issues poisoned the stacked weights enough that I could not just ship them, so v1 was trained fresh with the corrected pipeline (1024 split FPS, mesh signatures, proper EOS).
v2 takes that corrected fresh train (ORPO polish on the fixed setup) and merges it with the old six-run stack after an automatic weight correction: compute the delta between the fixed model and the stacked model, keep the largest systematic shifts (especially around the point projector and stop behavior), apply them to the stacked weights, then soup the repaired stack with the fixed model. The idea is to keep the CadQuery memory from the long stack while pulling it onto the corrected geometry and stop-token behavior.
If you only want one checkpoint, use this one. v1 remains up as the earlier public baseline.
Try it (Apple Silicon / MLX)
This is the easy path on Mac. The Hub folder root is a 6-bit MLX export.
huggingface-cli download noahclark556/mesh-to-cad-14b-v2 --local-dir ./mesh-to-cad-14b-v2
cd mesh-to-cad-14b-v2
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
python chat.py --input /path/to/part.stl
python chat.py --input /path/to/part.stl --renderA few things happen automatically so the path matches training:
- Input is resized so the longest bbox edge is 100.
mesh_sigis derived from the mesh when possible and inserted in the prefix (points + sig +<|gen|>).- Dense sampling uses fine tessellation and feature-aware points, then split FPS down to 1024 tokens.
--render runs the generated script, writes infer_out/<stem>.stl, and opens it. If CadQuery fails, the script errors after a light local cleanup (trimming an incomplete trailing line). There is no external repair API.
CadQuery is recommended for sampling that matches training, and required for --render:
mamba install -c conda-forge cadqueryUse conda-forge / OCP. Avoid pip-only CadQuery on Mac.
Try it (NVIDIA / Transformers)
Ollama, llama.cpp, and plain text servers will not work. This model needs a custom point projector and inputs_embeds. The second supported path is Hugging Face Transformers (CUDA is the practical target; MPS works for smaller smoke tests).
The Hub repo includes a torch/ folder with BF16 Transformers weights (not MLX packs). On CUDA you can load them in 8-bit with bitsandbytes to keep VRAM down. True 6-bit is the MLX path above; 8-bit here is the usual GPU stand-in.
cd mesh-to-cad-14b-v2
python3 -m venv .venv-torch && source .venv-torch/bin/activate
pip install -r requirements-torch.txt
python chat_torch.py --input /path/to/part.stl --model ./torch
# CUDA, lower VRAM:
python chat_torch.py --input /path/to/part.stl --model ./torch --load-in-8bit --renderSame flags as chat.py for temp / top-k / top-p / max-tokens / repetition-penalty / render. --model can be the repo root (auto-picks torch/) or the torch/ folder itself.
Pipeline
STL / point cloud
-> fine tessellation
-> 8192 feature-aware surface samples
-> split FPS (768 global + 256 feature) = 1024 tokens
-> 12-frequency Fourier encoding (75D) + linear projector
-> optional mesh_sig tokens (compact geometry summary)
-> <|gen|>
-> CadQuery PythonThe model does not see a chat-style system prompt. Conditioning is the prefix itself: projected point tokens, then (when available) a short mesh_sig string embedded as normal text tokens, then <|gen|>.
How this model was built
Early versions used a single FPS pass at 512 tokens on coarser surface samples. That was enough for simple blocks, but pockets, holes, and thin local detail tended to disappear before the model ever saw them.
What made a real difference:
- Finer tessellation when exporting STL for sampling, so edges and small faces survive into the point cloud.
- Feature-aware dense sampling (8192 points), so sharp edges and small faces are not starved by area weighting alone.
- Split FPS to 1024 tokens: most of the budget for global coverage (~768), with a reserved feature pool (~256) so local detail still gets tokens. Same count as a plain 1024 FPS, better retention of pockets/holes.
- Mesh signature (`mesh_sig`) - a compact discrete summary of the part (family / topology-ish cues in a short
SIG v1 ...string). It sits in the prefix between the point embeddings and<|gen|>, so the model gets an explicit high-level read of the shape before it starts writing code. In practice it plays a similar role to a system prompt (steering the generation with structured context), but it is geometry conditioning in the input prefix, not a chat system message. Adding it helped a lot on harder parts where points alone were ambiguous. - SFT on Qwen3-14B-Base with a Fourier point projector and a dedicated
<|gen|>generation boundary. - Additional ORPO preference passes to sharpen CadQuery quality on harder cases.
- v2 only: delta-correct the earlier six-run SFT stack against the fixed train, then merge (see "What's different in v2").
The training corpus is not released with this checkpoint. Provenance and licensing for the data are still private; this repo is the model and the inference helpers only.
What's in the repo
model*.safetensors- language model weights (MLX 6-bit, forchat.py)torch/- Transformers BF16 weights +projector.pt(forchat_torch.py)projector.pt- point-cloud -> embedding projector (root copy for MLX)- tokenizer / config (includes
<|gen|>) chat.py- MLX test path (Apple Silicon)chat_torch.py- Transformers test path (CUDA / MPS)mesh_to_cad/- shared sampling + prefix helpersrequirements.txt/requirements-torch.txt
Limits
This is still research software. Generated Python can fail to run or produce empty / invalid solids. Even when geometry looks close, the construction history may be very different from the original CAD.
Thin walls, hidden cavities, threads, assemblies, organic surfaces, and tiny features are difficult from exterior samples alone. Small holes, pockets, and fillets are often simplified or dropped. Absolute scale is normalized unless your own pipeline restores it.
Citation
If you use Mesh-to-CAD, please credit the project and link this model page. For commercial licensing, contact me.
Prior work
The point-cloud soft-token idea (sample points, Fourier-encode them, project into the LM embedding space, then generate CAD code) is not mine. I first saw that setup in:
- CAD-Recode (Rukhovich et al., ICCV 2025)
- cadrille (Kolodiazhnyi et al., ICLR 2026)
Credit to those authors for that geometry-prefix approach. Everything else here (data, training stack, mesh signatures, split FPS, CadQuery target, ORPO, etc.) is my own work. Citing them so nobody thinks I invented the soft-token pattern.
