Appearance
Repeaters
i want a way to automagically add/remove repeated elements like stairs (range for rise), studs (local building code)
Examples
- stairs: template = one step (w, rise, run) → count from gap range constraint
- studs: template = one stud (w, h, thickness) → count from spacing constraint
Further improvements
- fire blocks every 4' when wall height > 8'
- PROPOSAL FOR: how can engine keep track of templates, clones and fire blocks?
- stairs: add a new slider for "percentage nosing"
- trig rise and run
- diagonally positioned stud wall
- angles
done
- bug: "Adds a bookend clone at the envelope end" but does not increment displayed repeats count
- show a dimensional on the first fireblock
- show an extra dimensional on the final fireblock if it is a shortened one
- add two new booleans to SO -- and to portable SO and bump current version
-
is_diagonal-- default value is null- becomes true when the user clicks "diagonal"
- becomes false on "straight"
- never goes back to null
-
is_repeating-- default value is false- becomes true when user clicks diagonal or straight
- false when they click unrepeat
- simply removes the clones
-
- for non repeaters
- if no child -> "need one child for the template"
- otherwise -> two buttons "diagonal" and "straight"
- diagonal is for stairs, straight for non-stairs
- stairs
- remove extra "run" at top
- have no fire blocks (remove button)
- adjust tread depth according to run
- run is computed. depth should be run x 1.25
- for non stairs
- replace constraint seg control with a slider
- sticky at 12, 16, 24 inches
- range from 6" to 3'
- turn thumb white with faint border on stuck values
- label "spacing" below
- value label above thumb
- move to same row as fireblocks button
- thumb ignores click in bottom half of thumb
- values should be in whole units (eg. inches)
- slider has two lines (the fat one is the right length but should be thin, the thin one protrudes to the right)
- stickies do not stick, nor change color, nor are they the designated ones (12, 16, 24)
- replace constraint seg control with a slider
- move [un]repeat button -> center
- convert min max range to a "two thumbs" slider, range from 4" to 12"
- defaults: min -> 6, max -> 9
- add ticks for whole units
- value labels should react to slider thumbs
- shrink font -> 60%
- center the thumbs on the slider line
- whole units should be sticky not snapped
- snap positions should turn thumb white, with faint border (exactly like angles)
- allow user to choose 4 or 12 (not stop at 4 1/4 and 11 3/4)
- add label "rise range" below slider
- not show constraint for stairs
- move axis seg control -> [un]repeat row, far left
- move number of clones to same row as [un]repeat, at far left
- only show dimensionals on one clone/template
- rotate a template -> should instantly rotate the clones
- angles axis choice is wrong
-
duplication should exclude repeater clones from the new subtreeremoved dead code (duplicate_selected was never called)
Defer
- diagonal bracing: angled member across stud bay
- diagonal stud lengths (rake walls)
- repeated repeater
- four (six?) walls
- flooring sheets
- repeating angles (hah!)
Phase 1 — Data model
- add
repeaterto Smart_Object -
addremoved — repeater always uses first childis_template: booleanflag - serialize/deserialize repeater field in Smart_Object
Phase 2 — Engine: generate copies
-
Engine.sync_repeater(so)— marks first child as template, syncs clones -
Engine.sync_repeater(so)— evaluates count, creates/removes cloned children to match- clones copy template's dimensions
- each clone's position offset = template size × index (axis determined by template's largest dimension)
- decompose: call
sync_repeaterafter any propagation that touches a repeater SO
Phase 3 — Constraints integration
- when
propagate(so)runs andsois a repeater, callsync_repeater(so)after - when
propagate_all()runs, sync all repeater SOs after
Phase 4 — UI
- D_Selection: "repeat" button → marks SO as repeater, shows constraint options
- D_Hierarchy: badge repeated children with dim styling (e.g.
×3)
Phase 5 — Examples / test
- build a stairs preset in the library (step SO + repeater parent)
- build a studs preset (stud SO + repeater parent with code-derived spacing)
Design insight — no modes
stairs are just linear repeaters. a staircase is a parent SO rotated at atan(height/length), with steps repeating linearly along the hypotenuse. studs, joists, and ramps are the same — linear repeat, different template shape and parent orientation. no mode field needed.
the user's mental model: "i want a reasonable staircase from origin to extent." they set the envelope (height × length), the system picks a count that puts the gap (rise) within an acceptable range. rise and run fall out of the geometry — they're feedback, not input.
what distinguishes use cases is the template shape + parent rotation + constraints:
| Thing | Template shape | Parent rotation | Constraint |
|---|---|---|---|
| Stairs | step (wide, shallow) | atan(h/l) on repeat_axis | gap_min/gap_max (rise range) |
| Studs | stud (tall, narrow) | none | spacing (discrete: 12/16/24 OC) |
| Floor joists | joist (long, shallow) | none | spacing (discrete: 12/16/24 OC) |
| Roof joists | joist (long, shallow) | pitch angle | spacing (discrete: 12/16/24 OC) |
validation — phantom stairs: extend the stair pattern by one in each direction. step[-1] should sit just below floor. step[count] should land just beyond the top. if both match, the geometry is right.
Phase 6 — Repeater expansion
Phase 6.1 — Data model
- add
repeat_axis?: 0 | 1to repeater — which axis clones march along (never 2) - add
gap_min?: number/gap_max?: numberto repeater — constrained spacing range (mm) - add
spacing?: numberto repeater — discrete spacing quick-pick (mm) - no mode field — linear handles everything
- serialize/deserialize new fields in Smart_Object
- migrate: v6→v7, existing repeaters unchanged (new fields are all optional)
Phase 6.2 — Engine: resolve_gap + repeat_axis
-
resolve_gap(total_length, gap_min, gap_max)— find count wheretotal_length / countfalls within [min, max]; prefer even division, fall back to closest -
sync_repeaterusesrepeat_axiswhen set (falls back to current auto-detect from largest template dimension) - when
gap_min/gap_maxset: count =resolve_gap(parent_length_along_repeat_axis, gap_min, gap_max) - when
spacingset: count =parent_length / spacing(rounded) - repeater fields update via spread (not overwrite)
Phase 6.3 — UX
- repeat_axis selector (x / y toggle) in D_Selection when repeater is active
- gap range inputs (min/max) — shown when gap_min/gap_max are set; prefilled 152.4 / 203.2 mm for stairs
- spacing quick-pick: segmented control (12" / 16" / 24") — shown when spacing is set
- display: resolved gap, count, total length
Phase 6.4 — Presets
- stairs .di file: root SO (stair envelope, rotated) + template step child + repeater with gap_min/gap_max
- studs .di file: root SO (wall frame) + template stud child + repeater with spacing + repeat_axis
See [user manual/repeaters](../../user manual/repeaters.md)
Phase 6.5 — Test
- stairs: phantom stair validation, gap clamping, envelope resize adjusts count
- studs: three spacings, wall length changes, axis toggle
- joists: same as studs but template is horizontal — verify repeat works
- existing linear repeaters unchanged (regression)
Phase 7 — Improvements
- firewall blocking: horizontal member at mid-height between studs
- i want to rotate a stretch by swapping x and y axes
- for the template and for the repeater
- formulas and offsets
- button "duplicate" along side "delete all children"
- move repeaters UX -> below action row (delete ... show) in d hierarchy
- change 13 × 1' 4" (x) = 7' 7 1/2" -> 7 clones, 6 fire blocks
- stairs s&l increase to include "phantom" (hidden) top stair and bottom landing stair
- bookend clone count: display uses natural counting (includes template)
- fireblock dimensionals: first fireblock shows repeat-axis dimensional; last fireblock shows one too if shortened (different length than first)
- witness lines: perspective-correct per-vertex 3D projection (not forced parallel)
Regenerating from the library
clones are not stored. only the parent (with its repeater config) and the template (first child) are saved. on load, propagate_all() fires the post-propagate hook, which calls sync_repeater on every repeater — regenerating all clones from the template.
how stairs.di encodes diagonal without a mode flag:
the repeater config has repeat_axis: 1 (y) and gap_axis: 2 (z). when those differ, the engine knows it's diagonal:
gap_length = parent z height— the rise envelopecount = resolve_gap(gap_length, gap_min, gap_max)— how many steps fit the rise range- because
gap_ai !== repeat_ai, each clone is offset along two axes:stepalong y (run) — distributes treads across the rungap_stepalong z (rise) — distributes treads up the height
- clone i:
template_y + step × (i+1),template_z + gap_step × (i+1)
the diagonal is emergent — the engine doesn't know about "stairs." it just sees that the gap constraint axis differs from the repeat axis, so it offsets clones along both. each tread steps forward and up → staircase.
is_diagonal exists only for the UI — so the straight/diagonal segment knows which button to highlight, and which creation path to take when re-repeating.