From 828fb8b6daa76727fcf71408c1114176774cb3cb Mon Sep 17 00:00:00 2001 From: Carson Katri Date: Thu, 17 Nov 2022 13:07:50 -0500 Subject: [PATCH] Add enum generation --- api/node_mapper.py | 142 +++++++++++++++++++++++------ api/types.py | 32 ++++++- book/book.toml | 5 + book/src/api/basics/math_wrap.png | Bin 0 -> 25892 bytes book/src/api/basics/sockets.md | 26 ++++++ book/src/api/basics/using-nodes.md | 52 +++++++++-- book/src/setup/external-editing.md | 4 +- book/src/tutorials/city-builder.md | 2 +- book/style.css | 3 + 9 files changed, 226 insertions(+), 40 deletions(-) create mode 100644 book/src/api/basics/math_wrap.png create mode 100644 book/style.css diff --git a/api/node_mapper.py b/api/node_mapper.py index de81182..5fe50d6 100644 --- a/api/node_mapper.py +++ b/api/node_mapper.py @@ -1,5 +1,7 @@ import bpy import bl_ui +import itertools +import enum from .state import State from .types import * from ..absolute_path import absolute_path @@ -17,21 +19,39 @@ def build_node(node_type): for prop in node.bl_rna.properties: argname = prop.identifier.lower().replace(' ', '_') if argname in kwargs: - setattr(node, prop.identifier, kwargs[argname]) + value = kwargs[argname] + if isinstance(value, enum.Enum): + value = value.value + setattr(node, prop.identifier, value) for node_input in (node.inputs[1:] if _primary_arg is not None else node.inputs): argname = node_input.name.lower().replace(' ', '_') + all_with_name = [] + for node_input2 in (node.inputs[1:] if _primary_arg is not None else node.inputs): + if node_input2.name.lower().replace(' ', '_') == argname and node_input2.type == node_input.type: + all_with_name.append(node_input2) if argname in kwargs: - if node_input.is_multi_input and hasattr(kwargs[argname], '__iter__') and len(kwargs[argname]) > 0 and issubclass(type(next(iter(kwargs[argname]))), Type): - for x in kwargs[argname]: + def set_or_create_link(x, node_input): + if issubclass(type(x), Type): State.current_node_tree.links.new(x._socket, node_input) - elif issubclass(type(kwargs[argname]), Type): - State.current_node_tree.links.new(kwargs[argname]._socket, node_input) + else: + try: + node_input.default_value = x + except: + constant = Type(value=x) + State.current_node_tree.links.new(constant._socket, node_input) + 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): + for x in value: + for node_input in all_with_name: + State.current_node_tree.links.new(x._socket, node_input) + elif len(all_with_name) > 1 and issubclass(type(value), tuple) and len(value) > 0: + for i, x in enumerate(value): + set_or_create_link(x, all_with_name[i]) else: - try: - node_input.default_value = kwargs[argname] - except: - constant = Type(value=kwargs[argname]) - State.current_node_tree.links.new(constant._socket, node_input) + for node_input in all_with_name: + set_or_create_link(value, node_input) outputs = {} for node_output in node.outputs: if not node_output.enabled: @@ -49,6 +69,7 @@ def register_node(node_type, category_path=None): if node_type in registered_nodes: return snake_case_name = node_type.bl_rna.name.lower().replace(' ', '_') + node_namespace_name = snake_case_name.replace('_', ' ').title().replace(' ', '') globals()[snake_case_name] = build_node(node_type) globals()[snake_case_name].bl_category_path = category_path globals()[snake_case_name].bl_node_type = node_type @@ -58,6 +79,16 @@ def register_node(node_type, category_path=None): return build_node(node_type)(self, *args, **kwargs) return build setattr(Type, snake_case_name, build_node_method(node_type)) + parent_props = [prop.identifier for base in node_type.__bases__ for prop in base.bl_rna.properties] + for prop in node_type.bl_rna.properties: + if not prop.identifier in parent_props and prop.type == 'ENUM': + if node_namespace_name not in globals(): + class NodeNamespace: pass + NodeNamespace.__name__ = node_namespace_name + globals()[node_namespace_name] = NodeNamespace + enum_type_name = prop.identifier.replace('_', ' ').title().replace(' ', '') + enum_type = enum.Enum(enum_type_name, { map_case_name(i): i.identifier for i in prop.enum_items }) + setattr(globals()[node_namespace_name], enum_type_name, enum_type) registered_nodes.add(node_type) for category_name in list(filter(lambda x: x.startswith('NODE_MT_category_GEO_'), dir(bpy.types))): category = getattr(bpy.types, category_name) @@ -108,6 +139,7 @@ def create_documentation(): default_color = '#A1A1A1' docstrings = [] symbols = [] + enums = {} for func in sorted(documentation.keys()): try: method = documentation[func] @@ -117,15 +149,21 @@ def create_documentation(): props_inputs = {} symbol_inputs = {} parent_props = [prop.identifier for base in method.bl_node_type.__bases__ for prop in base.bl_rna.properties] + node_namespace_name = func.replace('_', ' ').title().replace(' ', '') for prop in method.bl_node_type.bl_rna.properties: if not prop.identifier in parent_props: if prop.type == 'ENUM': - enum_items = 'Literal[' + ', '.join(map(lambda i: f"'{i.identifier}'", prop.enum_items)) + ']' - props_inputs[prop.identifier] = f"{enum_items}" - symbol_inputs[prop.identifier] = enum_items + enum_name = prop.identifier.replace('_', ' ').title().replace(' ', '') + enum_cases = '\n '.join(map(lambda i: f"{map_case_name(i)} = '{i.identifier}'", prop.enum_items)) + if node_namespace_name not in enums: + enums[node_namespace_name] = [] + enums[node_namespace_name].append(f""" class {enum_name}(enum.Enum): + {enum_cases}""") + props_inputs[prop.identifier] = {f"{node_namespace_name}.{enum_name}":1} + symbol_inputs[prop.identifier] = {f"{node_namespace_name}.{enum_name}": 1} else: - props_inputs[prop.identifier] = f"{prop.type.title()}" - symbol_inputs[prop.identifier] = prop.type.title() + props_inputs[prop.identifier] = {f"{prop.type.title()}":1} + symbol_inputs[prop.identifier] = {prop.type.title(): 1} primary_arg = None for node_input in node_instance.inputs: name = node_input.name.lower().replace(' ', '_') @@ -134,13 +172,32 @@ def create_documentation(): typename = f"List[{typename}]" type_str = f"{typename}" if name in props_inputs: - props_inputs[name] = props_inputs[name] + f' | {type_str}' - symbol_inputs[name] = symbol_inputs[name] + f' | {typename}' + if type_str in props_inputs[name]: + props_inputs[name][type_str] += 1 + symbol_inputs[name][typename] += 1 + else: + props_inputs[name][type_str] = 1 + symbol_inputs[name][typename] = 1 else: - props_inputs[name] = type_str - symbol_inputs[name] = typename + props_inputs[name] = {type_str: 1} + symbol_inputs[name] = {typename: 1} if primary_arg is None: - primary_arg = (name, props_inputs[name]) + primary_arg = (name, list(props_inputs[name].keys())[0]) + def collapse_inputs(inputs): + for k, v in inputs.items(): + values = [] + for t, c in v.items(): + for c in range(1, c + 1): + value = "" + if c > 1: + value += "Tuple[" + value += ', '.join(itertools.repeat(t, c)) + if c > 1: + value += "]" + values.append(value) + inputs[k] = ' | '.join(values) + collapse_inputs(props_inputs) + collapse_inputs(symbol_inputs) arg_docs = [] symbol_args = [] for name, value in props_inputs.items(): @@ -181,14 +238,20 @@ def create_documentation(): """) - output_symbol_separator = '\n ' - symbol_return_type = f"_{func}_result" + output_symbol_separator = '\n ' if len(output_symbols) > 1: - symbols.append(f"""class {symbol_return_type}: - {output_symbol_separator.join(output_symbols)}""") - return_type_hint = list(symbol_outputs.values())[0] if len(output_symbols) == 1 else symbol_return_type + if node_namespace_name not in enums: + enums[node_namespace_name] = [] + enums[node_namespace_name].append(f""" class Result: + {output_symbol_separator.join(output_symbols)}""") + return_type_hint = list(symbol_outputs.values())[0] if len(output_symbols) == 1 else f"{node_namespace_name}.Result" symbols.append(f"""def {func}({', '.join(symbol_args)}) -> {return_type_hint}: \"\"\"![]({image}.webp)\"\"\"""") - except: + except Exception as e: + import os, sys + print(e) + exc_type, exc_obj, exc_tb = sys.exc_info() + fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1] + print(exc_type, fname, exc_tb.tb_lineno) continue bpy.data.node_groups.remove(temp_node_group) html = f""" @@ -228,15 +291,40 @@ def create_documentation(): newline = '\n' def type_symbol(t): return f"class {t.__name__}(Type): pass" + def enum_namespace(k): + return f"""class {k}: +{newline.join(enums[k])}""" contents = f"""from typing import * +import enum def tree(builder): \"\"\" Marks a function as a node tree. \"\"\" pass class Type: - {(newline + ' ').join(filter(lambda x: x.startswith('def'), symbols))} + def __add__(self, other) -> Type: return self + def __radd__(self, other) -> Type: return self + def __sub__(self, other) -> Type: return self + def __rsub__(self, other) -> Type: return self + def __mul__(self, other) -> Type: return self + def __rmul__(self, other) -> Type: return self + def __truediv__(self, other) -> Type: return self + def __rtruediv__(self, other) -> Type: return self + def __mod__(self, other) -> Type: return self + def __rmod__(self, other) -> Type: return self + def __eq__(self, other) -> Type: return self + def __ne__(self, other) -> Type: return self + def __lt__(self, other) -> Type: return self + def __le__(self, other) -> Type: return self + def __gt__(self, other) -> Type: return self + def __ge__(self, other) -> Type: return self + x = Type() + y = Type() + z = Type() + {(newline + ' ').join(map(lambda x: x.replace('(', '(self, '), filter(lambda x: x.startswith('def'), symbols)))} + {newline.join(map(type_symbol, Type.__subclasses__()))} +{newline.join(map(enum_namespace, enums.keys()))} {newline.join(symbols)}""" fpyi.write(contents) fpy.write(contents) diff --git a/api/types.py b/api/types.py index 82ad44e..30f1c33 100644 --- a/api/types.py +++ b/api/types.py @@ -3,6 +3,15 @@ from bpy.types import NodeSocketStandard import nodeitems_utils from .state import State +def map_case_name(i): + r = i.identifier.replace('_', ' ').title().replace(' ', '') + if r == 'None': + return 'NONE' + elif not r[0].isalpha(): + return f'_{r}' + else: + return r + # The base class all exposed socket types conform to. class Type: socket_type: str @@ -28,31 +37,46 @@ class Type: self._socket = socket self.socket_type = type(socket).__name__ - def _math(self, other, operation): + def _math(self, other, operation, reverse=False): math_node = State.current_node_tree.nodes.new('ShaderNodeVectorMath' if self._socket.type == 'VECTOR' else 'ShaderNodeMath') math_node.operation = operation - State.current_node_tree.links.new(self._socket, math_node.inputs[0]) + State.current_node_tree.links.new(self._socket, math_node.inputs[1 if reverse else 0]) if issubclass(type(other), Type): - State.current_node_tree.links.new(other._socket, math_node.inputs[1]) + State.current_node_tree.links.new(other._socket, math_node.inputs[0 if reverse else 1]) else: - math_node.inputs[1].default_value = other + math_node.inputs[0 if reverse else 1].default_value = other return Type(math_node.outputs[0]) def __add__(self, other): return self._math(other, 'ADD') + def __radd__(self, other): + return self._math(other, 'ADD', True) + def __sub__(self, other): return self._math(other, 'SUBTRACT') + def __rsub__(self, other): + return self._math(other, 'SUBTRACT', True) + def __mul__(self, other): return self._math(other, 'MULTIPLY') + def __rmul__(self, other): + return self._math(other, 'MULTIPLY', True) + def __truediv__(self, other): return self._math(other, 'DIVIDE') + def __rtruediv__(self, other): + return self._math(other, 'DIVIDE', True) + def __mod__(self, other): return self._math(other, 'MODULO') + def __rmod__(self, other): + return self._math(other, 'MODULO', True) + def _compare(self, other, operation): compare_node = State.current_node_tree.nodes.new('FunctionNodeCompare') compare_node.data_type = 'FLOAT' if self._socket.type == 'VALUE' else self._socket.type diff --git a/book/book.toml b/book/book.toml index 0857ae4..d8abed4 100644 --- a/book/book.toml +++ b/book/book.toml @@ -4,3 +4,8 @@ language = "en" multilingual = false src = "src" title = "Geometry Script" + +[output.html] +default-theme = "coal" +preferred-dark-theme = "coal" +additional-css = ["style.css"] \ No newline at end of file diff --git a/book/src/api/basics/math_wrap.png b/book/src/api/basics/math_wrap.png new file mode 100644 index 0000000000000000000000000000000000000000..1e764f29bfc0acecd99b95268ed6daab4ef078e3 GIT binary patch literal 25892 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 fgOfEz2cppc7K{Jftk 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 literal 0 HcmV?d00001 diff --git a/book/src/api/basics/sockets.md b/book/src/api/basics/sockets.md index 0269ae1..5c9323c 100644 --- a/book/src/api/basics/sockets.md +++ b/book/src/api/basics/sockets.md @@ -121,4 +121,30 @@ The same script without chaining calls is written more verbosely as: @tree("Cube Tree") def cube_tree(size: Vector): return mesh_to_volume(mesh=cube(size=size)) +``` + +### Spanning Multiple Lines + +Often times you want each chained calls to be on a separate line. There are a few ways to do this in Python: + +1. Newlines around arguments + +```python +cube( + size=size +).mesh_to_volume() +``` + +2. Parentheses + +```python +(cube(size=size) + .mesh_to_volume()) +``` + +3. Line continuation + +```python +cube(size=size) \ + .mesh_to_volume() ``` \ No newline at end of file diff --git a/book/src/api/basics/using-nodes.md b/book/src/api/basics/using-nodes.md index c988a67..a577670 100644 --- a/book/src/api/basics/using-nodes.md +++ b/book/src/api/basics/using-nodes.md @@ -20,7 +20,47 @@ The general process is: > Properties and inputs are different types of argument. A property is a value that cannot be connected to a socket. These are typically enums (displayed in the UI as a dropdown), with specific string values expected. Check the documentation for a node to see what the possible values are for a property. -Let's take a look at two nodes as an example. +## Enum Properties + +Many nodes have enum properties. For example, the math node lets you choose which operation to perform. You can pass a string to specify the enum case to use. But a safer way to set these values is with the autogenerated enum types. The enums are namespaced to the name of the node in PascalCase: + +```python +# Access it by Node.Enum Name.Case +math(operation=Math.Operation.Add) +math(operation=Math.Operation.Subtract) +math(operation='MULTIPLY') # Or manually pass a string +``` + +Internally, this type is generated as: + +```python +import enum +class Math: + class Operation(enum.Enum): + Add = 'ADD' + Subtract = 'SUBTRACT' + Multiply = 'MULTIPLY' + ... + ... +``` + +The cases will appear in code completion if you setup an [external editor](../../setup/external-editing.md). + +## Duplicate Names + +Some nodes use the same input name multiple times. For example, the *Math* node has three inputs named `value`. To specify each value, pass a tuple for the input: + +```python +math(operation=Math.Operation.Wrap, value=(0.5, 1, 0)) # Pass all 3 +math(operation=Math.Operation.Wrap, value=(0.5, 1)) # Only pass 2/3 +math(operation=Math.Operation.Wrap, value=0.5) # Only pass 1/3 +``` + +![](./math_wrap.png) + +## Examples + +Here are two examples to show how a node maps to a function. ### Cube @@ -72,8 +112,8 @@ size.cube(...) 1. Name `Capture Attribute` -> `capture_attribute` 2. Keyword Arguments * Properties - * `data_type: Literal['FLOAT', 'INT', 'FLOAT_VECTOR', 'FLOAT_COLOR', 'BYTE_COLOR', 'STRING', 'BOOLEAN', 'FLOAT2', 'INT8']` - * `domain: Literal['POINT', 'EDGE', 'FACE', 'CORNER', 'CURVE', 'INSTANCE']` + * `data_type: CaptureAttribute.DataType` + * `domain: CaptureAttribute.Domain` * Inputs * `geometry: Geometry` * `value: Vector | Float | Color | Bool | Int` @@ -82,7 +122,7 @@ size.cube(...) The node can now be used as a function: ```python -result = capture_attribute(data_type='BOOLEAN', geometry=cube_geo) # Specify a property and an input +result = capture_attribute(data_type=CaptureAttribute.DataType.Boolean, geometry=cube_geo) # Specify a property and an input result.geometry # Access the geometry result.attribute # Access the attribute ``` @@ -92,8 +132,8 @@ The generated documentation will show the signature, result type, and [chain syn #### Signature ```python capture_attribute( - data_type: Literal['FLOAT', 'INT', 'FLOAT_VECTOR', 'FLOAT_COLOR', 'BYTE_COLOR', 'STRING', 'BOOLEAN', 'FLOAT2', 'INT8'], - domain: Literal['POINT', 'EDGE', 'FACE', 'CORNER', 'CURVE', 'INSTANCE'], + data_type: CaptureAttribute.DataType, + domain: CaptureAttribute.Domain, geometry: Geometry, value: Vector | Float | Color | Bool | Int ) diff --git a/book/src/setup/external-editing.md b/book/src/setup/external-editing.md index d7bf4ff..87aa12c 100644 --- a/book/src/setup/external-editing.md +++ b/book/src/setup/external-editing.md @@ -3,9 +3,9 @@ Blender's *Text Editor* leaves a lot to be desired. Writing scripts without code completion can be tough. Using an external code editor is one way to improve the editing experience. -This guide will show how to setup [Visual Studio Code](https://code.visualstudio.com/) to edit Geometry Scripts. However, the same concepts apply to IDEs. +This guide will show how to setup [Visual Studio Code](https://code.visualstudio.com/) to edit Geometry Scripts. However, the same concepts apply to other IDEs. -> This guide assumes you have already installed Visual Studio Code and setup the [Python extension](https://marketplace.visualstudio.com/items?itemName=ms-python.python). If not, please follow the setup guides for those tools before continuing. +> This guide assumes you have already installed Visual Studio Code and setup the [Python extension](https://marketplace.visualstudio.com/items?itemName=ms-python.python). If not, please setup those tools before continuing. ## Code Completion When the Geometry Script add-on starts, it generates a Python typeshed file that can be used to provide code completion. diff --git a/book/src/tutorials/city-builder.md b/book/src/tutorials/city-builder.md index 2982a92..e638eb3 100644 --- a/book/src/tutorials/city-builder.md +++ b/book/src/tutorials/city-builder.md @@ -26,7 +26,7 @@ def city_builder( return geometry ``` -Run the script to create the tree, then add a *Geometry Nodes* modifier to your curve object and select the *City Builger* node group. +Run the script to create the tree, then add a *Geometry Nodes* modifier to your curve object and select the *City Builder* node group. ## Buildings Let's start with the buildings. We'll distribute points on a grid with `size_x` and `size_y`. diff --git a/book/style.css b/book/style.css new file mode 100644 index 0000000..73e1fda --- /dev/null +++ b/book/style.css @@ -0,0 +1,3 @@ +.coal { + --bg: #1C1C1C !important; +} \ No newline at end of file