@snaptrude/plugin-core 0.0.0-dev-20260907135026 → 0.0.0-dev-20260908074328
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/CHANGELOG.md +23 -0
- package/api-manifest.json +12 -151
- package/dist/api/core/geom/create/index.d.ts +86 -559
- package/dist/api/core/geom/create/index.d.ts.map +1 -1
- package/dist/api/core/geom/query/brep.d.ts +0 -50
- package/dist/api/core/geom/query/brep.d.ts.map +1 -1
- package/dist/api/core/geom/query/curve.d.ts +35 -58
- package/dist/api/core/geom/query/curve.d.ts.map +1 -1
- package/dist/api/core/geom/query/edge.d.ts +2 -2
- package/dist/api/core/geom/query/face.d.ts +0 -45
- package/dist/api/core/geom/query/face.d.ts.map +1 -1
- package/dist/api/core/geom/update/curve.d.ts +5 -5
- package/dist/api/core/geom/update/profile.d.ts +1 -1
- package/dist/api/design/create/index.d.ts +29 -6
- package/dist/api/design/create/index.d.ts.map +1 -1
- package/dist/api/design/delete/index.d.ts +20 -2
- package/dist/api/design/delete/index.d.ts.map +1 -1
- package/dist/api/design/query/geometry/index.d.ts +1 -27
- package/dist/api/design/query/geometry/index.d.ts.map +1 -1
- package/dist/api/design/query/index.d.ts +2 -47
- package/dist/api/design/query/index.d.ts.map +1 -1
- package/dist/api/design/query/spaces.d.ts +66 -5
- package/dist/api/design/query/spaces.d.ts.map +1 -1
- package/dist/api/design/update/index.d.ts +0 -87
- package/dist/api/design/update/index.d.ts.map +1 -1
- package/dist/api/entity/referenceLine.d.ts +2 -2
- package/dist/api/entity/referenceLine.d.ts.map +1 -1
- package/dist/api/entity/space.d.ts +7 -5
- package/dist/api/entity/space.d.ts.map +1 -1
- package/dist/api/entity/story.d.ts +4 -4
- package/dist/api/presentation/placedViews.d.ts +35 -1
- package/dist/api/presentation/placedViews.d.ts.map +1 -1
- package/dist/api/presentation/sheets.d.ts +20 -2
- package/dist/api/presentation/sheets.d.ts.map +1 -1
- package/dist/api/program/spreadsheet.d.ts +4 -4
- package/dist/errors/codes.d.ts +8 -3
- package/dist/errors/codes.d.ts.map +1 -1
- package/dist/handles.d.ts +3 -12
- package/dist/handles.d.ts.map +1 -1
- package/dist/index.cjs +2766 -3030
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2761 -2996
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/snaptrude-plugin-core-0.11.0.tgz +0 -0
- package/src/api/core/geom/create/index.ts +84 -588
- package/src/api/core/geom/query/brep.ts +0 -51
- package/src/api/core/geom/query/curve.ts +2 -25
- package/src/api/core/geom/query/edge.ts +2 -2
- package/src/api/core/geom/query/face.ts +0 -49
- package/src/api/design/create/index.ts +34 -8
- package/src/api/design/delete/index.ts +20 -1
- package/src/api/design/query/geometry/index.ts +0 -29
- package/src/api/design/query/index.ts +2 -50
- package/src/api/design/query/spaces.ts +68 -0
- package/src/api/design/update/index.ts +0 -94
- package/src/api/entity/space.ts +2 -0
- package/src/api/presentation/placedViews.ts +29 -1
- package/src/api/presentation/sheets.ts +19 -3
- package/src/errors/codes.ts +30 -2
- package/src/handles.ts +3 -15
- package/src/index.ts +0 -1
- package/test/errors.test.mjs +4 -0
- package/dist/massParameters.d.ts +0 -319
- package/dist/massParameters.d.ts.map +0 -1
- package/src/massParameters.ts +0 -472
- package/test/massParameters.test.mjs +0 -500
|
@@ -1,500 +0,0 @@
|
|
|
1
|
-
// Node built-in test runner suite for the shared mass-parameter record schemas.
|
|
2
|
-
// Run: node --test test/
|
|
3
|
-
// Imports the BUILT package (dist) — run `pnpm build` first.
|
|
4
|
-
import test from "node:test"
|
|
5
|
-
import assert from "node:assert/strict"
|
|
6
|
-
import {
|
|
7
|
-
MassParametersRecord,
|
|
8
|
-
MassParametersPatch,
|
|
9
|
-
OperationRecord,
|
|
10
|
-
ContourRecord,
|
|
11
|
-
ProfileRecord,
|
|
12
|
-
CurveRecord,
|
|
13
|
-
MAX_PROFILE_CURVES,
|
|
14
|
-
MAX_CONTOUR_HOLES,
|
|
15
|
-
MAX_SPLINE_THROUGH_POINTS,
|
|
16
|
-
MAX_SWEEP_PATH_POINTS,
|
|
17
|
-
MAX_LOFT_SECTIONS,
|
|
18
|
-
MAX_OPERATIONS,
|
|
19
|
-
MAX_OPERATION_EDGES,
|
|
20
|
-
MAX_OPERATION_FACES,
|
|
21
|
-
CurveHandle,
|
|
22
|
-
SplineHandle,
|
|
23
|
-
PluginGeomCreateBrepFromSweepArgs,
|
|
24
|
-
PluginGeomCreateBrepFromLoftArgs,
|
|
25
|
-
PluginGeomCreateBrepFromFilletArgs,
|
|
26
|
-
PluginGeomCreateBrepFromChamferArgs,
|
|
27
|
-
PluginGeomCreateBrepFromShellArgs,
|
|
28
|
-
PluginGeomCreateSplineFromControlPointsArgs,
|
|
29
|
-
PluginDesignUpdateParametersArgs,
|
|
30
|
-
} from "../dist/index.js"
|
|
31
|
-
|
|
32
|
-
const p = (x, y, z) => ({ x, y, z })
|
|
33
|
-
|
|
34
|
-
/** A square outer loop, no holes — the simplest legal contour record. */
|
|
35
|
-
const squareContour = {
|
|
36
|
-
outer: {
|
|
37
|
-
curves: [
|
|
38
|
-
{ type: "line", start: p(0, 0, 0), end: p(4, 0, 0) },
|
|
39
|
-
{ type: "line", start: p(4, 0, 0), end: p(4, 0, 4) },
|
|
40
|
-
{ type: "line", start: p(4, 0, 4), end: p(0, 0, 4) },
|
|
41
|
-
{ type: "line", start: p(0, 0, 4), end: p(0, 0, 0) },
|
|
42
|
-
],
|
|
43
|
-
},
|
|
44
|
-
holes: [],
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
test("accepts one record of every kind", () => {
|
|
48
|
-
const records = [
|
|
49
|
-
{ kind: "sphere", version: 1, values: { centre: p(0, 5, 0), radius: 5 } },
|
|
50
|
-
{
|
|
51
|
-
kind: "cylinder",
|
|
52
|
-
version: 1,
|
|
53
|
-
values: { base: p(0, 0, 0), axis: p(0, 1, 0), radius: 1, height: 4 },
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
kind: "cone",
|
|
57
|
-
version: 1,
|
|
58
|
-
values: { base: p(0, 0, 0), axis: p(0, 1, 0), baseRadius: 2, topRadius: 0, height: 3 },
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
kind: "torus",
|
|
62
|
-
version: 1,
|
|
63
|
-
values: { centre: p(0, 6, 0), axis: p(0, 1, 0), majorRadius: 5, minorRadius: 1 },
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
kind: "extrusion",
|
|
67
|
-
version: 1,
|
|
68
|
-
values: { contour: squareContour, direction: p(0, 1, 0), amount: 3 },
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
kind: "revolution",
|
|
72
|
-
version: 1,
|
|
73
|
-
values: {
|
|
74
|
-
contour: squareContour,
|
|
75
|
-
axisOrigin: p(0, 0, 0),
|
|
76
|
-
axisDirection: p(0, 1, 0),
|
|
77
|
-
angleInDegrees: 360,
|
|
78
|
-
},
|
|
79
|
-
},
|
|
80
|
-
{
|
|
81
|
-
kind: "loft",
|
|
82
|
-
version: 1,
|
|
83
|
-
values: { sections: [squareContour, squareContour], options: { compatibility: "auto" } },
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
kind: "sweep",
|
|
87
|
-
version: 1,
|
|
88
|
-
values: {
|
|
89
|
-
profile: squareContour,
|
|
90
|
-
path: [p(0, 0, 0), p(0, 3, 0)],
|
|
91
|
-
options: { transition: "round" },
|
|
92
|
-
},
|
|
93
|
-
},
|
|
94
|
-
]
|
|
95
|
-
for (const record of records) {
|
|
96
|
-
const parsed = MassParametersRecord.safeParse(record)
|
|
97
|
-
assert.equal(parsed.success, true, `${record.kind}: ${JSON.stringify(parsed.error?.issues)}`)
|
|
98
|
-
assert.deepEqual(parsed.data, record)
|
|
99
|
-
}
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
test("both spline curve forms round-trip inside a contour", () => {
|
|
103
|
-
const contour = {
|
|
104
|
-
outer: {
|
|
105
|
-
curves: [
|
|
106
|
-
{ type: "splineThrough", fitVersion: 1, throughPoints: [p(0, 0, 0), p(1, 0, 2), p(3, 0, 1)] },
|
|
107
|
-
{
|
|
108
|
-
type: "splinePoles",
|
|
109
|
-
degree: 3,
|
|
110
|
-
knots: [0, 1],
|
|
111
|
-
mults: [4, 4],
|
|
112
|
-
poles: [p(3, 0, 1), p(2, 0, 0), p(1, 0, -1), p(0, 0, 0)],
|
|
113
|
-
},
|
|
114
|
-
],
|
|
115
|
-
},
|
|
116
|
-
holes: [],
|
|
117
|
-
}
|
|
118
|
-
assert.equal(ContourRecord.safeParse(contour).success, true)
|
|
119
|
-
})
|
|
120
|
-
|
|
121
|
-
test("rejects a record that is not the truth", () => {
|
|
122
|
-
// Unknown key — the host names it back to the caller, never silently drops it.
|
|
123
|
-
assert.equal(
|
|
124
|
-
MassParametersRecord.safeParse({
|
|
125
|
-
kind: "torus",
|
|
126
|
-
version: 1,
|
|
127
|
-
values: { centre: p(0, 0, 0), axis: p(0, 1, 0), majorRadius: 5, minorRadius: 1, radius: 5 },
|
|
128
|
-
}).success,
|
|
129
|
-
false,
|
|
130
|
-
)
|
|
131
|
-
// Unknown schema version — restore degrades the mass to non-parametric.
|
|
132
|
-
assert.equal(
|
|
133
|
-
MassParametersRecord.safeParse({
|
|
134
|
-
kind: "sphere",
|
|
135
|
-
version: 2,
|
|
136
|
-
values: { centre: p(0, 0, 0), radius: 5 },
|
|
137
|
-
}).success,
|
|
138
|
-
false,
|
|
139
|
-
)
|
|
140
|
-
// Non-finite / non-positive values.
|
|
141
|
-
for (const radius of [0, -1, Number.NaN, Number.POSITIVE_INFINITY]) {
|
|
142
|
-
assert.equal(
|
|
143
|
-
MassParametersRecord.safeParse({
|
|
144
|
-
kind: "sphere",
|
|
145
|
-
version: 1,
|
|
146
|
-
values: { centre: p(0, 0, 0), radius },
|
|
147
|
-
}).success,
|
|
148
|
-
false,
|
|
149
|
-
`radius ${radius} must be rejected`,
|
|
150
|
-
)
|
|
151
|
-
}
|
|
152
|
-
// A spline record with neither form's fields.
|
|
153
|
-
assert.equal(CurveRecord.safeParse({ type: "splineThrough", throughPoints: [] }).success, false)
|
|
154
|
-
})
|
|
155
|
-
|
|
156
|
-
test("array caps are contract, not truncation", () => {
|
|
157
|
-
const points = (n) => Array.from({ length: n }, (_, i) => p(i, 0, 0))
|
|
158
|
-
const spline = (n) => ({ type: "splineThrough", fitVersion: 1, throughPoints: points(n) })
|
|
159
|
-
assert.equal(CurveRecord.safeParse(spline(MAX_SPLINE_THROUGH_POINTS)).success, true)
|
|
160
|
-
assert.equal(CurveRecord.safeParse(spline(MAX_SPLINE_THROUGH_POINTS + 1)).success, false)
|
|
161
|
-
|
|
162
|
-
const sweep = (n) => ({
|
|
163
|
-
kind: "sweep",
|
|
164
|
-
version: 1,
|
|
165
|
-
values: { profile: squareContour, path: points(n) },
|
|
166
|
-
})
|
|
167
|
-
assert.equal(MassParametersRecord.safeParse(sweep(MAX_SWEEP_PATH_POINTS)).success, true)
|
|
168
|
-
assert.equal(MassParametersRecord.safeParse(sweep(MAX_SWEEP_PATH_POINTS + 1)).success, false)
|
|
169
|
-
|
|
170
|
-
const loft = (n) => ({
|
|
171
|
-
kind: "loft",
|
|
172
|
-
version: 1,
|
|
173
|
-
values: { sections: Array.from({ length: n }, () => squareContour) },
|
|
174
|
-
})
|
|
175
|
-
assert.equal(MassParametersRecord.safeParse(loft(MAX_LOFT_SECTIONS)).success, true)
|
|
176
|
-
assert.equal(MassParametersRecord.safeParse(loft(MAX_LOFT_SECTIONS + 1)).success, false)
|
|
177
|
-
|
|
178
|
-
// The caps bind the RECORD only. The creator args (shipped in 0.9.6 without
|
|
179
|
-
// any cap) keep accepting an over-cap call — the host builds it and mints no
|
|
180
|
-
// record (the recipe-less fallback). See "wire schemas keep 0.9.6 acceptance".
|
|
181
|
-
const sweepArgs = (n) => ({ profile: "contour_1", path: points(n) })
|
|
182
|
-
assert.equal(
|
|
183
|
-
PluginGeomCreateBrepFromSweepArgs.safeParse(sweepArgs(MAX_SWEEP_PATH_POINTS)).success,
|
|
184
|
-
true,
|
|
185
|
-
)
|
|
186
|
-
const loftArgs = (n) => ({
|
|
187
|
-
bottomContour: "contour_1",
|
|
188
|
-
topContour: "contour_2",
|
|
189
|
-
intermediateContours: Array.from({ length: n }, (_, i) => `contour_${i + 3}`),
|
|
190
|
-
})
|
|
191
|
-
assert.equal(
|
|
192
|
-
PluginGeomCreateBrepFromLoftArgs.safeParse(loftArgs(MAX_LOFT_SECTIONS - 2)).success,
|
|
193
|
-
true,
|
|
194
|
-
)
|
|
195
|
-
|
|
196
|
-
// A contour's own arrays are bounded too — a 20 000-curve loop or a
|
|
197
|
-
// 5 000-hole contour must not ride the save/collab payload as a record.
|
|
198
|
-
const loop = (n) => ({
|
|
199
|
-
curves: Array.from({ length: n }, (_, i) => ({
|
|
200
|
-
type: "line",
|
|
201
|
-
start: p(i, 0, 0),
|
|
202
|
-
end: p(i + 1, 0, 0),
|
|
203
|
-
})),
|
|
204
|
-
})
|
|
205
|
-
assert.equal(ProfileRecord.safeParse(loop(MAX_PROFILE_CURVES)).success, true)
|
|
206
|
-
assert.equal(ProfileRecord.safeParse(loop(MAX_PROFILE_CURVES + 1)).success, false)
|
|
207
|
-
|
|
208
|
-
const holed = (n) => ({
|
|
209
|
-
outer: squareContour.outer,
|
|
210
|
-
holes: Array.from({ length: n }, () => squareContour.outer),
|
|
211
|
-
})
|
|
212
|
-
assert.equal(ContourRecord.safeParse(holed(MAX_CONTOUR_HOLES)).success, true)
|
|
213
|
-
assert.equal(ContourRecord.safeParse(holed(MAX_CONTOUR_HOLES + 1)).success, false)
|
|
214
|
-
})
|
|
215
|
-
|
|
216
|
-
/** sphere → shell → fillet: the chained record of binding-design-4b §5.2. */
|
|
217
|
-
const chainedRecord = {
|
|
218
|
-
kind: "sphere",
|
|
219
|
-
version: 1,
|
|
220
|
-
values: { centre: p(0, 23.6, 0), radius: 23.622 },
|
|
221
|
-
operations: [
|
|
222
|
-
{
|
|
223
|
-
op: "shell",
|
|
224
|
-
openFaces: [{ index: 3, kind: "plane", edgeCount: 2 }],
|
|
225
|
-
thickness: 1.181,
|
|
226
|
-
input: { faceCount: 4, edgeCount: 6 },
|
|
227
|
-
},
|
|
228
|
-
{
|
|
229
|
-
op: "fillet",
|
|
230
|
-
edges: [
|
|
231
|
-
{ index: 2, faces: [0, 3], curve: "arc" },
|
|
232
|
-
{ index: 5, faces: [1, 3], curve: "arc" },
|
|
233
|
-
],
|
|
234
|
-
radius: 0.787,
|
|
235
|
-
input: { faceCount: 8, edgeCount: 14 },
|
|
236
|
-
},
|
|
237
|
-
],
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
test("a chained operations record round-trips", () => {
|
|
241
|
-
const parsed = MassParametersRecord.safeParse(chainedRecord)
|
|
242
|
-
assert.equal(parsed.success, true, JSON.stringify(parsed.error?.issues))
|
|
243
|
-
assert.deepEqual(parsed.data, chainedRecord)
|
|
244
|
-
|
|
245
|
-
// `operations` is additive and optional — a 4A-era record still parses.
|
|
246
|
-
const { operations, ...base } = chainedRecord
|
|
247
|
-
assert.equal(MassParametersRecord.safeParse(base).success, true)
|
|
248
|
-
|
|
249
|
-
// Every op kind on its own.
|
|
250
|
-
const chamfer = {
|
|
251
|
-
op: "chamfer",
|
|
252
|
-
edges: [{ index: 0, faces: [0, 1], curve: "line" }],
|
|
253
|
-
distance: 0.1,
|
|
254
|
-
input: { faceCount: 6, edgeCount: 12 },
|
|
255
|
-
}
|
|
256
|
-
const offset = { op: "offset", distance: -0.4, input: { faceCount: 4, edgeCount: 6 } }
|
|
257
|
-
for (const op of [operations[0], operations[1], chamfer, offset]) {
|
|
258
|
-
assert.deepEqual(OperationRecord.parse(op), op)
|
|
259
|
-
}
|
|
260
|
-
})
|
|
261
|
-
|
|
262
|
-
test("operation records reject what cannot be replayed", () => {
|
|
263
|
-
const counts = { faceCount: 4, edgeCount: 6 }
|
|
264
|
-
// A selector list is never empty — an op that selected nothing is not a record.
|
|
265
|
-
assert.equal(OperationRecord.safeParse({ op: "fillet", edges: [], radius: 1, input: counts }).success, false)
|
|
266
|
-
// Fractional / negative indices cannot address a face or an edge.
|
|
267
|
-
assert.equal(
|
|
268
|
-
OperationRecord.safeParse({
|
|
269
|
-
op: "fillet",
|
|
270
|
-
edges: [{ index: 1.5, faces: [0, 1], curve: "arc" }],
|
|
271
|
-
radius: 1,
|
|
272
|
-
input: counts,
|
|
273
|
-
}).success,
|
|
274
|
-
false,
|
|
275
|
-
)
|
|
276
|
-
assert.equal(
|
|
277
|
-
OperationRecord.safeParse({
|
|
278
|
-
op: "fillet",
|
|
279
|
-
edges: [{ index: 0, faces: [0, -1], curve: "arc" }],
|
|
280
|
-
radius: 1,
|
|
281
|
-
input: counts,
|
|
282
|
-
}).success,
|
|
283
|
-
false,
|
|
284
|
-
)
|
|
285
|
-
// A face pair is exactly two faces.
|
|
286
|
-
assert.equal(
|
|
287
|
-
OperationRecord.safeParse({
|
|
288
|
-
op: "fillet",
|
|
289
|
-
edges: [{ index: 0, faces: [0, 1, 2], curve: "arc" }],
|
|
290
|
-
radius: 1,
|
|
291
|
-
input: counts,
|
|
292
|
-
}).success,
|
|
293
|
-
false,
|
|
294
|
-
)
|
|
295
|
-
// Unknown surface / curve kinds, unknown op, and a zero offset.
|
|
296
|
-
assert.equal(
|
|
297
|
-
OperationRecord.safeParse({
|
|
298
|
-
op: "shell",
|
|
299
|
-
openFaces: [{ index: 0, kind: "nurbs", edgeCount: 2 }],
|
|
300
|
-
thickness: 1,
|
|
301
|
-
input: counts,
|
|
302
|
-
}).success,
|
|
303
|
-
false,
|
|
304
|
-
)
|
|
305
|
-
assert.equal(OperationRecord.safeParse({ op: "draft", angle: 3, input: counts }).success, false)
|
|
306
|
-
assert.equal(OperationRecord.safeParse({ op: "offset", distance: 0, input: counts }).success, false)
|
|
307
|
-
// Non-positive scalars.
|
|
308
|
-
for (const radius of [0, -1, Number.NaN]) {
|
|
309
|
-
assert.equal(
|
|
310
|
-
OperationRecord.safeParse({
|
|
311
|
-
op: "fillet",
|
|
312
|
-
edges: [{ index: 0, faces: [0, 1], curve: "arc" }],
|
|
313
|
-
radius,
|
|
314
|
-
input: counts,
|
|
315
|
-
}).success,
|
|
316
|
-
false,
|
|
317
|
-
`radius ${radius} must be rejected`,
|
|
318
|
-
)
|
|
319
|
-
}
|
|
320
|
-
})
|
|
321
|
-
|
|
322
|
-
test("operation caps are contract, not truncation", () => {
|
|
323
|
-
const counts = { faceCount: 4, edgeCount: 6 }
|
|
324
|
-
const fillet = (n) => ({
|
|
325
|
-
op: "fillet",
|
|
326
|
-
edges: Array.from({ length: n }, (_, i) => ({ index: i, faces: [0, 1], curve: "line" })),
|
|
327
|
-
radius: 1,
|
|
328
|
-
input: counts,
|
|
329
|
-
})
|
|
330
|
-
assert.equal(OperationRecord.safeParse(fillet(MAX_OPERATION_EDGES)).success, true)
|
|
331
|
-
assert.equal(OperationRecord.safeParse(fillet(MAX_OPERATION_EDGES + 1)).success, false)
|
|
332
|
-
|
|
333
|
-
const shell = (n) => ({
|
|
334
|
-
op: "shell",
|
|
335
|
-
openFaces: Array.from({ length: n }, (_, i) => ({ index: i, kind: "plane", edgeCount: 4 })),
|
|
336
|
-
thickness: 1,
|
|
337
|
-
input: counts,
|
|
338
|
-
})
|
|
339
|
-
assert.equal(OperationRecord.safeParse(shell(MAX_OPERATION_FACES)).success, true)
|
|
340
|
-
assert.equal(OperationRecord.safeParse(shell(MAX_OPERATION_FACES + 1)).success, false)
|
|
341
|
-
|
|
342
|
-
const chain = (n) => ({ ...chainedRecord, operations: Array.from({ length: n }, () => fillet(1)) })
|
|
343
|
-
assert.equal(MassParametersRecord.safeParse(chain(MAX_OPERATIONS)).success, true)
|
|
344
|
-
assert.equal(MassParametersRecord.safeParse(chain(MAX_OPERATIONS + 1)).success, false)
|
|
345
|
-
|
|
346
|
-
// The creator args are NOT capped (see "wire schemas keep 0.9.6 acceptance").
|
|
347
|
-
const edges = (n) => Array.from({ length: n }, (_, i) => `edge_${i}`)
|
|
348
|
-
const faces = (n) => Array.from({ length: n }, (_, i) => `face_${i}`)
|
|
349
|
-
const filletArgs = (n) => ({ brep: "brep_1", edges: edges(n), radius: 1 })
|
|
350
|
-
assert.equal(PluginGeomCreateBrepFromFilletArgs.safeParse(filletArgs(MAX_OPERATION_EDGES)).success, true)
|
|
351
|
-
const shellArgs = (n) => ({ brep: "brep_1", openFaces: faces(n), thickness: 1 })
|
|
352
|
-
assert.equal(PluginGeomCreateBrepFromShellArgs.safeParse(shellArgs(MAX_OPERATION_FACES)).success, true)
|
|
353
|
-
})
|
|
354
|
-
|
|
355
|
-
test("wire schemas keep 0.9.6 acceptance: record caps never reach the call", () => {
|
|
356
|
-
// The five inputs the external review (2026-09-06, #5) ran against the
|
|
357
|
-
// published 0.9.6 schemas: all accepted there, so all must stay accepted
|
|
358
|
-
// (feedback-no-breaking-api-changes — a shipped member never narrows). The
|
|
359
|
-
// record caps above still hold; the host answers an over-cap call with a
|
|
360
|
-
// solid and no record, never with VALIDATION.
|
|
361
|
-
const pts = (n) => Array.from({ length: n }, (_, i) => p(i, 0, 0))
|
|
362
|
-
const ids = (prefix, n) => Array.from({ length: n }, (_, i) => `${prefix}_${i}`)
|
|
363
|
-
const accepted = [
|
|
364
|
-
[
|
|
365
|
-
"loft with 31 intermediates",
|
|
366
|
-
PluginGeomCreateBrepFromLoftArgs.safeParse({
|
|
367
|
-
bottomContour: "contour_1",
|
|
368
|
-
topContour: "contour_2",
|
|
369
|
-
intermediateContours: ids("contour", MAX_LOFT_SECTIONS - 1),
|
|
370
|
-
}),
|
|
371
|
-
],
|
|
372
|
-
[
|
|
373
|
-
"sweep with 513 path points",
|
|
374
|
-
PluginGeomCreateBrepFromSweepArgs.safeParse({
|
|
375
|
-
profile: "contour_1",
|
|
376
|
-
path: pts(MAX_SWEEP_PATH_POINTS + 1),
|
|
377
|
-
}),
|
|
378
|
-
],
|
|
379
|
-
[
|
|
380
|
-
"fillet with 65 edges",
|
|
381
|
-
PluginGeomCreateBrepFromFilletArgs.safeParse({
|
|
382
|
-
brep: "brep_1",
|
|
383
|
-
edges: ids("edge", MAX_OPERATION_EDGES + 1),
|
|
384
|
-
radius: 1,
|
|
385
|
-
}),
|
|
386
|
-
],
|
|
387
|
-
[
|
|
388
|
-
"chamfer with 65 edges",
|
|
389
|
-
PluginGeomCreateBrepFromChamferArgs.safeParse({
|
|
390
|
-
brep: "brep_1",
|
|
391
|
-
edges: ids("edge", MAX_OPERATION_EDGES + 1),
|
|
392
|
-
distance: 1,
|
|
393
|
-
}),
|
|
394
|
-
],
|
|
395
|
-
[
|
|
396
|
-
"shell with 33 open faces",
|
|
397
|
-
PluginGeomCreateBrepFromShellArgs.safeParse({
|
|
398
|
-
brep: "brep_1",
|
|
399
|
-
openFaces: ids("face", MAX_OPERATION_FACES + 1),
|
|
400
|
-
thickness: 1,
|
|
401
|
-
}),
|
|
402
|
-
],
|
|
403
|
-
]
|
|
404
|
-
for (const [what, result] of accepted) {
|
|
405
|
-
assert.equal(result.success, true, `${what} must stay schema-valid: ${JSON.stringify(result.error?.issues)}`)
|
|
406
|
-
}
|
|
407
|
-
})
|
|
408
|
-
|
|
409
|
-
test("MassParametersPatch takes both a partial and a full record", () => {
|
|
410
|
-
// Partial forms: a scalar, a point, a whole array, and the empty op list.
|
|
411
|
-
for (const patch of [
|
|
412
|
-
{ radius: 31.496 },
|
|
413
|
-
{ centre: p(1, 2, 3) },
|
|
414
|
-
{ path: [p(0, 0, 0), p(0, 3, 0)] },
|
|
415
|
-
{ sections: [squareContour, squareContour] },
|
|
416
|
-
{ contour: squareContour },
|
|
417
|
-
{ options: { seamAlignment: "authored" } },
|
|
418
|
-
{ options: { startScale: 1, endScale: 2, transition: { bevel: 0.2 } } },
|
|
419
|
-
{ operations: chainedRecord.operations },
|
|
420
|
-
{ operations: [] },
|
|
421
|
-
]) {
|
|
422
|
-
const parsed = MassParametersPatch.safeParse(patch)
|
|
423
|
-
assert.equal(parsed.success, true, `${JSON.stringify(patch)}: ${JSON.stringify(parsed.error?.issues)}`)
|
|
424
|
-
assert.deepEqual(parsed.data, patch)
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
// Full-record form: read → edit → send the whole thing back.
|
|
428
|
-
const echoed = MassParametersPatch.safeParse(chainedRecord)
|
|
429
|
-
assert.equal(echoed.success, true)
|
|
430
|
-
assert.deepEqual(echoed.data, chainedRecord)
|
|
431
|
-
|
|
432
|
-
// …including the record edited to drop every operation. `{ operations: [] }`
|
|
433
|
-
// is the documented drop idiom, so the whole-record route must take it too
|
|
434
|
-
// (the host normalises the empty list to an absent key before storing).
|
|
435
|
-
const dropped = { ...chainedRecord, operations: [] }
|
|
436
|
-
const droppedParse = MassParametersPatch.safeParse(dropped)
|
|
437
|
-
assert.equal(droppedParse.success, true, JSON.stringify(droppedParse.error?.issues))
|
|
438
|
-
assert.deepEqual(droppedParse.data, dropped)
|
|
439
|
-
assert.equal(MassParametersRecord.safeParse(dropped).success, true)
|
|
440
|
-
|
|
441
|
-
// Values outside the closed union are refused at the wire, not written on.
|
|
442
|
-
for (const patch of [
|
|
443
|
-
{ radius: "8" },
|
|
444
|
-
{ radius: Number.POSITIVE_INFINITY },
|
|
445
|
-
{ centre: { x: 0, y: 0 } },
|
|
446
|
-
{ operations: [{ op: "fillet", edges: [], radius: 1, input: { faceCount: 4, edgeCount: 6 } }] },
|
|
447
|
-
{ nested: { deeply: { wrong: true } } },
|
|
448
|
-
]) {
|
|
449
|
-
assert.equal(MassParametersPatch.safeParse(patch).success, false, JSON.stringify(patch))
|
|
450
|
-
}
|
|
451
|
-
})
|
|
452
|
-
|
|
453
|
-
test("the 4B call args parse", () => {
|
|
454
|
-
assert.equal(
|
|
455
|
-
PluginDesignUpdateParametersArgs.safeParse({ component: "component_1", patch: { radius: 8 } })
|
|
456
|
-
.success,
|
|
457
|
-
true,
|
|
458
|
-
)
|
|
459
|
-
assert.equal(
|
|
460
|
-
PluginDesignUpdateParametersArgs.safeParse({ component: "component_1", patch: { radius: null } })
|
|
461
|
-
.success,
|
|
462
|
-
false,
|
|
463
|
-
)
|
|
464
|
-
|
|
465
|
-
const poles = (n) => Array.from({ length: n }, (_, i) => `vec3_${i}`)
|
|
466
|
-
assert.equal(
|
|
467
|
-
PluginGeomCreateSplineFromControlPointsArgs.safeParse({ controlPoints: poles(4) }).success,
|
|
468
|
-
true,
|
|
469
|
-
)
|
|
470
|
-
assert.equal(
|
|
471
|
-
PluginGeomCreateSplineFromControlPointsArgs.safeParse({
|
|
472
|
-
controlPoints: poles(4),
|
|
473
|
-
options: { degree: 2 },
|
|
474
|
-
}).success,
|
|
475
|
-
true,
|
|
476
|
-
)
|
|
477
|
-
// degree is 2 or 3 — 1 is a polyline, 4+ is not offered.
|
|
478
|
-
for (const degree of [1, 4, 2.5]) {
|
|
479
|
-
assert.equal(
|
|
480
|
-
PluginGeomCreateSplineFromControlPointsArgs.safeParse({
|
|
481
|
-
controlPoints: poles(4),
|
|
482
|
-
options: { degree },
|
|
483
|
-
}).success,
|
|
484
|
-
false,
|
|
485
|
-
`degree ${degree} must be rejected`,
|
|
486
|
-
)
|
|
487
|
-
}
|
|
488
|
-
assert.equal(
|
|
489
|
-
PluginGeomCreateSplineFromControlPointsArgs.safeParse({ controlPoints: poles(2) }).success,
|
|
490
|
-
false,
|
|
491
|
-
)
|
|
492
|
-
})
|
|
493
|
-
|
|
494
|
-
test("CurveHandle accepts a spline handle", () => {
|
|
495
|
-
assert.equal(SplineHandle.parse("spline_1").id, "spline_1")
|
|
496
|
-
for (const id of ["line_1", "arc_1", "spline_1"]) {
|
|
497
|
-
assert.equal(CurveHandle.parse(id).id, id)
|
|
498
|
-
}
|
|
499
|
-
assert.equal(CurveHandle.safeParse("circle_1").success, false)
|
|
500
|
-
})
|