A CAD floor plan of a building, and two game units wandering inside that same building. Two wildly different applications — running on the same engine.
I just open-sourced Kimetron Isle, a framework-agnostic 2D placement engine where position is data, not decoration. v1.0.0, MIT licensed.
- Repo: https://github.com/cuculhart/kimetron
- Playground (no install, runs in the browser): https://cuculhart.github.io/kimetron/
The same engine, two faces
plan — a building sketch on a 10 mm grid. Walls are thickness×length rectangles that may not overlap each other; butt joints ("|-" T junctions) are legal because edge-touching doesn't count as an overlap. A door is the declared exception that may sit on a wall. Rulers, dimension lines, wall centerlines, and architectural areas (site / building / core / inner) are all derived — move a wall and the floor area changes.
game — two 300 mm units wander inside the building via engine.tryMove. Walls and rocks (locked, concave polygons) are impassable; the gate is the only way out. Units can't occupy the same space, and when two come within range an effect link appears — talk, attack, item exchange. The pond is a driver-side SVG pattern.
Same engine in both cases. The only difference is the policy each driver declares: "a door may overlap a wall", "rocks can't move", "units can't stack" — a few lines of driver code each.
Why build it
I built two production apps (Plesfeda, Sethera Piece) on React Flow, and learned that what a canvas library does for you stops at rendering, dragging, selection, and the viewport. Layout and domain semantics were always on my side — so the coupling was thin, and a replacement was feasible.
But a React Flow clone would be pointless. Every canvas/diagram library out there assumes screen coordinates, pixels, and a downward Y axis. In CAD, the coordinate itself is the design data — yet no engine treats it that way. That's the gap Kimetron Isle fills.
The world-coordinate model
- Real units — mm / m / inch / px as first-class units. Define a world where 1 unit = 1 mm; zoom is a drawing scale, not a magnification
- Selectable origin — bottom-left + Y-up (CAD/math) or top-left + Y-down (screen)
-
Built-in snapping — grid, vertices, edge midpoints, centers; drivers inject custom snaps via
snapExtra -
Collision & occupancy —
solidobjects can't overlap while edge-touching stays legal;canOverlapdeclares pair exceptions;collisionModepicks none/flag/reject -
Rotation & polygons — rotation honored all the way down to OBB/SAT collision;
kind:'polygon'hit-tests and collides correctly even when concave -
Programmatic movement —
objectsIn,canPlace,tryMoveso AI-driven motion goes through the same collision policy as human gestures - Undo & serialization — immutable model ops, document-level undo/redo, JSON serialize
Architecture
Three layers: @kimetron/core (vanilla TypeScript — model, geometry, selection, drag, hit-testing, no DOM), @kimetron/dom (SVG/CSS renderer + pointer input), @kimetron/react (a thin adapter).
Semantics live on the user's side: Kimetron assigns meaning only up to geometry and interaction. That's exactly why a floor-plan editor and a wandering-unit demo can share one engine.
Honest status
v1.0.0 is a stability commitment — additive-only changes since 0.6.0. There are still gaps: associated dimensions, named layers, and block references are 1.x territory; Svelte/Vue adapters are "structurally possible" rather than built; collaboration and cloud storage are deliberately out of scope.
If you're building something where placement itself carries meaning — floor plans, equipment layouts, seating charts, panel assignments, game maps — I'd love to hear what you'd put on it.
Links
- Repo: https://github.com/cuculhart/kimetron
- Playground: https://cuculhart.github.io/kimetron/
- Product page: https://cuculhart.com/oss/kimetron/index.en.html
Top comments (0)