Building Cadora: On-Device 3D CAD Where Face IDs Matter More Than Triangles

Aug 2026 · 4 min read

3d

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