Skip to content

feat(procedural-items): extrude shapes with F1 sections, holes and bevel (AK-03b) - #951

Closed
Aymericr wants to merge 7 commits into
fidelity/ak-03a-cylinder-optionsfrom
fidelity/ak-03b-extrude
Closed

Aymericr wants to merge 7 commits into
fidelity/ak-03a-cylinder-optionsfrom
fidelity/ak-03b-extrude

Conversation

@Aymericr

@Aymericr Aymericr commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

AK-03b: extrude shapes with F1 sections, holes and bevel

Stacked on #950 (AK-03a) → … → #945. Review the top commit only. This is part 2 of AK-03.

What. A v2 extrude shape: { primitive: 'extrude', section, length, bevel?, position, rotation?, slot }.

  • section is an F1 SectionProfile whose numbers may be expressions:

    • rectangle { width, depth, corner? };
    • round { radius, wall? };
    • oval { width, depth };
    • polygon { outer, holes? }, with 3–64 points per ring and up to 16 holes.

    Evaluation resolves it to the frozen numeric ResolvedSectionProfile, so no ref is allowed and recipes stay self-contained, as the CHALLENGE required.

  • Geometry. The section lies in local x/y and is extruded along local z over length, centred. The evaluated shape is re-centred in its box, which gives three things:

    • bounds, handles, the base shift, cut checks and surfaces keep reading every shape as an oriented box;
    • size is derived;
    • section is carried on the evaluation.
  • core/procedural-items/section.ts holds two helpers:

    • sectionRings(profile): turns a resolved section into rings. Rounds and ovals use 24 sides, rounded corners use 4 segments per quarter, repeated points are dropped, a ring needs an area, and holes must sit inside the outline. This is the seam for B-05's sweep, which will consume the same rings along a path. Sweep is not in this PR.
    • extrusionTriangles(): analytic. Earcut caps give V + 2H − 2 triangles each, and the side walls give 2 per edge per layer. The count is exact for rings without collinear points and an upper bound otherwise.
  • Builder. three's ExtrudeGeometry (MIT, already bundled). A bevel uses bevelOffset = −bevel, so the outline shrinks and bevels back out and the box never grows. Bevel is at most min(length, width, depth)/4 and at most 5 cm.

  • Other rules. extrude is v2-only and takes no size. Every other primitive now needs size; the schema made it optional only so extrude can omit it. An extrude cannot be a support shape.

Why. Audit limit #1. On /next, 525 elements (7.3 %) need extrusion: fascia and soffits (45/84), stair stringers and nosings (40/101), louvers (39/143), countertops and mouldings. It is also stage 1 of holes (limit #3): counters with a sink cut-out, grilles, frames.

Failing case first. Before the fix, core/src/procedural-items/extrude.test.ts failed 5 of 5 tests (scratch/AK-CORE/03b-failing-before.txt).

Proof.

  • E6 fascia return (trial element cabana-roof-east-end-closure). The sloped-top return was a box; now it is a polygon extrude of its four measured corners. Scored with the trial's compare.py against the /next source mesh, inside the return's box (scratch/AK-CORE/trial-proof/e6-return.out):

    Return R@2 P@2
    Box (before) 0.952 0.930
    Extrude (after) 1.000 1.000

    Both have 48 triangles. Over the whole element, R@2 goes from 0.9995 to 0.9998 (the return is small next to the cap).

  • Parity and fit. nodes geometry.test.ts covers rectangle, rounded rectangle, fully rounded ends, round, round tube, oval, a countertop with a sink hole, and both of those with a bevel. For each, the built triangles equal shapeTriangles, and the built bounding box equals the evaluated box to 1e-5.

  • Expressions and pose. Core tests confirm that a width parameter moves polygon vertices and that the re-centred position is correct.

  • bun test src (core): 3012 pass. bun test src/procedural-item (nodes): 52 pass. turbo check-types for core, nodes and editor: pass. bun run check: clean.

House effect: no change. The /next scenes have no procedural-item nodes; the E6 gain lands when converters write v2.

R7 size note. Each polygon vertex costs 2 values of the 12,000-value structural budget. A 64-point ring with 16 holes of 64 points is about 2,200 values. Hole patterns such as grilles should use a repeated part with a count rather than listing many holes (CHALLENGE AK-03 fix).

Bugbot fix (second commit): a bevel is now capped by the section's thinnest wall, not only by its box. The thinnest wall is the least distance from a vertex to any edge it does not touch, across the outline and holes; bevel ≤ wall / 2.5, and a bevelled section may have at most 256 points. Tested on the counter (its slot is 5 mm from the edge, so 2 mm is OK and 2.5 mm is refused) and on an L-section.

Rule Check
R1 Additive. size is optional in the TS type only because extrude omits it; every other primitive still requires it at parse
R2 ExtrudeGeometry runs once per (recipe, params) and is cached, at 0.25–2.7 ms per generator (CHALLENGE lpb bench)
R5 Oriented-box invariant, so handles, bounds, paint and surfaces are unchanged. No support surfaces on extrusions
R7 Sections are inline and resolved (no ref)
R3, R4, R6, R8, R9 Not touched

Local preview

Shared setup for the whole stack: previews/AK.md (preview.sh start AK-PV previews/AK/ak-after.json serves on :3301).
Setup is in previews/AK.md.

What you will notice

  • The counter's stone top is an extruded section with a round sink cut-out and a 3 mm bevelled edge. Before, it was a plain slab.

Where to look

Pose 3-counter-cylinders, the counter on the left.

Before / after

previews/AK/before-3-counter-cylinders.png / after-3-counter-cylinders.png.

Checked by me: the static renders at the listed poses (captured from :3100 and :3301). I did not click through the interactive steps in a browser; unit tests cover them.

Known limits

  • The bevel may be at most 2/5 of the section's thinnest wall.
  • Sweep along a path is deferred to B-05.

For Wassim to review:

  1. The section field name (F1's own), with length rather than depth, which collides with F1 rectangle.depth.
  2. The re-centring invariant for profile shapes.
  3. The polygonisation constants (24 sides for rounds, 4 segments per rounded corner).

🤖 Generated with Claude Code


Note

Medium Risk
Touches recipe schema, evaluation, and mesh generation with new validation paths; v2-only and additive, but incorrect section/bevel rules could reject valid designs or mis-size bounds.

Overview
Adds a version-2-only extrude shape to procedural recipes: { primitive: 'extrude', section, length, bevel?, … } instead of size. Recipe shapes are now a discriminated union on primitive (solidShape vs extrudeShape), with solidFields() so existing box/cylinder validation stays on sized primitives only.

Core evaluation resolves expression-driven F1 sections (rectangle, round, oval, polygon with holes, structural I/C/L/T/Z/rect-tube), builds rings via new section.ts (sectionRings, extrusionTriangles, sectionThickness), derives size and re-centred position from section bounds so extrusions still behave like oriented boxes for bounds/handles. Bevel is capped by length, 5 cm, and thinnest wall / 2.5. Polygon validation rejects crossing edges, nested holes, and invalid structural webs.

Rendering in geometry.ts builds extrusions with Three.js ExtrudeGeometry from those rings (optional inset bevel), treats extrude as v2 geometry for world-scale face-frame UVs, and counts triangles consistently with shapeTriangles.

Tests cover compile-time contract for non-extrude size, core extrude/bevel/validation cases, and nodes parity (triangle charge, bounding box, UVs on angled walls).

Reviewed by Cursor Bugbot for commit 39b36e5. Bugbot is set up for automated code reviews on this repo. Configure here.

Codex review round (rev-951-a)

Failing test first: test(... AK-03b, failing), then the fix.

  • R1 public API: shapes are a discriminated union on primitive. Non-extrude shapes keep a required size. v1 key order is kept so parsed v1 recipes serialize byte for byte (there is a byte-pin test).
  • UVs are face-frame and world-scale on extrude caps and walls.
  • Section checks reject hole edges that cross the outer ring or each other. Sections allow at most 256 points.
  • Structural sections (I, C, L, T, Z, rect-tube) are validated.
  • A bevel is capped by the thinnest wall.

Required change in the private bump PR

When private-editor bumps the editor/ pin past this PR, fix packages/ai/src/studio/procedural-normalize.ts (~lines 199–212) in that same bump PR, or the private typecheck fails.

  • The rounding clamp reads shape.radius and shape.size, then assigns .radius on the probe shape and on target.
  • RecipeShape is now a union: extrude shapes (this PR) and revolve shapes (feat(procedural-items): revolve shapes (AK-03c) #952) have neither radius nor size.
  • Fix: skip any shape whose primitive is 'extrude' or 'revolve' before the clamp. Those shapes can't carry rounding anyway.

@pascal

pascal Bot commented Sep 27, 2026

Copy link
Copy Markdown

I hit an error while handling your request (Model unavailable on AI Gateway free tier: Free tier users do not have access to this model. Upgrade to paid credits at https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai%3Fmodal%3Dtop-up for unrestricted…).

Please try again, rephrase, or reach out if it keeps failing.

Error id: d4e5e7db-a268-4f42-80b9-49d6749f0cf5

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread packages/core/src/procedural-items/recipe.ts
@Aymericr
Aymericr force-pushed the fidelity/ak-03b-extrude branch from faf6efe to 14a1676 Compare September 27, 2026 02:13
@Aymericr
Aymericr force-pushed the fidelity/ak-03a-cylinder-options branch from 87059c5 to 0315065 Compare September 27, 2026 02:32
@Aymericr
Aymericr force-pushed the fidelity/ak-03b-extrude branch from 14a1676 to a39be0a Compare September 27, 2026 02:32
@Aymericr
Aymericr force-pushed the fidelity/ak-03a-cylinder-options branch from 0315065 to 631d5dc Compare September 27, 2026 02:33
@Aymericr
Aymericr force-pushed the fidelity/ak-03b-extrude branch 2 times, most recently from e5aad07 to bf12a8a Compare September 27, 2026 22:27
Aymericr and others added 6 commits September 27, 2026 20:23
…vel (AK-03b)

A v2 'extrude' shape takes an expression-bearing F1 section (rectangle
with optional corner radius, round with optional wall, oval, or polygon
with up to 16 holes), a length and an optional bevel. Evaluation resolves
it to a numeric ResolvedSectionProfile, re-centred in its box, so bounds,
handles, surfaces and the base shift keep reading every shape as an
oriented box. core/procedural-items/section.ts turns a resolved section
into validated rings (the seam B-05's sweep will reuse) and counts an
extrusion's triangles analytically: earcut caps (V + 2H - 2) plus side
walls per layer. The builder uses three's ExtrudeGeometry; a bevel
shrinks the outline by its size and bevels back out, so the box does not
grow.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… wall (AK-03b)

The bevel insets every contour, so it must stay under the least distance
from any vertex to an edge it does not touch (outline and holes, at most
256 points), not just under the section's box.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…-03b, rebase onto the version-rule refactor)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… hole edges, structural sections (AK-03b, failing)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…trude UVs, section edge checks, structural sections (AK-03b)

- Recipe shapes are a discriminated union on primitive: sized primitives keep
  a required size (R1); extrude takes section and length.
- Extrusions take world-scale UVs from each triangle's own frame.
- sectionRings refuses crossing edges, nested holes and sections above 256
  points, and draws the F1 structural families (I, C, L, T, Z, rect-tube),
  which recipes may now use as Expr-bearing sections.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…erialize byte for byte (AK-03b)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Aymericr
Aymericr force-pushed the fidelity/ak-03a-cylinder-options branch from 107a0e1 to f2941e5 Compare September 28, 2026 03:15
@Aymericr
Aymericr force-pushed the fidelity/ak-03b-extrude branch from bf12a8a to 9a02e58 Compare September 28, 2026 03:16

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread packages/core/src/procedural-items/section.ts
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 39b36e5. Configure here.

thinnest = Math.min(thinnest, segmentDistance(point, other[j]!, other[k]!))
}
return thinnest
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bevel cap treats arcs as walls

Medium Severity

sectionThickness takes the nearest vertex-to-non-incident-edge distance, so on rounds, ovals, and rounded rectangles it measures the gap to the next tessellation chord rather than the real wall. A solid 24-gon of radius 5 cm then allows only about 1 mm of bevel, and a 3 cm rounded corner allows less than 1 mm, so a normal chamfer on those sections is refused as if a thin wall existed.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 39b36e5. Configure here.

@Aymericr

Copy link
Copy Markdown
Contributor Author

Landed on main via #971 (merge commit cf874a1), which contains this branch's commits and history.

@Aymericr Aymericr closed this Sep 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant