House parts (stdlib isokit/house): house_wall drawn along a line, with ends that adapt at corners, T-junctions and straight runs so no two walls overlap; window and door that sit on a wall and cut its opening (door styles panel, glass, double, sliding, opening; swing and hinge by rotation); floor (boards, tiles, carpet, stone); roof (hip, gable, flat, optional solar); stairs. Furniture (stdlib isokit/furniture): chair, sofa / armchair, table with chairs, desk with monitor and chair, TV, bed, cupboard, bookshelf, kitchen run, bathtub, shower, toilet, washbasin, plant, rug, floor lamp. Cut-open levels: on gf / 1f the walls in front of a room are cut low (rooms are found by their floors), the roof becomes an outline on the floor, and storeys above are left out. A part's storey comes from z (storey_h, default 3 m). Scripting: objects_of, objects_at, objects_on, mount_of, mounted, frame, to_world, to_local, local_of. Prefab meta: shape (line / rect), mount, storey. Studio: drag to draw lines and rectangles (walls, roads, fences, hedges, floors, areas, buildings, roofs). The Room tool draws a floor plus walls, without doubling walls that already stand on an edge. Windows and doors snap onto walls with a hover preview, and they move, turn, copy and delete with their wall in one undo step. New parts land on the storey the view shows. Palette categories collapse, and the palette scrolls apart from the object list. The MCP instructions explain house building, and list_prefabs shows sits_on. examples/house is a furnished bungalow; the web Studio gets a House demo button. Tests: wall corners and junctions, zero depth-sort cycles in every view, cut levels, and the house demo in every view; check_studio covers the room / window / door / storey flow, check_web the House demo.
8.9 KiB
Scripting and prefabs
Scenes and prefabs are written in Rhai, 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:
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(withchoices),material(a theme material, optionally withchoices),text. Writedef:for the default (defaultis a reserved word in Rhai). - How the Studio places it:
shape: "line"(drag along the map: setsrotand the length propw),shape: "rect"(drag a rectangle: setswandd), default a click.mount: ["house_wall"]: the prefab sits on objects of those types; the Studio snaps it onto the nearest one and moves, turns, copies and deletes it with its host.storey: true: placed on the storey the view shows (level1fputs it at z = 1 ×storey_h). oholdsid, 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()andfocus()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 (objects(): every document object, props resolved) |
objects_of(types) |
objects of a type or a list of types shown on this level |
objects_at(types, x, y) |
… whose footprint (p.w × p.d around the origin) holds the world point |
objects_on(types, x, y, tol) |
… whose centre line (local x, length p.w) passes within p.t / 2 + tol of the point, with u, v = the point in their frame |
mount_of(o, hosts[, tol]) |
the host o sits on (nearest centre line within p.t / 2 + tol, default 0.3 m, same height), with u, v = o's position in the host's frame; () if none |
mounted(h, types, hosts[, tol]) |
the objects of types whose host is h, each with u, v |
frame(o), frame(x, y, z, rot) |
draw in another frame from here on (a window draws in its wall's frame) |
to_world(x, y), to_local(x, y), local_of(o, x, y) |
between the current frame (or o's) and world plan coordinates |
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() |
<g> 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; for house
parts (stdlib/house.rhai, stdlib/furniture.rhai): storey_of(z),
house_view(o) (0 closed, 1 on the storey cut open, 2 above it: leave out),
wall_top(w), opening_at(o), s.slab(a, b, th, z0, z1, mat),
s.chair_at(x, y, bx, by, mat), s.office_chair(x, y, mat).
House parts
house_wall, window, door, floor, roof, stairs and the furniture
prefabs work together through the document, not through a parent object:
- A window or door at a point on a wall's centre line (within the wall's half
thickness + 0.3 m, same height) sits on that wall (
mount_of). The wall leaves a gap for it (mounted), the window draws the opening in the wall's frame with the wall's height, thickness and material. On no wall it stands alone as a short wall piece. Its rotation relative to the wall picks a door's swing side (0, 90: towards the wall's +y side) and hinge (0, 180: at the low end). - Wall ends adapt to the walls they meet, so no two walls overlap (the depth sort needs them apart): along a line two walls just meet, at a T the stem stops at the other wall's face, at a corner the wall along world x runs through and the other stops at it.
- A part's storey is
floor((z + 0.5) / storey_h)(storey_h: project parameter, default 3 m). On levelgf(1f, …) the parts on that storey are cut open: a wall stays full height only when a floor lies on the viewer's side of it and none behind it (it is a back wall), all others are cut to 1 m. Roofs on that storey become an outline on the floor, parts on storeys above are left out, the storeys below stay closed. - Ground-level floors and rugs go to the
floorlayer; upper floors are slabs whose top is at the object's z. Furniture starts 0.02 m above its z.
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 checkreports 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
floorlayer (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:
[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.