Add IRCSS gothic cone translation

This commit is contained in:
Meta Vens 2026-05-04 20:01:58 +01:00
commit fdda6184e8
8 changed files with 320 additions and 19 deletions

View file

@ -258,12 +258,15 @@ Completed Blender raycast minigame foothold:
Completed IRCSS French Houses foothold:
- `docs/research/translations/ircss-french-houses.md`
- Source group: `MakeSpiral`
- Verification: output curve converted to points matches source with ordered
max vertex delta `0.0`.
- Source groups: `MakeSpiral`, `PointyGothicCone`
- Verification: spiral output curve converted to points matches source with
ordered max vertex delta `0.0`; plain and ornamented gothic cone cases match
source evaluated mesh vertices with sorted max vertex delta `0.0`.
- Translation value: procedural-city corpus triage, spiral architectural
spines, and explicit blockers for repeat-zone feedback, repeat `Top` outputs,
foreach zones, gizmo nodes, and converter duplicate-key failures.
spines, roof-cap ornament grown from structural ribs, captured source points
through instancing, and explicit blockers for repeat-zone feedback, repeat
`Top` outputs, foreach zones, gizmo nodes, and converter duplicate-key
failures.
Inspected but deferred official demo files:

View file

@ -547,6 +547,49 @@ copies and become individual geometry you can bend, cut, or shade.
Verification cue: test more than one seed/control set. Scatter graphs can match
counts at one setting while density, scale, or captured-root behavior is wrong.
## Ornament Grows On Structure
Reach for this when a form has a big readable architectural body plus smaller
detail that should cling to that body: gothic caps, roof ribs, crenellations,
trim, bolts, vines, scales, and surface greebles.
Mental move: build the host silhouette first, capture the host features that
detail needs, then sample or convert those features into stations for the
ornament. Do not scatter decorative pieces in empty space and hope they look
attached.
```python
point_positions = _capture_attribute_item("VECTOR", "POINT", geometry, position(), "source position")
cone_seed = cylinder(fill_type="NGON", vertices=6, side_segments=1, fill_segments=1, radius=1.0, depth=1.0)
tapered_cone = scale_elements(
domain="FACE",
geometry=set_position(geometry=cone_seed.mesh, offset=(0.0, 0.0, 0.5)),
selection=cone_seed.top,
scale=0.10000002384185791,
scale_mode="Uniform",
)
edge_flags = _capture_attribute_item("BOOLEAN", "EDGE", tapered_cone, cap_or_body_edge, "edge trim")
cone_instances = instance_on_points(points=point_positions.geometry, instance=cone_with_skirt.mesh, scale=cone_scale)
realized_cones = realize_instances(geometry=cone_instances, realize_all=True, depth=0)
detail_edges = delete_geometry(mode="ALL", domain="EDGE", geometry=realized_cones, selection=edge_flags.edge_trim)
detail_points = resample_curve(
keep_last_segment=True,
curve=mesh_to_curve(mode="EDGES", mesh=detail_edges),
mode="Length",
length=gothic_detail_density,
)
details = instance_on_points(points=detail_points, instance=stretched_detail, rotation=detail_rotation, scale=detail_scale)
```
Metaphor in use: the big form is scaffolding, and ornament is ivy on that
scaffolding. If the scaffold is not built first, the ivy floats there looking
confident and wrong.
Verification cue: test the plain host and the ornamented host separately. The
IRCSS `PointyGothicCone` translation verifies both `SpawnDetails=False` and
`SpawnDetails=True` against evaluated source geometry.
## Constraint Helpers Are Bumpers
Reach for constraint helpers when a procedural system has a small physical rule

View file

@ -1,12 +1,13 @@
# Translation: IRCSS French Houses Foothold
# Translation: IRCSS French Houses Footholds
## Source
- Repository: https://github.com/IRCSS/Blender-Geometry-Node-French-Houses
- Source file:
`GeometryNodesFrenchHous.blend`
- Source group translated in this pass:
- Source groups translated so far:
- `MakeSpiral`
- `PointyGothicCone`
- Geometry Script recreation:
`examples/geometry_script/ircss_french_houses.py`
@ -28,7 +29,7 @@ parts, stalls, cliffs, roads, rivers, vegetation, banners, windows, doors, and
roof systems. This is the right quarry for procedural-city doctrine, but the
first accepted bite has to be small enough to verify.
## First Accepted Graph
## Accepted Graph: MakeSpiral
Source group: `MakeSpiral`
@ -63,9 +64,35 @@ Results from Blender 5.1.1:
| Case | Source vertices | Translated vertices | Delta | Status |
| --- | ---: | ---: | ---: | --- |
| MakeSpiral curve points | 200 | 200 | 0.0 | accepted |
| PointyGothicCone plain | 36 | 36 | 0.0 | accepted |
| PointyGothicCone with details | 138 | 138 | 0.0 | accepted |
The verifier converts the output curve to evaluated points and compares ordered
vertex positions.
## Accepted Graph: PointyGothicCone
Source group: `PointyGothicCone`
Geometry Script group: `VG Pointy Gothic Cone`
Mechanism:
- Capture each input point position before instancing starts.
- Build a tapered six-sided cone seed from a cylinder.
- Capture an edge-domain trim mask that marks cone cap/body edges.
- Instance one scaled cone at each input point.
- When `SpawnDetails` is enabled, delete the marked cone edges, convert the
remaining ribs to curves, resample them into detail stations, and instance
small tapered five-sided spikes along those ribs.
- Orient each detail spike toward the horizontal direction from the source
point to the current rib point.
Metaphor: the input points are roof pegs. The main cone is the roof cap hung on
each peg; the optional details grow from the cap ribs after the cap is already
real enough to have edges. Build the big readable silhouette first, then let
ornament use that structure.
The spiral case converts the output curve to evaluated points and compares
ordered vertex positions. Cone cases feed identical synthetic point meshes into
the source and translation, then compare sorted evaluated vertices.
## Blockers Exposed
@ -90,6 +117,11 @@ vertex positions.
- City-scale graphs are hierarchies of organs. Start with reusable spines,
tiles, rails, façade parts, and topology generators before translating a
thousand-node composer.
- Ornament often needs a structural host. `PointyGothicCone` first creates the
readable cap silhouette, then derives detail placement from the cap's own
realized ribs.
- Captures are not optional bookkeeping when instancing starts. The original
point position and cone edge mask both have to survive later domain changes.
- Converter drafts are still useful for triage: they quickly separate clean
helpers, repeat-zone blockers, foreach-zone blockers, gizmo-specific groups,
and false symbol names.

View file

@ -12,6 +12,21 @@ except ModuleNotFoundError:
load_repo_geometry_script()
from geometry_script import * # noqa: F403 - Geometry Script exposes node functions as DSL globals.
from geometry_script.api.node_mapper import OutputsList, set_or_create_link
from geometry_script.api.state import State
from geometry_script.api.types import Type
def _capture_attribute_item(data_type: str, domain: str, geometry, value, name: str):
node = State.current_node_tree.nodes.new("GeometryNodeCaptureAttribute")
node.capture_items.new(data_type, name)
node.active_index = 0
node.domain = domain
set_or_create_link(geometry, node.inputs[0])
set_or_create_link(value, node.inputs[1])
return OutputsList({"geometry": Type(node.outputs[0]), name.lower().replace(" ", "_"): Type(node.outputs[1])})
@tree("VG Make Spiral")
def vg_make_spiral(
frequency: Float = 12.0,
@ -40,11 +55,130 @@ def vg_make_spiral(
return {"Geometry": set_position(geometry=samples, position=combine_xyz(x=x, y=y, z=z), offset=(0.0, 0.0, 0.0))}
@tree("VG Pointy Gothic Cone")
def vg_pointy_gothic_cone(
geometry: Geometry,
column_radius: Float = 0.3,
peak_height: Float = 1.0,
gothic_detail_density: Float = 0.6,
detail_scale: Float = 0.1,
spawn_details: Bool = True,
rotation: Rotation = (0.0, 0.0, 0.0),
):
point_positions = _capture_attribute_item("VECTOR", "POINT", geometry, position(), "source position")
cone_seed = cylinder(fill_type="NGON", vertices=6, side_segments=1, fill_segments=1, radius=1.0, depth=1.0)
raised_cone = set_position(geometry=cone_seed.mesh, offset=(0.0, 0.0, 0.5))
tapered_cone = scale_elements(
domain="FACE",
geometry=raised_cone,
selection=cone_seed.top,
scale=0.10000002384185791,
scale_mode="Uniform",
axis=(1.0, 0.0, 0.0),
)
not_side = boolean_math(operation="NOT", boolean=cone_seed.side)
cap_or_body_edge = boolean_math(
operation="OR",
boolean=(boolean_math(operation="OR", boolean=(not_side, cone_seed.top)), cone_seed.bottom),
)
cone_edge_flags = _capture_attribute_item("BOOLEAN", "EDGE", tapered_cone, cap_or_body_edge, "edge trim")
scaled_cone_position = vector_math(
operation="MULTIPLY",
vector=(position(), combine_xyz(x=1.0, y=1.0, z=peak_height)),
)
shaped_cone = set_position(geometry=cone_edge_flags.geometry, position=scaled_cone_position, offset=(0.0, 0.0, 0.0))
skirt_depth = math(
operation="MINIMUM",
value=(math(operation="MULTIPLY", value=(peak_height, 0.20000000298023224)), 1.0),
)
cone_with_skirt = extrude_mesh(
mode="FACES",
mesh=shaped_cone,
selection=cone_seed.bottom,
offset_scale=skirt_depth,
individual=False,
)
cone_scale = combine_xyz(x=column_radius, y=column_radius, z=1.0)
cone_instances = instance_on_points(
points=point_positions.geometry,
instance=cone_with_skirt.mesh,
pick_instance=False,
rotation=rotation,
scale=cone_scale,
)
realized_cones = realize_instances(
realize_to_point_domain=True,
geometry=cone_instances,
realize_all=True,
depth=0,
)
details_disabled = boolean_math(operation="NOT", boolean=spawn_details)
cone_for_detail_edges = delete_geometry(mode="ALL", domain="POINT", geometry=realized_cones, selection=details_disabled)
cone_ribs = delete_geometry(mode="ALL", domain="EDGE", geometry=cone_for_detail_edges, selection=cone_edge_flags.edge_trim)
rib_curve = mesh_to_curve(mode="EDGES", mesh=cone_ribs)
detail_points = resample_curve(
keep_last_segment=True,
curve=rib_curve,
mode="Length",
count=5,
length=gothic_detail_density,
)
from_source_point = vector_math(operation="SUBTRACT", vector=(position(), point_positions.source_position))
z_from_source = separate_xyz(vector=from_source_point)
below_peak = compare(
operation="LESS_THAN",
data_type="FLOAT",
mode="ELEMENT",
a=math(operation="SUBTRACT", value=(math(operation="ABSOLUTE", value=z_from_source.z), peak_height)),
b=-0.09999924898147583,
)
detail_seed = cylinder(fill_type="NGON", vertices=5, side_segments=1, fill_segments=1, radius=1.0, depth=1.0)
detail_taper = scale_elements(
domain="FACE",
geometry=detail_seed.mesh,
selection=detail_seed.top,
scale=0.20000001788139343,
scale_mode="Uniform",
axis=(1.0, 0.0, 0.0),
)
raised_detail = set_position(geometry=detail_taper, offset=(0.0, 0.0, 0.30000001192092896))
stretched_detail = set_position(
geometry=raised_detail,
position=vector_math(operation="MULTIPLY", vector=(position(), (1.0, 1.0, 2.0))),
offset=(0.0, 0.0, 0.0),
)
horizontal_rib_direction = vector_math(operation="MULTIPLY", vector=(from_source_point, (1.0, 1.0, 0.0)))
detail_rotation = align_rotation_to_vector(axis="Z", pivot_axis="AUTO", factor=1.0, vector=horizontal_rib_direction)
detail_instances = instance_on_points(
points=detail_points,
selection=below_peak,
instance=stretched_detail,
pick_instance=False,
rotation=detail_rotation,
scale=detail_scale,
)
realized_details = realize_instances(
realize_to_point_domain=True,
geometry=detail_instances,
realize_all=True,
depth=0,
)
return {"Geometry": join_geometry(geometry=[realized_cones, realized_details])}
def _finalize_groups():
import bpy
groups = []
for name in ("VG Make Spiral",):
for name in ("VG Make Spiral", "VG Pointy Gothic Cone"):
group = bpy.data.node_groups.get(name)
if group:
group.use_fake_user = True

View file

@ -39,14 +39,15 @@ Accepted corpus families so far:
- Quellenform `CurveToMeshUV` utility sample.
- Official Blender demo tranche: instance attributes, index-of-nearest,
shortest-path labyrinth, and raycast minigame helpers.
- IRCSS French Houses foothold: `MakeSpiral -> VG Make Spiral`, verified with
200 ordered curve points matching source geometry at max delta `0.0`.
- IRCSS French Houses footholds: `MakeSpiral -> VG Make Spiral` and
`PointyGothicCone -> VG Pointy Gothic Cone`, verified against source
evaluated geometry with max delta `0.0`.
Current frontier: IRCSS French Houses city-scale graph work. The accepted
foothold is `MakeSpiral`. Mapped blockers are repeat-zone feedback state,
repeat `Top` boolean outputs, foreach zones, gizmo nodes, `nodes_to_script`
duplicate-key failures around axes-to-rotation, and a rejected empty-output
harness for `WindowBeams`.
footholds are `MakeSpiral` and `PointyGothicCone`. Mapped blockers are
repeat-zone feedback state, repeat `Top` boolean outputs, foreach zones, gizmo
nodes, `nodes_to_script` duplicate-key failures around axes-to-rotation, and a
rejected empty-output harness for `WindowBeams`.
Current authoring prior: use the full Python stack. Python structures intent,
tables, loops, variants, cleanup, validation, docs, and scene/render

View file

@ -28,3 +28,4 @@
{"ts": "2026-05-04T16:56:12+00:00", "type": "implementation", "status": "accepted", "note": "Expanded the verified Geometry Nodes translation corpus with official Blender demo samples: instance attributes grass tuft, index-of-nearest helpers, shortest-path labyrinth, and raycast minigame helpers. All new verifiers pass in Blender 5.1.1 with sorted max vertex delta 0.0 across their harness cases. Deferred official samples are classified as legacy-node, gizmo-node, simulation-zone, or repeat-zone targets rather than padded into doctrine."}
{"ts": "2026-05-04T17:15:09+00:00", "type": "research", "status": "accepted", "note": "Started IRCSS French Houses city-scale corpus target. Inspected 108 geometry node groups and verified first foothold MakeSpiral -> VG Make Spiral with 200 ordered curve points matching source at max delta 0.0. Recorded blockers from larger city graphs: repeat-zone feedback state, repeat Top output sockets, foreach zones, gizmo nodes, converter duplicate-key failures, and empty-output harness rejection."}
{"ts": "2026-05-04T18:01:03+00:00", "type": "maintenance", "status": "accepted", "note": "Distilled persistent state: handoff now points to corpus docs instead of duplicating completed graph lists; implementation plan reflects post-corpus contract and IRCSS frontier; compaction helper warns on bloated handoff and stale first-corpus planning."}
{"ts": "2026-05-04T19:01:27+00:00", "type": "verification", "status": "accepted", "note": "Added IRCSS PointyGothicCone translation as VG Pointy Gothic Cone; plain and ornamented harness cases match source evaluated geometry with sorted max vertex delta 0.0, proving ornament can be grown from captured structural ribs after instancing."}

View file

@ -91,7 +91,7 @@ data_flow:
- If state grows faster than understanding, cut it back.
current_status:
summary: VibeGeometry has an initial persistence scaffold plus verified Geometry Script translations for local math/utility samples, a first official Blender demo tranche, and an initial IRCSS French Houses city-scale foothold. The IRCSS pass verified MakeSpiral and mapped blockers in larger architectural systems: repeat-zone feedback, repeat Top outputs, foreach zones, gizmo nodes, and converter duplicate-key failures. The repo-local Geometry Script fork includes a Blender 5.1 nodes_to_script converter port plus nested-group and menu-socket fixes needed by these translations.
summary: VibeGeometry has an initial persistence scaffold plus verified Geometry Script translations for local math/utility samples, a first official Blender demo tranche, and two IRCSS French Houses city-scale footholds. The IRCSS pass verified MakeSpiral as an architectural spine and PointyGothicCone as ornament grown from a structural host, while mapping blockers in larger systems: repeat-zone feedback, repeat Top outputs, foreach zones, gizmo nodes, and converter duplicate-key failures. The repo-local Geometry Script fork includes a Blender 5.1 nodes_to_script converter port plus nested-group and menu-socket fixes needed by these translations.
next_action: Continue IRCSS French Houses by choosing the next city-organ target after either fixing repeat-zone/foreach/gizmo toolchain blockers or selecting another non-zone architectural helper with a non-empty behavioral harness.
remotes:
vibegeometry: https://github.com/GameCult/VibeGeometry

View file

@ -19,6 +19,32 @@ CASE = {
"Height": 3.0,
"HeightRadiusGain": 0.25,
}
CONE_CASES = [
{
"case": "pointy_gothic_cone_plain",
"inputs": {
"ColumnRadius": 0.45,
"PeakHeight": 1.4,
"GothicDetailDensity": 0.6,
"DetailScale": 0.1,
"SpawnDetails": False,
},
"point_count": 2,
"offset": (1.25, 0.0, 0.0),
},
{
"case": "pointy_gothic_cone_with_details",
"inputs": {
"ColumnRadius": 0.35,
"PeakHeight": 1.2,
"GothicDetailDensity": 0.55,
"DetailScale": 0.08,
"SpawnDetails": True,
},
"point_count": 1,
"offset": (0.0, 0.0, 0.0),
},
]
def _load_translation() -> None:
@ -78,6 +104,31 @@ def _build_spiral_wrapper(name: str, group: bpy.types.GeometryNodeTree):
return wrapper
def _add_mesh_line(nodes, *, count: int, offset):
line = nodes.new("GeometryNodeMeshLine")
line.mode = "OFFSET"
line.count_mode = "TOTAL"
_socket_by_name(line.inputs, "Count").default_value = count
_socket_by_name(line.inputs, "Offset").default_value = offset
return line
def _build_pointy_cone_wrapper(name: str, group: bpy.types.GeometryNodeTree, inputs: dict[str, object], point_count: int, offset):
wrapper = _new_geometry_group(name)
nodes = wrapper.nodes
links = wrapper.links
line = _add_mesh_line(nodes, count=point_count, offset=offset)
group_node = nodes.new("GeometryNodeGroup")
group_node.node_tree = group
_set_inputs(group_node, inputs)
links.new(_socket_by_name(line.outputs, "Mesh"), _socket_by_name(group_node.inputs, "Geometry"))
group_output = nodes.new("NodeGroupOutput")
links.new(_socket_by_name(group_node.outputs, "Geometry"), _socket_by_name(group_output.inputs, "Geometry"))
return wrapper
def _evaluated_vertices(group: bpy.types.GeometryNodeTree):
mesh = bpy.data.meshes.new(group.name + " Input Mesh")
obj = bpy.data.objects.new(group.name + " Object", mesh)
@ -111,7 +162,43 @@ def main() -> int:
"max_ordered_vertex_delta": max_delta,
}
result["ok"] = len(source) == len(translated) and max_delta <= 1e-6
payload = {"ok": result["ok"], "results": [result]}
results = [result]
for case in CONE_CASES:
source = _evaluated_vertices(
_build_pointy_cone_wrapper(
f"VG Verify Source {case['case']}",
bpy.data.node_groups["PointyGothicCone"],
case["inputs"],
case["point_count"],
case["offset"],
)
)
translated = _evaluated_vertices(
_build_pointy_cone_wrapper(
f"VG Verify Translated {case['case']}",
bpy.data.node_groups["VG Pointy Gothic Cone"],
case["inputs"],
case["point_count"],
case["offset"],
)
)
max_delta = (
max((math.dist(a, b) for a, b in zip(sorted(source), sorted(translated))), default=0.0)
if len(source) == len(translated)
else math.inf
)
results.append(
{
"case": case["case"],
"source_vertex_count": len(source),
"translated_vertex_count": len(translated),
"max_sorted_vertex_delta": max_delta,
"ok": len(source) == len(translated) and max_delta <= 1e-6,
}
)
payload = {"ok": all(item["ok"] for item in results), "results": results}
print("VG_IRCSS_FRENCH_HOUSES_TRANSLATION_BEHAVIOR " + json.dumps(payload, sort_keys=True))
return 0 if payload["ok"] else 1