Files
workinf_Blender_Wasm/tools/web/server_operator_properties.py
mes123456 f3e21c6b98
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled
chore: checkpoint web Blender migration source, tools and docs
Accumulated uncommitted work: GHOST Web backend, WebGL/GPU web layer,
web workflow engine and protocol, tooling, docs and task records.
Large generated evidence, user-testing archives, build snapshots and
imported test assets remain untracked.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-11-10 11:18:01 +08:00

103 lines
4.7 KiB
Python

"""RNA-derived property descriptions and validation inside the native Blender job."""
import math
MAX_OPERATOR_ARRAY_LENGTH = 64
MAX_OPERATOR_STRING_BYTES = 4096
def operator_property_spec(prop):
kind = str(getattr(prop, 'type', '')).upper()
if prop.identifier == 'rna_type' or kind not in ('BOOLEAN', 'INT', 'FLOAT', 'DOUBLE', 'ENUM', 'STRING'):
return None
length = int(getattr(prop, 'array_length', 0))
dimensions = tuple(getattr(prop, 'array_dimensions', ()))
if length > MAX_OPERATOR_ARRAY_LENGTH or sum(value > 0 for value in dimensions) > 1:
return None
if length and kind not in ('BOOLEAN', 'INT', 'FLOAT', 'DOUBLE'):
return None
flag = kind == 'ENUM' and bool(getattr(prop, 'is_enum_flag', False))
value_type = 'boolean' if kind == 'BOOLEAN' else 'number' if kind in ('INT', 'FLOAT', 'DOUBLE') else 'enum' if kind == 'ENUM' else 'string'
if length:
value_type += '-array'
elif flag:
value_type = 'enum-set'
spec = {'name': prop.identifier, 'type': value_type, 'required': bool(getattr(prop, 'is_required', False))}
for key, attr in (('label', 'name'), ('description', 'description'), ('subtype', 'subtype'), ('unit', 'unit')):
value = getattr(prop, attr, None)
if value:
spec[key] = str(value)
if length:
spec['arrayLength'] = length
if kind == 'INT':
spec['integer'] = True
try:
if length:
default = list(prop.default_array)
elif flag:
default = sorted(prop.default_flag)
else:
default = prop.default
if isinstance(default, (bool, int, float, str, list)):
values = default if isinstance(default, list) else [default]
if all(not isinstance(v, float) or math.isfinite(v) for v in values):
spec['defaultValue'] = default
except (AttributeError, TypeError, ValueError):
pass
for key, attr in (('min', 'hard_min'), ('max', 'hard_max')):
value = getattr(prop, attr, None)
if type(value) in (int, float) and math.isfinite(value):
spec[key] = value
if kind == 'ENUM':
items = [{'value': str(item.identifier), 'label': str(item.name)} for item in prop.enum_items if item.identifier]
if items:
spec['enumItems'] = items
return spec
def validated_operator_properties(target, properties):
"""Validate against the operator's live RNA immediately before execution."""
specs = {prop.identifier: operator_property_spec(prop) for prop in target.get_rna_type().properties}
result = {}
for name, value in properties.items():
spec = specs.get(name)
if spec is None:
raise ValueError('SERVER_OPERATOR_PROPERTY_BLOCKED: ' + name)
kind = spec['type']
if kind.endswith('-array'):
if not isinstance(value, list) or len(value) != spec['arrayLength']:
raise ValueError('SERVER_OPERATOR_PROPERTY_LENGTH: ' + name)
scalar_kind = kind.removesuffix('-array')
values = value
elif kind == 'enum-set':
if not isinstance(value, list) or len(value) > MAX_OPERATOR_ARRAY_LENGTH or any(type(v) is not str for v in value) or len(set(value)) != len(value):
raise ValueError('SERVER_OPERATOR_PROPERTY_TYPE: ' + name)
scalar_kind = 'enum'
values = value
else:
scalar_kind = kind
values = [value]
for item in values:
valid = False
if scalar_kind == 'boolean':
valid = type(item) is bool
elif scalar_kind == 'number':
valid = type(item) in (int, float) and math.isfinite(item)
if valid and spec.get('integer'):
valid = float(item).is_integer() and abs(item) <= 9007199254740991
if valid:
valid = spec.get('min', -math.inf) <= item <= spec.get('max', math.inf)
elif scalar_kind in ('string', 'enum'):
valid = type(item) is str and len(item.encode()) <= MAX_OPERATOR_STRING_BYTES
# Context-dependent enum callbacks may expose no items while describing.
if valid and scalar_kind == 'enum' and spec.get('enumItems'):
valid = item in {entry['value'] for entry in spec['enumItems']}
if not valid:
raise ValueError('SERVER_OPERATOR_PROPERTY_TYPE_OR_RANGE: ' + name)
if kind == 'enum-set':
result[name] = set(value)
elif spec.get('integer'):
result[name] = [int(v) for v in value] if isinstance(value, list) else int(value)
else:
result[name] = value
return result