OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps commits / 9cdfa83

docs: #120 L3 cable extras design (§14): strands, custom profile, drop, edge set, from edges, bundle
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
9cdfa8355da4367cbf39c1ee5498df3986ae26aa
by plankatron <fleeky@gmail.com> · 2026-10-19T13:50:02.000Z

Changed files (1) +159 -0

docs/node-tree-and-stacks.md +159 -0
……@@ -855,3 +855,162 @@ collisions.
8558556. Whole-selection targets, the Cutters collection, repeat: `gate_box_cutter` H-checks.
8568567. Q menu Change cut, Extract, Apply up to here: `gate_q_stack`.
8578578. Stack scroll preview: `gate_q_stack` S-checks.
858
859## 14. L3 — cable extras (DESIGN, user answers 2026-10-19)
860
861#120 L3's remaining cable items: cables from edges, a bundle between two face sets, drop to
862the ground, custom profiles. Cablerator 1.4.0's behaviour, read from its operators' settings
863and modal keys only (GPL: clean-room, no code or assets). Decided by the user:
864
865- **Bundle** = N ordinary curve + cable shapes in a new "Bundle" collection, ONE gesture.
866 Each cable stays editable; count / seed / tension are tuned in the tool and by Adjust
867 Last, not afterwards.
868- **Edge set** = Shift-click edges in edge mode (a cross-brush SET, as face mode has).
869- **Drop** moves every point but an open curve's two ends.
870- **Custom profile** = a closed CURVE the cable names, live; plus strands / spread
871 (Cablerator's multi-profile).
872
873### 14.1 Strands (every profile)
874
875Cablerator's multi-profile repeats the profile around the cable's axis. Ours, on
876`param_cable.gd`:
877
878| param | default | |
879|---|---|---|
880| `strands` | 1 | 1–12 copies of the profile around the axis |
881| `spread` | 1.0 | each copy's centre sits `spread` × r off the axis (strands > 1 only) |
882| `twist` | 0 | degrees the strands turn per metre (a rope) |
883
884- Strand j's ring k is the profile at `c + (n cos θ + b sin θ) · spread · r`, with
885 θ = 2πj / strands + twist · s, the profile itself unrotated in (n, b). Each strand's
886 segment k = the hull of ITS rings k and k+1: one convex piece per strand per segment.
887- `strands = 1` takes no offset and no twist, so every cable recorded before this folds to
888 the same brushes (gv stays 1; the new params have defaults).
889- Caps and rings stay one piece around the whole bundle (they read the cable's radius ×
890 (1 + spread) when strands > 1, so a plug still covers every strand).
891- Pieces grow × strands. Measured in the gate, not guessed: a 10 m cable at the defaults
892 with 7 strands.
893
894### 14.2 Custom profile (a closed curve, live)
895
896- `profile` gains `custom`, with a hidden `profile_curve` (the curve's dot), set by the
897 Shape panel's **Set profile** picker (pick a curve in the viewport; Cablerator's A).
898- **The 2D outline**, a pure function `CurveMath.profile_outline(curve)`:
899 - sample the closed curve (4 samples per segment, so a `vector`-handle corner is exact);
900 - its plane: Newell normal of the samples, origin = their centroid, u = the direction
901 to the first sample, v = normal × u (deterministic, no raycast);
902 - the 2D convex hull (a CONCAVE outline becomes its hull, as BoxCutter's custom
903 footprint does), collinear points dropped, at most 24 vertices (beyond that, every
904 other one is dropped until it fits: a hull of two 24-gons stays well under the plane
905 count Jolt refused at 63);
906 - normalised so the farthest vertex is 1: `radius` keeps meaning half the width, and
907 the taper and caps work unchanged.
908- The ring = the outline placed in (n, b) at c, × r.
909- **Live:** `_ix_followed_curve` returns BOTH `params.curve` and `params.profile_curve`
910 (it becomes a list), so an edit of the profile curve re-folds every cable using it,
911 and no other curve does. Flatten remaps `profile_curve` as it does `curve`.
912- Profile curve removed, open, or under 3 distinct points: the cable folds as `round`
913 (it does not vanish); undo brings the curve back and the profile with it.
914- X on a cable never removes its profile curve (only its own path curve, as today).
915
916### 14.3 Drop to the ground (`builder.drop_cable`)
917
918Cablerator's Drop the Cable, adapted:
919- **Acts on** the selected cables' curves and selected curves. One gesture: one
920 `curve.set {points}` per curve.
921- **Which points:** all but an open curve's two ends (they stay on their walls). A closed
922 curve drops every point.
923- **Step first** (the tool's one value, `step`, default 1 m, 0 = off): spans longer than
924 `step` get interior points first (at even arc length, CurveMath.sample), so the cable can
925 follow the ground rather than bridge it. Cablerator's Add Points.
926- **Each point:** a physics ray straight down (−Y), 200 m, players excluded, as
927 `_crosshair_hit`. A hit INSIDE the cable's own pieces (within FaceKit.ON_PLANE of all
928 of one piece's planes) is skipped: the ray restarts 1 mm below it. On a hit, the point
929 moves to `hit + hit_normal × r_here` (the cable's radius × the point's radius; 0 for a
930 bare curve) and its `n` becomes the hit normal. A miss leaves the point where it was.
931- **Handles:** `auto` / `vector` points re-derive from their new neighbours. `aligned` /
932 `free` handles are projected onto the hit plane, keeping their length (Cablerator).
933- The tool is modal (F3) only for `step`; Enter writes. Adjust Last re-runs it.
934
935### 14.4 The edge set (edge mode)
936
937- `_sel_edges: Array[Dictionary]` = `{b: brush key, p: [plane key, plane key]}` (the pair
938 sorted), durable like `_sel_faces`. Plain click restarts it; Shift toggles; it clears
939 outside edge mode. The active edge stays `_sel_edge_a/_b` for the existing tools.
940- **Resolving an edge** never needs the pick: its endpoints are the brush's corners lying
941 on BOTH planes (FaceKit.corners, ON_PLANE). An edge whose planes no longer meet in a
942 segment drops out of the set.
943- The highlight draws every edge in the set.
944
945### 14.5 Cables from edges (`builder.cable_from_edges`)
946
947- **Chains:** edges whose endpoints coincide (ON_PLANE) join into chains; a chain that
948 returns to its start is closed. At a vertex where 3+ set edges meet the chain stops
949 (Cablerator makes one spline per path; we make one cable per chain).
950- **The tool** (modal, keys from Cablerator's from-edges where free in the tool):
951 - P — points at Vertices / Centres (edge midpoints, which rounds corners off);
952 - O — offset: each point moves along its outward normal = the mean of the incident
953 faces' normals at it, normalised; default = the radius, so the cable sits ON the edge
954 rather than half inside;
955 - H — corners Round (`auto`) / Sharp (`vector`); an open chain's two ends are always
956 `vector`, so they run straight off the end instead of turning out along the normal
957 (the auto rule for a cable placed on a wall);
958 - S — radius, and the cable's profile params as Add → Cable has.
959- Enter = ONE gesture: a curve + cable per chain (n stored = the outward normal). The new
960 cables are selected. Adjust Last re-runs it.
961- Refused, with a status line: an empty edge set.
962
963### 14.6 Bundle between two face sets (`builder.cable_bundle`)
964
965Cablerator's Create a Cable Mass:
966- **Input:** the face set (`_sel_faces`, Shift-click across brushes). Its ISLANDS are the
967 connected components by shared corners (ON_PLANE). Exactly two, or it refuses ("select
968 exactly 2 face islands, found N"), as Cablerator does.
969- **End points** (Pcg32, `seed`):
970 - R on (default): a random point on the island — a face picked by area, then a
971 triangle of its fan by area, then a uniform point in it;
972 - R off: face centres, cycled (cable i uses centre i mod faces).
973 - Cable i runs from point A_i to point B_i; `n` = that face's normal.
974- **Shape of each cable:** both ends `free`: the end handle leaves along n with length
975 `tension` × chord / 3, each end's tension × (1 + `tension_jitter` × u), u ∈ [−1, 1] from
976 the same Pcg32. `slack` hangs it with `CurveOperator.hung` (interior points).
977- **Tool values** (tool-local keys, BoxCutter's rule): Z count (1–64, default 8),
978 D tension (1.0), N tension jitter (0.3), S radius, R random points, Q a new seed,
979 [ ] slack, plus the profile params.
980- **Enter** = ONE gesture: N × (`curve.create` + `shape.create {cable}`), then
981 `col.create "Bundle"` (under Scene) and `col.link` of the N `g:<shape dot>`. The bundle
982 is selected. Adjust Last re-runs with the same seed.
983- The map's `add_cable` splits into `_record_cable(points, closed, params)` (records only)
984 and the gesture around it, so from-edges and the bundle write many cables in one
985 gesture.
986
987### 14.7 Menus and keys
988
989Every command in a menu and on a key (standing rule):
990- Add ▸ Cable ▸ **From edges** and **Bundle**; Curve ▸ **Drop to ground**; the Q menu on
991 a cable or curve offers Drop.
992- Keys picked at build time against `keymap_gate` (no collisions, Blender meaning first)
993 and recorded here.
994
995### 14.8 Order and gates (each red first)
996
9971. Strands / spread / twist: `gate_cable_gen` S-checks — strands × segments pieces, each
998 convex; strand centres at spread · r; twist turns strand 0 by twist · s; **strands = 1
999 is byte-identical to before**.
10002. Custom profile: `gate_cable_profile` — a square closed curve gives the same pieces as
1001 `square` (after normalising); an L outline gives its hull; editing the profile curve
1002 re-folds the cable and an unrelated curve does not; profile removed → round; undo; flatten
1003 remaps it.
10043. Drop: `gate_cable_drop` — a cable across a floor with a box on it: interior points land
1005 at surface + r with n = the surface normal; ends unchanged; the cable's own pieces are
1006 skipped; a miss stays; `step` adds points; one Ctrl+Z.
10074. Edge set: `gate_edge_set` — Shift-click 3 edges of two brushes; the set survives a
1008 re-fold; endpoints resolve from the plane pair; plain click restarts.
10095. From edges: `gate_cable_edges` — a box top's 4 edges → one CLOSED cable of 4 points;
1010 two disjoint edges → two cables; Centres; offset along the mean normal; one gesture.
10116. Bundle: `gate_cable_bundle` — two islands → N cables; the same seed gives the same
1012 points; every end lies on its island's face (on the plane, inside the polygon) and
1013 leaves along its normal; the collection holds the N; one Ctrl+Z; 1 or 3 islands are
1014 refused.
10157. Measure: pieces and full-fold time for a 20-cable bundle and a 7-strand 10 m cable
1016 (the §8 perf item: 500 stacked objects ≈ 1.7 s per edit).