Spaces:
Running on Zero
Running on Zero
| """ | |
| Small-model layer. Default: Qwen/Qwen2.5-Coder-1.5B-Instruct. | |
| Swap MODEL_ID to Qwen2.5-Coder-0.5B-Instruct (smaller, stronger "build small" | |
| pitch) or -3B-Instruct (safety net) via env var. The model only ever emits the | |
| scene JSON defined in scene.py — never raw Three.js. | |
| UPGRADE PATH (recommended for reliability): replace the free-form generate() | |
| with grammar-constrained decoding (outlines / xgrammar / llguidance) bound to | |
| the Scene schema, so output is *guaranteed* valid JSON. See CLAUDE_CODE_BRIEF.md. | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import logging | |
| import os | |
| import threading | |
| MODEL_ID = os.environ.get("MODEL_ID", "Qwen/Qwen2.5-Coder-3B-Instruct") | |
| MAX_PROMPT_CHARS = int(os.environ.get("MAX_PROMPT_CHARS", "500")) | |
| log = logging.getLogger(__name__) | |
| SYSTEM = """You convert a short scene description into a JSON scene graph for a 3D renderer. | |
| Output ONLY valid JSON. No markdown, no comments, no prose, no code fences. | |
| Schema — primitive object: | |
| { | |
| "background": "#RRGGBB", | |
| "objects": [ | |
| { | |
| "shape": "box|sphere|cylinder|cone|torus|torusKnot|plane|tetrahedron|icosahedron|dodecahedron|octahedron|capsule|ring|circle|tube|roundedBox", | |
| "position": [x, y, z], | |
| "rotation": [x, y, z], | |
| "scale": [x, y, z], | |
| "color": "#RRGGBB", | |
| "material": "standard|basic|phong|wireframe", | |
| "preset": "gold|chrome|glass|neon|matte|plastic", | |
| "metalness": 0.0-1.0, | |
| "roughness": 0.0-1.0, | |
| "emissive": "#RRGGBB", | |
| "params": {} | |
| } | |
| ], | |
| "lights": [ | |
| {"type": "ambient|directional|point", "color": "#RRGGBB", "intensity": 1.0, "position": [x, y, z]} | |
| ], | |
| "animation": {"type": "none|rotate|float|orbit", "speed": 1.0, "axis": "x|y|z"} | |
| } | |
| For stars, badges, shields, hearts, hexagons, coins, logos, or emblems use an EXTRUDE object instead: | |
| { | |
| "type": "extrude", | |
| "shape": "star|heart|hexagon|badge|shield", | |
| "depth": 0.2, | |
| "bevel": true, | |
| "color": "#RRGGBB", | |
| "preset": "gold|chrome|glass|neon|matte|plastic", | |
| "metalness": 0.0-1.0, | |
| "roughness": 0.0-1.0, | |
| "emissive": "#RRGGBB", | |
| "position": [x, y, z] | |
| } | |
| For 3D text (words, numbers, short labels) use a TEXT3D object (Latin chars only, max 24 chars): | |
| { | |
| "type": "text3d", | |
| "text": "HELLO", | |
| "size": 0.8, | |
| "depth": 0.2, | |
| "bevel": true, | |
| "color": "#RRGGBB", | |
| "preset": "gold|chrome|glass|neon|matte|plastic", | |
| "metalness": 0.0-1.0, | |
| "roughness": 0.0-1.0, | |
| "position": [x, y, z] | |
| } | |
| Use text3d for: single words, numbers, initials, short labels (max 2 text objects per scene). | |
| For a badge/emblem with text on it, use the badge_with_text TEMPLATE — never place text3d manually on an extrude: | |
| { | |
| "background": "#0d1117", | |
| "template": { | |
| "name": "badge_with_text", | |
| "shape": "star|shield|hexagon|badge|heart", | |
| "text": "WORD", | |
| "color_badge": "#RRGGBB", | |
| "color_text": "#RRGGBB", | |
| "metalness": 0.0-1.0, | |
| "roughness": 0.0-1.0, | |
| "preset_badge": "gold|chrome|glass|neon|matte|plastic" | |
| }, | |
| "lights": [...], | |
| "animation": {...} | |
| } | |
| The template positions and scales the text automatically — never set text position or size. | |
| Material preset notes: | |
| - gold/chrome: use for shiny metallic looks; color overrides the base hue | |
| - glass: transparent refractive; color adds a tint | |
| - neon: glowing edge; set color to the desired glow color | |
| - matte: no shine, soft diffuse | |
| - plastic: slight sheen, clearcoat | |
| Omit preset to use metalness/roughness directly. | |
| To arrange multiple objects together use a GROUP node: | |
| { | |
| "type": "group", | |
| "layout": "row|column|stack|grid|none", | |
| "gap": 0.3, | |
| "cols": 3, | |
| "position": [x, y, z], | |
| "rotation": [x, y, z], | |
| "scale": [x, y, z], | |
| "children": [ ...any node types, including nested groups... ] | |
| } | |
| Layout rules: | |
| - "row": children spaced along x-axis, auto-centered | |
| - "column" or "stack": children spaced along y-axis, auto-centered (first child = bottom) | |
| - "grid": children tiled on x-z plane, cols wide | |
| - "none": children keep their own positions | |
| When layout is set, DO NOT set child positions — the compiler places them automatically. | |
| Groups can be nested. Use groups for: rows of items, stacked arrangements, any scene needing multiple evenly-spaced objects. | |
| Keep scenes small (1-6 objects total). Center the composition near the origin.""" | |
| FEWSHOT = [ | |
| {"role": "user", "content": "a glowing green icosahedron floating in the dark"}, | |
| {"role": "assistant", "content": json.dumps({ | |
| "background": "#05060a", | |
| "objects": [{ | |
| "shape": "icosahedron", "color": "#39ff14", "material": "standard", | |
| "metalness": 0.2, "roughness": 0.3, "emissive": "#0a3a0a", | |
| "position": [0, 0, 0], "params": {"radius": 0.8} | |
| }], | |
| "lights": [ | |
| {"type": "ambient", "intensity": 0.4}, | |
| {"type": "point", "color": "#39ff14", "intensity": 2.0, "position": [2, 2, 2]} | |
| ], | |
| "animation": {"type": "float", "speed": 1.2, "axis": "y"} | |
| })}, | |
| {"role": "user", "content": "three glowing cubes stacked into a tower"}, | |
| {"role": "assistant", "content": json.dumps({ | |
| "background": "#0b0e14", | |
| "objects": [ | |
| {"shape": "box", "color": "#39ff14", "emissive": "#0a3a0a", "material": "standard", | |
| "metalness": 0.2, "roughness": 0.3, "position": [0, 0.8, 0]}, | |
| {"shape": "box", "color": "#39ff14", "emissive": "#0a3a0a", "material": "standard", | |
| "metalness": 0.2, "roughness": 0.3, "position": [0, 0.0, 0]}, | |
| {"shape": "box", "color": "#39ff14", "emissive": "#0a3a0a", "material": "standard", | |
| "metalness": 0.2, "roughness": 0.3, "position": [0, -0.8, 0]}, | |
| ], | |
| "lights": [ | |
| {"type": "ambient", "intensity": 0.5}, | |
| {"type": "directional", "intensity": 1.1, "position": [5, 8, 6]}, | |
| ], | |
| "animation": {"type": "rotate", "speed": 1.0, "axis": "y"}, | |
| })}, | |
| {"role": "user", "content": "a red sphere floating above a flat grey plane"}, | |
| {"role": "assistant", "content": json.dumps({ | |
| "background": "#1a1a2e", | |
| "objects": [ | |
| {"shape": "sphere", "color": "#ff4444", "emissive": "#330000", "material": "standard", | |
| "metalness": 0.1, "roughness": 0.5, "position": [0, 0.8, 0], "params": {"radius": 0.5}}, | |
| {"shape": "plane", "color": "#555566", "material": "standard", | |
| "metalness": 0.0, "roughness": 0.9, "position": [0, -0.2, 0], | |
| "rotation": [-1.5708, 0, 0], "params": {"width": 6, "height": 6}}, | |
| ], | |
| "lights": [ | |
| {"type": "ambient", "intensity": 0.4}, | |
| {"type": "directional", "color": "#ffffff", "intensity": 1.2, "position": [5, 8, 6]}, | |
| ], | |
| "animation": {"type": "float", "speed": 0.8, "axis": "y"}, | |
| })}, | |
| {"role": "user", "content": "a classic burger"}, | |
| {"role": "assistant", "content": json.dumps({ | |
| "background": "#1a1209", | |
| "objects": [{ | |
| "type": "stack", | |
| "axis": "y", | |
| "gap": 0.02, | |
| "children": [ | |
| {"shape": "sphere", "color": "#c8a96e", "params": {"radius": 0.45}}, | |
| {"shape": "cylinder", "color": "#3a8a3a", "params": {"radiusTop": 0.52, "radiusBottom": 0.52, "height": 0.1}}, | |
| {"shape": "cylinder", "color": "#5a3a1a", "params": {"radiusTop": 0.5, "radiusBottom": 0.5, "height": 0.18}}, | |
| {"shape": "cylinder", "color": "#c8a96e", "params": {"radiusTop": 0.52, "radiusBottom": 0.55, "height": 0.32}}, | |
| ] | |
| }], | |
| "lights": [ | |
| {"type": "ambient", "intensity": 0.5}, | |
| {"type": "directional", "intensity": 1.3, "position": [5, 8, 6]}, | |
| ], | |
| "animation": {"type": "rotate", "speed": 0.8, "axis": "y"}, | |
| })}, | |
| {"role": "user", "content": "build me a snowman"}, | |
| {"role": "assistant", "content": json.dumps({ | |
| "background": "#0d1b2a", | |
| "template": {"name": "snowman", "color_body": "#e8e8e8", "color_hat": "#1a1a1a"}, | |
| "lights": [ | |
| {"type": "ambient", "intensity": 0.5}, | |
| {"type": "directional", "intensity": 1.3, "position": [5, 8, 6]}, | |
| ], | |
| "animation": {"type": "rotate", "speed": 0.5, "axis": "y"}, | |
| })}, | |
| {"role": "user", "content": "a cute capsule character with a rounded body"}, | |
| {"role": "assistant", "content": json.dumps({ | |
| "background": "#0d1b2a", | |
| "objects": [ | |
| {"shape": "capsule", "color": "#ff6b6b", "emissive": "#330000", "material": "standard", | |
| "metalness": 0.1, "roughness": 0.5, "position": [0, 0, 0], | |
| "params": {"radius": 0.35, "length": 0.6}}, | |
| {"shape": "sphere", "color": "#ffe8cc", "material": "standard", | |
| "metalness": 0.0, "roughness": 0.8, "position": [0, 0.72, 0], | |
| "params": {"radius": 0.28}}, | |
| ], | |
| "lights": [ | |
| {"type": "ambient", "intensity": 0.5}, | |
| {"type": "directional", "intensity": 1.3, "position": [5, 8, 6]}, | |
| ], | |
| "animation": {"type": "float", "speed": 1.0, "axis": "y"}, | |
| })}, | |
| {"role": "user", "content": "a stack of soft rounded boxes"}, | |
| {"role": "assistant", "content": json.dumps({ | |
| "background": "#0b0e14", | |
| "objects": [{ | |
| "type": "stack", "axis": "y", "gap": 0.08, | |
| "children": [ | |
| {"shape": "roundedBox", "color": "#ff9f43", "material": "standard", | |
| "metalness": 0.1, "roughness": 0.4, | |
| "params": {"width": 1.2, "height": 0.4, "depth": 1.2, "radius": 0.12}}, | |
| {"shape": "roundedBox", "color": "#ee5a24", "material": "standard", | |
| "metalness": 0.1, "roughness": 0.4, | |
| "params": {"width": 1.0, "height": 0.4, "depth": 1.0, "radius": 0.12}}, | |
| {"shape": "roundedBox", "color": "#c0392b", "material": "standard", | |
| "metalness": 0.1, "roughness": 0.4, | |
| "params": {"width": 0.8, "height": 0.4, "depth": 0.8, "radius": 0.12}}, | |
| ], | |
| }], | |
| "lights": [ | |
| {"type": "ambient", "intensity": 0.5}, | |
| {"type": "directional", "intensity": 1.3, "position": [5, 8, 6]}, | |
| ], | |
| "animation": {"type": "rotate", "speed": 0.8, "axis": "y"}, | |
| })}, | |
| {"role": "user", "content": "a gold star badge"}, | |
| {"role": "assistant", "content": json.dumps({ | |
| "background": "#1a1205", | |
| "objects": [{ | |
| "type": "extrude", "shape": "star", "depth": 0.3, "bevel": True, | |
| "color": "#ffd700", "material": "standard", | |
| "metalness": 0.9, "roughness": 0.2, "emissive": "#332200", | |
| "position": [0, 0, 0], | |
| }], | |
| "lights": [ | |
| {"type": "ambient", "intensity": 0.3}, | |
| {"type": "directional", "color": "#fff8e0", "intensity": 2.0, "position": [3, 5, 4]}, | |
| ], | |
| "animation": {"type": "rotate", "speed": 0.6, "axis": "y"}, | |
| })}, | |
| {"role": "user", "content": "a shield emblem"}, | |
| {"role": "assistant", "content": json.dumps({ | |
| "background": "#0d1117", | |
| "objects": [{ | |
| "type": "extrude", "shape": "shield", "depth": 0.25, "bevel": True, | |
| "color": "#3a6bc4", "material": "standard", | |
| "metalness": 0.5, "roughness": 0.3, "emissive": "#061833", | |
| "position": [0, 0, 0], | |
| }], | |
| "lights": [ | |
| {"type": "ambient", "intensity": 0.4}, | |
| {"type": "directional", "color": "#ffffff", "intensity": 1.8, "position": [4, 6, 3]}, | |
| ], | |
| "animation": {"type": "rotate", "speed": 0.5, "axis": "y"}, | |
| })}, | |
| {"role": "user", "content": "a glass star badge"}, | |
| {"role": "assistant", "content": json.dumps({ | |
| "background": "#0a0a18", | |
| "objects": [{ | |
| "type": "extrude", "shape": "star", "depth": 0.25, "bevel": True, | |
| "color": "#aaddff", "preset": "glass", | |
| "position": [0, 0, 0], | |
| }], | |
| "lights": [ | |
| {"type": "ambient", "intensity": 0.6}, | |
| {"type": "directional", "color": "#ffffff", "intensity": 2.0, "position": [5, 8, 4]}, | |
| {"type": "point", "color": "#88ccff", "intensity": 1.5, "position": [-3, 2, 3]}, | |
| ], | |
| "animation": {"type": "rotate", "speed": 0.5, "axis": "y"}, | |
| })}, | |
| {"role": "user", "content": "a neon glowing shield"}, | |
| {"role": "assistant", "content": json.dumps({ | |
| "background": "#050508", | |
| "objects": [{ | |
| "type": "extrude", "shape": "shield", "depth": 0.2, "bevel": True, | |
| "color": "#ff2266", "preset": "neon", | |
| "position": [0, 0, 0], | |
| }], | |
| "lights": [ | |
| {"type": "ambient", "intensity": 0.1}, | |
| {"type": "point", "color": "#ff2266", "intensity": 3.0, "position": [0, 1, 2]}, | |
| ], | |
| "animation": {"type": "float", "speed": 1.0, "axis": "y"}, | |
| })}, | |
| {"role": "user", "content": "a chrome hexagon"}, | |
| {"role": "assistant", "content": json.dumps({ | |
| "background": "#1a1a2e", | |
| "objects": [{ | |
| "type": "extrude", "shape": "hexagon", "depth": 0.3, "bevel": True, | |
| "color": "#ffffff", "preset": "chrome", | |
| "position": [0, 0, 0], | |
| }], | |
| "lights": [ | |
| {"type": "ambient", "intensity": 0.4}, | |
| {"type": "directional", "color": "#ffffff", "intensity": 2.5, "position": [4, 6, 3]}, | |
| {"type": "point", "color": "#88aaff", "intensity": 1.0, "position": [-3, 2, 2]}, | |
| ], | |
| "animation": {"type": "rotate", "speed": 0.6, "axis": "y"}, | |
| })}, | |
| {"role": "user", "content": "the word HELLO in 3D"}, | |
| {"role": "assistant", "content": json.dumps({ | |
| "background": "#0b0e14", | |
| "objects": [{ | |
| "type": "text3d", "text": "HELLO", | |
| "size": 0.8, "depth": 0.2, "bevel": True, | |
| "color": "#88ccff", "metalness": 0.3, "roughness": 0.4, | |
| "position": [0, 0, 0], | |
| }], | |
| "lights": [ | |
| {"type": "ambient", "intensity": 0.4}, | |
| {"type": "directional", "color": "#ffffff", "intensity": 2.0, "position": [5, 8, 5]}, | |
| ], | |
| "animation": {"type": "rotate", "speed": 0.5, "axis": "y"}, | |
| })}, | |
| {"role": "user", "content": "a gold 3D number 1"}, | |
| {"role": "assistant", "content": json.dumps({ | |
| "background": "#1a1205", | |
| "objects": [{ | |
| "type": "text3d", "text": "1", | |
| "size": 1.2, "depth": 0.3, "bevel": True, | |
| "color": "#ffd700", "preset": "gold", | |
| "position": [0, 0, 0], | |
| }], | |
| "lights": [ | |
| {"type": "ambient", "intensity": 0.3}, | |
| {"type": "directional", "color": "#fff8e0", "intensity": 2.5, "position": [4, 6, 4]}, | |
| ], | |
| "animation": {"type": "rotate", "speed": 0.5, "axis": "y"}, | |
| })}, | |
| {"role": "user", "content": "a star badge with the text PRO"}, | |
| {"role": "assistant", "content": json.dumps({ | |
| "background": "#0d1117", | |
| "template": { | |
| "name": "badge_with_text", | |
| "shape": "star", | |
| "text": "PRO", | |
| "color_badge": "#3a6bc4", | |
| "color_text": "#ffffff", | |
| "metalness": 0.7, | |
| "roughness": 0.2, | |
| }, | |
| "lights": [ | |
| {"type": "ambient", "intensity": 0.4}, | |
| {"type": "directional", "color": "#ffffff", "intensity": 2.0, "position": [3, 5, 4]}, | |
| ], | |
| "animation": {"type": "rotate", "speed": 0.5, "axis": "y"}, | |
| })}, | |
| {"role": "user", "content": "a shield badge with the text NEW"}, | |
| {"role": "assistant", "content": json.dumps({ | |
| "background": "#0d1117", | |
| "template": { | |
| "name": "badge_with_text", | |
| "shape": "shield", | |
| "text": "NEW", | |
| "color_badge": "#22aa66", | |
| "color_text": "#ffffff", | |
| "metalness": 0.5, | |
| "roughness": 0.3, | |
| }, | |
| "lights": [ | |
| {"type": "ambient", "intensity": 0.4}, | |
| {"type": "directional", "color": "#ffffff", "intensity": 2.0, "position": [4, 6, 4]}, | |
| ], | |
| "animation": {"type": "rotate", "speed": 0.5, "axis": "y"}, | |
| })}, | |
| {"role": "user", "content": "a row of three coins"}, | |
| {"role": "assistant", "content": json.dumps({ | |
| "background": "#1a1205", | |
| "objects": [{ | |
| "type": "group", | |
| "layout": "row", | |
| "gap": 0.4, | |
| "children": [ | |
| {"type": "extrude", "shape": "badge", "depth": 0.08, "bevel": True, | |
| "color": "#ffd700", "preset": "gold"}, | |
| {"type": "extrude", "shape": "badge", "depth": 0.08, "bevel": True, | |
| "color": "#ffd700", "preset": "gold"}, | |
| {"type": "extrude", "shape": "badge", "depth": 0.08, "bevel": True, | |
| "color": "#ffd700", "preset": "gold"}, | |
| ], | |
| }], | |
| "lights": [ | |
| {"type": "ambient", "intensity": 0.3}, | |
| {"type": "directional", "color": "#fff8e0", "intensity": 2.5, "position": [4, 6, 4]}, | |
| ], | |
| "animation": {"type": "rotate", "speed": 0.5, "axis": "y"}, | |
| })}, | |
| {"role": "user", "content": "a tall trophy: a star on a stacked column"}, | |
| {"role": "assistant", "content": json.dumps({ | |
| "background": "#1a1205", | |
| "objects": [{ | |
| "type": "group", | |
| "layout": "stack", | |
| "gap": 0.1, | |
| "children": [ | |
| {"shape": "cylinder", "color": "#ffd700", "preset": "gold", | |
| "params": {"radiusTop": 0.08, "radiusBottom": 0.18, "height": 1.2}}, | |
| {"type": "extrude", "shape": "star", "depth": 0.2, "bevel": True, | |
| "color": "#ffd700", "preset": "gold"}, | |
| ], | |
| }], | |
| "lights": [ | |
| {"type": "ambient", "intensity": 0.3}, | |
| {"type": "directional", "color": "#fff8e0", "intensity": 2.5, "position": [4, 6, 4]}, | |
| ], | |
| "animation": {"type": "rotate", "speed": 0.5, "axis": "y"}, | |
| })}, | |
| {"role": "user", "content": "three cubes in a row"}, | |
| {"role": "assistant", "content": json.dumps({ | |
| "background": "#0b0e14", | |
| "objects": [{ | |
| "type": "group", | |
| "layout": "row", | |
| "gap": 0.3, | |
| "children": [ | |
| {"shape": "box", "color": "#ff6b6b", "material": "standard", | |
| "metalness": 0.1, "roughness": 0.5}, | |
| {"shape": "box", "color": "#ffd700", "material": "standard", | |
| "metalness": 0.1, "roughness": 0.5}, | |
| {"shape": "box", "color": "#6bffb8", "material": "standard", | |
| "metalness": 0.1, "roughness": 0.5}, | |
| ], | |
| }], | |
| "lights": [ | |
| {"type": "ambient", "intensity": 0.5}, | |
| {"type": "directional", "intensity": 1.3, "position": [5, 8, 6]}, | |
| ], | |
| "animation": {"type": "rotate", "speed": 0.6, "axis": "y"}, | |
| })}, | |
| ] | |
| _tok = None | |
| _model = None | |
| _device = None | |
| _lock = threading.Lock() | |
| def _load(): | |
| global _tok, _model, _device | |
| with _lock: | |
| if _model is None: | |
| import torch | |
| from transformers import AutoModelForCausalLM, AutoTokenizer | |
| log.info("Loading model %s", MODEL_ID) | |
| _device = "cuda" if torch.cuda.is_available() else "cpu" | |
| _tok = AutoTokenizer.from_pretrained(MODEL_ID) | |
| _model = AutoModelForCausalLM.from_pretrained( | |
| MODEL_ID, torch_dtype=torch.bfloat16 | |
| ).to(_device) | |
| log.info("Model loaded on %s", _device) | |
| return _tok, _model, _device | |
| def run_llm(prompt: str, max_new_tokens: int = 512) -> str: | |
| """Generate raw text (expected to be a JSON object) from the model.""" | |
| if len(prompt) > MAX_PROMPT_CHARS: | |
| prompt = prompt[:MAX_PROMPT_CHARS] | |
| log.warning("Prompt truncated to %d chars", MAX_PROMPT_CHARS) | |
| tok, model, device = _load() | |
| messages = [{"role": "system", "content": SYSTEM}, *FEWSHOT, | |
| {"role": "user", "content": prompt}] | |
| text = tok.apply_chat_template( | |
| messages, tokenize=False, add_generation_prompt=True) | |
| inputs = tok(text, return_tensors="pt").to(device) | |
| out = model.generate( | |
| **inputs, | |
| max_new_tokens=max_new_tokens, | |
| do_sample=True, | |
| temperature=0.4, | |
| top_p=0.9, | |
| pad_token_id=tok.eos_token_id, | |
| ) | |
| gen = out[0][inputs.input_ids.shape[1]:] | |
| result = tok.decode(gen, skip_special_tokens=True) | |
| log.info("Generated %d chars for prompt: %.60s", len(result), prompt) | |
| return result | |
| def mock_scene_json(prompt: str) -> str: | |
| """Deterministic, no-GPU generator so the UI can be built/tested fast.""" | |
| p = (prompt or "").lower() | |
| color = "#88ccff" | |
| if "neon" in p or "green" in p: | |
| color = "#39ff14" | |
| elif "red" in p: | |
| color = "#ff4444" | |
| elif "gold" in p or "yellow" in p: | |
| color = "#ffcc33" | |
| elif "purple" in p or "violet" in p: | |
| color = "#9b59ff" | |
| material = "standard" | |
| if "glass" in p: | |
| material = "phong" | |
| elif "wire" in p: | |
| material = "wireframe" | |
| shape = "box" | |
| for key, val in [ | |
| ("torusknot", "torusKnot"), ("knot", "torusKnot"), | |
| ("torus", "torus"), ("donut", "torus"), | |
| ("sphere", "sphere"), ("ball", "sphere"), | |
| ("cone", "cone"), ("cylinder", "cylinder"), | |
| ("ico", "icosahedron"), ("dodeca", "dodecahedron"), | |
| ("octa", "octahedron"), ("cube", "box"), | |
| ]: | |
| if key in p: | |
| shape = val | |
| break | |
| anim = "rotate" | |
| if "float" in p: | |
| anim = "float" | |
| elif "still" in p or "static" in p: | |
| anim = "none" | |
| elif "orbit" in p: | |
| anim = "orbit" | |
| objects = [{"shape": shape, "color": color, "material": material, | |
| "position": [0, 0, 0]}] | |
| if "tree" in p: | |
| objects = [ | |
| {"shape": "cone", "color": "#2e8b57", "position": [0, 0.6, 0], | |
| "params": {"radius": 0.7, "height": 1.4}}, | |
| {"shape": "cylinder", "color": "#8b5a2b", "position": [0, -0.4, 0], | |
| "params": {"radiusTop": 0.15, "radiusBottom": 0.2, "height": 0.8}}, | |
| ] | |
| scene = { | |
| "background": "#0b0e14", | |
| "objects": objects, | |
| "lights": [ | |
| {"type": "ambient", "intensity": 0.5}, | |
| {"type": "directional", "intensity": 1.1, "position": [5, 8, 6]}, | |
| ], | |
| "animation": {"type": anim, "speed": 1.0, "axis": "y"}, | |
| } | |
| return json.dumps(scene) | |