157 lines
5.1 KiB
Python
157 lines
5.1 KiB
Python
"""Render template resolution service.
|
|
|
|
Used from Celery tasks (sync context) to find the best matching .blend template
|
|
for a given category + output type combination.
|
|
|
|
Cascade priority (first active match wins):
|
|
1. Exact: category_key + output_type linked via M2M
|
|
2. Category only: category_key + no output_types linked
|
|
3. OT only: category_key IS NULL + output_type linked via M2M
|
|
4. Global: category_key IS NULL + no output_types linked
|
|
5. No template → caller falls back to factory-settings behavior
|
|
"""
|
|
import logging
|
|
|
|
from sqlalchemy import create_engine, select, and_, exists
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.domains.materials.library_paths import resolve_asset_library_blend_path
|
|
from app.models.render_template import RenderTemplate
|
|
from app.models.system_setting import SystemSetting
|
|
from app.domains.rendering.models import render_template_output_types
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
_engine = None
|
|
|
|
|
|
def _get_engine():
|
|
global _engine
|
|
if _engine is None:
|
|
from app.config import settings as app_settings
|
|
_engine = create_engine(app_settings.database_url_sync)
|
|
return _engine
|
|
|
|
|
|
def resolve_template_for_session(
|
|
session: Session,
|
|
category_key: str | None = None,
|
|
output_type_id: str | None = None,
|
|
) -> RenderTemplate | None:
|
|
"""Find the best matching active render template on an existing sync session."""
|
|
active = RenderTemplate.is_active == True # noqa: E712
|
|
|
|
def _has_ot(ot_id):
|
|
return exists(
|
|
select(render_template_output_types.c.template_id).where(and_(
|
|
render_template_output_types.c.template_id == RenderTemplate.id,
|
|
render_template_output_types.c.output_type_id == ot_id,
|
|
))
|
|
)
|
|
|
|
_no_ots = ~exists(
|
|
select(render_template_output_types.c.template_id).where(
|
|
render_template_output_types.c.template_id == RenderTemplate.id,
|
|
)
|
|
)
|
|
|
|
if category_key and output_type_id:
|
|
row = session.execute(
|
|
select(RenderTemplate).where(and_(
|
|
active,
|
|
RenderTemplate.category_key == category_key,
|
|
_has_ot(output_type_id),
|
|
))
|
|
).unique().scalar_one_or_none()
|
|
if row:
|
|
return row
|
|
|
|
if category_key:
|
|
row = session.execute(
|
|
select(RenderTemplate).where(and_(
|
|
active,
|
|
RenderTemplate.category_key == category_key,
|
|
_no_ots,
|
|
))
|
|
).unique().scalar_one_or_none()
|
|
if row:
|
|
return row
|
|
|
|
if output_type_id:
|
|
row = session.execute(
|
|
select(RenderTemplate).where(and_(
|
|
active,
|
|
RenderTemplate.category_key.is_(None),
|
|
_has_ot(output_type_id),
|
|
))
|
|
).unique().scalar_one_or_none()
|
|
if row:
|
|
return row
|
|
|
|
return session.execute(
|
|
select(RenderTemplate).where(and_(
|
|
active,
|
|
RenderTemplate.category_key.is_(None),
|
|
_no_ots,
|
|
))
|
|
).scalar_one_or_none()
|
|
|
|
|
|
def resolve_template(
|
|
category_key: str | None = None,
|
|
output_type_id: str | None = None,
|
|
) -> RenderTemplate | None:
|
|
"""Find the best matching active render template.
|
|
|
|
Uses the M2M render_template_output_types table for output type matching.
|
|
Uses sync SQLAlchemy — safe for Celery tasks.
|
|
"""
|
|
engine = _get_engine()
|
|
with Session(engine) as session:
|
|
return resolve_template_for_session(
|
|
session,
|
|
category_key=category_key,
|
|
output_type_id=output_type_id,
|
|
)
|
|
|
|
|
|
def get_material_library_path_for_session(session: Session) -> str | None:
|
|
"""Return the active material library path on an existing sync session."""
|
|
from app.domains.materials.models import AssetLibrary
|
|
|
|
row = session.execute(
|
|
select(AssetLibrary).where(AssetLibrary.is_active == True).limit(1) # noqa: E712
|
|
).scalar_one_or_none()
|
|
if row:
|
|
resolved_path = resolve_asset_library_blend_path(
|
|
blend_file_path=row.blend_file_path,
|
|
asset_library_id=row.id,
|
|
)
|
|
if resolved_path:
|
|
if row.blend_file_path and resolved_path != row.blend_file_path:
|
|
logger.warning(
|
|
"Active asset library %s points to missing file %s; using %s instead",
|
|
row.id,
|
|
row.blend_file_path,
|
|
resolved_path,
|
|
)
|
|
return resolved_path
|
|
|
|
row = session.execute(
|
|
select(SystemSetting).where(SystemSetting.key == "material_library_path")
|
|
).scalar_one_or_none()
|
|
if row and row.value and row.value.strip():
|
|
resolved_path = resolve_asset_library_blend_path(blend_file_path=row.value.strip())
|
|
return resolved_path or row.value.strip()
|
|
return None
|
|
|
|
|
|
def get_material_library_path() -> str | None:
|
|
"""Return the blend_file_path of the first active AssetLibrary.
|
|
|
|
Falls back to the legacy material_library_path system setting.
|
|
"""
|
|
engine = _get_engine()
|
|
with Session(engine) as session:
|
|
return get_material_library_path_for_session(session)
|