from nodebpy import geometry as g
with g.tree("Hello World") as tree:
height = tree.inputs.float("Height", 3.0)
omega = tree.inputs.float("Omega", 2.0)
with g.Frame("Computing the wave"):
pos = g.Position().o.position
distance = g.Math.square_root(pos.x**2 + pos.y**2)
z = height * g.Math.sine(distance * omega) / distance
with g.Frame("Point offset & smooth"):
mesh = (
g.Grid(20, 20, 200, 200)
>> g.SetPosition(offset=g.CombineXYZ(z=z))
>> g.SetShadeSmooth.face()
)
mesh >> tree.outputs.geometry("Mesh")Comparing the API
How does the API compare to other existing solutions?
Several other notable projects have also attempted interfacing with node trees via code. They mostly fit into two categories: either storing & retrieving node trees via code (.json or the bpy API), or authoring node trees with a custom API and syntax. nodebpy mostly fits into the latter category.
Storing node trees as code
Converting node trees to Python API calls or .json gives a robust storage method, but this approach falls down in human authorability / readability. These projects are great for storage but less useful when wanting to write node trees from scratch:
- NodeToPython
- TreeClipper (used by this project for running some tests and web rendering)
geonodes
‘Hello World’
The hello world example from geonodes readme: https://github.com/al1brn/geonodes
from geonodes import *
with GeoNodes("Hello World"):
height = 3
omega = 2
grid = Mesh.Grid(vertices_x=200, vertices_y=200, size_x=20, size_y=20)
with Layout("Computing the wave"):
pos = nd.position
distance = gnmath.sqrt(pos.x**2 + pos.y**2)
z = height*gnmath.sin(distance*omega)/distance
with Layout("Point offset and smoothness"):
grid.offset = (0, 0, z)
grid.faces.smooth = True
grid.out()geometry-script
README Demo
from nodebpy import geometry as g
with g.tree("Repeat Grid") as tree:
geometry = tree.inputs.geometry("Geometry")
width = tree.inputs.integer("Width")
height = tree.inputs.integer("Height")
(
g.Grid(width, height, vertices_x=width, vertices_y=height)
>> g.MeshToPoints()
>> g.InstanceOnPoints(instance=geometry)
>> tree.outputs.geometry("Instances")
)from geometry_script import *
@tree("Repeat Grid")
def repeat_grid(geometry: Geometry, width: Int, height: Int):
g = grid(
size_x=width, size_y=height,
vertices_x=width, vertices_y=height
).mesh.mesh_to_points()
return g.instance_on_points(instance=geometry)Primitive Shapes
from nodebpy import geometry as g
with g.tree("Primitive Shapes") as tree:
join = g.JoinGeometry([g.Cube(), g.UVSphere(), g.Cylinder()])
join >> tree.outputs.geometry("Result")@tree("Primitive Shapes")
def primitive_shapes():
yield cube()
yield uv_sphere()
yield cylinder().meshVoxelise
Example script found here.
from nodebpy import geometry as g
with g.tree("Voxelise") as tree:
geo = tree.inputs.geometry("Geometry")
resolution = tree.inputs.float("Resolution", 0.2)
(
geo
>> g.MeshToVolume(interior_band_width=resolution)
>> g.DistributePointsInVolume(mode="Grid", spacing=resolution)
>> g.InstanceOnPoints(instance=g.Cube(size=resolution))
>> tree.outputs.geometry("Result")
)from geometry_script import *
@tree("Voxelize")
def voxelize(geometry: Geometry, resolution: Float = 0.2):
return geometry.mesh_to_volume(
interior_band_width=resolution,
fill_volume=False
).distribute_points_in_volume(
mode=DistributePointsInVolume.Mode.DENSITY_GRID,
spacing=resolution
).instance_on_points(
instance=cube(size=resolution)
)City Builder
Example script found here.
from nodebpy import geometry as g
with g.tree("Voxelise") as tree:
geo = tree.inputs.geometry("Geometry")
seed = tree.inputs.integer("Seed")
road_width = tree.inputs.float("Road Width", 0.25)
size_x = tree.inputs.float("Size X", 5.0)
size_y = tree.inputs.float("Size Y", 5.0)
density = tree.inputs.float("Density", 10.0)
building_size_min = tree.inputs.vector("Building Size Min", (0.1, 0.1, 0.2))
building_size_max = tree.inputs.vector("Building Size Max", (0.3, 0.3, 1.0))
curve_mesh = geo >> g.CurveToMesh(
profile_curve=g.CurveLine(
start=g.CombineXYZ(x=road_width * -0.5),
end=g.CombineXYZ(x=road_width * 0.5),
),
)
building_points = g.Grid(size_x, size_y) >> g.DistributePointsOnFaces(
density=density, seed=seed
)
road_points = geo >> g.CurveToPoints(mode="EVALUATED")
building_points = g.DeleteGeometry.point(
building_points,
selection=g.GeometryProximity(
target_element="POINTS",
target=road_points,
source_position=g.Position().o.position,
).o.distance
< road_width,
)
buildings = building_points >> g.InstanceOnPoints(
instance=g.Cube() >> g.TransformGeometry(translation=(0, 0, 0.5)),
scale=g.RandomValue.vector(
min=building_size_min, max=building_size_max, seed=seed
),
)
g.JoinGeometry((curve_mesh, buildings)) >> tree.outputs.geometry("Result")from geometry_script import *
@tree("City Builder")
def city_builder(
geometry: Geometry,
seed: Int,
road_width: Float = 0.25,
size_x: Float = 5,
size_y: Float = 5,
density: Float = 10,
building_size_min: Vector = (0.1, 0.1, 0.2),
building_size_max: Vector = (0.3, 0.3, 1),
):
# Road mesh
yield geometry.curve_to_mesh(
profile_curve=curve_line(
start=combine_xyz(x=road_width * -0.5), end=combine_xyz(x=road_width * 0.5)
)
)
# Building points
building_points = (
grid(size_x=size_x, size_y=size_y)
.distribute_points_on_faces(density=density, seed=seed)
.points
)
road_points = geometry.curve_to_points(mode=CurveToPoints.Mode.EVALUATED).points
# Delete points within the curve
building_points = building_points.delete_geometry(
domain=DeleteGeometry.Domain.POINT,
selection=geometry_proximity(
target_element=GeometryProximity.TargetElement.POINTS,
target=road_points,
source_position=position(),
).distance
< road_width,
)
# Building instances
yield building_points.instance_on_points(
instance=cube().transform(translation=(0, 0, 0.5)),
scale=random_value(
data_type=RandomValue.DataType.FLOAT_VECTOR,
min=building_size_min,
max=building_size_max,
seed=seed,
),
)