Add InputGroup to simplify large sets of inputs

This commit is contained in:
Carson Katri 2022-11-17 18:13:07 -05:00
commit aa11b398a0
4 changed files with 51 additions and 8 deletions

View file

@ -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)

17
api/static/input_group.py Normal file
View file

@ -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])

View file

@ -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)):

View file

@ -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: