""" Self-contained SVG isometric studio scene for Gradio — zero external dependencies. No Three.js, no CDN. Pure SVG + HTML + vanilla JS. """ import json from pathlib import Path def _load_characters_js() -> str: p = Path(__file__).parent / "sandbox_cache" / "characters.js" return p.read_text() if p.exists() else "" _CHARACTERS_JS = _load_characters_js() # --------------------------------------------------------------------------- # Isometric helpers # --------------------------------------------------------------------------- def _pts(pairs, ox=0, oy=0): return " ".join(f"{x+ox},{y+oy}" for x, y in pairs) def _darken(hex_color: str, factor: float = 0.65) -> str: h = hex_color.lstrip("#") if len(h) != 6: return hex_color r, g, b = int(h[:2], 16), int(h[2:4], 16), int(h[4:], 16) return f"#{int(r*factor):02x}{int(g*factor):02x}{int(b*factor):02x}" # Base desk polygon coordinates (desk slot 0, no offset) _DESK_TABLETOP = [(148,268),(240,220),(306,252),(214,300)] _DESK_LEFT = [(148,268),(148,288),(214,320),(214,300)] _DESK_RIGHT = [(214,300),(214,320),(306,270),(306,252)] _DESK_MAT = [(158,262),(238,218),(298,248),(218,292)] _MON_BACK = [(196,208),(218,198),(242,210),(220,220)] _MON_SCREEN = [(200,210),(218,202),(238,212),(220,220)] _MON_LINES = [(200,211,228,203),(200,215,224,207),(200,219,230,211)] # Base character polygons — head anchor at (174, 264) relative to slot 0 _CHAR_HEAD_TOP = [(174,264),(192,255),(202,259),(184,268)] _CHAR_HEAD_SIDE = [(184,268),(202,259),(203,261),(185,270)] _CHAR_BODY_TOP = [(176,276),(196,267),(204,271),(184,280)] _CHAR_BODY_SIDE = [(184,280),(202,271),(204,273),(186,282)] _CHAR_LEG_L = [(177,288),(185,284),(189,286),(181,290)] _CHAR_LEG_R = [(184,285),(192,281),(196,283),(188,287)] _CHAR_ARM_L = (186,270, 174,264) _CHAR_ARM_R = (196,266, 210,260) _CHAR_EYE_L = (178,258) _CHAR_EYE_R = (189,255) # Five desk slot offsets (ox, oy) _SLOTS = [ (0, 0), # back-left (188, 34), # back-center (350, 2), # back-right (0, 95), # front-left (188, 129), # front-center ] def _svg_desk(ox: int, oy: int) -> str: lines = "".join( f'' if i == 0 else f'' if i == 1 else f'' for i, (x1, y1, x2, y2) in enumerate(_MON_LINES) ) return ( f'' f'' f'' f'' f'' f'' + lines ) def _svg_char(ox: int, oy: int, color: str) -> str: dark = _darken(color) al = _CHAR_ARM_L ar = _CHAR_ARM_R el = _CHAR_EYE_L er = _CHAR_EYE_R return ( f'' f'' f'' f'' f'' f'' f'' f'' f'' f'' ) def build_studio_html(model_assignments: list[dict]) -> str: """ model_assignments: list of dicts: {model_id, role, character_fn, color, desk (1-5)} Returns self-contained HTML string. """ assignments_json = json.dumps(model_assignments) # Build SVG for each assigned desk + character desk_svg_parts = [] char_svg_parts = [] for i, a in enumerate(model_assignments[:5]): ox, oy = _SLOTS[i] desk_svg_parts.append(_svg_desk(ox, oy)) char_svg_parts.append(_svg_char(ox, oy, a.get("color", "#aaaaaa"))) desks_svg = "\n".join(desk_svg_parts) chars_svg = "\n".join(char_svg_parts) # Speech bubble anchor positions (SVG coords) per slot — above character head bubble_anchors = json.dumps([ {"svgX": 174 + ox, "svgY": 255 + oy} for ox, oy in _SLOTS ]) return f""" Studio
ASSET MANIFEST DESIGN SPEC gravity: 18 jumpForce: 9.5 laneWidth: 2 snapSpeed: 8 AI · COLLAB · BUILD GENERATING... 🍕 {desks_svg} {chars_svg}
Waiting…
"""