Files
wtd-libreflow-addon/config.py
T
Nero Frame 13andClaude Opus 5.5 a83bc959d0 Added README user guide, new shots start at frame 1001
README.md documents installation, the Sanity Check, Rename and Shot
Viewer tools, the config overrides and troubleshooting.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 16:21:45 +02:00

324 lines
9.6 KiB
Python

"""Production specs used by the checks.
Every UPPERCASE value can be overridden by a studio JSON file, see
``apply_overrides`` (path set in the add-on preferences or through the
``WTD_SANITY_CONFIG`` environment variable). Checks must read values at run
time (``config.FPS``), never copy them at import time, so overrides apply.
Values marked TODO are placeholders waiting for the real production specs.
"""
import copy
import json
import os
import sys
# --------------------------------------------------------------------------- #
# Scene / render
# --------------------------------------------------------------------------- #
FPS = 24
FPS_BASE = 1.0
RESOLUTION = (2048, 858)
RESOLUTION_PERCENTAGE = 100
PIXEL_ASPECT = (1.0, 1.0)
# Units. TODO: confirm with production (metric/meters assumed).
UNIT_SYSTEM = "METRIC"
UNIT_SCALE_LENGTH = 1.0
UNIT_LENGTH = "METERS"
# Color management. TODO: colorspace still to be determined by production.
# A value of None means "not checked".
COLOR_MANAGEMENT = {
"display_device": "sRGB",
"view_transform": "Standard",
"look": "None",
"exposure": 0.0,
"gamma": 1.0,
"sequencer_colorspace": "sRGB",
"working_space": None, # e.g. "Linear Rec.709" (Blender 5+). Check only, never auto-fixed.
}
# --------------------------------------------------------------------------- #
# File names. TODO: replace with the real Libreflow/Kitsu naming.
# Named group "asset" is used to guess the asset/character name.
# --------------------------------------------------------------------------- #
FILENAME_PATTERNS = {
"GENERAL": r"^(?:prp|set|chr)_(?P<asset>[A-Za-z0-9]+)_(?:mod|lineart|shading)(?:_v\d{3})?\.blend$",
"CHAR_MOD": r"^chr_(?P<asset>[A-Za-z0-9]+)_mod(?:_v\d{3})?\.blend$",
"CHAR_LINEART": r"^chr_(?P<asset>[A-Za-z0-9]+)_(?:lineart|shading)(?:_v\d{3})?\.blend$",
"LAYOUT": r"^(?P<sequence>sq\d{3})_(?P<shot>sh\d{4})_layout(?:_v\d{3})?\.blend$",
}
# --------------------------------------------------------------------------- #
# Naming
# --------------------------------------------------------------------------- #
# Body of a name after its prefix: snake_case or PascalCase tokens joined by "_".
NAME_BODY_PATTERN = r"[A-Za-z0-9]+(?:_[A-Za-z0-9]+)*"
# Object prefix per object type. Types not listed are not checked.
OBJECT_PREFIXES = {
"MESH": "GEO_",
"CURVE": "CRV_",
"SURFACE": "SRF_",
"FONT": "TXT_",
"EMPTY": "NUL_",
"ARMATURE": "RIG_",
"LATTICE": "LAT_",
"GREASEPENCIL": "LA_",
"CAMERA": "CAM_",
"LIGHT": "LGT_",
}
# Prefix per bpy.data collection.
DATABLOCK_PREFIXES = {
"collections": "COL_",
"materials": "MAT_",
"node_groups": "NG_",
}
# Mesh datablocks must carry the same name as their (single) object.
MESH_DATA_MATCHES_OBJECT = True
# Blender default names that must never survive in a published file.
DEFAULT_NAMES = {
"cube",
"plane",
"sphere",
"uvsphere",
"icosphere",
"cylinder",
"cone",
"torus",
"circle",
"grid",
"suzanne",
"monkey",
"empty",
"camera",
"light",
"lamp",
"point",
"sun",
"spot",
"area",
"material",
"collection",
"beziercurve",
"beziercircle",
"nurbscurve",
"nurbspath",
"text",
"armature",
"lattice",
"nodegroup",
"geometrynodes",
"stroke",
"gpencil",
"lineart",
"scene",
"world",
"object",
"mesh",
}
# --------------------------------------------------------------------------- #
# Structure
# --------------------------------------------------------------------------- #
# Collections that must exist. "{asset}" is replaced by the asset name.
REQUIRED_COLLECTIONS = []
# --------------------------------------------------------------------------- #
# Geometry
# --------------------------------------------------------------------------- #
MAX_FACE_SIDES = 4
# sin() of the reflex angle above which a corner counts as concave.
CONCAVE_TOLERANCE = 1e-4
# Open borders are reported as non-manifold unless this is True.
NON_MANIFOLD_ALLOW_BOUNDARY = False
UV_NEGATIVE_TOLERANCE = 1e-6
# --------------------------------------------------------------------------- #
# Objects
# --------------------------------------------------------------------------- #
ALLOWED_MODIFIERS = {"SUBSURF", "MULTIRES"}
ALLOWED_MODIFIERS_LINEART = {
"SUBSURF",
"MULTIRES",
"LINEART",
"GREASE_PENCIL_THICKNESS",
}
# Which transform channels must be applied.
APPLY_TRANSFORM = {"location": False, "rotation": True, "scale": True}
APPLY_TRANSFORM_TYPES = {"MESH", "CURVE", "SURFACE", "FONT", "META"}
TRANSFORM_TOLERANCE = 1e-5
# Object types that must not be present in asset files.
FORBIDDEN_ASSET_OBJECT_TYPES = {"LIGHT", "CAMERA"}
# --------------------------------------------------------------------------- #
# Characters
# --------------------------------------------------------------------------- #
# Reference JSON exported from the master character (see panel > Export
# Character Reference). Overridden by the add-on preferences or by the
# WTD_CHAR_REFERENCE environment variable.
CHAR_REFERENCE_PATH = ""
# Name of the character body object. "{asset}" is the character name.
CHAR_BODY_NAME = "GEO_{asset}_Body"
# Name keywords that identify inner mouth / eye parts (case-insensitive).
CHAR_PART_KEYWORDS = [
"eye",
"iris",
"pupil",
"cornea",
"sclera",
"lid",
"lash",
"mouth",
"teeth",
"tooth",
"tongue",
"gum",
"palate",
]
# Vertex groups the body mesh must have. TODO: real list.
CHAR_BODY_VERTEX_GROUPS = []
# --------------------------------------------------------------------------- #
# Layout
# --------------------------------------------------------------------------- #
# Instanced collections matching this pattern are sets that must sit at the origin.
SET_COLLECTION_PATTERN = r"^(?:SET|ENV|set|env)[_-]"
# Version token in a file or folder name (Libreflow stores v001/ folders).
VERSION_PATTERN = r"(?<![A-Za-z0-9])v(\d{3,4})(?![0-9])"
# Datablock types scanned for ".001" duplicated names.
DUPLICATE_NAME_TYPES = [
"objects",
"collections",
"cameras",
"materials",
"actions",
"node_groups",
]
# --------------------------------------------------------------------------- #
# Rename tool
# --------------------------------------------------------------------------- #
# Names are built as {prefix}_{name}_{side}_{number}_{suffix}, empty parts dropped.
# "By Type" prefixes come from OBJECT_PREFIXES / DATABLOCK_PREFIXES.
RENAME_SEPARATOR = "_"
RENAME_PADDING = 3
# Orientation tokens. TODO: confirm with production.
RENAME_SIDES = ["center", "left", "right", "front", "back", "top", "bottom"]
# "By Type" suffix per object type ("MATERIAL" for materials). TODO: confirm with production.
RENAME_TYPE_SUFFIXES = {
"MESH": "mesh",
"CURVE": "curve",
"SURFACE": "surface",
"FONT": "text",
"EMPTY": "empty",
"ARMATURE": "rig",
"LATTICE": "lattice",
"GREASEPENCIL": "gp",
"CAMERA": "cam",
"LIGHT": "light",
"MATERIAL": "mat",
}
# --------------------------------------------------------------------------- #
# Shot viewer
# --------------------------------------------------------------------------- #
# Groups of the rig quick selection: an armature goes to the first group whose
# pattern matches its name, one of its collections or its library file name.
# Unmatched armatures are listed under "Other". TODO: confirm with production.
SHOT_VIEWER_RIG_GROUPS = {
"Characters": r"(?i)^(?:chr|char)[_-]",
"Props": r"(?i)^(?:prp|prop)[_-]",
}
# Shots added by the shot viewer. TODO: confirm with production.
SHOT_NAME_FORMAT = "sh{:04d}"
SHOT_NUMBER_STEP = 10
SHOT_FRAME_START = 1001
SHOT_DEFAULT_DURATION = 48
# --------------------------------------------------------------------------- #
# UI
# --------------------------------------------------------------------------- #
CATEGORY_ORDER = [
"Scene",
"File",
"Naming",
"Structure",
"Geometry",
"UVs",
"Objects",
"Shading",
"Animation",
"Character",
"Lineart",
"Shot",
"Sound",
]
MAX_ISSUES_DRAWN = 30
# --------------------------------------------------------------------------- #
# Overrides
# --------------------------------------------------------------------------- #
_DEFAULTS = None
def _snapshot():
global _DEFAULTS
if _DEFAULTS is None:
module = sys.modules[__name__]
_DEFAULTS = {
k: copy.deepcopy(v)
for k, v in vars(module).items()
if k.isupper() and not k.startswith("_")
}
return _DEFAULTS
def apply_overrides(path=""):
"""Reset to defaults, then apply a JSON file of {"NAME": value} overrides.
``path`` falls back to the WTD_SANITY_CONFIG environment variable.
Returns the list of keys that were overridden.
"""
module = sys.modules[__name__]
defaults = _snapshot()
for key, value in defaults.items():
setattr(module, key, copy.deepcopy(value))
path = path or os.environ.get("WTD_SANITY_CONFIG", "")
if not path:
return []
with open(path, encoding="utf-8") as fh:
data = json.load(fh)
applied = []
for key, value in data.items():
if key not in defaults:
print(f"[WTD Sanity] Unknown config key ignored: {key}")
continue
default = defaults[key]
if isinstance(default, (set, frozenset)):
value = set(value)
elif isinstance(default, tuple):
value = tuple(value)
elif isinstance(default, dict) and isinstance(value, dict):
value = {**default, **value}
setattr(module, key, value)
applied.append(key)
return applied