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
package/dist-skill/CONTEXT.md
CHANGED
|
@@ -3832,7 +3832,15 @@ detectArrangement(): Sketch[]
|
|
|
3832
3832
|
#### `detectArrangementRegion()` — Select the single arrangement region that contains the given seed point. Throws if no region contains the seed.
|
|
3833
3833
|
|
|
3834
3834
|
```ts
|
|
3835
|
-
detectArrangementRegion(
|
|
3835
|
+
detectArrangementRegion(_seed: [ number, number ]): Sketch
|
|
3836
|
+
```
|
|
3837
|
+
|
|
3838
|
+
#### `toPolyline()` — Return the solved constrained path as a sampled 2D polyline.
|
|
3839
|
+
|
|
3840
|
+
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.
|
|
3841
|
+
|
|
3842
|
+
```ts
|
|
3843
|
+
toPolyline(samples?: number): [ number, number ][]
|
|
3836
3844
|
```
|
|
3837
3845
|
|
|
3838
3846
|
#### `withUpdatedConstraint()` — Re-solve the sketch after changing the value of one existing constraint.
|
|
@@ -4187,7 +4195,7 @@ Smooth curves, lofted surfaces, swept solids, splines, and high-level product sk
|
|
|
4187
4195
|
|
|
4188
4196
|
## Contents
|
|
4189
4197
|
|
|
4190
|
-
- [Curves & Surfacing](#curves-surfacing) — `hermiteTransitionG2`, `nurbs3d`, `spline2d`, `spline3d`, `loft`, `loftAlongSpine`, `sweep`, `variableSweep`, `nurbsSurface`, `surfacePatch`, `transitionCurve`, `transitionSurface`, `connectEdges`
|
|
4198
|
+
- [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`
|
|
4191
4199
|
- [Surface Members](#surface-members) — `surfaceBand`, `SurfaceBody`
|
|
4192
4200
|
- [Curve3D](#curve3d)
|
|
4193
4201
|
- [NurbsCurve3D](#nurbscurve3d)
|
|
@@ -4230,6 +4238,87 @@ Smooth curves, lofted surfaces, swept solids, splines, and high-level product sk
|
|
|
4230
4238
|
|
|
4231
4239
|
### Curves & Surfacing
|
|
4232
4240
|
|
|
4241
|
+
#### `Loft.station()` — Create a loft station from a 2D profile and an axis position.
|
|
4242
|
+
|
|
4243
|
+
```ts
|
|
4244
|
+
Loft.station(profile: Sketch, position: number): LoftStation
|
|
4245
|
+
```
|
|
4246
|
+
|
|
4247
|
+
`LoftStation`: `{ profile: Sketch, position: number }`
|
|
4248
|
+
|
|
4249
|
+
#### `Loft.leftRail()` — Create a guide rail that constrains the section-local negative-X side.
|
|
4250
|
+
|
|
4251
|
+
```ts
|
|
4252
|
+
Loft.leftRail(path: LoftGuideRailPath): LoftGuideRail
|
|
4253
|
+
```
|
|
4254
|
+
|
|
4255
|
+
`LoftGuideRail`: `{ side: LoftGuideRailSide, path: LoftGuideRailPath }`
|
|
4256
|
+
|
|
4257
|
+
#### `Loft.rightRail()` — Create a guide rail that constrains the section-local positive-X side.
|
|
4258
|
+
|
|
4259
|
+
```ts
|
|
4260
|
+
Loft.rightRail(path: LoftGuideRailPath): LoftGuideRail
|
|
4261
|
+
```
|
|
4262
|
+
|
|
4263
|
+
#### `Loft.frontRail()` — Create a guide rail that constrains the section-local positive-Y side.
|
|
4264
|
+
|
|
4265
|
+
```ts
|
|
4266
|
+
Loft.frontRail(path: LoftGuideRailPath): LoftGuideRail
|
|
4267
|
+
```
|
|
4268
|
+
|
|
4269
|
+
#### `Loft.backRail()` — Create a guide rail that constrains the section-local negative-Y side.
|
|
4270
|
+
|
|
4271
|
+
```ts
|
|
4272
|
+
Loft.backRail(path: LoftGuideRailPath): LoftGuideRail
|
|
4273
|
+
```
|
|
4274
|
+
|
|
4275
|
+
#### `Loft.centerRail()` — Create a guide rail that moves section centers along the loft.
|
|
4276
|
+
|
|
4277
|
+
```ts
|
|
4278
|
+
Loft.centerRail(path: LoftGuideRailPath): LoftGuideRail
|
|
4279
|
+
```
|
|
4280
|
+
|
|
4281
|
+
#### `Loft.pathOnXz()` — Place a 2D guide path onto the XZ plane.
|
|
4282
|
+
|
|
4283
|
+
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).
|
|
4284
|
+
|
|
4285
|
+
```ts
|
|
4286
|
+
Loft.pathOnXz(path: LoftPath2D, y?: number): Vec3[]
|
|
4287
|
+
```
|
|
4288
|
+
|
|
4289
|
+
#### `Loft.pathOnYz()` — Place a 2D guide path onto the YZ plane.
|
|
4290
|
+
|
|
4291
|
+
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).
|
|
4292
|
+
|
|
4293
|
+
```ts
|
|
4294
|
+
Loft.pathOnYz(path: LoftPath2D, x?: number): Vec3[]
|
|
4295
|
+
```
|
|
4296
|
+
|
|
4297
|
+
#### `Loft.pathOnXy()` — Place a 2D guide path onto the XY plane.
|
|
4298
|
+
|
|
4299
|
+
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.
|
|
4300
|
+
|
|
4301
|
+
```ts
|
|
4302
|
+
Loft.pathOnXy(path: LoftPath2D, z?: number): Vec3[]
|
|
4303
|
+
```
|
|
4304
|
+
|
|
4305
|
+
#### `Loft.withGuideRails()` — Loft through profile stations while forcing generated sections to follow guide rails.
|
|
4306
|
+
|
|
4307
|
+
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.
|
|
4308
|
+
|
|
4309
|
+
```ts
|
|
4310
|
+
Loft.withGuideRails(stations: LoftStation[], rails: LoftGuideRail[], options?: LoftWithGuideRailsOptions): Shape
|
|
4311
|
+
```
|
|
4312
|
+
|
|
4313
|
+
**`LoftOptions`**
|
|
4314
|
+
- `edgeLength?: number` — Marching-grid edge length for level-set meshing. Smaller = finer.
|
|
4315
|
+
- `boundsPadding?: number` — Optional extra bounds padding.
|
|
4316
|
+
|
|
4317
|
+
**`LoftWithGuideRailsOptions`** extends LoftOptions
|
|
4318
|
+
- `axis?: LoftAxis` — Primary station axis. Default Z.
|
|
4319
|
+
- `samples?: number` — Number of generated loft stations including ends. Default scales with station count.
|
|
4320
|
+
- `railSamples?: number` — Number of points sampled from curve-backed rails before axis interpolation. Default 64.
|
|
4321
|
+
|
|
4233
4322
|
#### `hermiteTransitionG2()` — Create a quintic Hermite transition curve between two edge endpoints (G2 continuity).
|
|
4234
4323
|
|
|
4235
4324
|
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.
|
|
@@ -4318,10 +4407,6 @@ Performance note: loft is significantly heavier than primitive/extrude/revolve.
|
|
|
4318
4407
|
loft(profiles: Sketch[], heights: number[], options?: LoftOptions): Shape
|
|
4319
4408
|
```
|
|
4320
4409
|
|
|
4321
|
-
**`LoftOptions`**
|
|
4322
|
-
- `edgeLength?: number` — Marching-grid edge length for level-set meshing. Smaller = finer.
|
|
4323
|
-
- `boundsPadding?: number` — Optional extra bounds padding.
|
|
4324
|
-
|
|
4325
4410
|
#### `loftAlongSpine()` — Loft between multiple profiles positioned along an arbitrary 3D spine curve.
|
|
4326
4411
|
|
|
4327
4412
|
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.
|
|
@@ -4999,6 +5084,21 @@ path().moveTo(0,0).lineTo(10,0).lineTo(10,5).mirror('x').close()
|
|
|
4999
5084
|
mirror(axis: "x" | "y" | [ number, number ]): this
|
|
5000
5085
|
```
|
|
5001
5086
|
|
|
5087
|
+
#### `toPolyline()` — Return the open path as a sampled 2D polyline.
|
|
5088
|
+
|
|
5089
|
+
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.
|
|
5090
|
+
|
|
5091
|
+
```ts
|
|
5092
|
+
const rail = path()
|
|
5093
|
+
.moveTo(24, 0)
|
|
5094
|
+
.bezierTo(32, 44, 28, 92, 18, 120)
|
|
5095
|
+
.toPolyline();
|
|
5096
|
+
```
|
|
5097
|
+
|
|
5098
|
+
```ts
|
|
5099
|
+
toPolyline(): [ number, number ][]
|
|
5100
|
+
```
|
|
5101
|
+
|
|
5002
5102
|
#### `closeOffset()` — Close the path and return an offset version of the filled Sketch. Positive delta expands outward, negative shrinks inward.
|
|
5003
5103
|
|
|
5004
5104
|
```ts
|
|
@@ -9287,9 +9387,15 @@ root = largest component by body count, object count, then bbox volume
|
|
|
9287
9387
|
rootDistance = shortest accumulated gap distance from root component
|
|
9288
9388
|
```
|
|
9289
9389
|
|
|
9390
|
+
For large scenes the manifest does not materialize the complete component gap
|
|
9391
|
+
graph, because that graph is quadratic in the number of components. The
|
|
9392
|
+
`gapEdgeCount` field reports the logical complete-graph edge count used by the
|
|
9393
|
+
analysis. `gapEdges` stores a compact evidence subset containing nearest-gap
|
|
9394
|
+
and root-parent edges.
|
|
9395
|
+
|
|
9290
9396
|
The PNG colors components from green at the root/near distances through yellow to
|
|
9291
9397
|
red at the farthest rooted component. The manifest stores the root component,
|
|
9292
|
-
maximum rooted distance,
|
|
9398
|
+
maximum rooted distance, compact gap edge evidence, nearest-gap data, and
|
|
9293
9399
|
shortest-path parent fields. The current v1 metric is bbox-based: it measures air
|
|
9294
9400
|
gaps between component bounding boxes, not exact closest mesh-surface distance.
|
|
9295
9401
|
|
|
@@ -9306,8 +9412,8 @@ collision = boolean intersection volume > 0.1mm^3
|
|
|
9306
9412
|
```
|
|
9307
9413
|
|
|
9308
9414
|
The manifest stores the inspected objects, collision pair names/ids, overlap
|
|
9309
|
-
volume, warnings, render style, and each collision finding's
|
|
9310
|
-
`color`, and `hex`. Exact interior pixels can be matched against
|
|
9415
|
+
volume, broadphase counters, warnings, render style, and each collision finding's
|
|
9416
|
+
`groupIndex`, `color`, and `hex`. Exact interior pixels can be matched against
|
|
9311
9417
|
`manifest.channels.collisions.collisions[].color`; antialiased edges may blend
|
|
9312
9418
|
with the ghosted source geometry. If `--focus PartA,PartB` is used, everything
|
|
9313
9419
|
except those objects is hidden, `PartA` and `PartB` are ghosted, and their
|
|
@@ -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
|
|
@@ -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
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Guided loft vs ProductSkin: olive oil bottle.
|
|
3
|
+
*
|
|
4
|
+
* Left: the new namespaced Loft.withGuideRails(...) primitive. Right: the
|
|
5
|
+
* equivalent Product.skin(...) authoring style for comparison.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
Product.scenePreset('product');
|
|
9
|
+
|
|
10
|
+
viewConfig({
|
|
11
|
+
camera: { position: [190, -250, 150], target: [8, 0, 88], fov: 34 },
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const darkOlive = {
|
|
15
|
+
color: '#34462f',
|
|
16
|
+
material: {
|
|
17
|
+
opacity: 0.58,
|
|
18
|
+
roughness: 0.14,
|
|
19
|
+
metalness: 0,
|
|
20
|
+
clearcoat: 1,
|
|
21
|
+
clearcoatRoughness: 0.04,
|
|
22
|
+
transmission: 0.28,
|
|
23
|
+
thickness: 4,
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
const labelStock = Product.materials.mattePlastic('#eee8d8');
|
|
27
|
+
const blackCap = Product.materials.softRubber({ color: '#171a15' });
|
|
28
|
+
|
|
29
|
+
const guidedStations = [
|
|
30
|
+
Loft.station(roundedRect(52, 34, 8), 0),
|
|
31
|
+
Loft.station(roundedRect(60, 38, 11), 78),
|
|
32
|
+
Loft.station(roundedRect(42, 27, 10), 128),
|
|
33
|
+
Loft.station(circle2d(12, 64), 166),
|
|
34
|
+
Loft.station(circle2d(14, 64), 184),
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
const leftSilhouette = path()
|
|
38
|
+
.moveTo(-26, 0)
|
|
39
|
+
.bezierTo(-31, 30, -33, 55, -31, 72)
|
|
40
|
+
.bezierTo(-30, 90, -27, 107, -25, 118)
|
|
41
|
+
.bezierTo(-20, 142, -12, 152, -12, 166)
|
|
42
|
+
.lineTo(-14, 184);
|
|
43
|
+
|
|
44
|
+
const rightSilhouette = path()
|
|
45
|
+
.moveTo(26, 0)
|
|
46
|
+
.bezierTo(31, 30, 33, 55, 31, 72)
|
|
47
|
+
.bezierTo(30, 90, 27, 107, 25, 118)
|
|
48
|
+
.bezierTo(20, 142, 12, 152, 12, 166)
|
|
49
|
+
.lineTo(14, 184);
|
|
50
|
+
|
|
51
|
+
const backCrown = path()
|
|
52
|
+
.moveTo(-17, 0)
|
|
53
|
+
.bezierTo(-21, 30, -22, 56, -20, 72)
|
|
54
|
+
.bezierTo(-19, 92, -17, 108, -16, 118)
|
|
55
|
+
.bezierTo(-13, 142, -10, 154, -10, 166)
|
|
56
|
+
.lineTo(-12, 184);
|
|
57
|
+
|
|
58
|
+
const frontCrown = path()
|
|
59
|
+
.moveTo(17, 0)
|
|
60
|
+
.bezierTo(22, 30, 24, 56, 22, 72)
|
|
61
|
+
.bezierTo(21, 92, 19, 108, 18, 118)
|
|
62
|
+
.bezierTo(14, 142, 10, 154, 10, 166)
|
|
63
|
+
.lineTo(12, 184);
|
|
64
|
+
|
|
65
|
+
const leftRail = Loft.pathOnXz(leftSilhouette);
|
|
66
|
+
const rightRail = Loft.pathOnXz(rightSilhouette);
|
|
67
|
+
const backRail = Loft.pathOnYz(backCrown);
|
|
68
|
+
const frontRail = Loft.pathOnYz(frontCrown);
|
|
69
|
+
|
|
70
|
+
const guidedBottle = Loft.withGuideRails(
|
|
71
|
+
guidedStations,
|
|
72
|
+
[
|
|
73
|
+
Loft.leftRail(leftRail),
|
|
74
|
+
Loft.rightRail(rightRail),
|
|
75
|
+
Loft.backRail(backRail),
|
|
76
|
+
Loft.frontRail(frontRail),
|
|
77
|
+
],
|
|
78
|
+
{ samples: 19, edgeLength: 1.3 },
|
|
79
|
+
)
|
|
80
|
+
.as('guided-loft-olive-glass')
|
|
81
|
+
.material(darkOlive.material)
|
|
82
|
+
.color(darkOlive.color);
|
|
83
|
+
|
|
84
|
+
const guidedLabel = Product.applyMaterial(
|
|
85
|
+
roundedRect(34, 58, 4)
|
|
86
|
+
.extrude(0.8)
|
|
87
|
+
.rotateX(90)
|
|
88
|
+
.translate(0, 22.5, 76)
|
|
89
|
+
.as('guided-loft-cream-label'),
|
|
90
|
+
labelStock,
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const guidedCap = Product.applyMaterial(cylinder(18, 12, 12, 64).translate(0, 0, 184).as('guided-loft-black-cap'), blackCap);
|
|
94
|
+
|
|
95
|
+
const guided = group(
|
|
96
|
+
{ name: 'body', shape: guidedBottle },
|
|
97
|
+
{ name: 'label', shape: guidedLabel },
|
|
98
|
+
{ name: 'cap', shape: guidedCap },
|
|
99
|
+
).translate(-52, 0, 0);
|
|
100
|
+
|
|
101
|
+
const productBottle = Product.skin('product-skin-olive-glass')
|
|
102
|
+
.axis('Z')
|
|
103
|
+
.stations([
|
|
104
|
+
Product.station('base').z(0).roundedRect(52, 34, 8),
|
|
105
|
+
Product.station('body').z(78).roundedRect(60, 38, 11),
|
|
106
|
+
Product.station('shoulder').z(128).roundedRect(42, 27, 10),
|
|
107
|
+
Product.station('neck').z(166).circle(24),
|
|
108
|
+
Product.station('lip').z(184).circle(28),
|
|
109
|
+
])
|
|
110
|
+
.rails({
|
|
111
|
+
leftSilhouette: Product.rail.polyline(leftRail),
|
|
112
|
+
frontCrown: Product.rail.polyline(frontRail),
|
|
113
|
+
})
|
|
114
|
+
.material(darkOlive)
|
|
115
|
+
.edgeLength(1.3)
|
|
116
|
+
.build();
|
|
117
|
+
|
|
118
|
+
const productLabel = Product.applyMaterial(
|
|
119
|
+
roundedRect(34, 58, 4)
|
|
120
|
+
.extrude(0.8)
|
|
121
|
+
.rotateX(90)
|
|
122
|
+
.translate(0, 22.5, 76)
|
|
123
|
+
.as('product-skin-cream-label'),
|
|
124
|
+
labelStock,
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
const productCap = Product.applyMaterial(cylinder(18, 12, 12, 64).translate(0, 0, 184).as('product-skin-black-cap'), blackCap);
|
|
128
|
+
|
|
129
|
+
const productSkinComparison = group(
|
|
130
|
+
{ name: 'body', shape: productBottle.toShape() },
|
|
131
|
+
{ name: 'label', shape: productLabel },
|
|
132
|
+
{ name: 'cap', shape: productCap },
|
|
133
|
+
).translate(52, 0, 0);
|
|
134
|
+
|
|
135
|
+
return [guided, productSkinComparison];
|