diff --git a/api/node_mapper.py b/api/node_mapper.py index 475170a..b914ec1 100644 --- a/api/node_mapper.py +++ b/api/node_mapper.py @@ -326,6 +326,7 @@ class Type: x = Type() y = Type() z = Type() + def capture(self, attribute: Type, **kwargs) -> Callable[[], Type]: return transfer_attribute {(newline + ' ').join(map(lambda x: x.replace('(', '(self, '), filter(lambda x: x.startswith('def'), symbols)))} {newline.join(map(type_symbol, Type.__subclasses__()))} diff --git a/api/types.py b/api/types.py index 0eaab6f..73320ad 100644 --- a/api/types.py +++ b/api/types.py @@ -134,10 +134,10 @@ class Type: def z(self): return self._get_xyz_component(2) - def capture(self, value): + def capture(self, value, **kwargs): data_type = socket_type_to_data_type(value._socket.type) - captured = self.capture_attribute(data_type=data_type, value=value) - return captured.geometry.transfer_attribute(data_type=data_type, attribute=captured.attribute) + captured = self.capture_attribute(data_type=data_type, value=value, **kwargs) + return lambda **kwargs: captured.geometry.transfer_attribute(data_type=data_type, attribute=captured.attribute, **kwargs) for standard_socket in list(filter(lambda x: 'NodeSocket' in x, dir(bpy.types))): name = standard_socket.replace('NodeSocket', '') diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 115b612..ebf733e 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -19,6 +19,7 @@ - [Node Groups](./api/advanced-scripting/node-groups.md) - [Generators](./api/advanced-scripting/generators.md) - [Input Groups](./api/advanced-scripting/input-groups.md) + - [Attributes](./api/advanced-scripting/attributes.md) # Tutorials diff --git a/book/src/api/advanced-scripting/attributes.md b/book/src/api/advanced-scripting/attributes.md new file mode 100644 index 0000000..b90900a --- /dev/null +++ b/book/src/api/advanced-scripting/attributes.md @@ -0,0 +1,66 @@ +# Attributes + +An important concept in Geometry Nodes is attributes. Many trees transfer attributes between geometry, using a combination of *Capture Attribute* and *Transfer Attribute*. + +Unfortunately, it takes quite a bit of code to use this common pattern. + +```python +@tree("Skin") +def skin(): + # Create a cube + c = cube() + # Capture the position + cube_position_attribute = c.capture_attribute( + data_type=CaptureAttribute.DataType.FLOAT_VECTOR, + value=position() + ) + # Create a sphere + sphere = uv_sphere() + # Transfer the position to the sphere + transferred_position = cube_position_attribute.geometry.transfer_attribute( + data_type=TransferAttribute.DataType.FLOAT_VECTOR, + attribute=cube_position_attribute.attribute + ) + # Make the sphere conform to the shape of the cube + return sphere.set_position(position=transferred_position) +``` + +Thankfully, a convenient `capture(...)` method is available on `Geometry`, which simplifies this function quite a bit. + +```python +@tree("Skin") +def skin(): + # Create a cube + c = cube() + # Capture the position + cube_position = c.capture(position()) + # Create a sphere + sphere = uv_sphere() + # Make the sphere conform to the shape of the cube + return sphere.set_position(position=cube_position()) +``` + +## How it Works + +Internally, `capture(...)` works just like the more manual approach. + +1. Capture the attribute from the source + +In the example above, we capture the `position()` from the cube. +The data type is automatically inferred from the input. If you want to customize other options, simply pass them as keyword arguments to `capture(...)`. + +```python +cube_position = c.capture(position()) +cube_position = c.capture(position(), domain=CaptureAttribute.Domain.FACE) # Optionally pass other arguments available on `capture_attribute`. +``` + +2. Transfer the attribute to the target + +`capture(...)` returns another function that calls `transfer_attribute` with the correct arguments passed automatically. +Call this returned function (which we store in the variable `cube_position`) to transfer the attribute. +In this example we also set the transferred cube position back onto the sphere. + +```python +sphere.set_position(position=cube_position()) +sphere.set_position(position=cube_position(mapping=TransferAttribute.Mapping.NEAREST)) # Optionally pass other arguments available on `transfer_attribute`. +``` \ No newline at end of file