281 lines
8.0 KiB
Python
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
|