From aa11b398a082229944595ab50aa52539f0958c21 Mon Sep 17 00:00:00 2001 From: Carson Katri Date: Thu, 17 Nov 2022 18:13:07 -0500 Subject: [PATCH] Add InputGroup to simplify large sets of inputs --- api/node_mapper.py | 15 ++++++++++++++- api/static/input_group.py | 17 +++++++++++++++++ api/tree.py | 25 +++++++++++++++++++------ api/types.py | 2 +- 4 files changed, 51 insertions(+), 8 deletions(-) create mode 100644 api/static/input_group.py diff --git a/api/node_mapper.py b/api/node_mapper.py index 5fe50d6..475170a 100644 --- a/api/node_mapper.py +++ b/api/node_mapper.py @@ -4,6 +4,7 @@ import itertools import enum from .state import State from .types import * +from .static.input_group import InputGroup from ..absolute_path import absolute_path class OutputsList(dict): @@ -13,6 +14,10 @@ class OutputsList(dict): def build_node(node_type): def build(_primary_arg=None, **kwargs): + for k, v in kwargs.items(): + if isinstance(v, InputGroup): + kwargs = { **kwargs, **v.__dict__ } + del kwargs[k] node = State.current_node_tree.nodes.new(node_type.__name__) if _primary_arg is not None: State.current_node_tree.links.new(_primary_arg._socket, node.inputs[0]) @@ -42,7 +47,7 @@ def build_node(node_type): value = kwargs[argname] if isinstance(value, enum.Enum): value = value.value - if node_input.is_multi_input and hasattr(value, '__iter__') and len() > 0 and issubclass(type(next(iter(value))), Type): + if node_input.is_multi_input and hasattr(value, '__iter__') and len(value) > 0 and issubclass(type(next(iter(value))), Type): for x in value: for node_input in all_with_name: State.current_node_tree.links.new(x._socket, node_input) @@ -326,6 +331,14 @@ class Type: {newline.join(map(type_symbol, Type.__subclasses__()))} {newline.join(map(enum_namespace, enums.keys()))} {newline.join(symbols)}""" + + static_path = absolute_path('api/static') + for path in os.listdir(static_path): + if os.path.splitext(path)[-1] != '.py': + continue + with open(os.path.join(static_path, path), 'r') as static_api: + contents += f"\n\n# {path}\n{static_api.read()}" + fpyi.write(contents) fpy.write(contents) diff --git a/api/static/input_group.py b/api/static/input_group.py new file mode 100644 index 0000000..0232349 --- /dev/null +++ b/api/static/input_group.py @@ -0,0 +1,17 @@ +class InputGroup: + """ + A group of inputs that will be expanded in the node tree. + + All properties must be annotated: + ```python + class MyInputGroup(InputGroup): + my_float: Float + my_bool: Bool + my_string # Invalid + ``` + """ + + def __init__(self, **kwargs): + for p in dir(self): + if p in kwargs: + setattr(self, p, kwargs[p]) \ No newline at end of file diff --git a/api/tree.py b/api/tree.py index 974f245..a95551c 100644 --- a/api/tree.py +++ b/api/tree.py @@ -7,6 +7,7 @@ except: from .state import State from .types import * from .node_mapper import * +from .static.input_group import * def _as_iterable(x): try: @@ -39,12 +40,19 @@ def tree(name): # Collect the inputs inputs = {} - for param in signature.parameters.values(): + input_groups = {} + def validate_param(param): if param.annotation == inspect.Parameter.empty: raise Exception(f"Tree input '{param.name}' has no type specified. Please annotate with a valid node input type.") if not issubclass(param.annotation, Type): raise Exception(f"Type of tree input '{param.name}' is not a valid 'Type' subclass.") - inputs[param.name] = (param.annotation, param.default) + for param in signature.parameters.values(): + if issubclass(param.annotation, InputGroup): + for group_param, annotation in param.annotation.__annotations__.items(): + inputs[group_param] = (annotation, inspect.Parameter.empty, param.name) + else: + validate_param(param) + inputs[param.name] = (param.annotation, param.default, None) # Create the input sockets and collect input values. for i, node_input in enumerate(node_group.inputs): @@ -52,7 +60,7 @@ def tree(name): for ni in node_group.inputs: node_group.inputs.remove(ni) break - builder_inputs = [] + builder_inputs = {} for i, arg in enumerate(inputs.items()): input_name = arg[0].replace('_', ' ').title() if len(node_group.inputs) > i: @@ -62,18 +70,23 @@ def tree(name): node_input = node_group.inputs.new(arg[1][0].socket_type, input_name) if arg[1][1] != inspect.Parameter.empty: node_input.default_value = arg[1][1] - builder_inputs.append(arg[1][0](group_input_node.outputs[i])) + if arg[1][2] is not None: + if arg[1][2] not in builder_inputs: + builder_inputs[arg[1][2]] = signature.parameters[arg[1][2]].annotation() + setattr(builder_inputs[arg[1][2]], arg[0], arg[1][0](group_input_node.outputs[i])) + else: + builder_inputs[arg[0]] = arg[1][0](group_input_node.outputs[i]) # Run the builder function State.current_node_tree = node_group if inspect.isgeneratorfunction(builder): - generated_outputs = [*builder(*builder_inputs)] + generated_outputs = [*builder(**builder_inputs)] if all(map(lambda x: issubclass(type(x), Type) and x._socket.type == 'GEOMETRY', generated_outputs)): outputs = join_geometry(geometry=generated_outputs) else: outputs = generated_outputs else: - outputs = builder(*builder_inputs) + outputs = builder(**builder_inputs) # Create the output sockets for i, result in enumerate(_as_iterable(outputs)): diff --git a/api/types.py b/api/types.py index cbefd62..4e2a878 100644 --- a/api/types.py +++ b/api/types.py @@ -4,7 +4,7 @@ import nodeitems_utils from .state import State def map_case_name(i): - return i.identifier.replace(' ', '_').upper() + return ('_' if not i.identifier[0].isalpha() else '') + i.identifier.replace(' ', '_').upper() # The base class all exposed socket types conform to. class Type: