diff --git a/api/static/repeat.py b/api/static/repeat.py new file mode 100644 index 0000000..df8f2bb --- /dev/null +++ b/api/static/repeat.py @@ -0,0 +1,51 @@ +import bpy +import inspect +import typing + +def repeat_zone(block: typing.Callable): + """ + Create a repeat input/output block. + + > Only available in Blender 4.0+. + """ + def wrapped(*args, **kwargs): + from geometry_script.api.node_mapper import OutputsList, set_or_create_link + from geometry_script.api.state import State + from geometry_script.api.types import Type, socket_class_to_data_type + + signature = inspect.signature(block) + + # setup zone + repeat_in = State.current_node_tree.nodes.new(bpy.types.GeometryNodeRepeatInput.__name__) + repeat_out = State.current_node_tree.nodes.new(bpy.types.GeometryNodeRepeatOutput.__name__) + repeat_in.pair_with_output(repeat_out) + + # clear state items + for item in repeat_out.repeat_items: + repeat_out.repeat_items.remove(item) + + # link the iteration count + set_or_create_link(args[0], repeat_in.inputs[0]) + + # create state items from block signature + repeat_items = {} + for param in signature.parameters.values(): + repeat_items[param.name] = (param.annotation, param.default, None, None) + for i, arg in enumerate(repeat_items.items()): + repeat_out.repeat_items.new(socket_class_to_data_type(arg[1][0].socket_type), arg[0].replace('_', ' ').title()) + # skip the first index, which is reserved for the iteration count + i = i + 1 + set_or_create_link(kwargs[arg[0]] if arg[0] in kwargs else args[i], repeat_in.inputs[i]) + + step = block(*[Type(o) for o in repeat_in.outputs[:-1]]) + + if isinstance(step, Type): + step = (step,) + for i, result in enumerate(step): + set_or_create_link(result, repeat_out.inputs[i]) + + if len(repeat_out.outputs[:-1]) == 1: + return Type(repeat_out.outputs[0]) + else: + return OutputsList({o.name.lower().replace(' ', '_'): Type(o) for o in repeat_out.outputs[:-1]}) + return wrapped \ No newline at end of file diff --git a/api/static/simulation.py b/api/static/simulation.py index ccb7d8d..39926b6 100644 --- a/api/static/simulation.py +++ b/api/static/simulation.py @@ -6,6 +6,8 @@ def simulation_zone(block: typing.Callable): """ Create a simulation input/output block. + In Blender 4.0+, you must return a boolean value for the "Skip" argument as the first element in the return tuple. + > Only available in Blender 3.6+. """ def wrapped(*args, **kwargs): @@ -37,7 +39,7 @@ def simulation_zone(block: typing.Callable): if isinstance(step, Type): step = (step,) for i, result in enumerate(step): - State.current_node_tree.links.new(result._socket, simulation_out.inputs[i]) + set_or_create_link(result, simulation_out.inputs[i]) if len(simulation_out.outputs[:-1]) == 1: return Type(simulation_out.outputs[0]) diff --git a/api/tree.py b/api/tree.py index 3300c3d..2ab774b 100644 --- a/api/tree.py +++ b/api/tree.py @@ -11,6 +11,7 @@ from .static.attribute import * from .static.curve import * from .static.expression import * from .static.input_group import * +from .static.repeat import * from .static.sample_mode import * from .static.simulation import * from .arrange import _arrange diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 45b7f80..a01665e 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -23,7 +23,8 @@ - [Boolean Math](./api/advanced-scripting/boolean-math.md) - [Curves](./api/advanced-scripting/curves.md) - [Drivers](./api/advanced-scripting/drivers.md) - - [Simulation](./api/advanced-scripting/simulation.md) + - [Simulation Zones](./api/advanced-scripting/simulation-zones.md) + - [Repeat Zones](./api/advanced-scripting/repeat-zones.md) # Tutorials diff --git a/book/src/api/advanced-scripting/repeat-zones.md b/book/src/api/advanced-scripting/repeat-zones.md new file mode 100644 index 0000000..b52c5d1 --- /dev/null +++ b/book/src/api/advanced-scripting/repeat-zones.md @@ -0,0 +1,42 @@ +# Repeat Zones + +Blender 4.0 introduced repeat zones. + +Using a *Repeat Input* and *Repeat Output* node, you can loop a block of nodes for a specific number of iterations. + +You must use the `@repeat_zone` decorator to create these special linked nodes. + +```python +from geometry_script import * + +@tree +def test_loop(geometry: Geometry): + @repeat_zone + def doubler(value: Float): + return value * 2 + return points(count=doubler(5, 1)) # double the input value 5 times. +``` + +The function should modify the input values and return them in the same order. + +When calling the repeat zone, pass the *Iterations* argument first, then any other arguments the function accepts. + +For example: + +```python +def doubler(value: Float) -> Float +``` + +would be called as: + +```python +doubler(iteration_count, value) +``` + +When a repeat zone has multiple arguments, return a tuple from the zone. + +```python +@repeat_zone +def multi_doubler(value1: Float, value2: Float): + return (value1 * 2, value2 * 2) +``` \ No newline at end of file diff --git a/book/src/api/advanced-scripting/simulation.md b/book/src/api/advanced-scripting/simulation-zones.md similarity index 60% rename from book/src/api/advanced-scripting/simulation.md rename to book/src/api/advanced-scripting/simulation-zones.md index 3c31614..cd5a945 100644 --- a/book/src/api/advanced-scripting/simulation.md +++ b/book/src/api/advanced-scripting/simulation-zones.md @@ -1,4 +1,4 @@ -# Simulation +# Simulation Zones Blender 3.6 includes simulation nodes. @@ -19,4 +19,20 @@ def test_sim(geometry: Geometry): The first argument should always be `delta_time`. Any other arguments must also be returned as a tuple with their modified values. Each frame, the result from the previous frame is passed into the zone's inputs. -The initial call to `my_sim` in `test_sim` provides the initial values for the simulation. \ No newline at end of file +The initial call to `my_sim` in `test_sim` provides the initial values for the simulation. + +## Blender 4.0+ + +A "Skip" argument was added to the *Simulation Output* node in Blender 4.0. + +Return a boolean value first from any simulation zone to determine whether the step should be skipped. + +The simplest way to migrate existing node trees is by adding `False` to the return tuple. + +```python +@simulation_zone +def my_sim(delta_time, geometry: Geometry, value: Float): + return (False, geometry, value) +``` + +You can pass any boolean value as the skip output. \ No newline at end of file