onshape 0.1.0 → 0.1.2

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.
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FeatureStudioManager = void 0;
4
+ exports.loadText = loadText;
5
+ const node_fs_1 = require("node:fs");
6
+ class FeatureStudioManager {
7
+ client;
8
+ constructor(client) {
9
+ this.client = client;
10
+ }
11
+ async create(documentId, workspaceId, name) {
12
+ return this.client.post(`/api/v6/featurestudios/d/${documentId}/w/${workspaceId}`, { name });
13
+ }
14
+ async getContents(documentId, workspaceId, elementId) {
15
+ return this.client.get(`/api/v6/featurestudios/d/${documentId}/w/${workspaceId}/e/${elementId}`);
16
+ }
17
+ async setContents(documentId, workspaceId, elementId, contents) {
18
+ return this.client.post(`/api/v6/featurestudios/d/${documentId}/w/${workspaceId}/e/${elementId}`, { contents });
19
+ }
20
+ async getSpecs(documentId, workspaceId, elementId) {
21
+ return this.client.get(`/api/v6/featurestudios/d/${documentId}/w/${workspaceId}/e/${elementId}/featurespecs`);
22
+ }
23
+ }
24
+ exports.FeatureStudioManager = FeatureStudioManager;
25
+ function loadText(inline, file) {
26
+ const raw = file ? (0, node_fs_1.readFileSync)(file, "utf8") : inline;
27
+ if (!raw)
28
+ throw new Error("Expected text via --contents or --contents-file");
29
+ return raw;
30
+ }
@@ -47,6 +47,34 @@ class PartStudioManager {
47
47
  rollbackIndex: index,
48
48
  });
49
49
  }
50
+ async validateFeature(documentId, workspaceId, elementId, featureId) {
51
+ const features = await this.getFeatures(documentId, workspaceId, elementId);
52
+ const states = isRecord(features) && isRecord(features.featureStates) ? features.featureStates : {};
53
+ const state = isRecord(states[featureId]) ? states[featureId] : {};
54
+ const status = typeof state.featureStatus === "string" ? state.featureStatus : null;
55
+ if (status === "ERROR") {
56
+ throw new Error(`Feature ${featureId} regenerated with status ERROR.`);
57
+ }
58
+ return { featureId, featureStatus: status };
59
+ }
60
+ async validatePartStudio(documentId, workspaceId, elementId, expectations = {}) {
61
+ const partsRaw = await this.getParts(documentId, workspaceId, elementId);
62
+ const parts = Array.isArray(partsRaw) ? partsRaw : [];
63
+ const massRaw = await this.client.get(`/api/v6/partstudios/d/${documentId}/w/${workspaceId}/e/${elementId}/massproperties`);
64
+ const bodiesRecord = isRecord(massRaw) && isRecord(massRaw.bodies) ? massRaw.bodies : {};
65
+ const bodyCount = Object.keys(bodiesRecord).length;
66
+ if (expectations.parts !== undefined && parts.length !== expectations.parts) {
67
+ throw new Error(`Expected ${expectations.parts} part(s), found ${parts.length}.`);
68
+ }
69
+ if (expectations.bodies !== undefined && bodyCount !== expectations.bodies) {
70
+ throw new Error(`Expected ${expectations.bodies} bod(y/ies), found ${bodyCount}.`);
71
+ }
72
+ return {
73
+ parts: parts.length,
74
+ bodies: bodyCount,
75
+ partIds: parts.map((part) => (isRecord(part) ? part.partId : undefined)).filter(Boolean),
76
+ };
77
+ }
50
78
  }
51
79
  exports.PartStudioManager = PartStudioManager;
52
80
  function loadJson(inline, file) {
@@ -55,3 +83,6 @@ function loadJson(inline, file) {
55
83
  throw new Error("Expected JSON via --json or --json-file");
56
84
  return JSON.parse(raw);
57
85
  }
86
+ function isRecord(value) {
87
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
88
+ }
@@ -0,0 +1,338 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.planeId = planeId;
4
+ exports.parsePoint2 = parsePoint2;
5
+ exports.buildCircleSketch = buildCircleSketch;
6
+ exports.buildCircleAxisSketch = buildCircleAxisSketch;
7
+ exports.buildCandyCanePathSketch = buildCandyCanePathSketch;
8
+ exports.buildExtrude = buildExtrude;
9
+ exports.buildRevolve = buildRevolve;
10
+ exports.buildOffsetPlane = buildOffsetPlane;
11
+ exports.buildSweep = buildSweep;
12
+ exports.buildBooleanUnion = buildBooleanUnion;
13
+ const INCH_TO_METER = 0.0254;
14
+ const PLANE_IDS = {
15
+ front: "JCC",
16
+ top: "JDC",
17
+ right: "JEC",
18
+ };
19
+ function planeId(name) {
20
+ const value = PLANE_IDS[name.toLowerCase()];
21
+ if (!value)
22
+ throw new Error(`Unknown plane '${name}'. Use Front, Top, or Right.`);
23
+ return value;
24
+ }
25
+ function parsePoint2(value) {
26
+ const parts = value.split(",").map((part) => Number(part.trim()));
27
+ if (parts.length !== 2 || parts.some((part) => !Number.isFinite(part))) {
28
+ throw new Error(`Expected a 2D point as x,y; got '${value}'.`);
29
+ }
30
+ return [parts[0], parts[1]];
31
+ }
32
+ function toMeters(value) {
33
+ return value * INCH_TO_METER;
34
+ }
35
+ function circleEntity(id, center, radius) {
36
+ return {
37
+ btType: "BTMSketchCurve-4",
38
+ entityId: id,
39
+ centerId: `${id}.center`,
40
+ geometry: {
41
+ btType: "BTCurveGeometryCircle-115",
42
+ radius: toMeters(radius),
43
+ xCenter: toMeters(center[0]),
44
+ yCenter: toMeters(center[1]),
45
+ xDir: 1,
46
+ yDir: 0,
47
+ clockwise: false,
48
+ },
49
+ isConstruction: false,
50
+ };
51
+ }
52
+ function lineEntity(id, start, end, isConstruction = false) {
53
+ const x1 = toMeters(start[0]);
54
+ const y1 = toMeters(start[1]);
55
+ const x2 = toMeters(end[0]);
56
+ const y2 = toMeters(end[1]);
57
+ const dx = x2 - x1;
58
+ const dy = y2 - y1;
59
+ const length = Math.hypot(dx, dy);
60
+ if (length === 0)
61
+ throw new Error("Line start and end must be different.");
62
+ return {
63
+ btType: "BTMSketchCurveSegment-155",
64
+ entityId: id,
65
+ startPointId: `${id}.start`,
66
+ endPointId: `${id}.end`,
67
+ startParam: 0,
68
+ endParam: length,
69
+ geometry: {
70
+ btType: "BTCurveGeometryLine-117",
71
+ pntX: x1,
72
+ pntY: y1,
73
+ dirX: dx / length,
74
+ dirY: dy / length,
75
+ },
76
+ isConstruction,
77
+ };
78
+ }
79
+ function arcEntity(id, center, radius, startAngle, endAngle) {
80
+ if (radius <= 0)
81
+ throw new Error("Arc radius must be positive.");
82
+ return {
83
+ btType: "BTMSketchCurveSegment-155",
84
+ entityId: id,
85
+ startPointId: `${id}.start`,
86
+ endPointId: `${id}.end`,
87
+ centerId: `${id}.center`,
88
+ startParam: (startAngle * Math.PI) / 180,
89
+ endParam: (endAngle * Math.PI) / 180,
90
+ geometry: {
91
+ btType: "BTCurveGeometryCircle-115",
92
+ radius: toMeters(radius),
93
+ xCenter: toMeters(center[0]),
94
+ yCenter: toMeters(center[1]),
95
+ xDir: 1,
96
+ yDir: 0,
97
+ clockwise: false,
98
+ },
99
+ isConstruction: false,
100
+ };
101
+ }
102
+ function sketchPlaneParameter(sketchPlaneId, featureId) {
103
+ if (featureId) {
104
+ return pQuery("sketchPlane", `query = qCreatedBy(makeId("${featureId}"), EntityType.FACE);`, featureId);
105
+ }
106
+ return {
107
+ btType: "BTMParameterQueryList-148",
108
+ queries: [{ btType: "BTMIndividualQuery-138", deterministicIds: [sketchPlaneId] }],
109
+ parameterId: "sketchPlane",
110
+ };
111
+ }
112
+ function sketch(name, sketchPlaneId, entities, featureId) {
113
+ return {
114
+ feature: {
115
+ btType: "BTMSketch-151",
116
+ featureType: "newSketch",
117
+ name,
118
+ suppressed: false,
119
+ parameters: [sketchPlaneParameter(sketchPlaneId, featureId)],
120
+ entities,
121
+ constraints: [],
122
+ },
123
+ };
124
+ }
125
+ function pEnum(parameterId, enumName, value) {
126
+ return {
127
+ btType: "BTMParameterEnum-145",
128
+ namespace: "",
129
+ enumName,
130
+ value,
131
+ parameterId,
132
+ parameterName: "",
133
+ libraryRelationType: "NONE",
134
+ };
135
+ }
136
+ function pBool(parameterId, value) {
137
+ return {
138
+ btType: "BTMParameterBoolean-144",
139
+ value,
140
+ parameterId,
141
+ parameterName: "",
142
+ libraryRelationType: "NONE",
143
+ };
144
+ }
145
+ function pQuantity(parameterId, value, units) {
146
+ return {
147
+ btType: "BTMParameterQuantity-147",
148
+ isInteger: false,
149
+ value,
150
+ units: "",
151
+ expression: `${value} ${units}`,
152
+ parameterId,
153
+ parameterName: "",
154
+ libraryRelationType: "NONE",
155
+ };
156
+ }
157
+ function pQuery(parameterId, queryString, featureId) {
158
+ return {
159
+ btType: "BTMParameterQueryList-148",
160
+ queries: [
161
+ {
162
+ btType: "BTMIndividualQuery-138",
163
+ queryStatement: null,
164
+ queryString,
165
+ ...(featureId ? { featureId } : {}),
166
+ deterministicIds: [],
167
+ },
168
+ ],
169
+ parameterId,
170
+ parameterName: "",
171
+ libraryRelationType: "NONE",
172
+ };
173
+ }
174
+ function pDeterministicQuery(parameterId, deterministicIds) {
175
+ return {
176
+ btType: "BTMParameterQueryList-148",
177
+ queries: [
178
+ {
179
+ btType: "BTMIndividualQuery-138",
180
+ deterministicIds,
181
+ },
182
+ ],
183
+ parameterId,
184
+ parameterName: "",
185
+ libraryRelationType: "NONE",
186
+ };
187
+ }
188
+ function sketchRegion(parameterId, sketchFeatureId) {
189
+ return {
190
+ btType: "BTMParameterQueryList-148",
191
+ queries: [
192
+ {
193
+ btType: "BTMIndividualSketchRegionQuery-140",
194
+ queryStatement: null,
195
+ filterInnerLoops: true,
196
+ queryString: `query = qSketchRegion(id + "${sketchFeatureId}", true);`,
197
+ featureId: sketchFeatureId,
198
+ deterministicIds: [],
199
+ },
200
+ ],
201
+ parameterId,
202
+ parameterName: "",
203
+ libraryRelationType: "NONE",
204
+ };
205
+ }
206
+ function sketchEdges(parameterId, sketchFeatureId) {
207
+ return pQuery(parameterId, `query = qCreatedBy(makeId("${sketchFeatureId}"), EntityType.EDGE);`, sketchFeatureId);
208
+ }
209
+ function buildCircleSketch(input) {
210
+ return sketch(input.name, planeId(input.plane), [circleEntity("circle.1", input.center, input.radius)], input.planeFeatureId);
211
+ }
212
+ function buildCircleAxisSketch(input) {
213
+ return sketch(input.name, planeId(input.plane), [
214
+ circleEntity("profile.circle", input.center, input.radius),
215
+ lineEntity("axis.1", input.axisStart, input.axisEnd, true),
216
+ ]);
217
+ }
218
+ function buildCandyCanePathSketch(input) {
219
+ const top = input.bottom + input.straightHeight;
220
+ const center = [input.x - input.hookRadius, top];
221
+ const totalSegments = Math.max(2, Math.floor(input.segments));
222
+ const arcLength = input.hookRadius * Math.abs((input.hookAngle * Math.PI) / 180);
223
+ const straightShare = input.straightHeight / (input.straightHeight + arcLength);
224
+ const straightSegments = Math.max(1, Math.round(totalSegments * straightShare));
225
+ const arcSegments = Math.max(1, totalSegments - straightSegments);
226
+ const entities = [];
227
+ for (let index = 0; index < straightSegments; index += 1) {
228
+ const y1 = input.bottom + (input.straightHeight * index) / straightSegments;
229
+ const y2 = input.bottom + (input.straightHeight * (index + 1)) / straightSegments;
230
+ entities.push(lineEntity(`path.stem.${index + 1}`, [input.x, y1], [input.x, y2]));
231
+ }
232
+ for (let index = 0; index < arcSegments; index += 1) {
233
+ const start = (input.hookAngle * index) / arcSegments;
234
+ const end = (input.hookAngle * (index + 1)) / arcSegments;
235
+ entities.push(arcEntity(`path.hook.${index + 1}`, center, input.hookRadius, start, end));
236
+ }
237
+ return sketch(input.name, planeId(input.plane), entities);
238
+ }
239
+ function buildExtrude(input) {
240
+ return {
241
+ btType: "BTFeatureDefinitionCall-1406",
242
+ feature: {
243
+ btType: "BTMFeature-134",
244
+ featureType: "extrude",
245
+ name: input.name,
246
+ suppressed: false,
247
+ namespace: "",
248
+ parameters: [
249
+ sketchRegion("entities", input.sketchFeatureId),
250
+ pEnum("operationType", "NewBodyOperationType", input.operationType),
251
+ pQuantity("depth", input.depth, "in"),
252
+ pBool("oppositeDirection", false),
253
+ pBool("defaultScope", true),
254
+ ],
255
+ },
256
+ };
257
+ }
258
+ function buildRevolve(input) {
259
+ return {
260
+ btType: "BTFeatureDefinitionCall-1406",
261
+ feature: {
262
+ btType: "BTMFeature-134",
263
+ featureType: "revolve",
264
+ name: input.name,
265
+ suppressed: false,
266
+ namespace: "",
267
+ parameters: [
268
+ pEnum("bodyType", "ExtendedToolBodyType", "SOLID"),
269
+ pEnum("operationType", "NewBodyOperationType", input.operationType),
270
+ sketchRegion("entities", input.sketchFeatureId),
271
+ pQuery("axis", `query = qConstructionFilter(qCreatedBy(makeId("${input.sketchFeatureId}"), EntityType.EDGE), ConstructionObject.YES);`, input.sketchFeatureId),
272
+ pBool("fullRevolve", false),
273
+ pEnum("endBound", "RevolveBoundingType", "BLIND"),
274
+ pQuantity("angle", input.angle, "deg"),
275
+ pBool("defaultScope", true),
276
+ ],
277
+ },
278
+ };
279
+ }
280
+ function buildOffsetPlane(input) {
281
+ return {
282
+ btType: "BTFeatureDefinitionCall-1406",
283
+ feature: {
284
+ btType: "BTMFeature-134",
285
+ featureType: "cPlane",
286
+ name: input.name,
287
+ suppressed: false,
288
+ namespace: "",
289
+ parameters: [
290
+ pEnum("cplaneType", "CPlaneType", "OFFSET"),
291
+ pDeterministicQuery("entities", [planeId(input.basePlane)]),
292
+ pQuantity("offset", input.offset, "in"),
293
+ pBool("oppositeDirection", false),
294
+ ],
295
+ },
296
+ };
297
+ }
298
+ function buildSweep(input) {
299
+ return {
300
+ btType: "BTFeatureDefinitionCall-1406",
301
+ feature: {
302
+ btType: "BTMFeature-134",
303
+ featureType: "sweep",
304
+ name: input.name,
305
+ suppressed: false,
306
+ namespace: "",
307
+ parameters: [
308
+ pEnum("bodyType", "ExtendedToolBodyType", "SOLID"),
309
+ pEnum("operationType", "NewBodyOperationType", input.operationType),
310
+ sketchRegion("profiles", input.profileSketchFeatureId),
311
+ sketchEdges("path", input.pathSketchFeatureId),
312
+ pEnum("profileControl", "ProfileControlMode", "NONE"),
313
+ pBool("hasTwist", false),
314
+ pBool("hasScale", false),
315
+ pBool("trimEnds", true),
316
+ pBool("defaultScope", true),
317
+ ],
318
+ },
319
+ };
320
+ }
321
+ function buildBooleanUnion() {
322
+ return {
323
+ btType: "BTFeatureDefinitionCall-1406",
324
+ feature: {
325
+ btType: "BTMFeature-134",
326
+ featureType: "booleanBodies",
327
+ name: "Union bodies",
328
+ suppressed: false,
329
+ namespace: "",
330
+ parameters: [
331
+ pEnum("operationType", "BooleanOperationType", "UNION"),
332
+ pBool("defaultScope", false),
333
+ pQuery("tools", "query = qAllModifiableSolidBodies();"),
334
+ pBool("toolsExplicit", true),
335
+ ],
336
+ },
337
+ };
338
+ }
package/dist/cli.js CHANGED
@@ -40,7 +40,9 @@ const credentials_1 = require("./credentials");
40
40
  const client_1 = require("./api/client");
41
41
  const documents_1 = require("./api/documents");
42
42
  const edges_1 = require("./api/edges");
43
+ const featurestudio_1 = require("./api/featurestudio");
43
44
  const partstudio_1 = require("./api/partstudio");
45
+ const modeling_1 = require("./builders/modeling");
44
46
  const output_1 = require("./output");
45
47
  async function main(argv) {
46
48
  try {
@@ -101,9 +103,22 @@ async function run(argv) {
101
103
  case "create-part-studio":
102
104
  case "delete-feature":
103
105
  case "delete-element":
106
+ case "create-feature-studio":
107
+ case "get-feature-studio":
108
+ case "set-feature-studio":
109
+ case "get-feature-studio-specs":
104
110
  case "add-feature":
105
111
  case "update-feature":
106
112
  case "rollback":
113
+ case "sketch-circle":
114
+ case "sketch-circle-axis":
115
+ case "sketch-candy-cane-path":
116
+ case "extrude":
117
+ case "revolve":
118
+ case "sweep":
119
+ case "offset-plane":
120
+ case "boolean-union":
121
+ case "validate-partstudio":
107
122
  case "get-edges":
108
123
  case "find-circular-edges":
109
124
  case "find-edges-by-feature":
@@ -127,7 +142,7 @@ function parseArgs(argv) {
127
142
  if (eq !== -1) {
128
143
  options[key] = item.slice(eq + 1);
129
144
  }
130
- else if (argv[index + 1] && !argv[index + 1].startsWith("-")) {
145
+ else if (argv[index + 1] && (!argv[index + 1].startsWith("-") || isNegativeValue(argv[index + 1]))) {
131
146
  options[key] = argv[index + 1];
132
147
  index += 1;
133
148
  }
@@ -147,6 +162,9 @@ function parseArgs(argv) {
147
162
  }
148
163
  return { command, positionals, options };
149
164
  }
165
+ function isNegativeValue(value) {
166
+ return /^-\d/.test(value) || /^-\.\d/.test(value);
167
+ }
150
168
  async function handleConfig(parsed) {
151
169
  const action = parsed.positionals[0];
152
170
  const store = new credentials_1.CredentialStore();
@@ -229,6 +247,7 @@ async function handleReadCommand(parsed) {
229
247
  const client = new client_1.OnshapeClient(creds);
230
248
  const docs = new documents_1.DocumentManager(client);
231
249
  const partstudios = new partstudio_1.PartStudioManager(client);
250
+ const featurestudios = new featurestudio_1.FeatureStudioManager(client);
232
251
  const edges = new edges_1.EdgeQuery(client);
233
252
  switch (parsed.command) {
234
253
  case "list-documents": {
@@ -321,10 +340,36 @@ async function handleReadCommand(parsed) {
321
340
  (0, output_1.emit)(await client.delete(`/api/v9/elements/d/${doc}/w/${ws}/e/${elem}`));
322
341
  return;
323
342
  }
343
+ case "create-feature-studio": {
344
+ const { doc, ws } = docWorkspace(parsed.options);
345
+ (0, output_1.emit)(await featurestudios.create(doc, ws, requiredOption(parsed.options, "name")));
346
+ return;
347
+ }
348
+ case "get-feature-studio": {
349
+ const { doc, ws, elem } = dwe(parsed.options);
350
+ (0, output_1.emit)(await featurestudios.getContents(doc, ws, elem));
351
+ return;
352
+ }
353
+ case "set-feature-studio": {
354
+ const { doc, ws, elem } = dwe(parsed.options);
355
+ try {
356
+ (0, output_1.emit)(await featurestudios.setContents(doc, ws, elem, (0, featurestudio_1.loadText)(stringOption(parsed.options, "contents"), stringOption(parsed.options, "contentsFile"))));
357
+ }
358
+ catch (error) {
359
+ const message = error instanceof Error ? error.message : String(error);
360
+ throw new output_1.CliError(message, null, 2);
361
+ }
362
+ return;
363
+ }
364
+ case "get-feature-studio-specs": {
365
+ const { doc, ws, elem } = dwe(parsed.options);
366
+ (0, output_1.emit)(await featurestudios.getSpecs(doc, ws, elem));
367
+ return;
368
+ }
324
369
  case "add-feature": {
325
370
  const { doc, ws, elem } = dwe(parsed.options);
326
371
  const feature = requiredJson(parsed.options);
327
- (0, output_1.emit)(await withFeatureId(partstudios.addFeature(doc, ws, elem, feature)));
372
+ (0, output_1.emit)(await addFeatureResult(partstudios, doc, ws, elem, feature, !parsed.options.noValidate));
328
373
  return;
329
374
  }
330
375
  case "update-feature": {
@@ -338,6 +383,101 @@ async function handleReadCommand(parsed) {
338
383
  (0, output_1.emit)(await partstudios.rollback(doc, ws, elem, requiredNumberOption(parsed.options, "index")));
339
384
  return;
340
385
  }
386
+ case "sketch-circle": {
387
+ const { doc, ws, elem } = dwe(parsed.options);
388
+ (0, output_1.emit)(await addFeatureResult(partstudios, doc, ws, elem, (0, modeling_1.buildCircleSketch)({
389
+ name: stringOption(parsed.options, "name") ?? "Sketch circle",
390
+ plane: stringOption(parsed.options, "plane") ?? "Front",
391
+ planeFeatureId: stringOption(parsed.options, "planeFeature"),
392
+ center: parsePointOption(parsed.options, "center"),
393
+ radius: requiredNumberOption(parsed.options, "radius"),
394
+ }), !parsed.options.noValidate));
395
+ return;
396
+ }
397
+ case "sketch-circle-axis": {
398
+ const { doc, ws, elem } = dwe(parsed.options);
399
+ (0, output_1.emit)(await addFeatureResult(partstudios, doc, ws, elem, (0, modeling_1.buildCircleAxisSketch)({
400
+ name: stringOption(parsed.options, "name") ?? "Sketch circle and axis",
401
+ plane: stringOption(parsed.options, "plane") ?? "Front",
402
+ center: parsePointOption(parsed.options, "center"),
403
+ radius: requiredNumberOption(parsed.options, "radius"),
404
+ axisStart: parsePointOption(parsed.options, "axisStart"),
405
+ axisEnd: parsePointOption(parsed.options, "axisEnd"),
406
+ }), !parsed.options.noValidate));
407
+ return;
408
+ }
409
+ case "sketch-candy-cane-path": {
410
+ const { doc, ws, elem } = dwe(parsed.options);
411
+ (0, output_1.emit)(await addFeatureResult(partstudios, doc, ws, elem, (0, modeling_1.buildCandyCanePathSketch)({
412
+ name: stringOption(parsed.options, "name") ?? "Candy cane centerline",
413
+ plane: stringOption(parsed.options, "plane") ?? "Front",
414
+ x: numberOption(parsed.options, "x", 0),
415
+ bottom: numberOption(parsed.options, "bottom", 0),
416
+ straightHeight: requiredNumberOption(parsed.options, "straightHeight"),
417
+ hookRadius: requiredNumberOption(parsed.options, "hookRadius"),
418
+ hookAngle: numberOption(parsed.options, "hookAngle", 210),
419
+ segments: numberOption(parsed.options, "segments", 24),
420
+ }), !parsed.options.noValidate));
421
+ return;
422
+ }
423
+ case "extrude": {
424
+ const { doc, ws, elem } = dwe(parsed.options);
425
+ (0, output_1.emit)(await addFeatureResult(partstudios, doc, ws, elem, (0, modeling_1.buildExtrude)({
426
+ name: stringOption(parsed.options, "name") ?? "Extrude",
427
+ sketchFeatureId: requiredOption(parsed.options, "sketch"),
428
+ depth: requiredNumberOption(parsed.options, "depth"),
429
+ operationType: stringOption(parsed.options, "op") ?? "NEW",
430
+ }), !parsed.options.noValidate));
431
+ return;
432
+ }
433
+ case "revolve": {
434
+ const { doc, ws, elem } = dwe(parsed.options);
435
+ (0, output_1.emit)(await addFeatureResult(partstudios, doc, ws, elem, (0, modeling_1.buildRevolve)({
436
+ name: stringOption(parsed.options, "name") ?? "Revolve",
437
+ sketchFeatureId: requiredOption(parsed.options, "sketch"),
438
+ angle: numberOption(parsed.options, "angle", 360),
439
+ operationType: stringOption(parsed.options, "op") ?? "NEW",
440
+ }), !parsed.options.noValidate));
441
+ return;
442
+ }
443
+ case "sweep": {
444
+ const { doc, ws, elem } = dwe(parsed.options);
445
+ (0, output_1.emit)(await addFeatureResult(partstudios, doc, ws, elem, (0, modeling_1.buildSweep)({
446
+ name: stringOption(parsed.options, "name") ?? "Sweep",
447
+ profileSketchFeatureId: requiredOption(parsed.options, "profile"),
448
+ pathSketchFeatureId: requiredOption(parsed.options, "path"),
449
+ operationType: stringOption(parsed.options, "op") ?? "NEW",
450
+ }), !parsed.options.noValidate));
451
+ return;
452
+ }
453
+ case "offset-plane": {
454
+ const { doc, ws, elem } = dwe(parsed.options);
455
+ (0, output_1.emit)(await addFeatureResult(partstudios, doc, ws, elem, (0, modeling_1.buildOffsetPlane)({
456
+ name: stringOption(parsed.options, "name") ?? "Offset plane",
457
+ basePlane: stringOption(parsed.options, "basePlane") ?? "Top",
458
+ offset: requiredNumberOption(parsed.options, "offset"),
459
+ }), !parsed.options.noValidate));
460
+ return;
461
+ }
462
+ case "boolean-union": {
463
+ const { doc, ws, elem } = dwe(parsed.options);
464
+ (0, output_1.emit)(await addFeatureResult(partstudios, doc, ws, elem, (0, modeling_1.buildBooleanUnion)(), !parsed.options.noValidate));
465
+ return;
466
+ }
467
+ case "validate-partstudio": {
468
+ const { doc, ws, elem } = dwe(parsed.options);
469
+ try {
470
+ (0, output_1.emit)(await partstudios.validatePartStudio(doc, ws, elem, {
471
+ parts: optionalNumberOption(parsed.options, "expectParts"),
472
+ bodies: optionalNumberOption(parsed.options, "expectBodies"),
473
+ }));
474
+ }
475
+ catch (error) {
476
+ const message = error instanceof Error ? error.message : String(error);
477
+ throw new output_1.CliError(message, null, 1);
478
+ }
479
+ return;
480
+ }
341
481
  case "get-edges": {
342
482
  const { doc, ws, elem } = dwe(parsed.options);
343
483
  (0, output_1.emit)(await edges.getEdges(doc, ws, elem));
@@ -362,10 +502,20 @@ async function handleReadCommand(parsed) {
362
502
  }
363
503
  }
364
504
  }
365
- async function withFeatureId(responsePromise) {
366
- const response = await responsePromise;
505
+ async function addFeatureResult(partstudios, doc, ws, elem, feature, validate) {
506
+ const response = await partstudios.addFeature(doc, ws, elem, feature);
367
507
  const featureId = isRecord(response) && isRecord(response.feature) ? response.feature.featureId ?? null : null;
368
- return { featureId, response };
508
+ const result = { featureId, response };
509
+ if (validate && typeof featureId === "string") {
510
+ try {
511
+ result.validation = await partstudios.validateFeature(doc, ws, elem, featureId);
512
+ }
513
+ catch (error) {
514
+ const message = error instanceof Error ? error.message : String(error);
515
+ throw new output_1.CliError(message, { featureId, response }, 1);
516
+ }
517
+ }
518
+ return result;
369
519
  }
370
520
  function isRecord(value) {
371
521
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
@@ -419,6 +569,15 @@ function requiredNumberOption(options, key) {
419
569
  function requiredOption(options, key) {
420
570
  return stringOption(options, key) ?? missing(key);
421
571
  }
572
+ function parsePointOption(options, key) {
573
+ try {
574
+ return (0, modeling_1.parsePoint2)(requiredOption(options, key));
575
+ }
576
+ catch (error) {
577
+ const message = error instanceof Error ? error.message : String(error);
578
+ throw new output_1.CliError(message, null, 2);
579
+ }
580
+ }
422
581
  function requiredJson(options) {
423
582
  try {
424
583
  return (0, partstudio_1.loadJson)(stringOption(options, "json"), stringOption(options, "jsonFile"));
@@ -467,9 +626,22 @@ Commands:
467
626
  create-part-studio
468
627
  delete-feature
469
628
  delete-element
629
+ create-feature-studio
630
+ get-feature-studio
631
+ set-feature-studio
632
+ get-feature-studio-specs
470
633
  add-feature
471
634
  update-feature
472
635
  rollback
636
+ sketch-circle
637
+ sketch-circle-axis
638
+ sketch-candy-cane-path
639
+ extrude
640
+ revolve
641
+ sweep
642
+ offset-plane
643
+ boolean-union
644
+ validate-partstudio
473
645
  get-edges
474
646
  find-circular-edges
475
647
  find-edges-by-feature
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "onshape",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Node.js CLI for Onshape CAD automation with the same JSON contract as onshape-cli.",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",