Multi-select, select joined walls and fences, pond with swimming fish
Deploy web Studio / deploy (push) Successful in 1m27s

Selection: S.sel stays the object picked last; S.selIds holds everything
selected.
- In the object list, Ctrl+click adds or removes an object and
  Shift+click selects a range.
- On the map, Ctrl/Shift+click toggles an object and Shift+drag draws a
  box that selects what lies wholly inside it (locked and hidden objects
  are left out).
- Ctrl+A selects everything that is not locked; Esc selects nothing.
- Clicking a wall or fence a second time selects every piece of the same
  type joined to it; another click goes back to that one piece.
- Dragging one selected object moves them all, together with the windows
  and doors on them, as one undo step. Arrow keys, R (turns the group
  about its middle), Ctrl+D, Delete, Lock and Hide all work on the whole
  selection.
- When all selected objects share a type, the property form edits them
  all; fields where they differ show "mixed".
- The agent's system prompt lists the selected objects.

The viewer outlines several objects with selectObj([ids]) and passes the
click event to onPick. A batch op now answers `ids`, one per operation,
so the copies from a group duplicate get selected.

Pond (Nature): natural, round or square, with a stone or grass edge,
fish, lily pads, spreading ripples and reed clumps. The fish come from a
new Swim primitive (script: s.swim). One matrix maps the object's frame
to the screen. Each fish turns round its ellipse with a CSS rotation,
so off-screen pausing and reduced motion apply as for the other
animations. The house demo gets a koi pond in the back garden.

Tests: ponds in every shape and rotation have the right number of fish,
no NaN and no depth-sort cycles, and stay inside their rectangle.
check_studio covers list and map multi-select, a group drag undone in
one step, joined-fence selection, a mixed-value form applied to all, box
select, delete and undo, Ctrl+A, Esc, and a new pond's fish.
This commit is contained in:
grabowski committed 2026-10-24 18:36:39 +07:00
1 parent e4017ce2a8
commit 19f4c83ae8
21 files changed
+759 -59

No files matched your search

+57
View File
@@ -485,6 +485,63 @@ fn fences_join_like_walls_in_every_design() {
.contains("iso-seam"));
}
#[test]
fn ponds_have_fish_swimming_in_every_shape() {
let r = Runtime::new(&Bundle::default()).unwrap();
for (shape, edge, rot) in [
("natural", "stone", 0.0),
("round", "grass", 90.0),
("square", "stone", 0.0),
] {
let mut doc = Doc::default();
let mut o = obj(
"pond",
"pond",
3.0,
-2.0,
json!({"w": 9, "d": 5, "shape": shape, "edge": edge, "fish": 6, "lilies": 4}),
);
o.rot = rot;
doc.objects.push(o);
for view in 0..4u8 {
let b = r.build("site", view, &cfg(), &doc).unwrap();
assert!(
b.scene.warnings.is_empty(),
"{shape}: {:?}",
b.scene.warnings
);
let out = render(&b.scene, view, &RenderOpts::default());
assert_eq!(
out.html.matches("iso-a iso-swim").count(),
6,
"{shape}/{view}"
);
assert!(!out.html.contains("NaN"), "{shape}/{view}");
assert!(out.html.contains("iso-fish iso-koi") && out.html.contains("iso-lily"));
assert!(out.html.contains("iso-a iso-rip"));
assert_eq!(out.cycles, 0);
}
// the pond stays inside its 9 x 5 m (turned: 5 x 9 m) rectangle; the reeds stand on its edge
let bx = r.build("site", 0, &cfg(), &doc).unwrap().boxes["pond"];
let (hw, hd) = if rot == 0.0 { (4.5, 2.5) } else { (2.5, 4.5) };
assert!(
bx[0] >= 3.0 - hw - 1e-6
&& bx[3] <= 3.0 + hw + 1e-6
&& bx[1] >= -2.0 - hd - 1e-6
&& bx[4] <= -2.0 + hd + 1e-6,
"{shape}: {bx:?}"
);
}
// no fish: no school
let mut doc = Doc::default();
doc.objects
.push(obj("p", "pond", 0.0, 0.0, json!({"fish": 0})));
let b = r.build("site", 0, &cfg(), &doc).unwrap();
assert!(!render(&b.scene, 0, &RenderOpts::default())
.html
.contains("iso-swim"));
}
#[test]
fn site_walls_join_at_corners() {
let r = Runtime::new(&Bundle::default()).unwrap();