Streets: sidewalks, road curves, junctions and roundabouts
Deploy web Studio / deploy (push) Successful in 4m31s
Deploy web Studio / deploy (push) Successful in 4m31s
Roads take sidewalks (none, both, left, right): 12 cm high paving with kerb faces drawn where they face the viewer. Road curve (radius, angle, left/right) carries a street round a bend; centre lines are true arcs when cut and offset, so curve joints are seamless. A road ending on another road's centre line opens that road's kerb and sidewalk (T junction); two roads ending at one corner close it. Roundabout: ring radius and width, one or two lanes, up to four arms with flared kerbs, splitter islands, give-way lines on the entry side (traffic on the left or right) and a central island with apron. Studio: a road run drawn corner by corner gets a curve in each corner (prefab meta `corner`). Tooltips list the streets each one meets. Engine: open `polyline` primitive (Prim::Poly open) so dashes run on round curves. examples/streets and a Streets demo in the web Studio; MCP guidance, docs. ROADMAP: backlog 5-7 done; multi-select and the hosted copy marked done. Tests: Rust 72, Studio 113, web 95.
This commit is contained in:
1 parent
2442966743
commit
c91d63663b
24 files changed
+989
-29
No files matched your search
@@ -149,6 +149,7 @@ def main():
|
||||
(web / "demo.json").write_text(json.dumps(demo_files(), ensure_ascii=False), encoding="utf-8")
|
||||
(web / "house.json").write_text(json.dumps(demo_files("house"), ensure_ascii=False), encoding="utf-8")
|
||||
(web / "factory.json").write_text(json.dumps(demo_files("factory"), ensure_ascii=False), encoding="utf-8")
|
||||
(web / "streets.json").write_text(json.dumps(demo_files("streets"), ensure_ascii=False), encoding="utf-8")
|
||||
|
||||
h = hashlib.sha256()
|
||||
for p in sorted((out / "assets").rglob("*")):
|
||||
|
||||
@@ -471,6 +471,38 @@ with sync_playwright() as p:
|
||||
pg.evaluate("__studio.select(null)")
|
||||
pg.wait_for_timeout(300)
|
||||
|
||||
# a road run drawn corner by corner off the main road: a curve rounds its corner, the road it
|
||||
# starts on gets a junction (its south kerb opens), and one undo step takes the run back
|
||||
pg.evaluate("__studio.view.focus([6, -27, 0], 2.2)")
|
||||
pg.wait_for_timeout(400)
|
||||
n4 = len(doc()["objects"])
|
||||
kerbs = lambda: pg.locator('.iso-svg g[data-obj="road"] .iso-kerb').count()
|
||||
k0 = kerbs()
|
||||
pg.locator('.st-tile[data-type="road"]').click()
|
||||
for x, y in [(-2, -19.3), (-2.1, -32), (20, -32.1)]:
|
||||
pg.mouse.click(*at(x, y))
|
||||
pg.wait_for_timeout(150)
|
||||
pg.mouse.move(*at(20, -36))
|
||||
pg.wait_for_timeout(150)
|
||||
check("corners rounded" in pg.locator("#hint").inner_text(), "a road run says its corners get rounded: " + pg.locator("#hint").inner_text()[:50])
|
||||
pg.keyboard.press("Enter")
|
||||
pg.wait_for_timeout(1200)
|
||||
run = doc()["objects"][n4:]
|
||||
got = sorted((o["type"], o["x"], o["y"], o.get("rot", 0), o["props"].get("w", o["props"].get("r"))) for o in run)
|
||||
want = sorted([("road", -2, -20.75, 90, 2.5), ("road", 14, -32, 0, 12), ("road_curve", -2, -22, 270, 10)])
|
||||
check(got == want, f"the run's corner gets a curve: {got}")
|
||||
rv = get("/api/render?level=site&rot=0")
|
||||
stub = next((o["id"] for o in run if o["type"] == "road" and o["x"] == -2), None)
|
||||
sb = rv["boxes"].get(stub, [0] * 6)
|
||||
check(abs(sb[4] + 19.5) < 1e-6 and kerbs() == k0 + 1,
|
||||
f"the run starts on the main road's centre line and opens its kerb: {sb[4]:.2f}, {k0} -> {kerbs()} kerb lines")
|
||||
pg.evaluate("__studio.select(null)")
|
||||
pg.wait_for_timeout(300)
|
||||
pg.screenshot(path=str(OUT / "studio-road-run.jpg"), type="jpeg", quality=80)
|
||||
pg.locator("#undo").click()
|
||||
pg.wait_for_timeout(900)
|
||||
check(len(doc()["objects"]) == n4 and kerbs() == k0, "one undo takes the road run back")
|
||||
|
||||
# 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")
|
||||
|
||||
@@ -326,6 +326,22 @@ try:
|
||||
check("(from robot-b)" in loads and flips == 32, f"robot arms pass the goods on ({flips} poses): " + loads)
|
||||
shot(pg, "web-factory.jpg")
|
||||
|
||||
# ---------------------------------------------------------------- the streets demo: sidewalks, curves, a roundabout
|
||||
pg.locator("#wb-projects").click()
|
||||
pg.locator("#wb-nstreets").click()
|
||||
pg.wait_for_load_state()
|
||||
ready(pg)
|
||||
pg.wait_for_timeout(500)
|
||||
ns = len(doc(pg)["objects"])
|
||||
walks = pg.evaluate("() => document.querySelectorAll('.iso-svg .iso-g-walk').length")
|
||||
gw = pg.evaluate("() => document.querySelectorAll('.iso-svg .iso-giveway').length")
|
||||
check(title(pg) == "isokit streets" and ns > 25 and walks > 15 and gw == 1 and pg.evaluate("() => __studio.S.warnings.length") == 0,
|
||||
f"Streets demo opens a town quarter ({ns} objects, {walks} sidewalk pieces, give-way lines, no warnings)")
|
||||
meets = pg.evaluate("() => Object.fromEntries((__studio.view.info('cross-st') || {rows: []}).rows)['Meets'] || ''")
|
||||
ring = pg.evaluate("() => Object.fromEntries((__studio.view.info('rb') || {rows: []}).rows)['Arms'] || ''")
|
||||
check(meets == "east-ave, park-rd" and ring.startswith("4:"), f"streets know what they meet: cross-st meets {meets}; roundabout arms {ring}")
|
||||
shot(pg, "web-streets.jpg")
|
||||
|
||||
# ---------------------------------------------------------------- a system asking for less motion
|
||||
# (Windows: Animation effects off): the view starts still, but the button stays and turns it on
|
||||
rctx = b.new_context(viewport={"width": 1200, "height": 800}, reduced_motion="reduce")
|
||||
|
||||
Reference in new issue
Block a user