# Scripting and prefabs Scenes and prefabs are written in [Rhai](https://rhai.rs/book/), a small scripting language embedded in isokit. Nothing is compiled: save the file and the Studio redraws. ## A prefab A prefab is an object type that can be placed in the Studio. It is two functions in a module that is listed in `isokit.toml` under `prelude`: ```rust fn meta_kiosk() { // label, palette group and property form #{ label: "Gate kiosk", category: "Site", icon: "⌂", help: "…", props: [ #{ key: "h", type: "float", def: 2.8, min: 2.2, max: 4.0, step: 0.1, label: "Height (m)" }, #{ key: "boom", type: "bool", def: true, label: "Boom barrier" }, ] } } fn prefab_kiosk(o) { // draw around (0, 0) in the object's own frame let s = sprite(); // sprite ids default to the object id s.box(-1.4, -1.1, 1.4, 1.1, 0.0, o.p.h, "brick", #{ top: "roof3" }); } ``` - Property types: `float`, `int`, `bool`, `choice` (with `choices`), `material` (a theme material, optionally with `choices`), `text`. Write `def:` for the default (`default` is a reserved word in Rhai). - `o` holds `id, type, name, x, y, z, rot, p` (the props with defaults filled in), `pts`, `tag`, `info`, `level`. - The engine sets the object's frame before calling the prefab. All coordinates are local: the origin is the object's position, and local x and y turn with `rot`. `vis()`, `dv()`, `tag()` and `focus()` use the frame too. - A prefab with the same name in the project overrides the built-in one. - If a prefab fails, the engine logs a warning for that object and still draws the rest. ## Drawing API Numbers can be ints or floats. Classes are CSS classes without the `iso-` prefix (`"wall"` becomes `iso-wall`). A material sets the face colour, which is shaded automatically by face direction. ### Scene | Function | | |---|---| | `sprite()` / `sprite(id)` / `sprite(id, cls)` | new sprite (the unit of the depth sort) in the current layer | | `ground()` | the shared unsorted ground sprite | | `layer("ground" \| "floor" \| "main")`, `layer(name, #{sort, zfirst, cls, before})` | where new sprites go | | `tag(x, y, z, text[, prio[, id[, kind]]])` | label; `kind` "signtag" / "warntag" always shows | | `info(id, name[, detail])`, `info(id, #{name, detail, rows: [[k, v], …]})` | tooltip | | `focus(x, y, z)`, `bounds(x0, y0, x1, y1[, depth])`, `defs(svg)`, `warn(msg)` | framing and extras | | `level()`, `rot()`, `cfg()`, `cfg(name)`, `objects()` | inputs | | `vis(nx, ny)` | is a face with that outward normal turned to the viewer? | | `dv(dx, dy, dz)` | screen vector of a world vector (CSS motion) | | `along_x()` | should a mover run along x in this rotation? | | `hash(x)`, `fmt(n)`, `round1(x)`, `fmin`, `fmax`, `clamp`, `px()` | helpers | | `rect(x0, y0, x1, y1)`, `ellipse(cx, cy, a, b, n)`, `rsegs(r)`, `sides(x0, y0, x1, y1)` | geometry | ### Sprite methods (chainable) | Method | | |---|---| | `box(x0, y0, x1, y1, z0, z1, mat[, #{id, top, side, no_top, no_side}])` | axis-aligned box | | `prism(pts, z0, z1, mat[, opts])` | convex counter-clockwise footprint; z0/z1 one value or one per vertex | | `cyl(cx, cy, r, z0, z1, mat[, opts])`, `frust(cx, cy, r0, r1, z0, z1, mat[, sides[, opts]])` | round things | | `slope(x0, x1, y0, y1, za, zb, th, mat)` | ramp / stair flight | | `face(pts3, mat[, #{id, two_sided}])` | any planar polygon, shaded and culled by its normal (roof slopes, gables) | | `poly(polys3, cls[, id])` | 3D polygons as one path (solar modules) | | `flat(poly or polys, z, cls[, id])` | flat polygon(s); later ones cut holes | | `open(a, b, [[u0, u1, z0, z1], …], cls[, id])` | windows / doors on the face a→b (outward normal on the right) | | `lines(segs, z, cls)`, `disc(cx, cy, z, r, cls[, id])` | markings | | `circle(x, y, z, r_px, cls[, dx, dy])`, `stem(x, y, z0, z1, w_px, cls)` | screen-space details (crowns, poles) | | `person(x, y, z[, cls])`, `sit(x, y, z, cls)`, `head(x, y, z, r)`, `led(x, y, z[, cls])` | figures | | `puffs(x, y, z[, kind[, n, dur]])`, `fan(x, y, z, r)` | animated steam / smoke / fans | | `seq([[x, y, \|q\| …], …])` | items drawn back to front by their anchor | | `group(cls[, css], \|q\| …)`, `begin(cls[, css])` … `end()` | `` around parts (animation) | | `ext(x, y, z)`, `ext_box(…)` | widen the sort box | | `raw([x, y, z,] svg)` | raw SVG (`{uid}` becomes the view's id) | Library helpers (`stdlib/*.rhai`): `s.tree_at(x, y, r)`, `s.car(x0, y0, x1, y1, cls)`, `s.drive(dx, dy, dur, delay)` … `s.end()`, `cut_walls(...)`, `win_list(...)`, `floor_of(level)`, `building_windows`, `gable_roof`, `flat_roof_kit`. ## Rules that keep the depth sort right These are lessons from the CNX/Mooban views, and the engine relies on them: - **One sprite per thing that can stand in front of another.** Split long walls, fences and solar rows into pieces (≤ 30 m). One big sprite overlapping many others creates sort cycles (`isokit check` reports them). - **Things under a cover belong in the cover's sprite**: draw cars under a carport before its posts and roof (see `prefab_carport`). - **Floors of cut-open storeys go in the `floor` layer** (ground floor) or in the same sprite as the storey below. Start walls and furniture standing on them a little above the floor (+0.02 m). - **Movers** (`drive`) run along the axis the sort compares second (`along_x()`). Along the other axis they stay still. - Street furniture stays off drive areas, and pipe runs end on walls or supports. ## Themes `theme.toml` stacks on the built-in palette: ```toml [palette.dark] brick = "#8a4f3e" [palette.light] brick = "#b8664c" [materials] brick = "brick" # class iso-brick, colour var(--iso-brick) [rules] ".iso-svg .iso-sign2" = "fill: #ffcc00; stroke: none" ``` `isokit check` warns about any class in the drawing that has no style.