forgecad 0.9.5 → 0.9.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{AdminPage-uTtcSXtn.js → AdminPage-Da6hhpJx.js} +1 -1
- package/dist/assets/{BlogPage-DYJMjWx3.js → BlogPage-Bl_sKeWb.js} +1 -1
- package/dist/assets/{DocsPage-C58f0K5v.js → DocsPage-Blz3Tp4j.js} +1 -1
- package/dist/assets/{EditorApp-DNH1TEz1.js → EditorApp-CuiPbtn5.js} +32 -7
- package/dist/assets/{EmbedViewer-CMXWA2LX.js → EmbedViewer-BFG6-Ufm.js} +2 -2
- package/dist/assets/{LandingPageProofDriven-CAu2OZFn.js → LandingPageProofDriven-DB9fQd5P.js} +1 -1
- package/dist/assets/{PricingPage-BIgW7m3X.js → PricingPage-BMxYT_F0.js} +1 -1
- package/dist/assets/{SettingsPage-N1l1tMXO.js → SettingsPage-VVQNrCAg.js} +1 -1
- package/dist/assets/{app-CFy7g5WP.js → app-Dl9ymBWC.js} +293 -36
- package/dist/assets/cli/{render-BrVVdj_T.js → render-CFtwKCCY.js} +10 -1081
- package/dist/assets/{sectionPlaneMath-CykEnkvQ.js → distance-BEC2RjJi.js} +1897 -288
- package/dist/assets/{evalWorker-c_SB9gg3.js → evalWorker-CRvbzTXm.js} +555 -83
- package/dist/assets/{manifold-Cjk7WhRs.js → manifold-B9QSr-qP.js} +1 -1
- package/dist/assets/{manifold-Dp6pvFr6.js → manifold-DpBXFS2K.js} +1 -1
- package/dist/assets/{manifold-CRoBhJKH.js → manifold-DzZ4VRPs.js} +2 -2
- package/dist/assets/{renderSceneState-3DfsSASX.js → renderSceneState-BuAXF2jh.js} +1 -1
- package/dist/assets/{reportWorker-BLkuIoS8.js → reportWorker-BNWEnRg1.js} +555 -83
- package/dist/cli/render.html +1 -1
- package/dist/docs/index.html +1 -1
- package/dist/docs-raw/beta-operations.md +4 -0
- package/dist/docs-raw/deployment.md +38 -23
- package/dist/docs-raw/generated/concepts.md +82 -5
- package/dist/docs-raw/generated/curves.md +97 -5
- package/dist/docs-raw/generated/sketch.md +9 -1
- package/dist/docs-raw/guides/inspection-bundles.md +9 -3
- package/dist/docs-raw/runbook.md +3 -3
- package/dist/index.html +1 -1
- package/dist/sitemap.xml +6 -6
- package/dist-cli/forgecad.js +828 -297
- package/dist-cli/forgecad.js.map +1 -1
- package/dist-skill/CONTEXT.md +115 -9
- package/dist-skill/docs/generated/curves.md +97 -5
- package/dist-skill/docs/generated/sketch.md +9 -1
- package/dist-skill/docs/guides/inspection-bundles.md +9 -3
- package/dist-skill/docs-dev/generated/curves.md +97 -5
- package/dist-skill/docs-dev/generated/sketch.md +9 -1
- package/dist-skill/docs-dev/guides/inspection-bundles.md +9 -3
- package/examples/api/guided-loft-olive-oil-bottle.forge.js +135 -0
- package/package.json +20 -2
|
@@ -36,6 +36,10 @@ All of these are thin wrappers around `scripts/beta.sh` /
|
|
|
36
36
|
`scripts/beta-local.sh`. They exist for operator ergonomics only; regular
|
|
37
37
|
ForgeCAD users never see them in the public product CLI.
|
|
38
38
|
|
|
39
|
+
Production has the matching `npm run prod:*` shortcuts in `scripts/prod.sh`
|
|
40
|
+
for the same reason: muscle memory should be environment-prefixed, even though
|
|
41
|
+
production is normally deployed by GitHub Actions on `mainline`.
|
|
42
|
+
|
|
39
43
|
**Useful overrides**:
|
|
40
44
|
- `FORGE_BETA_DEPLOY_CONFIG` — alternate Kamal config path
|
|
41
45
|
- `FORGE_BETA_DESTINATION` — alternate Kamal destination name (default `beta`)
|
|
@@ -55,26 +55,25 @@ All containers share the `kamal` Docker network. The web container reaches the b
|
|
|
55
55
|
|
|
56
56
|
```bash
|
|
57
57
|
# Deploy prod web app (zero-downtime rolling update)
|
|
58
|
-
|
|
58
|
+
npm run prod:deploy
|
|
59
59
|
|
|
60
60
|
# Deploy prod backend compute server
|
|
61
|
-
|
|
61
|
+
npm run prod:deploy:backend
|
|
62
62
|
|
|
63
63
|
# Deploy both prod services
|
|
64
|
-
|
|
64
|
+
npm run prod:deploy:all
|
|
65
65
|
|
|
66
66
|
# Deploy the same commit to beta (run BEFORE prod)
|
|
67
|
-
kamal deploy -d beta
|
|
68
|
-
|
|
69
|
-
# Same flow via the beta operator wrapper
|
|
70
67
|
npm run beta:deploy
|
|
68
|
+
|
|
69
|
+
# Open the environment after a deploy
|
|
71
70
|
npm run beta:smoke
|
|
71
|
+
npm run prod:smoke
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
-
Recommended flow for a change: merge to `mainline` → `
|
|
75
|
-
→ verify on `beta.forgecad.io` → `
|
|
76
|
-
|
|
77
|
-
runbook.
|
|
74
|
+
Recommended flow for a change: merge to `mainline` → `npm run beta:deploy`
|
|
75
|
+
→ verify on `beta.forgecad.io` → `npm run prod:deploy`.
|
|
76
|
+
See [beta-operations.md](beta-operations.md) for the full beta runbook.
|
|
78
77
|
|
|
79
78
|
Kamal builds the Docker image on the server via SSH (`builder: remote:`), pushes to GHCR, pulls on the server, starts the new container, waits for healthcheck, switches traffic, stops old container. Zero downtime.
|
|
80
79
|
|
|
@@ -90,16 +89,16 @@ Push to `mainline` triggers `.github/workflows/deploy.yml` which runs `kamal dep
|
|
|
90
89
|
ssh hetzner 'docker ps -a --format "{{.Names}}\t{{.Status}}" | grep "^forgecad-web-" | sed -E "s/^forgecad-web-([a-f0-9]+)(_replaced_[^[:space:]]+)?\t/\1\t/"'
|
|
91
90
|
|
|
92
91
|
# Roll the web app back to the last known-good version:
|
|
93
|
-
|
|
92
|
+
npm run prod:rollback -- <VERSION>
|
|
94
93
|
|
|
95
94
|
# Backend rollback:
|
|
96
|
-
|
|
95
|
+
npm run prod:rollback:backend -- <VERSION>
|
|
97
96
|
```
|
|
98
97
|
|
|
99
98
|
Example:
|
|
100
99
|
|
|
101
100
|
```bash
|
|
102
|
-
|
|
101
|
+
npm run prod:rollback -- db9c49b058aaa379d9d622456cb249f9b0ddc335
|
|
103
102
|
```
|
|
104
103
|
|
|
105
104
|
Use the most recent healthy version from the `docker ps -a` output above. The
|
|
@@ -126,10 +125,22 @@ version is the hex suffix in the Kamal container name, e.g.
|
|
|
126
125
|
|
|
127
126
|
## Operator Shortcuts
|
|
128
127
|
|
|
129
|
-
Routine beta admin work is wrapped by repo-local scripts so the
|
|
130
|
-
safe and memorable without changing the public ForgeCAD user CLI:
|
|
128
|
+
Routine production and beta admin work is wrapped by repo-local scripts so the
|
|
129
|
+
flow stays safe and memorable without changing the public ForgeCAD user CLI:
|
|
131
130
|
|
|
132
131
|
```bash
|
|
132
|
+
npm run prod:deploy
|
|
133
|
+
npm run prod:smoke
|
|
134
|
+
npm run prod:deploy:backend
|
|
135
|
+
npm run prod:deploy:all
|
|
136
|
+
npm run prod:rollback -- <VERSION>
|
|
137
|
+
npm run prod:logs
|
|
138
|
+
npm run prod:logs:backend
|
|
139
|
+
npm run prod:shell
|
|
140
|
+
npm run prod:db
|
|
141
|
+
npm run prod:health
|
|
142
|
+
npm run prod:errors -- web 1000
|
|
143
|
+
|
|
133
144
|
npm run beta:setup
|
|
134
145
|
npm run beta:deploy
|
|
135
146
|
npm run beta:smoke
|
|
@@ -142,6 +153,10 @@ npm run beta:local
|
|
|
142
153
|
`FORGE_ENVIRONMENT=beta` backend on localhost, so you can open the browser and
|
|
143
154
|
judge the UX without touching Cloudflare.
|
|
144
155
|
|
|
156
|
+
These package scripts are thin wrappers around `scripts/prod.sh`,
|
|
157
|
+
`scripts/beta.sh`, and Kamal. Use raw Kamal commands when debugging Kamal
|
|
158
|
+
itself; otherwise prefer the environment-prefixed shortcuts.
|
|
159
|
+
|
|
145
160
|
---
|
|
146
161
|
|
|
147
162
|
## Environment Variables
|
|
@@ -267,10 +282,10 @@ Use the observability stack first for incidents:
|
|
|
267
282
|
SSH access: `ssh hetzner` (see `~/.ssh/config`).
|
|
268
283
|
|
|
269
284
|
```bash
|
|
270
|
-
# Kamal commands (run from project root locally)
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
285
|
+
# Kamal-backed commands (run from project root locally)
|
|
286
|
+
npm run prod:logs # web app logs
|
|
287
|
+
npm run prod:logs:backend # backend logs
|
|
288
|
+
npm run prod:shell # shell into web container
|
|
274
289
|
kamal proxy details # proxy routing info
|
|
275
290
|
|
|
276
291
|
# Direct SSH
|
|
@@ -281,10 +296,10 @@ ssh hetzner "df -h / && free -h"
|
|
|
281
296
|
### Production scripts
|
|
282
297
|
|
|
283
298
|
```bash
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
bash scripts/prod/db.sh "SELECT ..." #
|
|
299
|
+
npm run prod:health # containers, disk, memory
|
|
300
|
+
npm run prod:errors -- web 1000 # error logs only
|
|
301
|
+
npm run prod:db # interactive psql via Kamal
|
|
302
|
+
bash scripts/prod/db.sh "SELECT ..." # one-shot SQL query
|
|
288
303
|
```
|
|
289
304
|
|
|
290
305
|
### Email not delivered
|
|
@@ -7,7 +7,7 @@ Every public API function belongs to one of 16 fundamental concepts. This docume
|
|
|
7
7
|
- **[C1: Primitive Construction](#c1-primitive-construction)** — Create geometry from parameters — no input geometry required. *(76 functions)*
|
|
8
8
|
- **[C2: Boolean Combination](#c2-boolean-combination)** — Combine same-dimension geometry using CSG set operations. *(6 functions)*
|
|
9
9
|
- **[C3: Rigid Transform](#c3-rigid-transform)** — Reposition or reorient geometry without changing its shape. *(3 functions)*
|
|
10
|
-
- **[C4: Dimensional Promotion](#c4-dimensional-promotion)** — Convert a 2D profile into a 3D solid (extrude, revolve, loft, sweep). *(
|
|
10
|
+
- **[C4: Dimensional Promotion](#c4-dimensional-promotion)** — Convert a 2D profile into a 3D solid (extrude, revolve, loft, sweep). *(40 functions)*
|
|
11
11
|
- **[C5: Topology Query](#c5-topology-query)** — Select or inspect named faces and edges on a shape. *(3 functions)*
|
|
12
12
|
- **[C6: Edge Feature](#c6-edge-feature)** — Modify edges of a solid — fillets, chamfers, draft, offset. *(7 functions)*
|
|
13
13
|
- **[C7: Pattern Replication](#c7-pattern-replication)** — Duplicate geometry in regular arrangements (linear, circular, mirror). *(6 functions)*
|
|
@@ -1002,6 +1002,87 @@ composeChain(...steps: TransformInput[]): Transform
|
|
|
1002
1002
|
|
|
1003
1003
|
Convert a 2D profile into a 3D solid (extrude, revolve, loft, sweep).
|
|
1004
1004
|
|
|
1005
|
+
#### `Loft.station()` — Create a loft station from a 2D profile and an axis position.
|
|
1006
|
+
|
|
1007
|
+
```ts
|
|
1008
|
+
Loft.station(profile: Sketch, position: number): LoftStation
|
|
1009
|
+
```
|
|
1010
|
+
|
|
1011
|
+
`LoftStation`: `{ profile: Sketch, position: number }`
|
|
1012
|
+
|
|
1013
|
+
#### `Loft.leftRail()` — Create a guide rail that constrains the section-local negative-X side.
|
|
1014
|
+
|
|
1015
|
+
```ts
|
|
1016
|
+
Loft.leftRail(path: LoftGuideRailPath): LoftGuideRail
|
|
1017
|
+
```
|
|
1018
|
+
|
|
1019
|
+
`LoftGuideRail`: `{ side: LoftGuideRailSide, path: LoftGuideRailPath }`
|
|
1020
|
+
|
|
1021
|
+
#### `Loft.rightRail()` — Create a guide rail that constrains the section-local positive-X side.
|
|
1022
|
+
|
|
1023
|
+
```ts
|
|
1024
|
+
Loft.rightRail(path: LoftGuideRailPath): LoftGuideRail
|
|
1025
|
+
```
|
|
1026
|
+
|
|
1027
|
+
#### `Loft.frontRail()` — Create a guide rail that constrains the section-local positive-Y side.
|
|
1028
|
+
|
|
1029
|
+
```ts
|
|
1030
|
+
Loft.frontRail(path: LoftGuideRailPath): LoftGuideRail
|
|
1031
|
+
```
|
|
1032
|
+
|
|
1033
|
+
#### `Loft.backRail()` — Create a guide rail that constrains the section-local negative-Y side.
|
|
1034
|
+
|
|
1035
|
+
```ts
|
|
1036
|
+
Loft.backRail(path: LoftGuideRailPath): LoftGuideRail
|
|
1037
|
+
```
|
|
1038
|
+
|
|
1039
|
+
#### `Loft.centerRail()` — Create a guide rail that moves section centers along the loft.
|
|
1040
|
+
|
|
1041
|
+
```ts
|
|
1042
|
+
Loft.centerRail(path: LoftGuideRailPath): LoftGuideRail
|
|
1043
|
+
```
|
|
1044
|
+
|
|
1045
|
+
#### `Loft.pathOnXz()` — Place a 2D guide path onto the XZ plane.
|
|
1046
|
+
|
|
1047
|
+
The path's first coordinate becomes X and its second coordinate becomes Z. Use this for left/right silhouette rails authored with `path()` or `constrainedSketch()`.
|
|
1048
|
+
|
|
1049
|
+
```ts
|
|
1050
|
+
Loft.pathOnXz(path: LoftPath2D, y?: number): Vec3[]
|
|
1051
|
+
```
|
|
1052
|
+
|
|
1053
|
+
#### `Loft.pathOnYz()` — Place a 2D guide path onto the YZ plane.
|
|
1054
|
+
|
|
1055
|
+
The path's first coordinate becomes Y and its second coordinate becomes Z. Use this for front/back crown rails authored with `path()` or `constrainedSketch()`.
|
|
1056
|
+
|
|
1057
|
+
```ts
|
|
1058
|
+
Loft.pathOnYz(path: LoftPath2D, x?: number): Vec3[]
|
|
1059
|
+
```
|
|
1060
|
+
|
|
1061
|
+
#### `Loft.pathOnXy()` — Place a 2D guide path onto the XY plane.
|
|
1062
|
+
|
|
1063
|
+
The path's first coordinate becomes X and its second coordinate becomes Y. Use this when lofting along X or Y and a rail lives in a horizontal sketch plane.
|
|
1064
|
+
|
|
1065
|
+
```ts
|
|
1066
|
+
Loft.pathOnXy(path: LoftPath2D, z?: number): Vec3[]
|
|
1067
|
+
```
|
|
1068
|
+
|
|
1069
|
+
#### `Loft.withGuideRails()` — Loft through profile stations while forcing generated sections to follow guide rails.
|
|
1070
|
+
|
|
1071
|
+
Stations define the cross-section family. Guide rails define the side or center paths the loft must pass through. With opposite side rails, the section is scaled to touch both rails. With one side rail, the section keeps its interpolated size unless a center rail is also present.
|
|
1072
|
+
|
|
1073
|
+
```ts
|
|
1074
|
+
Loft.withGuideRails(stations: LoftStation[], rails: LoftGuideRail[], options?: LoftWithGuideRailsOptions): Shape
|
|
1075
|
+
```
|
|
1076
|
+
|
|
1077
|
+
**`LoftOptions`**
|
|
1078
|
+
- `edgeLength?: number` — Marching-grid edge length for level-set meshing. Smaller = finer.
|
|
1079
|
+
- `boundsPadding?: number` — Optional extra bounds padding.
|
|
1080
|
+
|
|
1081
|
+
**`LoftWithGuideRailsOptions`** extends LoftOptions
|
|
1082
|
+
- `axis?: LoftAxis` — Primary station axis. Default Z.
|
|
1083
|
+
- `samples?: number` — Number of generated loft stations including ends. Default scales with station count.
|
|
1084
|
+
- `railSamples?: number` — Number of points sampled from curve-backed rails before axis interpolation. Default 64.
|
|
1085
|
+
|
|
1005
1086
|
#### `Analysis.EdgeContinuity()`
|
|
1006
1087
|
|
|
1007
1088
|
```ts
|
|
@@ -1312,10 +1393,6 @@ Performance note: loft is significantly heavier than primitive/extrude/revolve.
|
|
|
1312
1393
|
loft(profiles: Sketch[], heights: number[], options?: LoftOptions): Shape
|
|
1313
1394
|
```
|
|
1314
1395
|
|
|
1315
|
-
**`LoftOptions`**
|
|
1316
|
-
- `edgeLength?: number` — Marching-grid edge length for level-set meshing. Smaller = finer.
|
|
1317
|
-
- `boundsPadding?: number` — Optional extra bounds padding.
|
|
1318
|
-
|
|
1319
1396
|
#### `loftAlongSpine()` — Loft between multiple profiles positioned along an arbitrary 3D spine curve.
|
|
1320
1397
|
|
|
1321
1398
|
Unlike loft() which only supports Z heights, loftAlongSpine() places each profile at a position along a 3D spine, oriented perpendicular to the spine tangent. This enables lofting along curved paths — e.g., a wing root-to-tip transition that follows a swept-back leading edge.
|
|
@@ -9,7 +9,7 @@ Smooth curves, lofted surfaces, swept solids, splines, and high-level product sk
|
|
|
9
9
|
|
|
10
10
|
## Contents
|
|
11
11
|
|
|
12
|
-
- [Curves & Surfacing](#curves-surfacing) — `hermiteTransitionG2`, `nurbs3d`, `spline2d`, `spline3d`, `loft`, `loftAlongSpine`, `sweep`, `variableSweep`, `nurbsSurface`, `surfacePatch`, `transitionCurve`, `transitionSurface`, `connectEdges`
|
|
12
|
+
- [Curves & Surfacing](#curves-surfacing) — `Loft.station`, `Loft.leftRail`, `Loft.rightRail`, `Loft.frontRail`, `Loft.backRail`, `Loft.centerRail`, `Loft.pathOnXz`, `Loft.pathOnYz`, `Loft.pathOnXy`, `Loft.withGuideRails`, `hermiteTransitionG2`, `nurbs3d`, `spline2d`, `spline3d`, `loft`, `loftAlongSpine`, `sweep`, `variableSweep`, `nurbsSurface`, `surfacePatch`, `transitionCurve`, `transitionSurface`, `connectEdges`
|
|
13
13
|
- [Surface Members](#surface-members) — `surfaceBand`, `SurfaceBody`
|
|
14
14
|
- [Curve3D](#curve3d)
|
|
15
15
|
- [NurbsCurve3D](#nurbscurve3d)
|
|
@@ -52,6 +52,87 @@ Smooth curves, lofted surfaces, swept solids, splines, and high-level product sk
|
|
|
52
52
|
|
|
53
53
|
### Curves & Surfacing
|
|
54
54
|
|
|
55
|
+
#### `Loft.station()` — Create a loft station from a 2D profile and an axis position.
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
Loft.station(profile: Sketch, position: number): LoftStation
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`LoftStation`: `{ profile: Sketch, position: number }`
|
|
62
|
+
|
|
63
|
+
#### `Loft.leftRail()` — Create a guide rail that constrains the section-local negative-X side.
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
Loft.leftRail(path: LoftGuideRailPath): LoftGuideRail
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`LoftGuideRail`: `{ side: LoftGuideRailSide, path: LoftGuideRailPath }`
|
|
70
|
+
|
|
71
|
+
#### `Loft.rightRail()` — Create a guide rail that constrains the section-local positive-X side.
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
Loft.rightRail(path: LoftGuideRailPath): LoftGuideRail
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
#### `Loft.frontRail()` — Create a guide rail that constrains the section-local positive-Y side.
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
Loft.frontRail(path: LoftGuideRailPath): LoftGuideRail
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
#### `Loft.backRail()` — Create a guide rail that constrains the section-local negative-Y side.
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
Loft.backRail(path: LoftGuideRailPath): LoftGuideRail
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
#### `Loft.centerRail()` — Create a guide rail that moves section centers along the loft.
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
Loft.centerRail(path: LoftGuideRailPath): LoftGuideRail
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
#### `Loft.pathOnXz()` — Place a 2D guide path onto the XZ plane.
|
|
96
|
+
|
|
97
|
+
The path's first coordinate becomes X and its second coordinate becomes Z. Use this for left/right silhouette rails authored with [`path()`](/docs/sketch#path) or [`constrainedSketch()`](/docs/sketch#constrainedsketch).
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
Loft.pathOnXz(path: LoftPath2D, y?: number): Vec3[]
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
#### `Loft.pathOnYz()` — Place a 2D guide path onto the YZ plane.
|
|
104
|
+
|
|
105
|
+
The path's first coordinate becomes Y and its second coordinate becomes Z. Use this for front/back crown rails authored with [`path()`](/docs/sketch#path) or [`constrainedSketch()`](/docs/sketch#constrainedsketch).
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
Loft.pathOnYz(path: LoftPath2D, x?: number): Vec3[]
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
#### `Loft.pathOnXy()` — Place a 2D guide path onto the XY plane.
|
|
112
|
+
|
|
113
|
+
The path's first coordinate becomes X and its second coordinate becomes Y. Use this when lofting along X or Y and a rail lives in a horizontal sketch plane.
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
Loft.pathOnXy(path: LoftPath2D, z?: number): Vec3[]
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
#### `Loft.withGuideRails()` — Loft through profile stations while forcing generated sections to follow guide rails.
|
|
120
|
+
|
|
121
|
+
Stations define the cross-section family. Guide rails define the side or center paths the loft must pass through. With opposite side rails, the section is scaled to touch both rails. With one side rail, the section keeps its interpolated size unless a center rail is also present.
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
Loft.withGuideRails(stations: LoftStation[], rails: LoftGuideRail[], options?: LoftWithGuideRailsOptions): Shape
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**`LoftOptions`**
|
|
128
|
+
- `edgeLength?: number` — Marching-grid edge length for level-set meshing. Smaller = finer.
|
|
129
|
+
- `boundsPadding?: number` — Optional extra bounds padding.
|
|
130
|
+
|
|
131
|
+
**`LoftWithGuideRailsOptions`** extends LoftOptions
|
|
132
|
+
- `axis?: LoftAxis` — Primary station axis. Default Z.
|
|
133
|
+
- `samples?: number` — Number of generated loft stations including ends. Default scales with station count.
|
|
134
|
+
- `railSamples?: number` — Number of points sampled from curve-backed rails before axis interpolation. Default 64.
|
|
135
|
+
|
|
55
136
|
#### `hermiteTransitionG2()` — Create a quintic Hermite transition curve between two edge endpoints (G2 continuity).
|
|
56
137
|
|
|
57
138
|
The curve starts at `a.point` tangent to `a.tangent` with curvature `a.curvature`, and ends at `b.point` tangent to `b.tangent` with curvature `b.curvature`, with smooth G2-continuous interpolation matching position, tangent, and curvature.
|
|
@@ -140,10 +221,6 @@ Performance note: loft is significantly heavier than primitive/extrude/revolve.
|
|
|
140
221
|
loft(profiles: Sketch[], heights: number[], options?: LoftOptions): Shape
|
|
141
222
|
```
|
|
142
223
|
|
|
143
|
-
**`LoftOptions`**
|
|
144
|
-
- `edgeLength?: number` — Marching-grid edge length for level-set meshing. Smaller = finer.
|
|
145
|
-
- `boundsPadding?: number` — Optional extra bounds padding.
|
|
146
|
-
|
|
147
224
|
#### `loftAlongSpine()` — Loft between multiple profiles positioned along an arbitrary 3D spine curve.
|
|
148
225
|
|
|
149
226
|
Unlike loft() which only supports Z heights, loftAlongSpine() places each profile at a position along a 3D spine, oriented perpendicular to the spine tangent. This enables lofting along curved paths — e.g., a wing root-to-tip transition that follows a swept-back leading edge.
|
|
@@ -821,6 +898,21 @@ path().moveTo(0,0).lineTo(10,0).lineTo(10,5).mirror('x').close()
|
|
|
821
898
|
mirror(axis: "x" | "y" | [ number, number ]): this
|
|
822
899
|
```
|
|
823
900
|
|
|
901
|
+
#### `toPolyline()` — Return the open path as a sampled 2D polyline.
|
|
902
|
+
|
|
903
|
+
This is for construction geometry such as guide rails, measured centerlines, and curve-driven helpers where the authored path should stay open instead of becoming a filled sketch or stroked profile.
|
|
904
|
+
|
|
905
|
+
```ts
|
|
906
|
+
const rail = path()
|
|
907
|
+
.moveTo(24, 0)
|
|
908
|
+
.bezierTo(32, 44, 28, 92, 18, 120)
|
|
909
|
+
.toPolyline();
|
|
910
|
+
```
|
|
911
|
+
|
|
912
|
+
```ts
|
|
913
|
+
toPolyline(): [ number, number ][]
|
|
914
|
+
```
|
|
915
|
+
|
|
824
916
|
#### `closeOffset()` — Close the path and return an offset version of the filled Sketch. Positive delta expands outward, negative shrinks inward.
|
|
825
917
|
|
|
826
918
|
```ts
|
|
@@ -1457,7 +1457,15 @@ detectArrangement(): Sketch[]
|
|
|
1457
1457
|
#### `detectArrangementRegion()` — Select the single arrangement region that contains the given seed point. Throws if no region contains the seed.
|
|
1458
1458
|
|
|
1459
1459
|
```ts
|
|
1460
|
-
detectArrangementRegion(
|
|
1460
|
+
detectArrangementRegion(_seed: [ number, number ]): Sketch
|
|
1461
|
+
```
|
|
1462
|
+
|
|
1463
|
+
#### `toPolyline()` — Return the solved constrained path as a sampled 2D polyline.
|
|
1464
|
+
|
|
1465
|
+
Use this when a construction rail was authored with `constrainedSketch()` and should feed another operation such as `Loft.pathOnXz(...)`. The sketch must contain exactly one profile path.
|
|
1466
|
+
|
|
1467
|
+
```ts
|
|
1468
|
+
toPolyline(samples?: number): [ number, number ][]
|
|
1461
1469
|
```
|
|
1462
1470
|
|
|
1463
1471
|
#### `withUpdatedConstraint()` — Re-solve the sketch after changing the value of one existing constraint.
|
|
@@ -190,9 +190,15 @@ root = largest component by body count, object count, then bbox volume
|
|
|
190
190
|
rootDistance = shortest accumulated gap distance from root component
|
|
191
191
|
```
|
|
192
192
|
|
|
193
|
+
For large scenes the manifest does not materialize the complete component gap
|
|
194
|
+
graph, because that graph is quadratic in the number of components. The
|
|
195
|
+
`gapEdgeCount` field reports the logical complete-graph edge count used by the
|
|
196
|
+
analysis. `gapEdges` stores a compact evidence subset containing nearest-gap
|
|
197
|
+
and root-parent edges.
|
|
198
|
+
|
|
193
199
|
The PNG colors components from green at the root/near distances through yellow to
|
|
194
200
|
red at the farthest rooted component. The manifest stores the root component,
|
|
195
|
-
maximum rooted distance,
|
|
201
|
+
maximum rooted distance, compact gap edge evidence, nearest-gap data, and
|
|
196
202
|
shortest-path parent fields. The current v1 metric is bbox-based: it measures air
|
|
197
203
|
gaps between component bounding boxes, not exact closest mesh-surface distance.
|
|
198
204
|
|
|
@@ -209,8 +215,8 @@ collision = boolean intersection volume > 0.1mm^3
|
|
|
209
215
|
```
|
|
210
216
|
|
|
211
217
|
The manifest stores the inspected objects, collision pair names/ids, overlap
|
|
212
|
-
volume, warnings, render style, and each collision finding's
|
|
213
|
-
`color`, and `hex`. Exact interior pixels can be matched against
|
|
218
|
+
volume, broadphase counters, warnings, render style, and each collision finding's
|
|
219
|
+
`groupIndex`, `color`, and `hex`. Exact interior pixels can be matched against
|
|
214
220
|
`manifest.channels.collisions.collisions[].color`; antialiased edges may blend
|
|
215
221
|
with the ghosted source geometry. If `--focus PartA,PartB` is used, everything
|
|
216
222
|
except those objects is hidden, `PartA` and `PartB` are ghosted, and their
|
package/dist/docs-raw/runbook.md
CHANGED
|
@@ -25,7 +25,7 @@ For platform feature docs (auth, projects, sharing, admin, email) see [platform/
|
|
|
25
25
|
| SSH to production | `ssh hetzner` |
|
|
26
26
|
| Open observability UI | Connect to Tailscale, then open `http://100.118.68.93:3000` |
|
|
27
27
|
| Check observability stack | `bash scripts/prod/observability-status.sh` |
|
|
28
|
-
| Roll back bad web deploy | `
|
|
28
|
+
| Roll back bad web deploy | `npm run prod:rollback -- <VERSION>` |
|
|
29
29
|
| Verify npm tarball in Docker | `npm run release:docker:all` |
|
|
30
30
|
|
|
31
31
|
---
|
|
@@ -375,13 +375,13 @@ user flows, roll back to the previous healthy web version immediately:
|
|
|
375
375
|
|
|
376
376
|
```bash
|
|
377
377
|
ssh hetzner 'docker ps -a --format "{{.Names}}\t{{.Status}}" | grep "^forgecad-web-" | sed -E "s/^forgecad-web-([a-f0-9]+)(_replaced_[^[:space:]]+)?\t/\1\t/"'
|
|
378
|
-
|
|
378
|
+
npm run prod:rollback -- <VERSION>
|
|
379
379
|
```
|
|
380
380
|
|
|
381
381
|
Example:
|
|
382
382
|
|
|
383
383
|
```bash
|
|
384
|
-
|
|
384
|
+
npm run prod:rollback -- db9c49b058aaa379d9d622456cb249f9b0ddc335
|
|
385
385
|
```
|
|
386
386
|
|
|
387
387
|
After rollback, verify a real user-facing path, not just `/api/health`. For
|
package/dist/index.html
CHANGED
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
56
56
|
html, body, #root { width: 100%; min-height: 100%; background: var(--fc-bg); color: var(--fc-text); font-family: system-ui, -apple-system, sans-serif; }
|
|
57
57
|
</style>
|
|
58
|
-
<script type="module" crossorigin src="/assets/app-
|
|
58
|
+
<script type="module" crossorigin src="/assets/app-Dl9ymBWC.js"></script>
|
|
59
59
|
<link rel="stylesheet" crossorigin href="/assets/app-CsHnaBWt.css">
|
|
60
60
|
</head>
|
|
61
61
|
<body>
|
package/dist/sitemap.xml
CHANGED
|
@@ -2,37 +2,37 @@
|
|
|
2
2
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
3
3
|
<url>
|
|
4
4
|
<loc>https://forgecad.io/</loc>
|
|
5
|
-
<lastmod>2026-05-
|
|
5
|
+
<lastmod>2026-05-17</lastmod>
|
|
6
6
|
<changefreq>weekly</changefreq>
|
|
7
7
|
<priority>1.0</priority>
|
|
8
8
|
</url>
|
|
9
9
|
<url>
|
|
10
10
|
<loc>https://forgecad.io/docs</loc>
|
|
11
|
-
<lastmod>2026-05-
|
|
11
|
+
<lastmod>2026-05-17</lastmod>
|
|
12
12
|
<changefreq>weekly</changefreq>
|
|
13
13
|
<priority>0.8</priority>
|
|
14
14
|
</url>
|
|
15
15
|
<url>
|
|
16
16
|
<loc>https://forgecad.io/blog</loc>
|
|
17
|
-
<lastmod>2026-05-
|
|
17
|
+
<lastmod>2026-05-17</lastmod>
|
|
18
18
|
<changefreq>weekly</changefreq>
|
|
19
19
|
<priority>0.7</priority>
|
|
20
20
|
</url>
|
|
21
21
|
<url>
|
|
22
22
|
<loc>https://forgecad.io/pricing</loc>
|
|
23
|
-
<lastmod>2026-05-
|
|
23
|
+
<lastmod>2026-05-17</lastmod>
|
|
24
24
|
<changefreq>monthly</changefreq>
|
|
25
25
|
<priority>0.6</priority>
|
|
26
26
|
</url>
|
|
27
27
|
<url>
|
|
28
28
|
<loc>https://forgecad.io/examples</loc>
|
|
29
|
-
<lastmod>2026-05-
|
|
29
|
+
<lastmod>2026-05-17</lastmod>
|
|
30
30
|
<changefreq>weekly</changefreq>
|
|
31
31
|
<priority>0.6</priority>
|
|
32
32
|
</url>
|
|
33
33
|
<url>
|
|
34
34
|
<loc>https://forgecad.io/blog/hello-forgecad-io</loc>
|
|
35
|
-
<lastmod>2026-05-
|
|
35
|
+
<lastmod>2026-05-17</lastmod>
|
|
36
36
|
<changefreq>monthly</changefreq>
|
|
37
37
|
<priority>0.5</priority>
|
|
38
38
|
</url>
|