OverviewFilesCommitsIssuesDocsReleasesPeers

hardops / utility / geometry_nodes / compat.py

160 lines · python ref:
import bpy


'''Bridge for the geometry nodes modifier input storage change in Blender 5.2.\n
Up to 5.1 inputs live in the modifier's IDProperties, keyed by socket identifier:\n
    modifier['Socket_1']\n
    modifier['Socket_1_use_attribute']\n
    modifier['Socket_1_attribute_name']\n
5.2 dropped IDProperty support on NodesModifier entirely and moved inputs to typed RNA:\n
    modifier.properties.inputs.Socket_1.value\n
    modifier.properties.inputs.Socket_1.type       # VALUE / ATTRIBUTE / LAYER\n
    modifier.properties.inputs.Socket_1.attribute_name\n
Subscripting a modifier on 5.2 raises TypeError: this type doesn't support IDProperties.
'''

RNA_INPUTS = 'properties' in bpy.types.NodesModifier.bl_rna.properties


def socket(modifier, identifier):
    '''RNA input struct for identifier, or None. 5.2+ only'''

    return getattr(modifier.properties.inputs, identifier, None)


class ModifierInputs:
    '''Dict-like view of a geometry nodes modifier's inputs, keyed by socket identifier'''

    __slots__ = ('modifier',)

    def __init__(self, modifier):
        self.modifier = modifier

    def __contains__(self, identifier):
        if not RNA_INPUTS:
            return identifier in self.modifier

        return hasattr(self.modifier.properties.inputs, identifier)

    def __getitem__(self, identifier):
        if not RNA_INPUTS:
            return self.modifier[identifier]

        input = socket(self.modifier, identifier)
        if input is None:
            raise KeyError(identifier)

        try:
            return input.value

        except AttributeError: # geometry sockets carry no value
            raise KeyError(identifier)

    def __setitem__(self, identifier, value):
        if not RNA_INPUTS:
            self.modifier[identifier] = value
            return

        input = socket(self.modifier, identifier)
        if input is None:
            raise KeyError(identifier)

        input.value = value

    def get(self, identifier, default=None):
        try:
            return self[identifier]

        except KeyError:
            return default


def supports_attribute(modifier, identifier) -> bool:
    '''True if the input can be driven by an attribute'''

    if not RNA_INPUTS:
        return f'{identifier}_use_attribute' in modifier

    input = socket(modifier, identifier)

    return bool(input) and hasattr(input, 'attribute_name')


def get_use_attribute(modifier, identifier) -> bool:
    if not RNA_INPUTS:
        return bool(modifier.get(f'{identifier}_use_attribute', False))

    input = socket(modifier, identifier)

    return bool(input) and input.type != 'VALUE'


def get_attribute_name(modifier, identifier) -> str:
    if not RNA_INPUTS:
        return modifier.get(f'{identifier}_attribute_name', '')

    input = socket(modifier, identifier)
    if input is None:
        return ''

    return getattr(input, 'attribute_name', '')


def set_attribute_name(modifier, identifier, name):
    if not RNA_INPUTS:
        modifier[f'{identifier}_attribute_name'] = name
        return

    input = socket(modifier, identifier)
    if input is None:
        return

    input.attribute_name = name


def value_prop(modifier, identifier):
    '''(data, property) pair for drawing the input's value with layout.prop'''

    if not RNA_INPUTS:
        return modifier, f'["{identifier}"]'

    return socket(modifier, identifier), 'value'


def attribute_name_prop(modifier, identifier):
    '''(data, property) pair for drawing the input's attribute name with layout.prop'''

    if not RNA_INPUTS:
        return modifier, f'["{identifier}_attribute_name"]'

    return socket(modifier, identifier), 'attribute_name'


def output_attribute_name_prop(modifier, identifier):
    '''(data, property) pair for drawing an output's attribute name, or (None, '') if absent'''

    if not RNA_INPUTS:
        id_name = f'{identifier}_attribute_name'
        if id_name not in modifier:
            return None, ''

        return modifier, f'["{id_name}"]'

    output = getattr(modifier.properties.outputs, identifier, None)
    if output is None or not hasattr(output, 'attribute_name'):
        return None, ''

    return output, 'attribute_name'


def set_output_attribute_name(modifier, identifier, name):
    if not RNA_INPUTS:
        modifier[f'{identifier}_attribute_name'] = name
        return

    output = getattr(modifier.properties.outputs, identifier, None)
    if output is None:
        return

    output.attribute_name = name