|
|
""" |
|
|
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") |
|
|
|
|
|
|
|
|
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}") |
|
|
|
|
|
|
|
|
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.") |
|
|
|
|
|
|
|
|
light_obj = { |
|
|
"name": name, |
|
|
"light_type": light_type, |
|
|
"color": color, |
|
|
"intensity": intensity, |
|
|
"cast_shadow": cast_shadow |
|
|
} |
|
|
|
|
|
|
|
|
if light_type != "ambient": |
|
|
if position: |
|
|
light_obj["position"] = position |
|
|
else: |
|
|
|
|
|
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}) |
|
|
|
|
|
|
|
|
if light_type in ["directional", "spot"] and target: |
|
|
light_obj["target"] = target |
|
|
|
|
|
|
|
|
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") |
|
|
|
|
|
|
|
|
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") |
|
|
|
|
|
|
|
|
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") |
|
|
|
|
|
|
|
|
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") |
|
|
|
|
|
|
|
|
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") |
|
|
|
|
|
|
|
|
if "material" not in obj: |
|
|
obj["material"] = {} |
|
|
|
|
|
|
|
|
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" |
|
|
|
|
|
|
|
|
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: |
|
|
|
|
|
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 |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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") |
|
|
|
|
|
|
|
|
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") |
|
|
|
|
|
|
|
|
if "material" not in obj: |
|
|
obj["material"] = {} |
|
|
|
|
|
|
|
|
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"] |
|
|
} |
|
|
|