Commit
·
cc6d03f
1
Parent(s):
6fbfc6a
added lfs and files
Browse files- .gitattributes +1 -0
- backend/tools/environment_tools.py +278 -0
- backend/tools/ui_tools.py +224 -0
- models/Norod78/huggingface_emoji.glb +3 -0
- models/kenney/brick_kit/brick_1x1.glb +3 -0
- models/kenney/brick_kit/brick_1x2.glb +3 -0
- models/kenney/brick_kit/brick_1x4.glb +3 -0
- models/kenney/brick_kit/brick_2x2.glb +3 -0
- models/kenney/brick_kit/brick_2x4.glb +3 -0
- models/kenney/brick_kit/plate_1x2.glb +3 -0
- models/kenney/brick_kit/plate_2x2.glb +3 -0
- models/kenney/brick_kit/plate_2x4.glb +3 -0
- models/kenney/brick_kit/plate_4x4.glb +3 -0
- models/kenney/brick_kit/slope_2x2.glb +3 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
*.glb filter=lfs diff=lfs merge=lfs -text
|
backend/tools/environment_tools.py
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Environment Tools
|
| 3 |
+
Skybox, particles, and other environmental effects
|
| 4 |
+
"""
|
| 5 |
+
from typing import Dict, Any, Optional
|
| 6 |
+
from backend.storage import storage
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
# =============================================================================
|
| 10 |
+
# Skybox Tools
|
| 11 |
+
# =============================================================================
|
| 12 |
+
|
| 13 |
+
def add_skybox(
|
| 14 |
+
scene_id: str,
|
| 15 |
+
preset: str = "day",
|
| 16 |
+
turbidity: float = 10.0,
|
| 17 |
+
rayleigh: float = 2.0,
|
| 18 |
+
sun_elevation: float = 45.0,
|
| 19 |
+
sun_azimuth: float = 180.0
|
| 20 |
+
) -> Dict[str, Any]:
|
| 21 |
+
"""
|
| 22 |
+
Add a procedural sky to the scene using Three.js Sky shader.
|
| 23 |
+
|
| 24 |
+
The sky simulates atmospheric scattering for realistic outdoor environments.
|
| 25 |
+
|
| 26 |
+
Args:
|
| 27 |
+
scene_id: ID of the scene
|
| 28 |
+
preset: Quick preset - "day", "sunset", "noon", "dawn", "night" (overrides other params)
|
| 29 |
+
turbidity: Haziness of the atmosphere (2.0=clear, 10.0=hazy, 20.0=foggy)
|
| 30 |
+
rayleigh: Amount of Rayleigh scattering (affects blue color of sky)
|
| 31 |
+
sun_elevation: Sun angle from horizon in degrees (0=horizon, 90=overhead)
|
| 32 |
+
sun_azimuth: Sun compass direction in degrees (0=north, 90=east, 180=south)
|
| 33 |
+
|
| 34 |
+
Returns:
|
| 35 |
+
Dictionary with skybox settings and message
|
| 36 |
+
"""
|
| 37 |
+
scene = storage.get(scene_id)
|
| 38 |
+
if not scene:
|
| 39 |
+
raise ValueError(f"Scene '{scene_id}' not found")
|
| 40 |
+
|
| 41 |
+
if "environment" not in scene:
|
| 42 |
+
scene["environment"] = {}
|
| 43 |
+
|
| 44 |
+
# Apply presets
|
| 45 |
+
presets = {
|
| 46 |
+
"day": {"turbidity": 10.0, "rayleigh": 2.0, "sun_elevation": 45.0, "sun_azimuth": 180.0},
|
| 47 |
+
"noon": {"turbidity": 8.0, "rayleigh": 1.5, "sun_elevation": 85.0, "sun_azimuth": 180.0},
|
| 48 |
+
"sunset": {"turbidity": 4.0, "rayleigh": 4.0, "sun_elevation": 5.0, "sun_azimuth": 180.0},
|
| 49 |
+
"dawn": {"turbidity": 4.0, "rayleigh": 4.0, "sun_elevation": 5.0, "sun_azimuth": 0.0},
|
| 50 |
+
"night": {"turbidity": 20.0, "rayleigh": 0.1, "sun_elevation": -10.0, "sun_azimuth": 180.0},
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
if preset in presets:
|
| 54 |
+
params = presets[preset]
|
| 55 |
+
turbidity = params["turbidity"]
|
| 56 |
+
rayleigh = params["rayleigh"]
|
| 57 |
+
sun_elevation = params["sun_elevation"]
|
| 58 |
+
sun_azimuth = params["sun_azimuth"]
|
| 59 |
+
|
| 60 |
+
# Validate ranges
|
| 61 |
+
turbidity = max(0.1, min(20.0, turbidity))
|
| 62 |
+
rayleigh = max(0.0, min(4.0, rayleigh))
|
| 63 |
+
sun_elevation = max(-90.0, min(90.0, sun_elevation))
|
| 64 |
+
sun_azimuth = sun_azimuth % 360.0
|
| 65 |
+
|
| 66 |
+
skybox = {
|
| 67 |
+
"enabled": True,
|
| 68 |
+
"type": "procedural",
|
| 69 |
+
"turbidity": turbidity,
|
| 70 |
+
"rayleigh": rayleigh,
|
| 71 |
+
"sun_elevation": sun_elevation,
|
| 72 |
+
"sun_azimuth": sun_azimuth,
|
| 73 |
+
"preset": preset
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
scene["environment"]["skybox"] = skybox
|
| 77 |
+
storage.save(scene)
|
| 78 |
+
|
| 79 |
+
return {
|
| 80 |
+
"scene_id": scene_id,
|
| 81 |
+
"message": f"Added {preset} sky (sun at {sun_elevation}° elevation)",
|
| 82 |
+
"skybox": skybox
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
def remove_skybox(scene_id: str) -> Dict[str, Any]:
|
| 87 |
+
"""
|
| 88 |
+
Remove the skybox from the scene, reverting to solid background color.
|
| 89 |
+
|
| 90 |
+
Args:
|
| 91 |
+
scene_id: ID of the scene
|
| 92 |
+
|
| 93 |
+
Returns:
|
| 94 |
+
Dictionary with confirmation message
|
| 95 |
+
"""
|
| 96 |
+
scene = storage.get(scene_id)
|
| 97 |
+
if not scene:
|
| 98 |
+
raise ValueError(f"Scene '{scene_id}' not found")
|
| 99 |
+
|
| 100 |
+
if "environment" not in scene:
|
| 101 |
+
scene["environment"] = {}
|
| 102 |
+
|
| 103 |
+
# Check if skybox exists
|
| 104 |
+
had_skybox = scene["environment"].get("skybox", {}).get("enabled", False)
|
| 105 |
+
|
| 106 |
+
scene["environment"]["skybox"] = {"enabled": False}
|
| 107 |
+
storage.save(scene)
|
| 108 |
+
|
| 109 |
+
if had_skybox:
|
| 110 |
+
return {
|
| 111 |
+
"scene_id": scene_id,
|
| 112 |
+
"message": "Removed skybox, reverted to solid background color"
|
| 113 |
+
}
|
| 114 |
+
else:
|
| 115 |
+
return {
|
| 116 |
+
"scene_id": scene_id,
|
| 117 |
+
"message": "No skybox was active"
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
# =============================================================================
|
| 122 |
+
# Particle System Tools
|
| 123 |
+
# =============================================================================
|
| 124 |
+
|
| 125 |
+
def add_particles(
|
| 126 |
+
scene_id: str,
|
| 127 |
+
preset: str,
|
| 128 |
+
position: Optional[Dict[str, float]] = None,
|
| 129 |
+
particle_id: Optional[str] = None
|
| 130 |
+
) -> Dict[str, Any]:
|
| 131 |
+
"""
|
| 132 |
+
Add a particle effect to the scene using preset configurations.
|
| 133 |
+
|
| 134 |
+
Args:
|
| 135 |
+
scene_id: ID of the scene
|
| 136 |
+
preset: Effect type - "fire", "smoke", "sparkle", "rain", "snow"
|
| 137 |
+
position: {x, y, z} position for localized effects (fire, smoke, sparkle)
|
| 138 |
+
For weather effects (rain, snow), position is ignored (covers world)
|
| 139 |
+
particle_id: Optional unique identifier for this particle system
|
| 140 |
+
|
| 141 |
+
Returns:
|
| 142 |
+
Dictionary with particle system info and message
|
| 143 |
+
"""
|
| 144 |
+
scene = storage.get(scene_id)
|
| 145 |
+
if not scene:
|
| 146 |
+
raise ValueError(f"Scene '{scene_id}' not found")
|
| 147 |
+
|
| 148 |
+
valid_presets = ["fire", "smoke", "sparkle", "rain", "snow"]
|
| 149 |
+
if preset not in valid_presets:
|
| 150 |
+
raise ValueError(f"Invalid preset '{preset}'. Must be one of: {valid_presets}")
|
| 151 |
+
|
| 152 |
+
if "particles" not in scene:
|
| 153 |
+
scene["particles"] = []
|
| 154 |
+
|
| 155 |
+
# Generate ID if not provided
|
| 156 |
+
if not particle_id:
|
| 157 |
+
particle_id = f"{preset}_{len(scene['particles'])}"
|
| 158 |
+
|
| 159 |
+
# Check for duplicate ID
|
| 160 |
+
for p in scene["particles"]:
|
| 161 |
+
if p.get("id") == particle_id:
|
| 162 |
+
raise ValueError(f"Particle system with id '{particle_id}' already exists")
|
| 163 |
+
|
| 164 |
+
# Default position for localized effects
|
| 165 |
+
if position is None:
|
| 166 |
+
position = {"x": 0, "y": 1, "z": 0}
|
| 167 |
+
|
| 168 |
+
# Preset configurations
|
| 169 |
+
preset_configs = {
|
| 170 |
+
"fire": {
|
| 171 |
+
"count": 200,
|
| 172 |
+
"size": 0.3,
|
| 173 |
+
"color_start": "#ff6600",
|
| 174 |
+
"color_end": "#ff0000",
|
| 175 |
+
"velocity": {"x": 0, "y": 2, "z": 0},
|
| 176 |
+
"lifetime": 1.5,
|
| 177 |
+
"spread": 0.5,
|
| 178 |
+
"localized": True
|
| 179 |
+
},
|
| 180 |
+
"smoke": {
|
| 181 |
+
"count": 100,
|
| 182 |
+
"size": 0.8,
|
| 183 |
+
"color_start": "#666666",
|
| 184 |
+
"color_end": "#333333",
|
| 185 |
+
"velocity": {"x": 0, "y": 1, "z": 0},
|
| 186 |
+
"lifetime": 3.0,
|
| 187 |
+
"spread": 1.0,
|
| 188 |
+
"localized": True
|
| 189 |
+
},
|
| 190 |
+
"sparkle": {
|
| 191 |
+
"count": 50,
|
| 192 |
+
"size": 0.1,
|
| 193 |
+
"color_start": "#ffffff",
|
| 194 |
+
"color_end": "#ffff00",
|
| 195 |
+
"velocity": {"x": 0, "y": 0.5, "z": 0},
|
| 196 |
+
"lifetime": 2.0,
|
| 197 |
+
"spread": 2.0,
|
| 198 |
+
"localized": True
|
| 199 |
+
},
|
| 200 |
+
"rain": {
|
| 201 |
+
"count": 1000,
|
| 202 |
+
"size": 0.05,
|
| 203 |
+
"color_start": "#aaccff",
|
| 204 |
+
"color_end": "#88aadd",
|
| 205 |
+
"velocity": {"x": 0, "y": -15, "z": 0},
|
| 206 |
+
"lifetime": 2.0,
|
| 207 |
+
"spread": 50.0,
|
| 208 |
+
"localized": False
|
| 209 |
+
},
|
| 210 |
+
"snow": {
|
| 211 |
+
"count": 500,
|
| 212 |
+
"size": 0.15,
|
| 213 |
+
"color_start": "#ffffff",
|
| 214 |
+
"color_end": "#eeeeff",
|
| 215 |
+
"velocity": {"x": 0.5, "y": -2, "z": 0.3},
|
| 216 |
+
"lifetime": 8.0,
|
| 217 |
+
"spread": 50.0,
|
| 218 |
+
"localized": False
|
| 219 |
+
}
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
config = preset_configs[preset]
|
| 223 |
+
|
| 224 |
+
particle_system = {
|
| 225 |
+
"id": particle_id,
|
| 226 |
+
"preset": preset,
|
| 227 |
+
"position": position,
|
| 228 |
+
"enabled": True,
|
| 229 |
+
**config
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
scene["particles"].append(particle_system)
|
| 233 |
+
storage.save(scene)
|
| 234 |
+
|
| 235 |
+
if config["localized"]:
|
| 236 |
+
pos_str = f" at ({position['x']}, {position['y']}, {position['z']})"
|
| 237 |
+
else:
|
| 238 |
+
pos_str = " (world-wide weather effect)"
|
| 239 |
+
|
| 240 |
+
return {
|
| 241 |
+
"scene_id": scene_id,
|
| 242 |
+
"particle_id": particle_id,
|
| 243 |
+
"message": f"Added {preset} particle effect{pos_str}",
|
| 244 |
+
"particle_system": particle_system
|
| 245 |
+
}
|
| 246 |
+
|
| 247 |
+
|
| 248 |
+
def remove_particles(scene_id: str, particle_id: str) -> Dict[str, Any]:
|
| 249 |
+
"""
|
| 250 |
+
Remove a particle system from the scene.
|
| 251 |
+
|
| 252 |
+
Args:
|
| 253 |
+
scene_id: ID of the scene
|
| 254 |
+
particle_id: ID of the particle system to remove
|
| 255 |
+
|
| 256 |
+
Returns:
|
| 257 |
+
Dictionary with confirmation message
|
| 258 |
+
"""
|
| 259 |
+
scene = storage.get(scene_id)
|
| 260 |
+
if not scene:
|
| 261 |
+
raise ValueError(f"Scene '{scene_id}' not found")
|
| 262 |
+
|
| 263 |
+
if "particles" not in scene or not scene["particles"]:
|
| 264 |
+
raise ValueError("Scene has no particle systems")
|
| 265 |
+
|
| 266 |
+
original_count = len(scene["particles"])
|
| 267 |
+
scene["particles"] = [p for p in scene["particles"] if p.get("id") != particle_id]
|
| 268 |
+
|
| 269 |
+
if len(scene["particles"]) == original_count:
|
| 270 |
+
raise ValueError(f"Particle system '{particle_id}' not found")
|
| 271 |
+
|
| 272 |
+
storage.save(scene)
|
| 273 |
+
|
| 274 |
+
return {
|
| 275 |
+
"scene_id": scene_id,
|
| 276 |
+
"message": f"Removed particle system '{particle_id}'",
|
| 277 |
+
"particle_id": particle_id
|
| 278 |
+
}
|
backend/tools/ui_tools.py
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
UI Tools
|
| 3 |
+
2D overlay elements for text, bars, and HUD components
|
| 4 |
+
"""
|
| 5 |
+
from typing import Dict, Any, Optional
|
| 6 |
+
from backend.storage import storage
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def render_text_on_screen(
|
| 10 |
+
scene_id: str,
|
| 11 |
+
text: str,
|
| 12 |
+
x: float = 50.0,
|
| 13 |
+
y: float = 10.0,
|
| 14 |
+
font_size: int = 24,
|
| 15 |
+
color: str = "#ffffff",
|
| 16 |
+
text_id: Optional[str] = None,
|
| 17 |
+
font_family: str = "Arial",
|
| 18 |
+
text_align: str = "center",
|
| 19 |
+
background_color: Optional[str] = None,
|
| 20 |
+
padding: int = 8
|
| 21 |
+
) -> Dict[str, Any]:
|
| 22 |
+
"""
|
| 23 |
+
Render text on the screen as a 2D overlay.
|
| 24 |
+
|
| 25 |
+
Args:
|
| 26 |
+
scene_id: ID of the scene
|
| 27 |
+
text: The text to display
|
| 28 |
+
x: Horizontal position in percentage (0=left edge, 50=center, 100=right edge)
|
| 29 |
+
y: Vertical position in percentage (0=top edge, 50=center, 100=bottom edge)
|
| 30 |
+
font_size: Font size in pixels (default: 24)
|
| 31 |
+
color: Text color as hex (default: "#ffffff")
|
| 32 |
+
text_id: Optional unique identifier for this text element (for updates/removal)
|
| 33 |
+
font_family: CSS font family (default: "Arial")
|
| 34 |
+
text_align: Text alignment - "left", "center", "right" (default: "center")
|
| 35 |
+
background_color: Optional background color (e.g., "#000000" for black box behind text)
|
| 36 |
+
padding: Padding around text in pixels when background is set (default: 8)
|
| 37 |
+
|
| 38 |
+
Returns:
|
| 39 |
+
Dictionary with text element info and message
|
| 40 |
+
"""
|
| 41 |
+
scene = storage.get(scene_id)
|
| 42 |
+
if not scene:
|
| 43 |
+
raise ValueError(f"Scene '{scene_id}' not found")
|
| 44 |
+
|
| 45 |
+
if "ui_elements" not in scene:
|
| 46 |
+
scene["ui_elements"] = []
|
| 47 |
+
|
| 48 |
+
# Generate ID if not provided
|
| 49 |
+
if not text_id:
|
| 50 |
+
text_id = f"text_{len([e for e in scene['ui_elements'] if e.get('element_type') == 'text'])}"
|
| 51 |
+
|
| 52 |
+
# Remove existing element with same ID (update behavior)
|
| 53 |
+
scene["ui_elements"] = [e for e in scene["ui_elements"] if e.get("id") != text_id]
|
| 54 |
+
|
| 55 |
+
# Clamp position values
|
| 56 |
+
x = max(0.0, min(100.0, x))
|
| 57 |
+
y = max(0.0, min(100.0, y))
|
| 58 |
+
|
| 59 |
+
text_element = {
|
| 60 |
+
"id": text_id,
|
| 61 |
+
"element_type": "text",
|
| 62 |
+
"text": text,
|
| 63 |
+
"x": x,
|
| 64 |
+
"y": y,
|
| 65 |
+
"font_size": font_size,
|
| 66 |
+
"color": color,
|
| 67 |
+
"font_family": font_family,
|
| 68 |
+
"text_align": text_align,
|
| 69 |
+
"background_color": background_color,
|
| 70 |
+
"padding": padding,
|
| 71 |
+
"visible": True
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
scene["ui_elements"].append(text_element)
|
| 75 |
+
storage.save(scene)
|
| 76 |
+
|
| 77 |
+
return {
|
| 78 |
+
"scene_id": scene_id,
|
| 79 |
+
"text_id": text_id,
|
| 80 |
+
"message": f"Rendered text '{text[:30]}{'...' if len(text) > 30 else ''}' at ({x}%, {y}%)",
|
| 81 |
+
"text_element": text_element
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
def render_bar_on_screen(
|
| 86 |
+
scene_id: str,
|
| 87 |
+
x: float = 10.0,
|
| 88 |
+
y: float = 10.0,
|
| 89 |
+
width: float = 200.0,
|
| 90 |
+
height: float = 20.0,
|
| 91 |
+
value: float = 100.0,
|
| 92 |
+
max_value: float = 100.0,
|
| 93 |
+
bar_color: str = "#00ff00",
|
| 94 |
+
background_color: str = "#333333",
|
| 95 |
+
border_color: Optional[str] = "#ffffff",
|
| 96 |
+
bar_id: Optional[str] = None,
|
| 97 |
+
label: Optional[str] = None,
|
| 98 |
+
show_value: bool = False
|
| 99 |
+
) -> Dict[str, Any]:
|
| 100 |
+
"""
|
| 101 |
+
Render a progress/health bar on the screen as a 2D overlay.
|
| 102 |
+
|
| 103 |
+
Args:
|
| 104 |
+
scene_id: ID of the scene
|
| 105 |
+
x: Horizontal position in percentage (0=left edge, 50=center, 100=right edge)
|
| 106 |
+
y: Vertical position in percentage (0=top edge, 50=center, 100=bottom edge)
|
| 107 |
+
width: Bar width in pixels (default: 200)
|
| 108 |
+
height: Bar height in pixels (default: 20)
|
| 109 |
+
value: Current value (default: 100)
|
| 110 |
+
max_value: Maximum value (default: 100)
|
| 111 |
+
bar_color: Fill color for the bar (default: "#00ff00" green)
|
| 112 |
+
background_color: Background color (default: "#333333" dark gray)
|
| 113 |
+
border_color: Optional border color (default: "#ffffff" white)
|
| 114 |
+
bar_id: Optional unique identifier for this bar (for updates/removal)
|
| 115 |
+
label: Optional label text above the bar
|
| 116 |
+
show_value: Show numeric value on the bar (default: False)
|
| 117 |
+
|
| 118 |
+
Returns:
|
| 119 |
+
Dictionary with bar element info and message
|
| 120 |
+
"""
|
| 121 |
+
scene = storage.get(scene_id)
|
| 122 |
+
if not scene:
|
| 123 |
+
raise ValueError(f"Scene '{scene_id}' not found")
|
| 124 |
+
|
| 125 |
+
if "ui_elements" not in scene:
|
| 126 |
+
scene["ui_elements"] = []
|
| 127 |
+
|
| 128 |
+
# Generate ID if not provided
|
| 129 |
+
if not bar_id:
|
| 130 |
+
bar_id = f"bar_{len([e for e in scene['ui_elements'] if e.get('element_type') == 'bar'])}"
|
| 131 |
+
|
| 132 |
+
# Remove existing element with same ID (update behavior)
|
| 133 |
+
scene["ui_elements"] = [e for e in scene["ui_elements"] if e.get("id") != bar_id]
|
| 134 |
+
|
| 135 |
+
# Clamp values
|
| 136 |
+
x = max(0.0, min(100.0, x))
|
| 137 |
+
y = max(0.0, min(100.0, y))
|
| 138 |
+
value = max(0.0, min(max_value, value))
|
| 139 |
+
percentage = (value / max_value) * 100.0 if max_value > 0 else 0.0
|
| 140 |
+
|
| 141 |
+
bar_element = {
|
| 142 |
+
"id": bar_id,
|
| 143 |
+
"element_type": "bar",
|
| 144 |
+
"x": x,
|
| 145 |
+
"y": y,
|
| 146 |
+
"width": width,
|
| 147 |
+
"height": height,
|
| 148 |
+
"value": value,
|
| 149 |
+
"max_value": max_value,
|
| 150 |
+
"percentage": percentage,
|
| 151 |
+
"bar_color": bar_color,
|
| 152 |
+
"background_color": background_color,
|
| 153 |
+
"border_color": border_color,
|
| 154 |
+
"label": label,
|
| 155 |
+
"show_value": show_value,
|
| 156 |
+
"visible": True
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
scene["ui_elements"].append(bar_element)
|
| 160 |
+
storage.save(scene)
|
| 161 |
+
|
| 162 |
+
label_str = f"'{label}' " if label else ""
|
| 163 |
+
return {
|
| 164 |
+
"scene_id": scene_id,
|
| 165 |
+
"bar_id": bar_id,
|
| 166 |
+
"message": f"Rendered {label_str}bar at ({x}%, {y}%) - {value}/{max_value} ({percentage:.0f}%)",
|
| 167 |
+
"bar_element": bar_element
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
def remove_ui_element(scene_id: str, element_id: str) -> Dict[str, Any]:
|
| 172 |
+
"""
|
| 173 |
+
Remove a UI element from the screen.
|
| 174 |
+
|
| 175 |
+
Args:
|
| 176 |
+
scene_id: ID of the scene
|
| 177 |
+
element_id: ID of the UI element to remove (text_id or bar_id)
|
| 178 |
+
|
| 179 |
+
Returns:
|
| 180 |
+
Dictionary with confirmation message
|
| 181 |
+
"""
|
| 182 |
+
scene = storage.get(scene_id)
|
| 183 |
+
if not scene:
|
| 184 |
+
raise ValueError(f"Scene '{scene_id}' not found")
|
| 185 |
+
|
| 186 |
+
if "ui_elements" not in scene or not scene["ui_elements"]:
|
| 187 |
+
raise ValueError("Scene has no UI elements")
|
| 188 |
+
|
| 189 |
+
original_count = len(scene["ui_elements"])
|
| 190 |
+
scene["ui_elements"] = [e for e in scene["ui_elements"] if e.get("id") != element_id]
|
| 191 |
+
|
| 192 |
+
if len(scene["ui_elements"]) == original_count:
|
| 193 |
+
raise ValueError(f"UI element '{element_id}' not found")
|
| 194 |
+
|
| 195 |
+
storage.save(scene)
|
| 196 |
+
|
| 197 |
+
return {
|
| 198 |
+
"scene_id": scene_id,
|
| 199 |
+
"message": f"Removed UI element '{element_id}'",
|
| 200 |
+
"element_id": element_id
|
| 201 |
+
}
|
| 202 |
+
|
| 203 |
+
|
| 204 |
+
def get_ui_elements(scene_id: str) -> Dict[str, Any]:
|
| 205 |
+
"""
|
| 206 |
+
Get all UI elements in the scene.
|
| 207 |
+
|
| 208 |
+
Args:
|
| 209 |
+
scene_id: ID of the scene
|
| 210 |
+
|
| 211 |
+
Returns:
|
| 212 |
+
Dictionary with all UI elements
|
| 213 |
+
"""
|
| 214 |
+
scene = storage.get(scene_id)
|
| 215 |
+
if not scene:
|
| 216 |
+
raise ValueError(f"Scene '{scene_id}' not found")
|
| 217 |
+
|
| 218 |
+
ui_elements = scene.get("ui_elements", [])
|
| 219 |
+
|
| 220 |
+
return {
|
| 221 |
+
"scene_id": scene_id,
|
| 222 |
+
"ui_elements": ui_elements,
|
| 223 |
+
"count": len(ui_elements)
|
| 224 |
+
}
|
models/Norod78/huggingface_emoji.glb
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:ca45de5f071b4e818d832940bf6fc59ff18fd0a94227d2c0e9e04722ed5b4524
|
| 3 |
+
size 953016
|
models/kenney/brick_kit/brick_1x1.glb
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:766918841cedaf66bf15360e3e69225f8835b43a5e12eb32b900cbc0c07301ef
|
| 3 |
+
size 4756
|
models/kenney/brick_kit/brick_1x2.glb
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:eb44790d950228bd44f37a9241375a13f958259a90772902caa52811be435c14
|
| 3 |
+
size 6152
|
models/kenney/brick_kit/brick_1x4.glb
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:55c687e131221895acd02c9f655562ec340c9c6f4e25c483cbc611478d4d2961
|
| 3 |
+
size 8956
|
models/kenney/brick_kit/brick_2x2.glb
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:478bffd9580ca8d1a20334ff362ff4eb9a0f94ed048c1dadf7952cc5ff670366
|
| 3 |
+
size 8956
|
models/kenney/brick_kit/brick_2x4.glb
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:fc06ca10036ff13f3afa83bcb7462fa239c8468ea67881f16d5c38b5dd7e8ca6
|
| 3 |
+
size 15044
|
models/kenney/brick_kit/plate_1x2.glb
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:59cdc39c6c2b10cc0a2182375f5db524a4b7c7ed5affb63cc2045ce8f5485902
|
| 3 |
+
size 6156
|
models/kenney/brick_kit/plate_2x2.glb
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:3c39e29ae9aaa218ebd68db5639b65f3648508de72811f80df50c82df6949989
|
| 3 |
+
size 8956
|
models/kenney/brick_kit/plate_2x4.glb
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:3ba7dc2b828ca9315d70d0bbe68a277f3676238802c5d96550cdb3818aa7fe30
|
| 3 |
+
size 15044
|
models/kenney/brick_kit/plate_4x4.glb
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:6eee58211ba9bf9e0ea192585bd8cb4f3f3708f29d2ff4a8bb9336f758e72f59
|
| 3 |
+
size 26660
|
models/kenney/brick_kit/slope_2x2.glb
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:342da3ab77e3c92724942584b036e74d4b19af1629cf17488ad2a1fc089f539d
|
| 3 |
+
size 6472
|