Skip to content

Commit da99654

Browse files
committed
Add repeat zone support
1 parent e3befbe commit da99654

File tree

6 files changed

+117
-4
lines changed

6 files changed

+117
-4
lines changed

api/static/repeat.py

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import bpy
2+
import inspect
3+
import typing
4+
5+
def repeat_zone(block: typing.Callable):
6+
"""
7+
Create a repeat input/output block.
8+
9+
> Only available in Blender 4.0+.
10+
"""
11+
def wrapped(*args, **kwargs):
12+
from geometry_script.api.node_mapper import OutputsList, set_or_create_link
13+
from geometry_script.api.state import State
14+
from geometry_script.api.types import Type, socket_class_to_data_type
15+
16+
signature = inspect.signature(block)
17+
18+
# setup zone
19+
repeat_in = State.current_node_tree.nodes.new(bpy.types.GeometryNodeRepeatInput.__name__)
20+
repeat_out = State.current_node_tree.nodes.new(bpy.types.GeometryNodeRepeatOutput.__name__)
21+
repeat_in.pair_with_output(repeat_out)
22+
23+
# clear state items
24+
for item in repeat_out.repeat_items:
25+
repeat_out.repeat_items.remove(item)
26+
27+
# link the iteration count
28+
set_or_create_link(args[0], repeat_in.inputs[0])
29+
30+
# create state items from block signature
31+
repeat_items = {}
32+
for param in signature.parameters.values():
33+
repeat_items[param.name] = (param.annotation, param.default, None, None)
34+
for i, arg in enumerate(repeat_items.items()):
35+
repeat_out.repeat_items.new(socket_class_to_data_type(arg[1][0].socket_type), arg[0].replace('_', ' ').title())
36+
# skip the first index, which is reserved for the iteration count
37+
i = i + 1
38+
set_or_create_link(kwargs[arg[0]] if arg[0] in kwargs else args[i], repeat_in.inputs[i])
39+
40+
step = block(*[Type(o) for o in repeat_in.outputs[:-1]])
41+
42+
if isinstance(step, Type):
43+
step = (step,)
44+
for i, result in enumerate(step):
45+
set_or_create_link(result, repeat_out.inputs[i])
46+
47+
if len(repeat_out.outputs[:-1]) == 1:
48+
return Type(repeat_out.outputs[0])
49+
else:
50+
return OutputsList({o.name.lower().replace(' ', '_'): Type(o) for o in repeat_out.outputs[:-1]})
51+
return wrapped

api/static/simulation.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ def simulation_zone(block: typing.Callable):
66
"""
77
Create a simulation input/output block.
88
9+
In Blender 4.0+, you must return a boolean value for the "Skip" argument as the first element in the return tuple.
10+
911
> Only available in Blender 3.6+.
1012
"""
1113
def wrapped(*args, **kwargs):
@@ -37,7 +39,7 @@ def wrapped(*args, **kwargs):
3739
if isinstance(step, Type):
3840
step = (step,)
3941
for i, result in enumerate(step):
40-
State.current_node_tree.links.new(result._socket, simulation_out.inputs[i])
42+
set_or_create_link(result, simulation_out.inputs[i])
4143

4244
if len(simulation_out.outputs[:-1]) == 1:
4345
return Type(simulation_out.outputs[0])

api/tree.py

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from .static.curve import *
1212
from .static.expression import *
1313
from .static.input_group import *
14+
from .static.repeat import *
1415
from .static.sample_mode import *
1516
from .static.simulation import *
1617
from .arrange import _arrange

book/src/SUMMARY.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
- [Boolean Math](./api/advanced-scripting/boolean-math.md)
2424
- [Curves](./api/advanced-scripting/curves.md)
2525
- [Drivers](./api/advanced-scripting/drivers.md)
26-
- [Simulation](./api/advanced-scripting/simulation.md)
26+
- [Simulation Zones](./api/advanced-scripting/simulation-zones.md)
27+
- [Repeat Zones](./api/advanced-scripting/repeat-zones.md)
2728

2829
# Tutorials
2930

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Repeat Zones
2+
3+
Blender 4.0 introduced repeat zones.
4+
5+
Using a *Repeat Input* and *Repeat Output* node, you can loop a block of nodes for a specific number of iterations.
6+
7+
You must use the `@repeat_zone` decorator to create these special linked nodes.
8+
9+
```python
10+
from geometry_script import *
11+
12+
@tree
13+
def test_loop(geometry: Geometry):
14+
@repeat_zone
15+
def doubler(value: Float):
16+
return value * 2
17+
return points(count=doubler(5, 1)) # double the input value 5 times.
18+
```
19+
20+
The function should modify the input values and return them in the same order.
21+
22+
When calling the repeat zone, pass the *Iterations* argument first, then any other arguments the function accepts.
23+
24+
For example:
25+
26+
```python
27+
def doubler(value: Float) -> Float
28+
```
29+
30+
would be called as:
31+
32+
```python
33+
doubler(iteration_count, value)
34+
```
35+
36+
When a repeat zone has multiple arguments, return a tuple from the zone.
37+
38+
```python
39+
@repeat_zone
40+
def multi_doubler(value1: Float, value2: Float):
41+
return (value1 * 2, value2 * 2)
42+
```

book/src/api/advanced-scripting/simulation.md renamed to book/src/api/advanced-scripting/simulation-zones.md

+18-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Simulation
1+
# Simulation Zones
22

33
Blender 3.6 includes simulation nodes.
44

@@ -19,4 +19,20 @@ def test_sim(geometry: Geometry):
1919

2020
The first argument should always be `delta_time`. Any other arguments must also be returned as a tuple with their modified values.
2121
Each frame, the result from the previous frame is passed into the zone's inputs.
22-
The initial call to `my_sim` in `test_sim` provides the initial values for the simulation.
22+
The initial call to `my_sim` in `test_sim` provides the initial values for the simulation.
23+
24+
## Blender 4.0+
25+
26+
A "Skip" argument was added to the *Simulation Output* node in Blender 4.0.
27+
28+
Return a boolean value first from any simulation zone to determine whether the step should be skipped.
29+
30+
The simplest way to migrate existing node trees is by adding `False` to the return tuple.
31+
32+
```python
33+
@simulation_zone
34+
def my_sim(delta_time, geometry: Geometry, value: Float):
35+
return (False, geometry, value)
36+
```
37+
38+
You can pass any boolean value as the skip output.

0 commit comments

Comments
 (0)