Building Cadora: On-Device 3D CAD Where Face IDs Matter More Than Triangles
Aug 2026 · 4 min read

I shipped Cadora — a 3D CAD and architectural modelling app for mobile. Solid modelling, parametric sketching, measurement, and multi-format export all run on the device, offline.
Most “3D on Flutter” posts stop at a viewer. Cadora is an editor with a geometry kernel.
The problem that isn’t glamorous
GPUs speak triangles. CAD users speak faces, edges, and sketches.
The flat top of a box is two triangles and one face. If you only store a triangle soup, Push/Pull, face materials, and precise picking fall apart the moment you boolean or transform the body. The model still looks fine. It stops behaving like CAD.
So the interesting constraint became:
Can a phone run a real modelling kernel — not just a mesh viewer — and keep logical faces continuous through the ops people actually use?
Face IDs are the product
In Cadora’s Mesh, every triangle carries a face id. Triangles that share an id belong to one logical CAD face.
That continuity is what makes modelling history usable:
- selection highlights a face a human recognises
- Push/Pull operates on that face
- materials attach to faces, not random triangles
- face ids are meant to survive booleans and transforms
Get this wrong and you ship a pretty renderer. Get it right and you ship a CAD app.
What’s in the kernel
The geometry stack is pure Dart — no Flutter imports — so it runs on the Dart VM in tests:
- Math — Vec2/3, Mat4, quats, planes, rays, AABBs
- Geometry — meshes with face ids, primitives, extrusion family, BSP booleans, modify ops (shell, fillet/chamfer, patterns, mirror), repair, measurement
- Sketch — parametric entities (line, rect, circle, arc, polygon, ellipse, spline, Bézier…), snapping, a constraint solver, planar-graph profile finding
- Document — immutable doc model, materials, layers
The renderer depends only on dart:ui (no widgets): orbit camera, solid/wireframe, crease edges, planar shadows, adaptive grid, and ray-cast picking at object / face / edge / vertex.
Sketch → solid is the core loop: draw on a plane, constrain it, extrude or revolve, then keep editing faces.
Export without a desktop
Models leave the phone as:
- STL (binary + ASCII) for print/slice
- OBJ + MTL
- GLB for web/AR viewers
- STEP AP214 (faceted B-rep — honest about curves becoming facets)
- DXF (3DFACE)
- PNG of the current view
Import covers STL, OBJ, and glTF/GLB. Projects persist as a versioned .cadora format with Drift for the project index, autosave, and crash recovery.
Why on-device
A modelling session with no network still has to work. Keeping the kernel local means:
- no upload of unfinished geometry to a CAD cloud
- deterministic ops you can unit-test without a GPU farm
- a clear boundary: Flutter owns UI; the kernel owns truth
264 tests exist partly because the kernel doesn’t need a widget tree to prove a boolean or a sketch solve.
What I shipped
Flutter + Riverpod + GoRouter. Editor with dozens of tools, templates, a learn track, settings that change real behaviour, and a paywall. Android is the verified ship target in this delivery; the iOS host is present but wasn’t the focus of that pass.
Store links:
What I’d still harden
STEP/DXF will always be a faceted compromise on mobile — say so in the export sheet, don’t oversell NURBS. Integration tests that call pumpAndSettle will hang forever if autosave/spinners keep scheduling frames (bounded pumps only). And a kernel this size will keep earning edge cases around repair after aggressive booleans.
The bet: triangles are for drawing; face IDs are for thinking.
If you’re putting CAD on mobile, start with the identity of a face — not the shader.
- flutter
- cad
- 3d
- geometry
- on-device
- mesh
- mobile