From c8becc2e595c557516e59c07e76217dfc88999a4 Mon Sep 17 00:00:00 2001 From: grabowski Date: Sun, 25 Oct 2026 18:46:47 +0700 Subject: [PATCH] Point handles for routes and outlines; Area corners; bus goes round right Studio: one selected object with pts (a route, an outlined area) shows a handle on each point and a + in the middle of each side: drag a point to move it, drag a + to add one there, double-click a point to remove it (open routes keep 2, outlines 3). Points snap to the grid and onto wall, fence and road ends, at any angle; one undo step each. Handles keep their screen size (new viewer callback onView on every redraw) and follow re-renders and selection. The viewer captures the pointer, so a double- click targets the drawing: the last point pressed is remembered instead. Prefab meta `outline`: the prefab draws pts as its outline. Area has it: Edit corners turns its rectangle into four points, Rectangle again removes them. Routes: where a path doubles back, the lane offset puts a point on each side, so it rounds into a U-turn instead of a diagonal across the lanes. The streets demo bus now runs east, three quarters round the roundabout (clockwise, left-hand traffic), down south street, turns and comes back by the short way, instead of going round the wrong way on its way out. Tests: Rust 81 (a doubling-back route: each mover once, no sort cycles), Studio 134 (corners, move, add, remove, undo), web 100. --- README.md | 1 + crates/isokit-script/src/doc.rs | 4 + crates/isokit-script/src/tests.rs | 20 ++++- crates/isokit-script/stdlib/ground.rhai | 4 +- crates/isokit-script/stdlib/routes.rhai | 11 ++- docs/ROADMAP.md | 5 +- docs/SCRIPTING.md | 9 +++ examples/streets/scene.json | 2 +- studio/studio.css | 6 ++ studio/studio.js | 101 +++++++++++++++++++++++- tools/check_studio.py | 35 ++++++++ viewer/isokit-view.js | 2 + 12 files changed, 189 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 488593e..9d331d1 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,7 @@ Try the demos: `isokit studio examples/demo` (a works site), | Several | Ctrl+click (map or list) adds or takes out one, Shift+click in the list a range, Shift+drag on the map a box, Ctrl+A all; then drag one to move them all, arrows, R, Ctrl+D, Delete and the property form work on all of them | | Group | select several, then ▤ Group or Ctrl+G; Ctrl+Shift+G ungroups. In the object list a group folds open and shut, a click on it selects all of it (drag one on the map to move them all), its eye and lock hide or lock all of it; drag rows into or out of a group | | Order | the object list is the drawing order of ground pieces (areas, beds, ponds, roads): higher lies on top. Drag rows, or ▲ ▼ / Ctrl+] Ctrl+[ (with Shift: to the front / back) | +| Points | a selected route or outlined area shows its points: drag one to move it, drag a + (middle of a side) to add one, double-click one to remove it; points snap to the grid and onto wall, fence and road ends. An Area's Edit corners turns its rectangle into points | | Move | drag it, or arrow keys by the snap step (0.1 / 0.5 / 1 / 5 m) | | Turn | R / Shift+R or ⟲ ⟳ (quarter turns), or type any angle in Rotation | | Edit | the right panel: size, storeys, colours, solar, tooltip text, label, levels | diff --git a/crates/isokit-script/src/doc.rs b/crates/isokit-script/src/doc.rs index 7167e98..9ab5f66 100644 --- a/crates/isokit-script/src/doc.rs +++ b/crates/isokit-script/src/doc.rs @@ -155,6 +155,10 @@ pub struct PrefabMeta { /// Studio puts one in each corner of a run drawn corner by corner. #[serde(default, skip_serializing_if = "String::is_empty")] pub corner: String, + /// The prefab draws the object's `pts` as its outline when it has them (areas): + /// the Studio offers to turn its rectangle into corners that can be dragged. + #[serde(default, skip_serializing_if = "is_false")] + pub outline: bool, #[serde(default)] pub props: Vec, /// Module the prefab comes from ("isokit/site", "lib/kiosk.rhai"). diff --git a/crates/isokit-script/src/tests.rs b/crates/isokit-script/src/tests.rs index b29e9c5..38f327f 100644 --- a/crates/isokit-script/src/tests.rs +++ b/crates/isokit-script/src/tests.rs @@ -936,8 +936,24 @@ fn route_movers_show_once_each_at_every_moment() { ); deer.rot = 30.0; deer.pts = Some(vec![[-6.0, 0.0], [6.0, 0.0]]); + // a loop that doubles back twice (a spur up to a stop and back, a U-turn at the west end): + // each way keeps to its side, so the strips never overlap + let mut spur = obj( + "spur", + "route", + 0.0, + 40.0, + json!({"kind": "bus", "n": 2, "closed": true}), + ); + spur.pts = Some(vec![[-20.0, 0.0], [20.0, 0.0], [20.0, 15.0], [20.0, 0.0]]); let doc = Doc { - objects: vec![cars, walk, deer, obj("t1", "tree", 0.0, -14.0, json!({}))], + objects: vec![ + cars, + walk, + deer, + spur, + obj("t1", "tree", 0.0, -14.0, json!({})), + ], ..Default::default() }; for view in 0..4u8 { @@ -947,7 +963,7 @@ fn route_movers_show_once_each_at_every_moment() { assert_eq!(out.cycles, 0, "view {view}: depth-sort cycles"); // one