Files
wtd-libreflow-addon/config.py
T

281 lines
8.0 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",
]
# --------------------------------------------------------------------------- #
# 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