chore: snapshot workflow migration progress
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
"""Generic template-input application for Blender template scenes.
|
||||
|
||||
Template inputs are exposed to the scene via custom properties and optional
|
||||
visibility/selection markers. This keeps legacy templates untouched while
|
||||
allowing graph workflows to pass structured overrides into `.blend` scenes.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
import bpy # type: ignore[import]
|
||||
|
||||
|
||||
_MARKER_PROP_NAMES = (
|
||||
"hartomat_template_input",
|
||||
"hartomat.template_input",
|
||||
"template_input",
|
||||
"schaeffler_template_input",
|
||||
)
|
||||
_MARKER_KEY_PROP_NAMES = (
|
||||
"hartomat_template_input_key",
|
||||
"hartomat.template_input_key",
|
||||
"template_input_key",
|
||||
"schaeffler_template_input_key",
|
||||
)
|
||||
_MARKER_VALUE_PROP_NAMES = (
|
||||
"hartomat_template_input_value",
|
||||
"hartomat.template_input_value",
|
||||
"template_input_value",
|
||||
"schaeffler_template_input_value",
|
||||
)
|
||||
_NAME_PATTERNS = (
|
||||
re.compile(r"template_input__(?P<key>[^_]+)__(?P<value>[^_]+)", re.IGNORECASE),
|
||||
re.compile(r"template-input:(?P<key>[^=]+)=(?P<value>.+)", re.IGNORECASE),
|
||||
re.compile(r"ti::(?P<key>[^:]+)::(?P<value>.+)", re.IGNORECASE),
|
||||
)
|
||||
|
||||
|
||||
def _normalize_template_inputs(template_inputs: dict[str, Any] | None) -> dict[str, str]:
|
||||
normalized: dict[str, str] = {}
|
||||
for raw_key, raw_value in (template_inputs or {}).items():
|
||||
key = str(raw_key or "").strip()
|
||||
if not key or raw_value is None:
|
||||
continue
|
||||
if isinstance(raw_value, bool):
|
||||
value = "true" if raw_value else "false"
|
||||
else:
|
||||
value = str(raw_value).strip()
|
||||
if value:
|
||||
normalized[key] = value
|
||||
return normalized
|
||||
|
||||
|
||||
def _scene_targets():
|
||||
yield ("collection", bpy.data.collections)
|
||||
yield ("object", bpy.data.objects)
|
||||
yield ("world", bpy.data.worlds)
|
||||
|
||||
|
||||
def _extract_marker(target) -> tuple[str, str] | None:
|
||||
for prop_name in _MARKER_PROP_NAMES:
|
||||
raw = target.get(prop_name)
|
||||
if not raw:
|
||||
continue
|
||||
if isinstance(raw, str):
|
||||
text = raw.strip()
|
||||
if not text:
|
||||
continue
|
||||
if text.startswith("{"):
|
||||
try:
|
||||
payload = json.loads(text)
|
||||
except Exception:
|
||||
payload = None
|
||||
if isinstance(payload, dict):
|
||||
key = str(payload.get("key", "")).strip()
|
||||
value = str(payload.get("value", "")).strip()
|
||||
if key and value:
|
||||
return key, value
|
||||
if "=" in text:
|
||||
key, value = text.split("=", 1)
|
||||
key = key.strip()
|
||||
value = value.strip()
|
||||
if key and value:
|
||||
return key, value
|
||||
|
||||
key = None
|
||||
value = None
|
||||
for prop_name in _MARKER_KEY_PROP_NAMES:
|
||||
raw = target.get(prop_name)
|
||||
if raw:
|
||||
key = str(raw).strip()
|
||||
if key:
|
||||
break
|
||||
for prop_name in _MARKER_VALUE_PROP_NAMES:
|
||||
raw = target.get(prop_name)
|
||||
if raw is not None:
|
||||
value = str(raw).strip()
|
||||
if value:
|
||||
break
|
||||
if key and value:
|
||||
return key, value
|
||||
|
||||
name = getattr(target, "name", "") or ""
|
||||
for pattern in _NAME_PATTERNS:
|
||||
match = pattern.search(name)
|
||||
if not match:
|
||||
continue
|
||||
key = match.group("key").strip()
|
||||
value = match.group("value").strip()
|
||||
if key and value:
|
||||
return key, value
|
||||
return None
|
||||
|
||||
|
||||
def _find_layer_collection(layer_collection, collection_name: str):
|
||||
if layer_collection.collection.name == collection_name:
|
||||
return layer_collection
|
||||
for child in layer_collection.children:
|
||||
found = _find_layer_collection(child, collection_name)
|
||||
if found is not None:
|
||||
return found
|
||||
return None
|
||||
|
||||
|
||||
def _apply_collection_visibility(collection, *, enabled: bool) -> None:
|
||||
collection.hide_render = not enabled
|
||||
collection.hide_viewport = not enabled
|
||||
for view_layer in bpy.context.scene.view_layers:
|
||||
layer_collection = _find_layer_collection(view_layer.layer_collection, collection.name)
|
||||
if layer_collection is not None:
|
||||
layer_collection.exclude = not enabled
|
||||
layer_collection.hide_viewport = not enabled
|
||||
|
||||
|
||||
def _apply_object_visibility(obj, *, enabled: bool) -> None:
|
||||
obj.hide_render = not enabled
|
||||
obj.hide_viewport = not enabled
|
||||
try:
|
||||
obj.hide_set(not enabled)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _apply_world_selection(world, *, enabled: bool) -> None:
|
||||
if enabled:
|
||||
bpy.context.scene.world = world
|
||||
|
||||
|
||||
def apply_template_inputs(template_inputs: dict[str, Any] | None) -> None:
|
||||
normalized = _normalize_template_inputs(template_inputs)
|
||||
if not normalized:
|
||||
return
|
||||
|
||||
scene = bpy.context.scene
|
||||
for key, value in normalized.items():
|
||||
scene[f"template_input__{key}"] = value
|
||||
scene[f"hartomat_template_input__{key}"] = value
|
||||
|
||||
matched_targets = 0
|
||||
for kind, targets in _scene_targets():
|
||||
for target in targets:
|
||||
marker = _extract_marker(target)
|
||||
if marker is None:
|
||||
continue
|
||||
key, expected_value = marker
|
||||
if key not in normalized:
|
||||
continue
|
||||
enabled = normalized[key] == expected_value
|
||||
matched_targets += 1
|
||||
if kind == "collection":
|
||||
_apply_collection_visibility(target, enabled=enabled)
|
||||
elif kind == "object":
|
||||
_apply_object_visibility(target, enabled=enabled)
|
||||
elif kind == "world":
|
||||
_apply_world_selection(target, enabled=enabled)
|
||||
|
||||
print(
|
||||
f"[blender_render] applied template_inputs keys={sorted(normalized)} matched_targets={matched_targets}",
|
||||
flush=True,
|
||||
)
|
||||
Reference in New Issue
Block a user