Point handles for routes and outlines; Area corners; bus goes round right
Deploy web Studio / deploy (push) Successful in 1m4s

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.
This commit is contained in:
grabowski committed 2026-10-25 18:46:47 +07:00
1 parent 540b37020c
commit c8becc2e59
12 files changed
+189 -11

No files matched your search

+35
View File
@@ -661,6 +661,41 @@ with sync_playwright() as p:
pg.evaluate("__studio.select(null)")
pg.wait_for_timeout(300)
# point handles: the yard (an area, 24 x 18 m at 14, 4) gets corners; one is dragged, a point is
# added by dragging the + of a side and removed again by a double-click; undos restore the rectangle
pg.evaluate("__studio.view.focus([14, 4, 0], 1.6)")
pg.wait_for_timeout(400)
pg.evaluate("__studio.select('yard')")
pg.wait_for_timeout(400)
pg.locator('#right button[data-act="pts"]').click()
pg.wait_for_timeout(900)
y0 = obj("yard").get("pts")
nh = pg.evaluate("() => [document.querySelectorAll('.st-hpt').length, document.querySelectorAll('.st-hmid').length]")
check(y0 == [[-12, -9], [12, -9], [12, 9], [-12, 9]] and nh == [4, 4], f"Edit corners: the yard becomes 4 points with handles: {y0}, {nh}")
def handle_drag(a, b):
pg.mouse.move(*at(*a))
pg.mouse.down()
pg.mouse.move(*at((a[0] + b[0]) / 2, (a[1] + b[1]) / 2), steps=4)
pg.mouse.move(*at(*b), steps=4)
pg.mouse.up()
pg.wait_for_timeout(900)
handle_drag((26, 13), (24, 16))
y1 = obj("yard")["pts"]
handle_drag((14, -5), (14, -8))
y2 = obj("yard")["pts"]
pg.mouse.dblclick(*at(14, -8))
pg.wait_for_timeout(900)
y3 = obj("yard")["pts"]
check(y1[2] == [10, 12] and y2 == [[-12, -9], [0, -12], [12, -9], [10, 12], [-12, 9]] and y3 == [[-12, -9], [12, -9], [10, 12], [-12, 9]],
f"handles move a corner {y1[2]}, add a point on a side {y2[1] if len(y2) > 4 else y2}, a double-click removes it ({len(y3)} left)")
pg.screenshot(path=str(OUT / "studio-handles.jpg"), type="jpeg", quality=80)
for _ in range(4):
pg.locator("#undo").click()
pg.wait_for_timeout(700)
check("pts" not in obj("yard"), "four undos give the yard its rectangle back")
pg.evaluate("__studio.select(null)")
pg.wait_for_timeout(300)
# an agent edits over MCP: the open Studio follows
init = mcp({"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {"protocolVersion": "2025-06-18", "capabilities": {}, "clientInfo": {"name": "test", "version": "0"}}})
check(init["result"]["serverInfo"]["name"] == "isokit", "MCP initialize")