Expand verified geometry node corpus

This commit is contained in:
Meta Vens 2026-05-04 16:00:47 +01:00
commit 9bc09bb7c2
17 changed files with 1578 additions and 28 deletions

View file

@ -74,6 +74,7 @@ current VibeGeometry toolchain branch, and applies the patches stored in:
```text
patches/geometry-script/0001-fix-nested-tree-group-references-on-blender-4.patch
patches/geometry-script/0002-Port-node-tree-conversion-operator.patch
patches/geometry-script/0003-Tolerate-Blender-menu-socket-defaults.patch
```
`external/` is intentionally ignored by the VibeGeometry repository. The clone
@ -93,6 +94,8 @@ This is the branch VibeGeometry should use locally. It contains:
- the nested `@tree` group fix from
`vibegeometry/blender-5-nested-tree-groups`
- a Blender 5.1 port of the historical `nodes_to_script` converter prototype
- a Blender 5.1 menu-socket default tolerance fix so group construction does
not fail when `NodeSocketMenu` enum items are not populated yet
The converter adds:
@ -123,6 +126,10 @@ Known limits:
- Generated names and ordering reflect extraction mechanics, not spatial intent.
- Behavioral equivalence still needs the same evaluated-geometry or scalar
harness verification used by the hand translations.
- Blender 5 capture items are still a live gap in Geometry Script's generic
node builder. The accepted `VG Field Value` translation creates typed capture
items explicitly in script instead of relying on the generated
`capture_attribute(...)` wrapper.
## Upstream PR Branch

View file

@ -134,13 +134,32 @@ Completed field value helper entry:
`Mesh Line.Count` with the result; next-digit branch helper matches source
across four cases spanning both sides of `Max Precision`; delete-mask scalar
helper matches source across 195 digit/segment-position cases with zero
mismatches.
mismatches; full `Field Value` default-route geometry matches source with
256 vertices and sorted max vertex delta `0.0`.
- Toolchain note: full `Field Value` required explicit Blender 5 capture-item
creation and a Geometry Script fork fix for menu socket defaults.
Boundary note: the full `Field Value` graph is a 176-node composition involving
menu switches, capture attributes, sample-index reads, instances, transforms,
and delete geometry. Before translating it by hand, test or port upstream's
historical `nodes_to_script` converter so VibeGeometry gets a generated first
draft to inspect.
Completed parametric/polar equation entry:
- `docs/research/translations/shriinivas-paramnpolareq.md`
- Source groups: `NodeGroup`, `NodeGroup.001`, `NodeGroup.002`,
`NodeGroup.003`, `Archimedes Spiral`, `Epicycloid`,
`Geometry Nodes Group.002`
- Verification: scalar equation helpers match by placing outputs into a single
vertex position; Archimedes, epicycloid, and mirrored root spiral evaluated
vertices match source geometry with max sorted deltas at or near `0.0`.
- Translation value: accumulated parameter ramps, parametric equations, polar
conversion, mirrored curve streams, and scalar-harness design.
Started CurveToMeshUV utility entry:
- `docs/research/translations/quellenform-curve-to-mesh-uv.md`
- Source group translated so far: `Auto Smooth`
- Verification: source and translated `Auto Smooth` preserve cube geometry and
match smooth-face flags in Blender 5.1.1.
- Boundary: the main `Curve to Mesh UV` group is a 126-node utility graph and
should be approached through staged subcontracts: curve resampling, spline
parameter UVs, cap masks, named attributes, then final mesh output.
## Extraction Workflow

View file

@ -145,6 +145,42 @@ Verification cue: test more than one parameter value. Field graphs often look
right at one value and reveal swapped axes or stale constants when controls
move.
## Parameters Are Conveyors
Reach for `Accumulate Field` when a curve or repeated structure needs a steady
parameter ramp: `t`, `theta`, distance, age, row number, phase, growth amount.
Mental move: make a boring sequence of stations first, compute the step between
stations, then let the graph stamp each station with the accumulated parameter.
The equation or deformation reads that parameter downstream.
```python
step = math(
operation=Math.Operation.DIVIDE,
value=(max_value, math(operation=Math.Operation.SUBTRACT, value=(resolution, 1.0))),
)
theta = accumulate_field(
data_type=AccumulateField.DataType.FLOAT,
domain=AccumulateField.Domain.POINT,
value=step,
).trailing
mesh = mesh_line(
mode=MeshLine.Mode.OFFSET,
count_mode=MeshLine.CountMode.TOTAL,
count=resolution,
offset=(0.0, 0.0, 1.0),
)
geometry = mesh.set_position(position=combine_xyz(x=x_from_theta, y=y_from_theta, z=0.0))
```
Metaphor in use: the mesh line is a conveyor belt. `Accumulate Field` stamps
each station with a larger number, and the curve equation tells that station
where to stand.
Verification cue: compare evaluated vertices at a non-default resolution and
range. Off-by-one errors hide at endpoints.
## Set Position Is The Hinge
Reach for `set_position` when your graph has built an invisible spatial rule
@ -211,6 +247,44 @@ attribute lets some stations use a wider bit without changing the rail.
Verification cue: inspect mesh vertex counts and bounding dimensions, not just
the existence of a `Curve to Mesh` node.
## Surface Passes Are Polish Gates
Reach for surface-state utilities when geometry already exists but needs
controlled smoothing, normals, material assignment, UVs, or attributes before
it is usable downstream.
Mental move: keep shape generation separate from finishing state. In `VG Auto
Smooth`, the mesh enters unchanged, edge angle and existing smooth flags decide
edge smoothing, then the face domain is stamped smooth.
```python
shallow_enough = compare(
operation=Compare.Operation.LESS_EQUAL,
data_type=Compare.DataType.FLOAT,
mode=Compare.Mode.ELEMENT,
a=edge_angle().unsigned_angle,
b=angle,
)
edge_pass = set_shade_smooth(
domain=SetShadeSmooth.Domain.EDGE,
mesh=geometry,
selection=is_edge_smooth(),
shade_smooth=shallow_enough & is_face_smooth(),
)
geometry = set_shade_smooth(
domain=SetShadeSmooth.Domain.FACE,
mesh=edge_pass,
selection=True,
shade_smooth=True,
)
```
Metaphor in use: this is a polishing gate, not a forge. The form passes
through; the gate decides which surface state is allowed to survive.
Verification cue: compare surface flags as well as vertices. A finishing graph
can preserve shape perfectly while changing the thing it was built to control.
## Field Masks Are Chisels
Reach for field masks when a regular seed mesh should become a shaped part by
@ -354,6 +428,38 @@ anything cumulative.
Verification cue: test the composed graph, not only the segment. A baton can be
correct locally but passed to the wrong next socket.
## Capture Before The Crowd
Reach for `Capture Attribute` when a value or index must survive a domain
change, instancing layer, or later deletion pass.
Mental move: before geometry enters a crowd of instances, pin a name and domain
to the value that later stages still need. In `VG Field Value`, the source
value, glyph face index, digit-slot index, and vector-component index each have
to ride through different instancing layers.
```python
glyph_faces = _capture_attribute_item("INT", "FACE", segment_glyph, index(), "int field")
digit_indexed = _capture_attribute_item("INT", "INSTANCE", digit_instances, index(), "int field")
hidden_segments = vg_delete_segments(
digit=final_digit_code,
segment_position=glyph_faces.int_field,
)
visible = delete_geometry(
mode=DeleteGeometry.Mode.ALL,
domain=DeleteGeometry.Domain.FACE,
geometry=realized_digits,
selection=hidden_segments,
)
```
Metaphor in use: capture is a luggage tag. Once geometry moves through airports
of instances and domains, untagged values are gone in the baggage system.
Verification cue: test the full composed graph after realization or deletion,
not just the captured helper. The point of capture is later survival.
## Branch Helpers Are Switchboards
Reach for branch helpers when the same output contract can be fed by multiple

View file

@ -0,0 +1,64 @@
# Translation: quellenform Curve To Mesh UV Foothold
## Source
- Repository: https://github.com/quellenform/blender-CurveToMeshUV
- Source file:
`Curve to Mesh UV v1.7.02.230213-FREE - Blender 3.4+.blend`
- Source group translated in this pass:
- `Auto Smooth`
- Geometry Script recreation:
`examples/geometry_script/quellenform_curve_to_mesh_uv.py`
## Why This Graph
The main `Curve to Mesh UV` group is a 126-node utility network with curve
resampling, spline parameters, UV fields, capture attributes, endpoint masks,
domain-size accounting, and named attribute outputs. That is useful quarry, but
not a single-pass snack.
`Auto Smooth` is the small utility attached to that system. It teaches how a
graph can be a surface-finishing gate instead of a shape generator.
## Auto Smooth Map
Source group: `Auto Smooth`
Geometry Script group: `VG Auto Smooth`
Mechanism:
- Read the unsigned mesh edge angle.
- Compare it against the exposed `Angle`.
- AND that threshold result with the existing face smooth state.
- On the edge domain, set shade smooth for already-smooth edges using that
thresholded condition.
- On the face domain, set shade smooth true for the full mesh.
Metaphor: the group is a polishing gate. It does not build the object. It
decides where the surface may keep a smooth handshake across edges and then
hands the mesh back to the larger utility.
## Verification
Run:
```powershell
& 'C:\Program Files (x86)\Steam\steamapps\common\Blender\blender.exe' --background 'E:\Projects\VibeGeometry\experiments\source-repos\blender-CurveToMeshUV\Curve to Mesh UV v1.7.02.230213-FREE - Blender 3.4+.blend' --python '.\tools\verify_curve_to_mesh_uv_translations.py'
```
Results from Blender 5.1.1:
| Case | Source | Translated | Delta | Status |
| --- | ---: | ---: | ---: | --- |
| Auto Smooth cube vertices | 8 | 8 | 0.0 | accepted |
| Auto Smooth smooth faces | 6 | 6 | exact flags | accepted |
## Lessons
- Not every reusable node group creates form. Some preserve or condition
surface state for a larger graph.
- Edge-domain and face-domain passes can be stacked deliberately: first decide
edge continuity, then stamp final face smoothness.
- Utility mega-graphs should be entered through their small helpers first.
They expose the local contracts the main group relies on.

View file

@ -11,6 +11,7 @@
- `Delete Segments`
- `Next Digit`
- `Digit At`
- `Field Value`
- Geometry Script recreation:
`examples/geometry_script/shriinivas_fieldvalue.py`
@ -24,18 +25,6 @@ Together they show the first display-system layers: tiny glyph geometry,
field-sculpted segment geometry, segment composition, segment visibility masks,
and value-only digit extraction.
## Current Boundary
The remaining full `Field Value` group has 176 nodes and combines menu switches,
capture attributes across multiple domains, sampled fields, instancing,
instance transforms, delete-geometry selection, material assignment, and the
helpers translated here.
That graph should not be translated manually as one heroic sweep. The next
right move is to port or test the upstream `nodes_to_script` prototype against
this graph so the first draft can be generated, inspected, and then cleaned
with doctrine instead of hand-copied node by node.
## Create Decimal Map
Source group: `Create Decimal`
@ -183,6 +172,43 @@ Metaphor: `Next Digit` is the read head for the display tape. Before the decimal
boundary it reads the fractional reel; after that boundary it reads the whole
number reel.
## Field Value Map
Source group: `Field Value`
Geometry Script group: `VG Field Value`
Mechanism:
- Use menu switches to route three control choices:
input type (`Float` or `Vector`), source domain (`Point`, `Edge`, or `Face`),
and text alignment (`Center`, `Right`, or `Left`).
- Capture the requested float or vector field on point, edge, and face domains,
then choose the captured geometry and captured value stream for the active
domain.
- Convert edge or face domains to points so the display can instance one value
readout per source element.
- Build one seven-segment glyph, capture each glyph face index, then instance
that glyph across digit slots, vector component slots, and source elements.
- Sample source position and normal by domain index so each readout can be
placed and oriented back onto the element that produced it.
- Compute the active scalar value for the current component, split it into
whole/fractional parts, decide visible slots, negative sign, decimal point,
overflow, and leading-zero behavior.
- Call `VG Next Digit` for digit extraction and `VG Delete Segments` to delete
the dark faces from each glyph.
- Realize instances and assign the requested material.
Metaphor: the full graph is a label printer mounted on the mesh. It reads a
value from each element, assembles a small digit rack, swings the rack onto the
element normal, then punches out the unlit segments.
Implementation note: Blender 5 capture items and menu sockets exposed two
Geometry Script drift points. `VG Field Value` uses a local
`_capture_attribute_item(...)` helper to create typed capture items explicitly,
and the VibeGeometry Geometry Script fork tolerates menu socket defaults whose
enum items do not exist at group-interface construction time.
## Verification
Run:
@ -207,6 +233,7 @@ Results from Blender 5.1.1:
| Digit At `1234.0`, position `1` | 3 | 3 | 0 | accepted |
| Digit At `98765.0`, position `2` | 7 | 7 | 0 | accepted |
| Digit At `120305.0`, position `3` | 0 | 0 | 0 | accepted |
| Field Value default-route vertices | 256 | 256 | 0.0 | accepted |
| Next Digit branch cases | 4 cases | 4 cases | 0 mismatches | accepted |
| Delete Segments grid | 195 cases | 195 cases | 0 mismatches | accepted |
@ -236,3 +263,8 @@ Results from Blender 5.1.1:
verifies positions before and after `Max Precision`, because the interesting
behavior is not digit extraction itself but choosing which number part feeds
extraction.
- Capture attributes are a loading dock for fields. Once a graph crosses into
instancing, capture the values and indices that later stages must still know.
- Full display graphs are stacked assemblies: source element points carry
value racks; value racks carry component stacks; component stacks carry digit
slots; digit slots carry segment faces. Preserve each layer's index baton.

View file

@ -0,0 +1,113 @@
# Translation: Shriinivas Parametric And Polar Equations
## Source
- Repository: https://github.com/Shriinivas/geometrynodes
- Source file: `paramnpolareq.blend`
- Source groups:
- `NodeGroup`
- `NodeGroup.001`
- `NodeGroup.002`
- `NodeGroup.003`
- `Archimedes Spiral`
- `Epicycloid`
- `Geometry Nodes Group.002`
- Geometry Script recreation:
`examples/geometry_script/shriinivas_paramnpolareq.py`
## Why This Graph
This sample teaches parametric curve construction without hiding behind final
rendering polish. The source graphs make a line of points, accumulate a
parameter over that line, turn the parameter into `x/y` fields, and then move
the points into curve shapes.
## Equation Helpers
`VG Param Epicycloid X` and `VG Param Epicycloid Y` recreate the source
epicycloid scalar helpers:
```text
x = (a + b) * cos(t) - b * cos((a / b + 1) * t)
y = (a + b) * sin(t) - b * sin((a / b + 1) * t)
```
`VG Polar Spiral X` and `VG Polar Spiral Y` recreate the polar helper pair:
```text
r = a + b * theta
x = cos(theta) * r
y = sin(theta) * r
```
Metaphor: these helpers are plotter pens without paper. They answer "where
should the point be for this parameter?" but they do not decide how many points
exist or how the curve is sampled.
## Parameter Conveyor
`VG Archimedes Spiral`, `VG Epicycloid`, and `VG Mirrored Root Spiral` share a
sampling spine:
- Create a `Mesh Line` with `Resolution` points.
- Compute `step = max_value / (Resolution - 1)`.
- Feed `step` into `Accumulate Field`.
- Use the trailing accumulated value as `theta` or `t`.
- Compute `x/y`, combine them into a position, and move the mesh line with
`Set Position`.
Metaphor: the mesh line is a conveyor belt. `Accumulate Field` stamps each
station with a progressively larger parameter, and the equation helper tells
that station where to stand.
## Mirrored Root Spiral
Source group `Geometry Nodes Group.002` is a two-arm spiral:
```text
theta = accumulated(2 * pi * Round Count)
r = a * sqrt(theta)
x = r * cos(theta)
y = r * sin(theta)
```
The source builds a forward point stream and a mirrored stream at `(-x, -y)`,
then joins them.
Metaphor: the first arm is a chalk line unwinding from the center; the second
arm is the same chalk line reflected through the origin.
## Verification
Run:
```powershell
& 'C:\Program Files (x86)\Steam\steamapps\common\Blender\blender.exe' --background '.\experiments\source-blends\shriinivas-paramnpolareq.blend' --python '.\tools\verify_paramnpolareq_translations.py'
```
Results from Blender 5.1.1:
| Case | Source | Translated | Delta | Status |
| --- | ---: | ---: | ---: | --- |
| Epicycloid X scalar vertex | 1 vertex | 1 vertex | 0.0 | accepted |
| Epicycloid Y scalar vertex | 1 vertex | 1 vertex | 0.0 | accepted |
| Polar X scalar vertex | 1 vertex | 1 vertex | 0.0 | accepted |
| Polar Y scalar vertex | 1 vertex | 1 vertex | 0.0 | accepted |
| Archimedes Spiral vertices | 64 | 64 | 4.47e-8 | accepted |
| Epicycloid vertices | 80 | 80 | 0.0 | accepted |
| Mirrored Root Spiral vertices | 192 | 192 | 0.0 | accepted |
The scalar helpers are verified by putting the scalar into a single vertex's X
position. The curve groups are verified by sorted evaluated vertex comparison.
## Lessons
- `Accumulate Field` is the natural way to build a parameter ramp inside a
node graph. Python should describe the ramp; the graph should evaluate it.
- Equation helpers are worth naming when several curve generators need the same
scalar law.
- Verifiers should normalize socket names when comparing source groups to
generated groups. `maxT` and `Max T` are the same control for behavior even
when the UI spelling drifts.
- A scalar harness should preserve numeric value, not just count geometry.
Driving a vertex coordinate is often cleaner than driving `Mesh Line.Count`.