Skip to content

Commit 63a7516

Browse files
committed
Refactor simulation API to use simulation_zone decorator
1 parent 467ae71 commit 63a7516

File tree

4 files changed

+76
-47
lines changed

4 files changed

+76
-47
lines changed

api/node_mapper.py

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,21 @@ class OutputsList(dict):
1414
__setattr__ = dict.__setitem__
1515
__delattr__ = dict.__delitem__
1616

17+
def set_or_create_link(x, node_input):
18+
if issubclass(type(x), Type):
19+
State.current_node_tree.links.new(x._socket, node_input)
20+
else:
21+
def link_constant():
22+
constant = Type(value=x)
23+
State.current_node_tree.links.new(constant._socket, node_input)
24+
if node_input.hide_value:
25+
link_constant()
26+
else:
27+
try:
28+
node_input.default_value = x
29+
except:
30+
link_constant()
31+
1732
def build_node(node_type):
1833
def build(_primary_arg=None, **kwargs):
1934
for k, v in kwargs.copy().items():
@@ -48,20 +63,6 @@ def build(_primary_arg=None, **kwargs):
4863
if node_input2.name.lower().replace(' ', '_') == argname and node_input2.type == node_input.type:
4964
all_with_name.append(node_input2)
5065
if argname in kwargs:
51-
def set_or_create_link(x, node_input):
52-
if issubclass(type(x), Type):
53-
State.current_node_tree.links.new(x._socket, node_input)
54-
else:
55-
def link_constant():
56-
constant = Type(value=x)
57-
State.current_node_tree.links.new(constant._socket, node_input)
58-
if node_input.hide_value:
59-
link_constant()
60-
else:
61-
try:
62-
node_input.default_value = x
63-
except:
64-
link_constant()
6566
value = kwargs[argname]
6667
if isinstance(value, enum.Enum):
6768
value = value.value

api/static/simulation.py

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,46 @@
1+
import bpy
12
import inspect
23
import typing
34

4-
class SimulationInput:
5-
class DeltaTime: pass
6-
class ElapsedTime: pass
7-
8-
def simulation(block: typing.Callable[typing.Any, 'Geometry']):
5+
def simulation_zone(block: typing.Callable):
96
"""
107
Create a simulation input/output block.
118
12-
> Only available in the `geometry-node-simulation` branch of Blender 3.5.
9+
> Only available in Blender 3.6+.
1310
"""
14-
def wrapped(geometry: 'Geometry', *args, **kwargs):
15-
from geometry_script import simulation_input, simulation_output
16-
simulation_in = simulation_input(geometry=geometry)
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+
1716
signature = inspect.signature(block)
18-
for key, value in signature.parameters.items():
19-
match value.annotation:
20-
case SimulationInput.DeltaTime:
21-
kwargs[key] = simulation_in.delta_time
22-
case SimulationInput.ElapsedTime:
23-
kwargs[key] = simulation_in.elapsed_time
24-
return simulation_output(geometry=block(simulation_in.geometry, *args, **kwargs)).geometry
17+
18+
# setup zone
19+
simulation_in = State.current_node_tree.nodes.new(bpy.types.GeometryNodeSimulationInput.__name__)
20+
simulation_out = State.current_node_tree.nodes.new(bpy.types.GeometryNodeSimulationOutput.__name__)
21+
simulation_in.pair_with_output(simulation_out)
22+
23+
# clear state items
24+
for item in simulation_out.state_items:
25+
simulation_out.state_items.remove(item)
26+
27+
# create state items from block signature
28+
state_items = {}
29+
for param in [*signature.parameters.values()][1:]:
30+
state_items[param.name] = (param.annotation, param.default, None, None)
31+
for i, arg in enumerate(state_items.items()):
32+
simulation_out.state_items.new(socket_class_to_data_type(arg[1][0].socket_type), arg[0].replace('_', ' ').title())
33+
set_or_create_link(kwargs[arg[0]] if arg[0] in kwargs else args[i], simulation_in.inputs[i])
34+
35+
step = block(*[Type(o) for o in simulation_in.outputs[:-1]])
36+
37+
if isinstance(step, Type):
38+
step = (step,)
39+
for i, result in enumerate(step):
40+
State.current_node_tree.links.new(result._socket, simulation_out.inputs[i])
41+
42+
if len(simulation_out.outputs[:-1]) == 1:
43+
return Type(simulation_out.outputs[0])
44+
else:
45+
return OutputsList({o.name.lower().replace(' ', '_'): Type(o) for o in simulation_out.outputs[:-1]})
2546
return wrapped

api/types.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,15 @@ def socket_type_to_data_type(socket_type):
2020
case _:
2121
return socket_type
2222

23+
def socket_class_to_data_type(socket_class_name):
24+
match socket_class_name:
25+
case 'NodeSocketGeometry':
26+
return 'GEOMETRY'
27+
case 'NodeSocketFloat':
28+
return 'FLOAT'
29+
case _:
30+
return socket_class_name
31+
2332
# The base class all exposed socket types conform to.
2433
class _TypeMeta(type):
2534
def __getitem__(self, args):
@@ -217,6 +226,8 @@ def transfer(self, attribute, **kwargs):
217226
return self.transfer_attribute(data_type=data_type, attribute=attribute, **kwargs)
218227

219228
def __getitem__(self, subscript):
229+
if self._socket.type == 'VECTOR' and isinstance(subscript, int):
230+
return self._get_xyz_component(subscript)
220231
if isinstance(subscript, tuple):
221232
accessor = subscript[0]
222233
args = subscript[1:]
Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,22 @@
11
# Simulation
22

3-
> This API is subject to change as future builds of Blender with simulation nodes are released.
4-
5-
The `geometry-nodes-simulation` branch of Blender 3.5 includes support for "simulation nodes".
3+
Blender 3.6 includes simulation nodes.
64

75
Using a *Simulation Input* and *Simulation Output* node, you can create effects that change over time.
86

9-
As a convenience, the `@simulation` decorator is provided to make simulation node blocks easier to create.
7+
As a convenience, the `@simulation_zone` decorator is provided to make simulation node blocks easier to create.
108

119
```python
12-
@simulation
13-
def move_over_time(
14-
geometry: Geometry, # the first input must be `Geometry`
15-
speed: Float,
16-
dt: SimulationInput.DeltaTime, # Automatically passes the delta time on any argument annotated with `SimulationInput.DeltaTime`.
17-
elapsed: SimulationInput.ElapsedTime, # Automatically passes the elapsed time
18-
) -> Geometry:
19-
return geometry.set_position(
20-
offset=combine_xyz(x=speed)
21-
)
22-
```
10+
from geometry_script import *
2311

24-
Every frame the argument `geometry` will be set to the geometry from the previous frame. This allows the offset to accumulate over time.
12+
@tree
13+
def test_sim(geometry: Geometry):
14+
@simulation_zone
15+
def my_sim(delta_time, geometry: Geometry, value: Float):
16+
return (geometry, value)
17+
return my_sim(geometry, 0.26).value
18+
```
2519

26-
The `SimulationInput.DeltaTime`/`SimulationInput.ElapsedTime` types mark arguments that should be given the outputs from the *Simulation Input* node.
20+
The first argument should always be `delta_time`. Any other arguments must also be returned as a tuple with their modified values.
21+
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.

0 commit comments

Comments
 (0)