Build your own house: walls, windows, doors, floors, roof, stairs, furniture
Deploy web Studio / deploy (push) Successful in 3m27s

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.
This commit is contained in:
grabowski committed 2026-10-24 17:39:04 +07:00
1 parent 84309a7a08
commit 95ea4a8512
26 files changed
+2137 -64

No files matched your search

+12 -3
View File
@@ -16,7 +16,12 @@ const INSTRUCTIONS: &str = "isokit edits an isometric scene (a site plan drawn a
World coordinates are metres: x east, y north, z up. The scene is a list of objects; each has a type (a prefab), \
a position (x, y = centre of its footprint), a rotation in 90 degree steps and props from the prefab's schema. \
Start with project_info and list_prefabs, then add_object / update_object. Use render_view (summary) to see where \
everything ended up and check_project to find problems. Edits are saved at once and show live in an open Studio.";
everything ended up and check_project to find problems. Edits are saved at once and show live in an open Studio. \
Houses are built from parts (categories House and Furniture): a floor per room (w x d), a house_wall along each edge \
(x, y = middle of the wall line, rot 0 runs along x, rot 90 along y, w = length; walls meet at their centre lines), \
then windows and doors with x, y on a wall line (they sit on that wall and cut their opening; a door swings towards \
the wall's +y side, rot + 180 swings it the other way), a roof (w x d = outer size of the walls) and furniture \
(fronts face local -y, backs go against a wall). Level gf shows the house cut open; a part's storey is its z / 3 m.";
fn tool(name: &str, description: &str, props: Value, required: &[&str]) -> Value {
json!({ "name": name, "description": description,
@@ -140,7 +145,7 @@ pub fn call_tool<S: Store>(ws: &mut Workspace<S>, name: &str, a: &Map<String, Va
.iter()
.filter(|m| c.as_ref().is_none_or(|c| m.category.eq_ignore_ascii_case(c)))
.map(|m| {
json!({ "type": m.kind, "label": m.label, "category": m.category, "help": m.help,
let mut v = json!({ "type": m.kind, "label": m.label, "category": m.category, "help": m.help,
"props": m.props.iter().map(|p| {
let mut d = json!({ "key": p.key, "type": p.kind, "default": p.default });
if let Some(x) = p.min { d["min"] = json!(x); }
@@ -148,7 +153,11 @@ pub fn call_tool<S: Store>(ws: &mut Workspace<S>, name: &str, a: &Map<String, Va
if !p.choices.is_empty() { d["choices"] = json!(p.choices); }
if !p.label.is_empty() { d["label"] = json!(p.label); }
d
}).collect::<Vec<_>>() })
}).collect::<Vec<_>>() });
if !m.mount.is_empty() {
v["sits_on"] = json!(m.mount);
}
v
})
.collect();
text(json!(list))
+26
View File
@@ -72,6 +72,32 @@ fn demo_passes_the_check() {
assert!(f.is_empty(), "{f:?}");
}
/// The house demo: every view draws every object without warnings or depth-sort cycles
/// (walls must not overlap), and the project passes the check.
#[test]
fn house_demo_renders_every_view_cleanly() {
let p =
Project::load(Path::new(env!("CARGO_MANIFEST_DIR")).join("../../examples/house")).unwrap();
assert!(p.runtime.is_ok(), "{:?}", p.runtime.as_ref().err());
for l in p.manifest.levels() {
for rot in 0..4 {
let v = p.render(&l.id, rot, None, false).unwrap();
assert!(v.warnings.is_empty(), "{}/{rot}: {:?}", l.id, v.warnings);
assert_eq!(v.cycles, 0, "{}/{rot}: depth-sort cycles", l.id);
assert!(!v.html.contains("NaN"));
for o in &p.doc.objects {
assert!(
v.boxes.contains_key(&o.id),
"{} not drawn in {}/{rot}",
o.id,
l.id
);
}
}
}
assert!(check::check(&p).is_empty(), "{:?}", check::check(&p));
}
#[test]
fn static_page_and_svg_export() {
let p = Project::load(demo()).unwrap();