Support generators and add City Builder example

pull/10/head
Carson Katri 2022-11-14 22:32:32 -05:00
rodzic c28d6ced93
commit 3850ec011c
6 zmienionych plików z 91 dodań i 13 usunięć

Wyświetl plik

@ -93,6 +93,38 @@ socket_integer * 2 # creates a `Math` node that multiplies the `Integer` node by
socket_integer = random_value(data_type='INT', seed=socket_integer) # use the `Integer` node as the seed for a new random integer
```
### Generators
Tree builder functions can also be generators:
```python
@tree
def cube_and_cylinder():
yield cube()
yield cylinder().mesh
```
Because all of the generated values are `Geometry` types, they will be automatically joined with a *Join Geometry* node and sent as the output.
You can `yield` other types as well, however:
```python
@tree
def cube_cylinder_and_int():
yield cube() # The first output of a Geometry Nodes tree must be `Geometry`
yield cylinder()
yield 5
```
This will create three outputs, one for the cube, one for the cylinder, and one for an *Integer* node with the value `5`.
### Default Values
```python
@tree
def cube_with_height(height: Float = 0.5):
return cube(size=combine_xyz(z=height))
```
The value `0.5` will show as the default in the Geometry Nodes modifier.
### Available Nodes
Every node available in your current version of Blender can be used as a function. The name will be converted to snake case:
```python

Wyświetl plik

@ -102,7 +102,7 @@ def register():
bpy.types.Scene.geometry_script_settings = bpy.props.PointerProperty(type=GeometryScriptSettings)
bpy.app.timers.register(auto_resolve)
bpy.app.timers.register(auto_resolve, persistent=True)
def unregister():
bpy.utils.unregister_class(TEXT_MT_templates_geometryscript)

Wyświetl plik

@ -34,6 +34,8 @@ def build_node(node_type):
State.current_node_tree.links.new(constant._socket, node_input)
outputs = {}
for node_output in node.outputs:
if not node_output.enabled:
continue
outputs[node_output.name.lower().replace(' ', '_')] = Type(node_output)
if len(outputs) == 1:
return list(outputs.values())[0]

Wyświetl plik

@ -66,7 +66,14 @@ def tree(name):
# Run the builder function
State.current_node_tree = node_group
outputs = builder(*builder_inputs)
if inspect.isgeneratorfunction(builder):
generated_outputs = [*builder(*builder_inputs)]
if all(map(lambda x: issubclass(type(x), Type) and x._socket.type == 'GEOMETRY', generated_outputs)):
outputs = join_geometry(geometry=generated_outputs)
else:
outputs = generated_outputs
else:
outputs = builder(*builder_inputs)
# Create the output sockets
for i, result in enumerate(_as_iterable(outputs)):

Wyświetl plik

@ -81,23 +81,28 @@ class Type:
def __ge__(self, other):
return self._compare(other, 'GREATER_EQUAL')
def _get_xyz_component(self, component):
if self._socket.type != 'VECTOR':
raise Exception("`x`, `y`, `z` properties are not available on non-Vector types.")
separate_node = State.current_node_tree.nodes.new('ShaderNodeSeparateXYZ')
State.current_node_tree.links.new(self._socket, separate_node.inputs[0])
return Type(separate_node.outputs[component])
@property
def x(self):
return self._get_xyz_component(0)
@property
def y(self):
return self._get_xyz_component(1)
@property
def z(self):
return self._get_xyz_component(2)
for standard_socket in list(filter(lambda x: 'NodeSocket' in x, dir(bpy.types))):
name = standard_socket.replace('NodeSocket', '')
if len(name) < 1:
continue
globals()[name] = type(name, (Type,), { 'socket_type': standard_socket, '__module__': Type.__module__ })
if name == 'Vector':
def get_component(component):
@property
def get(self):
separate_node = State.current_node_tree.nodes.new('ShaderNodeSeparateXYZ')
State.current_node_tree.links.new(self._socket, separate_node.inputs[0])
return Type(separate_node.outputs[component])
return get
globals()[name].x = get_component(0)
globals()[name].y = get_component(1)
globals()[name].z = get_component(2)
if name == 'Int':
class IntIterator:
def __init__(self, integer):

Wyświetl plik

@ -0,0 +1,32 @@
# Create a curve object as the base.
# In Edit Mode, use the Draw tool to create new roads in the city.
from geometry_script import *
@tree("City Builder")
def city_builder(geometry: Geometry, building_size_min: Vector = (0.1, 0.1, 0.2), building_size_max: Vector = (0.3, 0.3, 1), size_x: Float = 5.0, size_y: Float = 5.0, road_width: Float = 0.25, seed: Int = 0, resolution: Int = 60):
# Road geometry from input curves
road_points = geometry.curve_to_points().points
yield geometry.curve_to_mesh(
profile_curve=curve_line(
start=combine_xyz(x=road_width * -0.5),
end=combine_xyz(x=road_width / 2)
)
)
# Randomly distribute buildings on a grid
building_points = grid(
size_x=size_x, size_y=size_y,
vertices_x=resolution, vertices_y=resolution
).distribute_points_on_faces(
seed=seed
# Delete invalid building points based on proximity to a road
).points.delete_geometry(
domain='POINT',
selection=road_points.geometry_proximity(target_element='POINTS', source_position=position()).distance < road_width * 2
)
random_scale = random_value(data_type='FLOAT_VECTOR', min=building_size_min, max=building_size_max, seed=seed + id())
yield building_points.instance_on_points(
instance=cube(size=(1, 1, 1)).transform(translation=(0, 0, 0.5)),
scale=random_scale
)