Table of contents
SketchBIM — Increment 2: Editing, Dimensions, Levels and Roofs
This increment amends the existing SketchBIM specification. Where it contradicts the original, this file wins. Read the original spec first: the single-source-of-truth rule (one shared model, views are projections) and the circular-arcs-only rule remain fully in force and are never relaxed below.
A. Amendments to existing constraints
A1. Scope change — multi-storey and roofs are now IN scope. Remove "multi-storey buildings" from the exclusion list. Roofs are in scope as defined in section F. Everything else previously excluded stays excluded: no IFC or model-exchange interoperability, no structural calculation, no collaboration, no authentication, no printing/PDF export, no non-circular curves.
A2. Schema version bump. These changes alter the persisted model. Bump
schemaVersion and write a migration from the previous version that loads old
files without data loss (walls with no level are assigned to a default ground
level). An old file must still open.
A3. Selection becomes a set. Selection is now a set of element ids, not a single id. Ctrl+click (Cmd on macOS) adds to or removes from the set. Every feature below that acts on "the selection" acts on the whole set. Migrate the existing single-selection state before building anything else in this increment.
B. Moving elements
Selecting an element and dragging its body moves the whole element, not just an endpoint. Endpoint handles keep their existing behaviour for reshaping.
- Dragging the body of a straight wall translates its centreline.
- Dragging the body of a curved wall translates its centre point; radius and sweep angles are unchanged.
- Moving a wall carries its openings, its layers and any dimensions anchored to it.
- Multiple selected elements move together as a rigid group.
- Movement respects snap-to-grid and endpoint snapping.
C. Arc endpoint editing
Dragging an arc's endpoint must have one defined meaning, because the operation is under-determined:
- Default: the centre and radius stay fixed; the dragged endpoint slides along the circle, changing only the sweep angle.
- With a modifier key held: the arc is re-fitted through the new endpoint while preserving the other endpoint and the current midpoint, producing a new centre and radius.
- A separate handle at the arc midpoint changes the radius while keeping both endpoints fixed.
Document the chosen modifier key in the UI. Reject any drag that would produce a degenerate arc (zero radius, zero sweep, or a radius smaller than half the wall thickness) and leave the geometry unchanged rather than half-applying the edit.
D. Dimensions — anchoring
Dimensions currently reference raw coordinates. Replace that with anchored references: a dimension endpoint stores a reference to an element and an anchor on it, and the coordinate is resolved from the model at render time.
- Anchor kinds: wall start point, wall end point, wall centreline point at a given ratio, layer boundary, opening edge, level elevation.
- The two endpoints of a dimension may anchor to the same element or to two different elements.
- When an anchored element moves, is reshaped, or has its layers changed, the dimension follows and its value recomputes automatically. This must work without any explicit refresh.
- When an anchored element is deleted, the dimension is deleted with it and the user is told what was removed.
- Snapping while placing a dimension highlights the candidate anchor before the click, so the user can see what they are attaching to.
E. Dimensions — presentation
Dimensions gain an offset: a signed perpendicular distance between the measured geometry and the drawn dimension line.
- Dragging a dimension changes only its offset. The measured value never changes as a result of dragging — moving the annotation is not moving the geometry.
- Render extension lines from each anchor point out to the dimension line, with a small gap at the anchor end.
- Render an arrowhead at each end of the dimension line, pointing outward at the measured extent.
- When the dimension line is too short for the text and both arrowheads, place the arrowheads outside the extension lines pointing inward, and move the text to one side.
- Offsets persist in the saved file.
F. Editing in the front and side views
Elevation views become editable: elements can be created and modified there, not only in the top view. This is under-determined by nature — an elevation is a projection and loses one axis — so the following rules are mandatory.
F1. Active work plane. Elevation views carry an active work plane: a depth along the axis the view projects away. It is user-settable and always visible in the UI. Elements created in an elevation view are placed on the active work plane. Never guess a depth silently.
F2. Edits touch only visible degrees of freedom. Dragging an element in an elevation view changes only the two axes that view displays. The depth axis is read from the model and preserved exactly. An edit in an elevation must never alter a coordinate the user cannot see in that view.
F3. Depth ordering and picking. Where several elements overlap in projection, clicking selects the one nearest the viewer; repeated clicks at the same point cycle through the stack, with the current candidate highlighted.
F4. Curved walls in elevation are constrained. A curved wall is foreshortened in elevation, so its plan geometry (centre, radius, sweep angles) is read-only in elevation views and editable only in the top view. Its height, its layers and its openings remain fully editable in elevation. Show a short inline explanation when the user attempts a blocked edit — do not fail silently.
F5. Consistency. Every elevation edit mutates the shared model. The top, bottom, section and 3D views update reactively. Elevation views hold no local geometry state.
G. Merging elements
With two or more elements selected, a merge action combines them. Merging is only valid under explicit preconditions; otherwise it must refuse.
Mergeable when all of: the elements are walls; they share an endpoint; they are geometrically continuous (collinear for straight walls, or co-circular with the same centre and radius for arcs, or tangent at the shared endpoint); they have identical thickness; they have identical layer stacks; they belong to the same level.
On success: a single wall replaces them, spanning the combined extent. Openings and anchored dimensions are re-anchored to the merged wall and keep their positions. The operation appears as one entry in the model history.
On failure: refuse and state the specific reason ("different thickness", "not continuous", "different layers"). Do not fall back to grouping, do not approximate the geometry to force a merge, and do not merge partially.
H. Levels (storeys)
Introduce a Level entity: a name, a base elevation, and a default wall height.
- Every wall belongs to exactly one level. A wall's base elevation comes from its level; its own height stays per-wall.
- Levels are created, renamed, reordered by elevation, and deleted. Deleting a level requires confirmation and deletes its walls.
- One level is active. The top and bottom views edit the active level and display the level below in a dimmed, non-selectable reference style.
- Elevation and section views show all levels at once, correctly stacked.
- The 3D preview renders all levels.
- Copying a level duplicates its walls at the next elevation — this is the fastest path to a plausible multi-storey model and is worth building.
I. Roofs
A roof is bounded deliberately: it is a set of planar slopes over a closed footprint, not a general modelling surface.
- A roof references a closed footprint (a loop of walls on a level) and a pitch angle, and generates sloped planes from the eave lines up to a ridge.
- Support three configurations only: mono-pitch, gable (dual-pitch), and hip. Nothing else in this increment — no dormers, no valleys between wings, no roof windows.
- Roof thickness is a single value; no layered roof build-up.
- Walls under a roof are clipped to the roof underside. A straight wall whose top is cut by a sloped plane is extruded from its elevation profile — which is simply a trapezoid or a gable pentagon — along its thickness. A curved wall under a roof is approximated with the existing horizontal-band splitting mechanism, stepping the top edge; reuse that code, do not add CSG.
- Roofs render in all five 2D views and in 3D. In the top view, show the roof outline and ridge lines.
- If a footprint is not closed, refuse to create the roof and highlight the gap.
J. Verification
Beyond the existing acceptance criteria, all of the following must hold:
- Dragging a wall body moves the whole wall; its openings and anchored dimensions follow with no manual refresh
- A dimension anchored between two different walls updates when either wall moves, and survives a save/load round trip
- Dragging a dimension changes its offset only; the measured value is unchanged
- Extension lines and both arrowheads render correctly, including the short-dimension fallback layout
- A wall created in the front view lands on the active work plane and is correct in the top view
- Dragging an element in the side view leaves its depth coordinate exactly unchanged
- Attempting to reshape a curved wall's plan geometry in an elevation view is blocked with a visible explanation
- Two collinear walls with identical thickness and layers merge into one, preserving openings and dimensions
- Merging two walls with different layer stacks is refused with a specific reason and leaves both walls untouched
- A two-level model renders stacked correctly in section and in 3D
- A gable roof clips the walls beneath it, with no gap and no overlap at the wall/roof junction
- A file saved with the previous schema version still opens, with its walls assigned to a default ground level
- Verified in a real browser via
chrome-devtools; console clean