Spaces:
Sleeping
Sleeping
| """ | |
| Rendering & Lighting Tools | |
| Fine-grained control over scene lighting, materials, background, fog, | |
| post-processing effects, and camera effects. | |
| """ | |
| from typing import Dict, Any, Optional | |
| from backend.storage import storage | |
| def add_light( | |
| scene_id: str, | |
| light_type: str, | |
| name: str, | |
| color: str = "#ffffff", | |
| intensity: float = 1.0, | |
| position: Optional[Dict[str, float]] = None, | |
| target: Optional[Dict[str, float]] = None, | |
| cast_shadow: bool = False, | |
| spot_angle: Optional[float] = None | |
| ) -> Dict[str, Any]: | |
| """ | |
| Implementation: Add a new light source to the scene | |
| Args: | |
| scene_id: ID of the scene | |
| light_type: "ambient" | "directional" | "point" | "spot" | |
| name: Light identifier (e.g., "Torch1", "MainLight") | |
| color: Hex color (default: "#ffffff") | |
| intensity: Brightness 0.0-2.0 (default: 1.0) | |
| position: Position for directional/point/spot lights | |
| target: Target position for directional/spot lights | |
| cast_shadow: Enable shadows (default: False) | |
| spot_angle: Cone angle in degrees (spot lights only) | |
| Returns: | |
| Dictionary with light details and message | |
| """ | |
| scene = storage.get(scene_id) | |
| if not scene: | |
| raise ValueError(f"Scene '{scene_id}' not found") | |
| # Validate light type | |
| valid_types = ["ambient", "directional", "point", "spot"] | |
| if light_type not in valid_types: | |
| raise ValueError(f"Invalid light_type '{light_type}'. Must be one of: {valid_types}") | |
| # Check for duplicate name | |
| if "lights" not in scene: | |
| scene["lights"] = [] | |
| for light in scene["lights"]: | |
| if light.get("name") == name: | |
| raise ValueError(f"Light with name '{name}' already exists. Use update_light() to modify it.") | |
| # Create light object | |
| light_obj = { | |
| "name": name, | |
| "light_type": light_type, | |
| "color": color, | |
| "intensity": intensity, | |
| "cast_shadow": cast_shadow | |
| } | |
| # Add position for non-ambient lights | |
| if light_type != "ambient": | |
| if position: | |
| light_obj["position"] = position | |
| else: | |
| # Default positions | |
| defaults = { | |
| "directional": {"x": 50, "y": 50, "z": 50}, | |
| "point": {"x": 0, "y": 5, "z": 0}, | |
| "spot": {"x": 0, "y": 5, "z": 0} | |
| } | |
| light_obj["position"] = defaults.get(light_type, {"x": 0, "y": 5, "z": 0}) | |
| # Add target for directional/spot lights | |
| if light_type in ["directional", "spot"] and target: | |
| light_obj["target"] = target | |
| # Add spot angle for spot lights | |
| if light_type == "spot": | |
| light_obj["spot_angle"] = spot_angle if spot_angle else 45.0 | |
| scene["lights"].append(light_obj) | |
| storage.save(scene) | |
| return { | |
| "scene_id": scene_id, | |
| "message": f"Added {light_type} light '{name}' to scene", | |
| "light": light_obj | |
| } | |
| def remove_light(scene_id: str, light_name: str) -> Dict[str, Any]: | |
| """ | |
| Implementation: Remove a light from the scene | |
| Args: | |
| scene_id: ID of the scene | |
| light_name: Name of light to remove | |
| Returns: | |
| Dictionary with confirmation message | |
| """ | |
| scene = storage.get(scene_id) | |
| if not scene: | |
| raise ValueError(f"Scene '{scene_id}' not found") | |
| if "lights" not in scene or not scene["lights"]: | |
| raise ValueError(f"Scene has no lights to remove") | |
| # Find and remove light | |
| original_count = len(scene["lights"]) | |
| scene["lights"] = [light for light in scene["lights"] if light.get("name") != light_name] | |
| if len(scene["lights"]) == original_count: | |
| raise ValueError(f"Light '{light_name}' not found in scene") | |
| storage.save(scene) | |
| return { | |
| "scene_id": scene_id, | |
| "message": f"Removed light '{light_name}' from scene", | |
| "light_name": light_name | |
| } | |
| def update_light( | |
| scene_id: str, | |
| light_name: str, | |
| color: Optional[str] = None, | |
| intensity: Optional[float] = None, | |
| position: Optional[Dict[str, float]] = None, | |
| cast_shadow: Optional[bool] = None | |
| ) -> Dict[str, Any]: | |
| """ | |
| Implementation: Update existing light properties | |
| Args: | |
| scene_id: ID of the scene | |
| light_name: Name of light to update | |
| color: New color (optional) | |
| intensity: New intensity (optional) | |
| position: New position (optional) | |
| cast_shadow: Enable/disable shadows (optional) | |
| Returns: | |
| Dictionary with updated light and message | |
| """ | |
| scene = storage.get(scene_id) | |
| if not scene: | |
| raise ValueError(f"Scene '{scene_id}' not found") | |
| if "lights" not in scene or not scene["lights"]: | |
| raise ValueError(f"Scene has no lights") | |
| # Find light | |
| light = None | |
| for l in scene["lights"]: | |
| if l.get("name") == light_name: | |
| light = l | |
| break | |
| if not light: | |
| raise ValueError(f"Light '{light_name}' not found in scene") | |
| # Update properties | |
| updated_props = [] | |
| if color is not None: | |
| light["color"] = color | |
| updated_props.append(f"color={color}") | |
| if intensity is not None: | |
| light["intensity"] = intensity | |
| updated_props.append(f"intensity={intensity}") | |
| if position is not None: | |
| light["position"] = position | |
| updated_props.append(f"position={position}") | |
| if cast_shadow is not None: | |
| light["cast_shadow"] = cast_shadow | |
| updated_props.append(f"shadows={'on' if cast_shadow else 'off'}") | |
| storage.save(scene) | |
| return { | |
| "scene_id": scene_id, | |
| "message": f"Updated light '{light_name}': {', '.join(updated_props)}", | |
| "light": light | |
| } | |
| def get_lights(scene_id: str) -> Dict[str, Any]: | |
| """ | |
| Implementation: Get all lights in the scene | |
| Args: | |
| scene_id: ID of the scene | |
| Returns: | |
| Dictionary with list of all lights | |
| """ | |
| scene = storage.get(scene_id) | |
| if not scene: | |
| raise ValueError(f"Scene '{scene_id}' not found") | |
| lights = scene.get("lights", []) | |
| return { | |
| "scene_id": scene_id, | |
| "lights": lights, | |
| "count": len(lights) | |
| } | |
| def update_object_material( | |
| scene_id: str, | |
| object_id: str, | |
| color: Optional[str] = None, | |
| metalness: Optional[float] = None, | |
| roughness: Optional[float] = None, | |
| opacity: Optional[float] = None, | |
| emissive: Optional[str] = None, | |
| emissive_intensity: Optional[float] = None | |
| ) -> Dict[str, Any]: | |
| """ | |
| Implementation: Update an object's material properties | |
| Args: | |
| scene_id: ID of the scene | |
| object_id: Object to update | |
| color: Hex color (optional) | |
| metalness: 0.0-1.0 (optional) | |
| roughness: 0.0-1.0 (optional) | |
| opacity: 0.0-1.0 (optional) | |
| emissive: Emissive color for glow (optional) | |
| emissive_intensity: Glow strength (optional) | |
| Returns: | |
| Dictionary with updated material and message | |
| """ | |
| scene = storage.get(scene_id) | |
| if not scene: | |
| raise ValueError(f"Scene '{scene_id}' not found") | |
| if "objects" not in scene or not scene["objects"]: | |
| raise ValueError(f"Scene has no objects") | |
| # Find object | |
| obj = None | |
| for o in scene["objects"]: | |
| if o.get("object_id") == object_id: | |
| obj = o | |
| break | |
| if not obj: | |
| raise ValueError(f"Object '{object_id}' not found in scene") | |
| # Ensure material exists | |
| if "material" not in obj: | |
| obj["material"] = {} | |
| # Update material properties | |
| updated_props = [] | |
| if color is not None: | |
| obj["material"]["color"] = color | |
| updated_props.append(f"color={color}") | |
| if metalness is not None: | |
| obj["material"]["metalness"] = max(0.0, min(1.0, metalness)) | |
| updated_props.append(f"metalness={metalness}") | |
| if roughness is not None: | |
| obj["material"]["roughness"] = max(0.0, min(1.0, roughness)) | |
| updated_props.append(f"roughness={roughness}") | |
| if opacity is not None: | |
| obj["material"]["opacity"] = max(0.0, min(1.0, opacity)) | |
| updated_props.append(f"opacity={opacity}") | |
| if emissive is not None: | |
| obj["material"]["emissive"] = emissive | |
| updated_props.append(f"emissive={emissive}") | |
| if emissive_intensity is not None: | |
| obj["material"]["emissive_intensity"] = emissive_intensity | |
| updated_props.append(f"emissive_intensity={emissive_intensity}") | |
| storage.save(scene) | |
| return { | |
| "scene_id": scene_id, | |
| "object_id": object_id, | |
| "message": f"Updated material: {', '.join(updated_props)}", | |
| "material": obj["material"] | |
| } | |
| def set_background_color( | |
| scene_id: str, | |
| color: Optional[str] = None, | |
| bg_type: str = "solid", | |
| gradient_top: Optional[str] = None, | |
| gradient_bottom: Optional[str] = None | |
| ) -> Dict[str, Any]: | |
| """ | |
| Implementation: Set scene background color | |
| Args: | |
| scene_id: ID of the scene | |
| color: Hex color for solid background | |
| bg_type: "solid" | "gradient" | |
| gradient_top: Top color for gradient | |
| gradient_bottom: Bottom color for gradient | |
| Returns: | |
| Dictionary with background settings and message | |
| """ | |
| scene = storage.get(scene_id) | |
| if not scene: | |
| raise ValueError(f"Scene '{scene_id}' not found") | |
| if "environment" not in scene: | |
| scene["environment"] = {} | |
| if bg_type == "gradient": | |
| if not gradient_top or not gradient_bottom: | |
| raise ValueError("gradient_top and gradient_bottom are required for gradient backgrounds") | |
| scene["environment"]["background_type"] = "gradient" | |
| scene["environment"]["background_gradient_top"] = gradient_top | |
| scene["environment"]["background_gradient_bottom"] = gradient_bottom | |
| message = f"Set background to gradient: {gradient_top} → {gradient_bottom}" | |
| else: | |
| if not color: | |
| raise ValueError("color is required for solid backgrounds") | |
| scene["environment"]["background_type"] = "solid" | |
| scene["environment"]["background_color"] = color | |
| message = f"Set background to {color}" | |
| storage.save(scene) | |
| return { | |
| "scene_id": scene_id, | |
| "message": message, | |
| "background": scene["environment"] | |
| } | |
| def set_fog( | |
| scene_id: str, | |
| enabled: bool, | |
| color: Optional[str] = None, | |
| near: Optional[float] = None, | |
| far: Optional[float] = None, | |
| density: Optional[float] = None | |
| ) -> Dict[str, Any]: | |
| """ | |
| Implementation: Set atmospheric fog | |
| Args: | |
| scene_id: ID of the scene | |
| enabled: Enable/disable fog | |
| color: Fog color (default: "#aaaaaa") | |
| near: Start distance for linear fog | |
| far: End distance for linear fog | |
| density: Density for exponential fog | |
| Returns: | |
| Dictionary with fog settings and message | |
| """ | |
| scene = storage.get(scene_id) | |
| if not scene: | |
| raise ValueError(f"Scene '{scene_id}' not found") | |
| if "environment" not in scene: | |
| scene["environment"] = {} | |
| if "fog" not in scene["environment"]: | |
| scene["environment"]["fog"] = {} | |
| fog = scene["environment"]["fog"] | |
| fog["enabled"] = enabled | |
| if enabled: | |
| fog["color"] = color if color else "#aaaaaa" | |
| # Determine fog type | |
| if density is not None: | |
| fog["type"] = "exponential" | |
| fog["density"] = density | |
| message = f"Enabled exponential fog (density={density}, color={fog['color']})" | |
| elif near is not None and far is not None: | |
| fog["type"] = "linear" | |
| fog["near"] = near | |
| fog["far"] = far | |
| message = f"Enabled linear fog (near={near}, far={far}, color={fog['color']})" | |
| else: | |
| # Default linear fog | |
| fog["type"] = "linear" | |
| fog["near"] = 10 | |
| fog["far"] = 50 | |
| message = f"Enabled linear fog (near=10, far=50, color={fog['color']})" | |
| else: | |
| message = "Disabled fog" | |
| storage.save(scene) | |
| return { | |
| "scene_id": scene_id, | |
| "message": message, | |
| "fog": fog | |
| } | |
| # ============================================================================= | |
| # Post-Processing Tools | |
| # ============================================================================= | |
| def set_bloom( | |
| scene_id: str, | |
| enabled: bool, | |
| strength: float = 1.0, | |
| radius: float = 0.4, | |
| threshold: float = 0.8 | |
| ) -> Dict[str, Any]: | |
| """ | |
| Configure bloom (glow) post-processing effect. | |
| Bloom creates a glow effect around bright areas of the scene, | |
| simulating how cameras capture bright light sources. | |
| Args: | |
| scene_id: ID of the scene | |
| enabled: Enable/disable bloom | |
| strength: Bloom intensity (0.0-3.0, default: 1.0) | |
| radius: Bloom spread/blur radius (0.0-1.0, default: 0.4) | |
| threshold: Brightness threshold to trigger bloom (0.0-1.0, default: 0.8) | |
| Returns: | |
| Dictionary with bloom settings and message | |
| """ | |
| scene = storage.get(scene_id) | |
| if not scene: | |
| raise ValueError(f"Scene '{scene_id}' not found") | |
| if "post_processing" not in scene: | |
| scene["post_processing"] = {} | |
| bloom = { | |
| "enabled": enabled, | |
| "strength": max(0.0, min(3.0, strength)), | |
| "radius": max(0.0, min(1.0, radius)), | |
| "threshold": max(0.0, min(1.0, threshold)) | |
| } | |
| scene["post_processing"]["bloom"] = bloom | |
| storage.save(scene) | |
| if enabled: | |
| message = f"Enabled bloom (strength={strength}, radius={radius}, threshold={threshold})" | |
| else: | |
| message = "Disabled bloom" | |
| return { | |
| "scene_id": scene_id, | |
| "message": message, | |
| "bloom": bloom | |
| } | |
| def set_ssao( | |
| scene_id: str, | |
| enabled: bool, | |
| radius: float = 0.5, | |
| intensity: float = 1.0, | |
| bias: float = 0.025 | |
| ) -> Dict[str, Any]: | |
| """ | |
| Configure Screen Space Ambient Occlusion (SSAO). | |
| SSAO adds soft shadows in corners and crevices where ambient light | |
| would naturally be occluded, adding depth and realism. | |
| Args: | |
| scene_id: ID of the scene | |
| enabled: Enable/disable SSAO | |
| radius: Sample radius in world units (0.1-2.0, default: 0.5) | |
| intensity: Shadow intensity (0.0-2.0, default: 1.0) | |
| bias: Depth bias to prevent self-occlusion (0.001-0.1, default: 0.025) | |
| Returns: | |
| Dictionary with SSAO settings and message | |
| """ | |
| scene = storage.get(scene_id) | |
| if not scene: | |
| raise ValueError(f"Scene '{scene_id}' not found") | |
| if "post_processing" not in scene: | |
| scene["post_processing"] = {} | |
| ssao = { | |
| "enabled": enabled, | |
| "radius": max(0.1, min(2.0, radius)), | |
| "intensity": max(0.0, min(2.0, intensity)), | |
| "bias": max(0.001, min(0.1, bias)) | |
| } | |
| scene["post_processing"]["ssao"] = ssao | |
| storage.save(scene) | |
| if enabled: | |
| message = f"Enabled SSAO (radius={radius}, intensity={intensity})" | |
| else: | |
| message = "Disabled SSAO" | |
| return { | |
| "scene_id": scene_id, | |
| "message": message, | |
| "ssao": ssao | |
| } | |
| def set_color_grading( | |
| scene_id: str, | |
| enabled: bool, | |
| brightness: float = 0.0, | |
| contrast: float = 1.0, | |
| saturation: float = 1.0, | |
| hue: float = 0.0, | |
| exposure: float = 1.0, | |
| gamma: float = 1.0 | |
| ) -> Dict[str, Any]: | |
| """ | |
| Configure color grading post-processing. | |
| Adjust overall image colors for cinematic looks or stylized effects. | |
| Args: | |
| scene_id: ID of the scene | |
| enabled: Enable/disable color grading | |
| brightness: Brightness adjustment (-1.0 to 1.0, default: 0.0) | |
| contrast: Contrast multiplier (0.0-2.0, default: 1.0) | |
| saturation: Color saturation (0.0=grayscale, 1.0=normal, 2.0=vivid) | |
| hue: Hue shift in degrees (-180 to 180, default: 0) | |
| exposure: Exposure adjustment (0.0-3.0, default: 1.0) | |
| gamma: Gamma correction (0.5-2.5, default: 1.0) | |
| Returns: | |
| Dictionary with color grading settings and message | |
| """ | |
| scene = storage.get(scene_id) | |
| if not scene: | |
| raise ValueError(f"Scene '{scene_id}' not found") | |
| if "post_processing" not in scene: | |
| scene["post_processing"] = {} | |
| color_grading = { | |
| "enabled": enabled, | |
| "brightness": max(-1.0, min(1.0, brightness)), | |
| "contrast": max(0.0, min(2.0, contrast)), | |
| "saturation": max(0.0, min(2.0, saturation)), | |
| "hue": max(-180, min(180, hue)), | |
| "exposure": max(0.0, min(3.0, exposure)), | |
| "gamma": max(0.5, min(2.5, gamma)) | |
| } | |
| scene["post_processing"]["color_grading"] = color_grading | |
| storage.save(scene) | |
| if enabled: | |
| adjustments = [] | |
| if brightness != 0.0: | |
| adjustments.append(f"brightness={brightness}") | |
| if contrast != 1.0: | |
| adjustments.append(f"contrast={contrast}") | |
| if saturation != 1.0: | |
| adjustments.append(f"saturation={saturation}") | |
| if hue != 0.0: | |
| adjustments.append(f"hue={hue}°") | |
| if exposure != 1.0: | |
| adjustments.append(f"exposure={exposure}") | |
| if gamma != 1.0: | |
| adjustments.append(f"gamma={gamma}") | |
| if adjustments: | |
| message = f"Enabled color grading ({', '.join(adjustments)})" | |
| else: | |
| message = "Enabled color grading (default settings)" | |
| else: | |
| message = "Disabled color grading" | |
| return { | |
| "scene_id": scene_id, | |
| "message": message, | |
| "color_grading": color_grading | |
| } | |
| def set_vignette( | |
| scene_id: str, | |
| enabled: bool, | |
| intensity: float = 0.5, | |
| smoothness: float = 0.5 | |
| ) -> Dict[str, Any]: | |
| """ | |
| Configure vignette effect (darkened edges). | |
| Vignette darkens the corners and edges of the screen, | |
| drawing focus to the center of the image. | |
| Args: | |
| scene_id: ID of the scene | |
| enabled: Enable/disable vignette | |
| intensity: Darkness of the vignette (0.0-1.0, default: 0.5) | |
| smoothness: Softness of the vignette edge (0.0-1.0, default: 0.5) | |
| Returns: | |
| Dictionary with vignette settings and message | |
| """ | |
| scene = storage.get(scene_id) | |
| if not scene: | |
| raise ValueError(f"Scene '{scene_id}' not found") | |
| if "post_processing" not in scene: | |
| scene["post_processing"] = {} | |
| vignette = { | |
| "enabled": enabled, | |
| "intensity": max(0.0, min(1.0, intensity)), | |
| "smoothness": max(0.0, min(1.0, smoothness)) | |
| } | |
| scene["post_processing"]["vignette"] = vignette | |
| storage.save(scene) | |
| if enabled: | |
| message = f"Enabled vignette (intensity={intensity}, smoothness={smoothness})" | |
| else: | |
| message = "Disabled vignette" | |
| return { | |
| "scene_id": scene_id, | |
| "message": message, | |
| "vignette": vignette | |
| } | |
| def get_post_processing(scene_id: str) -> Dict[str, Any]: | |
| """ | |
| Get all post-processing settings for the scene. | |
| Args: | |
| scene_id: ID of the scene | |
| Returns: | |
| Dictionary with all post-processing settings | |
| """ | |
| scene = storage.get(scene_id) | |
| if not scene: | |
| raise ValueError(f"Scene '{scene_id}' not found") | |
| post_processing = scene.get("post_processing", {}) | |
| return { | |
| "scene_id": scene_id, | |
| "post_processing": post_processing | |
| } | |
| # ============================================================================= | |
| # Camera Effects Tools | |
| # ============================================================================= | |
| def set_depth_of_field( | |
| scene_id: str, | |
| enabled: bool, | |
| focus_distance: float = 10.0, | |
| aperture: float = 0.025, | |
| max_blur: float = 0.01 | |
| ) -> Dict[str, Any]: | |
| """ | |
| Configure depth of field (DoF) camera effect. | |
| Depth of field blurs objects that are not at the focus distance, | |
| simulating how real camera lenses focus on a specific plane. | |
| Args: | |
| scene_id: ID of the scene | |
| enabled: Enable/disable depth of field | |
| focus_distance: Distance to the focal plane in units (default: 10.0) | |
| aperture: Aperture size, affects blur amount (0.001-0.1, default: 0.025) | |
| max_blur: Maximum blur strength (0.0-0.05, default: 0.01) | |
| Returns: | |
| Dictionary with DoF settings and message | |
| """ | |
| scene = storage.get(scene_id) | |
| if not scene: | |
| raise ValueError(f"Scene '{scene_id}' not found") | |
| if "camera_effects" not in scene: | |
| scene["camera_effects"] = {} | |
| dof = { | |
| "enabled": enabled, | |
| "focus_distance": max(0.1, focus_distance), | |
| "aperture": max(0.001, min(0.1, aperture)), | |
| "max_blur": max(0.0, min(0.05, max_blur)) | |
| } | |
| scene["camera_effects"]["depth_of_field"] = dof | |
| storage.save(scene) | |
| if enabled: | |
| message = f"Enabled depth of field (focus={focus_distance}m, aperture={aperture})" | |
| else: | |
| message = "Disabled depth of field" | |
| return { | |
| "scene_id": scene_id, | |
| "message": message, | |
| "depth_of_field": dof | |
| } | |
| def set_motion_blur( | |
| scene_id: str, | |
| enabled: bool, | |
| intensity: float = 0.5, | |
| samples: int = 8 | |
| ) -> Dict[str, Any]: | |
| """ | |
| Configure motion blur camera effect. | |
| Motion blur adds blur in the direction of camera or object movement, | |
| creating a sense of speed and smooth motion. | |
| Args: | |
| scene_id: ID of the scene | |
| enabled: Enable/disable motion blur | |
| intensity: Blur intensity (0.0-2.0, default: 0.5) | |
| samples: Quality samples for blur (4-32, default: 8) | |
| Returns: | |
| Dictionary with motion blur settings and message | |
| """ | |
| scene = storage.get(scene_id) | |
| if not scene: | |
| raise ValueError(f"Scene '{scene_id}' not found") | |
| if "camera_effects" not in scene: | |
| scene["camera_effects"] = {} | |
| motion_blur = { | |
| "enabled": enabled, | |
| "intensity": max(0.0, min(2.0, intensity)), | |
| "samples": max(4, min(32, samples)) | |
| } | |
| scene["camera_effects"]["motion_blur"] = motion_blur | |
| storage.save(scene) | |
| if enabled: | |
| message = f"Enabled motion blur (intensity={intensity}, samples={samples})" | |
| else: | |
| message = "Disabled motion blur" | |
| return { | |
| "scene_id": scene_id, | |
| "message": message, | |
| "motion_blur": motion_blur | |
| } | |
| def set_chromatic_aberration( | |
| scene_id: str, | |
| enabled: bool, | |
| intensity: float = 0.005 | |
| ) -> Dict[str, Any]: | |
| """ | |
| Configure chromatic aberration effect. | |
| Chromatic aberration simulates lens imperfection by separating | |
| color channels at the edges of the screen. | |
| Args: | |
| scene_id: ID of the scene | |
| enabled: Enable/disable chromatic aberration | |
| intensity: Effect strength (0.0-0.05, default: 0.005) | |
| Returns: | |
| Dictionary with chromatic aberration settings and message | |
| """ | |
| scene = storage.get(scene_id) | |
| if not scene: | |
| raise ValueError(f"Scene '{scene_id}' not found") | |
| if "camera_effects" not in scene: | |
| scene["camera_effects"] = {} | |
| chromatic = { | |
| "enabled": enabled, | |
| "intensity": max(0.0, min(0.05, intensity)) | |
| } | |
| scene["camera_effects"]["chromatic_aberration"] = chromatic | |
| storage.save(scene) | |
| if enabled: | |
| message = f"Enabled chromatic aberration (intensity={intensity})" | |
| else: | |
| message = "Disabled chromatic aberration" | |
| return { | |
| "scene_id": scene_id, | |
| "message": message, | |
| "chromatic_aberration": chromatic | |
| } | |
| def get_camera_effects(scene_id: str) -> Dict[str, Any]: | |
| """ | |
| Get all camera effects settings for the scene. | |
| Args: | |
| scene_id: ID of the scene | |
| Returns: | |
| Dictionary with all camera effects settings | |
| """ | |
| scene = storage.get(scene_id) | |
| if not scene: | |
| raise ValueError(f"Scene '{scene_id}' not found") | |
| camera_effects = scene.get("camera_effects", {}) | |
| return { | |
| "scene_id": scene_id, | |
| "camera_effects": camera_effects | |
| } | |
| # ============================================================================= | |
| # Toon/Cel Shading Tools | |
| # ============================================================================= | |
| def update_material_to_toon( | |
| scene_id: str, | |
| object_id: str, | |
| enabled: bool = True, | |
| color: Optional[str] = None, | |
| gradient_steps: int = 3, | |
| outline: bool = True, | |
| outline_color: str = "#000000", | |
| outline_thickness: float = 0.03 | |
| ) -> Dict[str, Any]: | |
| """ | |
| Convert an object's material to toon/cel-shaded style. | |
| Toon shading creates a cartoon-like appearance with discrete shading bands | |
| instead of smooth gradients, commonly used in anime and cel-animation styles. | |
| Args: | |
| scene_id: ID of the scene | |
| object_id: ID of the object to update | |
| enabled: Enable/disable toon shading (True to apply, False to revert to standard) | |
| color: Base color for toon material (optional, keeps existing if not set) | |
| gradient_steps: Number of shading steps (2=hard, 3=medium, 5=soft, default: 3) | |
| outline: Add black outline effect (default: True) | |
| outline_color: Color of the outline (default: "#000000") | |
| outline_thickness: Thickness of outline (0.01-0.1, default: 0.03) | |
| Returns: | |
| Dictionary with updated material info and message | |
| """ | |
| scene = storage.get(scene_id) | |
| if not scene: | |
| raise ValueError(f"Scene '{scene_id}' not found") | |
| if "objects" not in scene or not scene["objects"]: | |
| raise ValueError("Scene has no objects") | |
| # Find object | |
| obj = None | |
| for o in scene["objects"]: | |
| if o.get("id") == object_id or o.get("object_id") == object_id: | |
| obj = o | |
| break | |
| if not obj: | |
| raise ValueError(f"Object '{object_id}' not found in scene") | |
| # Ensure material exists | |
| if "material" not in obj: | |
| obj["material"] = {} | |
| # Update toon properties | |
| if enabled: | |
| obj["material"]["toon"] = { | |
| "enabled": True, | |
| "gradient_steps": max(2, min(10, gradient_steps)), | |
| "outline": outline, | |
| "outline_color": outline_color, | |
| "outline_thickness": max(0.01, min(0.1, outline_thickness)) | |
| } | |
| if color: | |
| obj["material"]["color"] = color | |
| message = f"Applied toon shading to '{object_id}' ({gradient_steps} steps{', with outline' if outline else ''})" | |
| else: | |
| obj["material"]["toon"] = {"enabled": False} | |
| message = f"Disabled toon shading on '{object_id}' (reverted to standard material)" | |
| storage.save(scene) | |
| return { | |
| "scene_id": scene_id, | |
| "object_id": object_id, | |
| "message": message, | |
| "material": obj["material"] | |
| } | |