update
This commit is contained in:
116
src/utils/overlay_assets.py
Normal file
116
src/utils/overlay_assets.py
Normal file
@@ -0,0 +1,116 @@
|
||||
"""
|
||||
Utilities for managing image overlay assets, including SVG rasterization.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import math
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
class SVGConversionError(RuntimeError):
|
||||
"""Raised when an SVG overlay cannot be rasterized."""
|
||||
pass
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class RasterizationResult:
|
||||
"""Metadata about a rasterized overlay image."""
|
||||
|
||||
path: str
|
||||
applied_scale: float
|
||||
|
||||
|
||||
def _get_bpy_module():
|
||||
"""Return bpy module if available, otherwise None."""
|
||||
return sys.modules.get("bpy")
|
||||
|
||||
|
||||
def is_svg_path(path: str) -> bool:
|
||||
"""Return True if the provided path looks like an SVG file."""
|
||||
if not path:
|
||||
return False
|
||||
return str(path).lower().endswith(".svg")
|
||||
|
||||
|
||||
def get_overlay_cache_dir() -> str:
|
||||
"""
|
||||
Return a writable directory for cached overlay images.
|
||||
|
||||
Prefers Blender's CONFIG directory when available, otherwise falls back
|
||||
to the system temporary directory.
|
||||
"""
|
||||
base_dir = None
|
||||
bpy_module = _get_bpy_module()
|
||||
|
||||
if bpy_module is not None:
|
||||
bpy_utils = getattr(bpy_module, "utils", None)
|
||||
user_resource = getattr(bpy_utils, "user_resource", None)
|
||||
if callable(user_resource):
|
||||
try:
|
||||
base_dir = user_resource('CONFIG')
|
||||
except Exception:
|
||||
base_dir = None
|
||||
|
||||
if not base_dir:
|
||||
bpy_app = getattr(bpy_module, "app", None)
|
||||
base_dir = getattr(bpy_app, "tempdir", None)
|
||||
|
||||
if not base_dir:
|
||||
base_dir = tempfile.gettempdir()
|
||||
|
||||
cache_dir = os.path.join(os.path.abspath(base_dir), "text_texture_generator", "overlay_cache")
|
||||
try:
|
||||
os.makedirs(cache_dir, exist_ok=True)
|
||||
except OSError:
|
||||
# Fall back to a temporary directory specific to this process.
|
||||
fallback_dir = os.path.join(tempfile.gettempdir(), "text_texture_generator", "overlay_cache")
|
||||
os.makedirs(fallback_dir, exist_ok=True)
|
||||
cache_dir = fallback_dir
|
||||
|
||||
return cache_dir
|
||||
|
||||
|
||||
def ensure_svg_raster(image_path: str, scale: float) -> RasterizationResult:
|
||||
"""
|
||||
Ensure a rasterized PNG exists for the given SVG and return its path with applied scale.
|
||||
|
||||
The rasterization scale is applied during conversion so the caller should not
|
||||
reapply the same scale.
|
||||
"""
|
||||
if not image_path or not os.path.exists(image_path):
|
||||
raise SVGConversionError(f"SVG overlay path does not exist: {image_path}")
|
||||
|
||||
try:
|
||||
import cairosvg # type: ignore
|
||||
except ImportError as import_error:
|
||||
raise SVGConversionError(
|
||||
"SVG overlay support requires the 'cairosvg' package. "
|
||||
"Install it via pip (pip install cairosvg) inside Blender's Python environment."
|
||||
) from import_error
|
||||
|
||||
# Guard against invalid scales (NaN, infinity, zero, negative)
|
||||
if not isinstance(scale, (int, float)) or not math.isfinite(scale):
|
||||
raise SVGConversionError(f"Invalid overlay scale for SVG: {scale!r}")
|
||||
|
||||
if scale <= 0:
|
||||
raise SVGConversionError("Overlay scale must be greater than zero for SVG rasterization.")
|
||||
|
||||
normalized_scale = max(float(scale), 0.01)
|
||||
source_mtime = os.path.getmtime(image_path)
|
||||
cache_dir = get_overlay_cache_dir()
|
||||
|
||||
cache_key = f"{os.path.abspath(image_path)}|{source_mtime:.6f}|{normalized_scale:.6f}"
|
||||
cache_name = hashlib.sha256(cache_key.encode("utf-8")).hexdigest()[:32] + ".png"
|
||||
cache_path = os.path.join(cache_dir, cache_name)
|
||||
|
||||
if not os.path.exists(cache_path):
|
||||
# Perform rasterization and write to cache path
|
||||
with open(cache_path, "wb") as cache_file:
|
||||
cairosvg.svg2png(url=image_path, scale=normalized_scale, write_to=cache_file) # type: ignore[arg-type]
|
||||
|
||||
return RasterizationResult(path=cache_path, applied_scale=1.0)
|
||||
Reference in New Issue
Block a user