🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

onshape

Package Overview
Dependencies
Maintainers
1
Versions
6
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

onshape - npm Package Compare versions

Comparing version
0.1.4
to
0.2.0
+60
dist/api/assemblies.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AssemblyManager = void 0;
/** Assembly create/read/insert/mate/BOM/transform. Port of onshape_cli/api/assemblies.py.
* Mates, mate connectors, and groups are added as raw feature envelopes via addFeature
* (see builders/advanced.ts buildAssembly*). */
class AssemblyManager {
client;
constructor(client) {
this.client = client;
}
async createAssembly(documentId, workspaceId, name) {
return this.client.post(`/api/v6/assemblies/d/${documentId}/w/${workspaceId}`, { name });
}
async getFeatures(documentId, workspaceId, elementId) {
return this.client.get(`/api/v6/assemblies/d/${documentId}/w/${workspaceId}/e/${elementId}/features`);
}
async getBom(documentId, workspaceId, elementId, opts = {}) {
return this.client.get(`/api/v6/assemblies/d/${documentId}/w/${workspaceId}/e/${elementId}/bom`, {
indented: String(opts.indented ?? true),
multiLevel: String(opts.multiLevel ?? false),
generateIfAbsent: String(opts.generateIfAbsent ?? true),
});
}
async massProperties(documentId, workspaceId, elementId) {
return this.client.get(`/api/v6/assemblies/d/${documentId}/w/${workspaceId}/e/${elementId}/massproperties`);
}
async insertInstance(documentId, workspaceId, elementId, opts) {
const body = { documentId: opts.sourceDocumentId, elementId: opts.sourceElementId };
if (opts.sourceVersionId)
body.versionId = opts.sourceVersionId;
if (opts.partId) {
body.partId = opts.partId;
body.includePartTypes = ["PARTS"];
}
if (opts.isAssembly)
body.isAssembly = true;
if (opts.isWholePartStudio)
body.isWholePartStudio = true;
if (opts.configuration)
body.configuration = opts.configuration;
return this.client.post(`/api/v6/assemblies/d/${documentId}/w/${workspaceId}/e/${elementId}/instances`, body);
}
async addFeature(documentId, workspaceId, elementId, feature) {
return this.client.post(`/api/v6/assemblies/d/${documentId}/w/${workspaceId}/e/${elementId}/features`, feature);
}
async deleteInstance(documentId, workspaceId, elementId, nodeId) {
return this.client.delete(`/api/v6/assemblies/d/${documentId}/w/${workspaceId}/e/${elementId}/instance/nodeid/${nodeId}`);
}
/** Apply a 16-float row-major 4x4 transform to occurrence paths (POST, not PATCH). */
async transformOccurrences(documentId, workspaceId, elementId, occurrencePaths, transform, opts = {}) {
const body = {
isRelative: opts.isRelative ?? true,
occurrences: occurrencePaths.map((path) => ({ path })),
transform,
};
return this.client.post(`/api/v6/assemblies/d/${documentId}/w/${workspaceId}/e/${elementId}/occurrencetransforms`, body);
}
}
exports.AssemblyManager = AssemblyManager;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ConfigurationManager = void 0;
/** Element configurations. Port of onshape_cli/api/configurations.py. */
class ConfigurationManager {
client;
constructor(client) {
this.client = client;
}
async getConfiguration(documentId, workspaceId, elementId) {
return this.client.get(`/api/v6/elements/d/${documentId}/w/${workspaceId}/e/${elementId}/configuration`);
}
/** Encode [{parameterId, parameterValue}, ...] -> {encodedId, queryParam}. No ws segment. */
async encodeConfiguration(documentId, elementId, parameters) {
return this.client.post(`/api/v6/elements/d/${documentId}/e/${elementId}/configurationencodings`, { parameters });
}
}
exports.ConfigurationManager = ConfigurationManager;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DrawingManager = void 0;
/** Drawing create + view read. Port of onshape_cli/api/drawings.py.
* Export goes through ExportManager.exportTranslation(kind="drawings"). */
class DrawingManager {
client;
constructor(client) {
this.client = client;
}
/** Create a drawing of a part / Part Studio / assembly. References the source
* by version (create a version first). */
async createDrawing(documentId, workspaceId, opts) {
const body = {
drawingName: opts.name,
externalDocumentId: opts.sourceDocumentId ?? documentId,
externalDocumentVersionId: opts.sourceVersionId,
elementId: opts.sourceElementId,
};
if (opts.partId)
body.partId = opts.partId;
return this.client.post(`/api/v6/drawings/d/${documentId}/w/${workspaceId}/create`, body);
}
async getViews(documentId, workspaceId, elementId) {
return this.client.get(`/api/v6/drawings/d/${documentId}/w/${workspaceId}/e/${elementId}/views`);
}
}
exports.DrawingManager = DrawingManager;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ExportManager = void 0;
const node_fs_1 = require("node:fs");
const promises_1 = require("node:timers/promises");
// Isometric camera (3x4 row-major view matrix) — a sensible default.
const ISO_VIEW = "0.707,0.707,0,0,-0.408,0.408,0.816,0,0.577,-0.577,0.577,0";
function isRecord(value) {
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
}
/** STL/STEP/3MF export, thumbnails, shaded renders, and mass properties.
* Port of onshape_cli/api/export.py. */
class ExportManager {
client;
constructor(client) {
this.client = client;
}
async massProperties(documentId, workspaceId, elementId, configuration) {
return this.client.get(`/api/v6/partstudios/d/${documentId}/w/${workspaceId}/e/${elementId}/massproperties`, {
configuration,
});
}
async exportStl(documentId, workspaceId, elementId, outputPath, opts = {}) {
const path = `/api/v6/partstudios/d/${documentId}/w/${workspaceId}/e/${elementId}/stl`;
const { buffer } = await this.client.getBinary(path, {
mode: opts.binary === false ? "text" : "binary",
units: opts.units ?? "inch",
grouping: "true",
scale: opts.scale ?? 1.0,
resolution: opts.resolution ?? "medium",
configuration: opts.configuration,
}, "application/vnd.onshape.v1+octet-stream");
(0, node_fs_1.writeFileSync)(outputPath, buffer);
return outputPath;
}
async exportTranslation(documentId, workspaceId, elementId, outputPath, opts = {}) {
const formatName = opts.formatName ?? "STEP";
const elementKind = opts.elementKind ?? "partstudios";
const pollInterval = opts.pollInterval ?? 1.5;
const timeout = opts.timeout ?? 120.0;
const createPath = `/api/v6/${elementKind}/d/${documentId}/w/${workspaceId}/e/${elementId}/translations`;
const body = { formatName, storeInDocument: false, flattenAssemblies: false };
if (opts.configuration)
body.configuration = opts.configuration;
const job = (await this.client.post(createPath, body));
const translationId = isRecord(job) ? job.id : undefined;
if (!translationId)
throw new Error(`Translation not started: ${JSON.stringify(job)}`);
let elapsed = 0;
let state = (isRecord(job) && job.requestState) || "ACTIVE";
let result = job;
while (state === "ACTIVE" && elapsed < timeout) {
await (0, promises_1.setTimeout)(pollInterval * 1000);
elapsed += pollInterval;
result = (await this.client.get(`/api/v6/translations/${translationId}`));
state = (isRecord(result) && result.requestState) || "ACTIVE";
}
if (state !== "DONE")
throw new Error(`Translation failed/timeout (state=${state}): ${JSON.stringify(result)}`);
const externalIds = (isRecord(result) && Array.isArray(result.resultExternalDataIds) ? result.resultExternalDataIds : []);
if (!externalIds.length)
throw new Error(`No result data: ${JSON.stringify(result)}`);
const resultDid = (isRecord(result) && result.resultDocumentId) || documentId;
const { buffer } = await this.client.getBinary(`/api/v6/documents/d/${resultDid}/externaldata/${externalIds[0]}`);
(0, node_fs_1.writeFileSync)(outputPath, buffer);
return outputPath;
}
async thumbnailInfo(documentId, workspaceId, elementId) {
return this.client.get(`/api/v6/thumbnails/d/${documentId}/w/${workspaceId}/e/${elementId}`);
}
async getThumbnail(documentId, workspaceId, elementId, outputPath, opts = {}) {
const size = opts.size ?? "600x340";
const info = (await this.thumbnailInfo(documentId, workspaceId, elementId));
const sizes = isRecord(info) && Array.isArray(info.sizes) ? info.sizes : [];
if (!sizes.length) {
throw new Error("No thumbnail available yet — Onshape renders thumbnails asynchronously; retry shortly after the element is created/edited.");
}
const chosen = sizes.find((s) => s.size === size) ?? sizes[0];
const href = chosen.href;
if (!href)
throw new Error(`Thumbnail entry has no href: ${JSON.stringify(chosen)}`);
const { buffer } = await this.client.getBinaryUrl(String(href));
(0, node_fs_1.writeFileSync)(outputPath, buffer);
return outputPath;
}
async shadedView(documentId, workspaceId, elementId, outputPath, opts = {}) {
const elementKind = opts.elementKind ?? "partstudios";
const path = `/api/v6/${elementKind}/d/${documentId}/w/${workspaceId}/e/${elementId}/shadedviews`;
const params = {
viewMatrix: opts.viewMatrix ?? ISO_VIEW,
outputWidth: opts.width ?? 600,
outputHeight: opts.height ?? 340,
pixelSize: 0,
};
if (opts.showEdges ?? true) {
params.edges = "show";
params.showAllParts = "true";
}
if (opts.configuration)
params.configuration = opts.configuration;
const resp = (await this.client.get(path, params));
const images = isRecord(resp) && Array.isArray(resp.images) ? resp.images : [];
if (!images.length)
throw new Error(`No shaded image returned: ${JSON.stringify(resp)}`);
(0, node_fs_1.writeFileSync)(outputPath, Buffer.from(images[0], "base64"));
return outputPath;
}
}
exports.ExportManager = ExportManager;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MetadataManager = void 0;
/** Element/part properties. Port of onshape_cli/api/metadata.py. */
class MetadataManager {
client;
constructor(client) {
this.client = client;
}
async getElementMetadata(documentId, workspaceId, elementId) {
return this.client.get(`/api/v6/metadata/d/${documentId}/w/${workspaceId}/e/${elementId}`);
}
async getPartMetadata(documentId, workspaceId, elementId, partId) {
return this.client.get(`/api/v6/metadata/d/${documentId}/w/${workspaceId}/e/${elementId}/p/${partId}`);
}
/** Set element (or part) properties. `properties` is [{propertyId, value}, ...].
* POST (PATCH returns 405); only editable, non-null properties are accepted. */
async setElementMetadata(documentId, workspaceId, elementId, properties, partId) {
const path = partId
? `/api/v6/metadata/d/${documentId}/w/${workspaceId}/e/${elementId}/p/${partId}`
: `/api/v6/metadata/d/${documentId}/w/${workspaceId}/e/${elementId}`;
return this.client.post(path, { properties });
}
}
exports.MetadataManager = MetadataManager;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.VariableManager = void 0;
function isRecord(value) {
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
}
/** Part Studio variable table. Port of onshape_cli/api/variables.py. */
class VariableManager {
client;
constructor(client) {
this.client = client;
}
async getVariables(documentId, workspaceId, elementId) {
const response = await this.client.get(`/api/v9/partstudios/d/${documentId}/w/${workspaceId}/e/${elementId}/variables`);
const items = Array.isArray(response) ? response : [];
return items.map((item) => ({
name: isRecord(item) ? item.name ?? "" : "",
expression: isRecord(item) ? item.expression ?? "" : "",
description: isRecord(item) ? item.description ?? null : null,
}));
}
async setVariable(documentId, workspaceId, elementId, name, expression, description) {
const data = { name, expression };
if (description)
data.description = description;
return this.client.post(`/api/v9/partstudios/d/${documentId}/w/${workspaceId}/e/${elementId}/variables`, data);
}
}
exports.VariableManager = VariableManager;
"use strict";
// Advanced feature builders for Onshape — fillet, chamfer, shell, draft, revolve,
// boolean, mirror, patterns, offset-plane, and assembly mate/connector/group.
// 1:1 port of onshape_cli/builders/advanced.py.
//
// Every builder emits the standard BTFeatureDefinitionCall-1406 envelope the
// POST .../features endpoint expects. Selection is query-string based so it
// survives topology changes; explicit deterministic IDs are also supported.
Object.defineProperty(exports, "__esModule", { value: true });
exports.qBodyOfFeature = exports.qAllBodies = exports.qCircularEdges = exports.qEdgesOfFeature = exports.qAllEdges = void 0;
exports.featureCall = featureCall;
exports.pQuery = pQuery;
exports.pSketchRegion = pSketchRegion;
exports.pQuantity = pQuantity;
exports.pEnum = pEnum;
exports.pBool = pBool;
exports.buildFillet = buildFillet;
exports.buildChamfer = buildChamfer;
exports.buildShell = buildShell;
exports.buildDraft = buildDraft;
exports.buildRevolveAxis = buildRevolveAxis;
exports.buildBoolean = buildBoolean;
exports.buildMirror = buildMirror;
exports.buildLinearPattern = buildLinearPattern;
exports.buildCircularPattern = buildCircularPattern;
exports.buildOffsetPlaneSelect = buildOffsetPlaneSelect;
exports.buildAssemblyMate = buildAssemblyMate;
exports.buildAssemblyMateConnector = buildAssemblyMateConnector;
exports.buildAssemblyGroup = buildAssemblyGroup;
// --- Low-level parameter helpers -------------------------------------------
function featureCall(featureType, name, parameters, namespace = "") {
return {
btType: "BTFeatureDefinitionCall-1406",
feature: {
btType: "BTMFeature-134",
featureType,
name,
suppressed: false,
namespace,
parameters,
},
};
}
function pQuery(parameterId, opts = {}) {
const query = {
btType: "BTMIndividualQuery-138",
deterministicIds: opts.deterministicIds ?? [],
};
if (opts.queryString) {
query.queryStatement = null;
query.queryString = opts.queryString;
}
if (opts.featureId)
query.featureId = opts.featureId;
return {
btType: "BTMParameterQueryList-148",
queries: [query],
parameterId,
parameterName: "",
libraryRelationType: "NONE",
};
}
function pSketchRegion(parameterId, sketchFeatureId) {
return {
btType: "BTMParameterQueryList-148",
queries: [
{
btType: "BTMIndividualSketchRegionQuery-140",
queryStatement: null,
filterInnerLoops: true,
queryString: `query = qSketchRegion(id + "${sketchFeatureId}", true);`,
featureId: sketchFeatureId,
deterministicIds: [],
},
],
parameterId,
parameterName: "",
libraryRelationType: "NONE",
};
}
function pQuantity(parameterId, value, units = "in", opts = {}) {
let expression;
if (opts.variable)
expression = `#${opts.variable}`;
else if (opts.isInteger)
expression = `${Math.trunc(value)}`;
else if (units)
expression = `${value} ${units}`;
else
expression = `${value}`;
return {
btType: "BTMParameterQuantity-147",
isInteger: opts.isInteger ?? false,
value,
units: "",
expression,
parameterId,
parameterName: "",
libraryRelationType: "NONE",
};
}
function pEnum(parameterId, enumName, value) {
return {
btType: "BTMParameterEnum-145",
namespace: "",
enumName,
value,
parameterId,
parameterName: "",
libraryRelationType: "NONE",
};
}
function pBool(parameterId, value) {
return {
btType: "BTMParameterBoolean-144",
value,
parameterId,
parameterName: "",
libraryRelationType: "NONE",
};
}
// --- Reusable selection query strings --------------------------------------
const qAllEdges = () => "query = qOwnedByBody(qAllModifiableSolidBodies(), EntityType.EDGE);";
exports.qAllEdges = qAllEdges;
const qEdgesOfFeature = (featureId) => `query = qCreatedBy(makeId("${featureId}"), EntityType.EDGE);`;
exports.qEdgesOfFeature = qEdgesOfFeature;
const qCircularEdges = () => "query = qGeometry(qOwnedByBody(qAllModifiableSolidBodies(), EntityType.EDGE), GeometryType.CIRCLE);";
exports.qCircularEdges = qCircularEdges;
const qAllBodies = () => "query = qAllModifiableSolidBodies();";
exports.qAllBodies = qAllBodies;
const qBodyOfFeature = (featureId) => `query = qCreatedBy(makeId("${featureId}"), EntityType.BODY);`;
exports.qBodyOfFeature = qBodyOfFeature;
function resolveEntityQuery(parameterId, sel, entity = "EDGE") {
if (sel.edgeIds && sel.edgeIds.length)
return pQuery(parameterId, { deterministicIds: sel.edgeIds });
if (sel.queryString)
return pQuery(parameterId, { queryString: sel.queryString });
if (sel.featureId) {
const qs = entity === "EDGE" ? (0, exports.qEdgesOfFeature)(sel.featureId) : (0, exports.qBodyOfFeature)(sel.featureId);
return pQuery(parameterId, { queryString: qs });
}
if (sel.circular)
return pQuery(parameterId, { queryString: (0, exports.qCircularEdges)() });
if (sel.selectAll)
return pQuery(parameterId, { queryString: (0, exports.qAllEdges)() });
throw new Error("No selection: pass edges, query, feature, --all, or --circular");
}
// --- Feature builders ------------------------------------------------------
function buildFillet(opts) {
const entities = resolveEntityQuery("entities", opts);
return featureCall("fillet", opts.name ?? "Fillet", [
entities,
pQuantity("radius", opts.radius ?? 0.1, "in", { variable: opts.radiusVariable }),
pEnum("filletType", "FilletType", opts.filletType ?? "EDGE"),
]);
}
function buildChamfer(opts) {
const entities = resolveEntityQuery("entities", opts);
const chamferType = opts.chamferType ?? "EQUAL_OFFSETS";
const params = [
entities,
pEnum("chamferType", "ChamferType", chamferType),
pQuantity("width", opts.width ?? 0.1, "in", { variable: opts.widthVariable }),
];
if (chamferType === "OFFSET_ANGLE" && opts.angle !== undefined) {
params.push(pQuantity("angle", opts.angle, "deg"));
}
return featureCall("chamfer", opts.name ?? "Chamfer", params);
}
function buildShell(opts) {
let entities;
if (opts.faceIds && opts.faceIds.length)
entities = pQuery("entities", { deterministicIds: opts.faceIds });
else if (opts.queryString)
entities = pQuery("entities", { queryString: opts.queryString });
else
entities = pQuery("entities", { deterministicIds: [] });
const inward = opts.inward ?? true;
return featureCall("shell", opts.name ?? "Shell", [
entities,
pQuantity("thickness", opts.thickness ?? 0.125, "in", { variable: opts.thicknessVariable }),
pBool("oppositeDirection", !inward),
]);
}
function buildDraft(opts) {
return featureCall("draft", opts.name ?? "Draft", [
pQuery("neutralPlane", { queryString: opts.neutralPlaneQuery }),
pQuery("draftFaces", { queryString: opts.faceQuery }),
pQuantity("angle", opts.angle ?? 3.0, "deg"),
pBool("oppositeDirection", false),
]);
}
function buildRevolveAxis(opts) {
const axis = opts.axisIds && opts.axisIds.length
? pQuery("axis", { deterministicIds: opts.axisIds })
: pQuery("axis", { queryString: opts.axisQuery });
const full = (opts.revolveType ?? "FULL") === "FULL";
const params = [
pEnum("bodyType", "ExtendedToolBodyType", "SOLID"),
pEnum("operationType", "NewBodyOperationType", opts.operationType ?? "NEW"),
pSketchRegion("entities", opts.sketchFeatureId),
axis,
pBool("fullRevolve", full),
];
if (!full) {
params.push(pEnum("endBound", "RevolveBoundingType", "BLIND"));
params.push(pQuantity("angle", opts.angle ?? 360.0, "deg"));
}
params.push(pBool("defaultScope", true));
return featureCall("revolve", opts.name ?? "Revolve", params);
}
function buildBoolean(opts) {
const operationType = opts.operationType ?? "UNION";
let tools;
if (opts.toolIds && opts.toolIds.length)
tools = pQuery("tools", { deterministicIds: opts.toolIds });
else if (opts.toolsQuery)
tools = pQuery("tools", { queryString: opts.toolsQuery });
else
tools = pQuery("tools", { queryString: (0, exports.qAllBodies)() });
const params = [
pEnum("operationType", "BooleanOperationType", operationType),
pBool("defaultScope", false),
tools,
pBool("toolsExplicit", true),
];
if (operationType === "SUBTRACTION") {
params.push(pBool("targetsAndToolsNeedGrouping", false));
if (opts.targetsQuery)
params.push(pQuery("targets", { queryString: opts.targetsQuery }));
}
return featureCall("booleanBodies", opts.name ?? "Boolean", params);
}
function buildMirror(opts) {
let plane;
if (opts.mirrorPlaneIds && opts.mirrorPlaneIds.length)
plane = pQuery("mirrorPlane", { deterministicIds: opts.mirrorPlaneIds });
else if (opts.mirrorPlaneQuery)
plane = pQuery("mirrorPlane", { queryString: opts.mirrorPlaneQuery });
else
throw new Error("Provide --plane-ids or --plane-query");
return featureCall("mirror", opts.name ?? "Mirror", [
pEnum("patternType", "MirrorType", opts.patternType ?? "PART"),
pEnum("operationType", "NewBodyOperationType", "NEW"),
pQuery("entities", { queryString: opts.entitiesQuery }),
plane,
]);
}
function buildLinearPattern(opts) {
const direction = opts.directionIds && opts.directionIds.length
? pQuery("directionOne", { deterministicIds: opts.directionIds })
: pQuery("directionOne", { queryString: opts.directionQuery });
return featureCall("linearPattern", opts.name ?? "Linear Pattern", [
pEnum("patternType", "PatternType", opts.patternType ?? "PART"),
pEnum("operationType", "NewBodyOperationType", "NEW"),
pQuery("entities", { queryString: opts.entitiesQuery }),
direction,
pBool("oppositeDirection", opts.opposite ?? false),
pQuantity("distance", opts.distance),
pQuantity("instanceCount", opts.instanceCount, "", { isInteger: true }),
pBool("hasSecondDir", false),
]);
}
function buildCircularPattern(opts) {
const axis = opts.axisIds && opts.axisIds.length
? pQuery("axis", { deterministicIds: opts.axisIds })
: pQuery("axis", { queryString: opts.axisQuery });
return featureCall("circularPattern", opts.name ?? "Circular Pattern", [
pEnum("patternType", "PatternType", opts.patternType ?? "PART"),
pEnum("operationType", "NewBodyOperationType", "NEW"),
pQuery("entities", { queryString: opts.entitiesQuery }),
axis,
pQuantity("angle", opts.angle ?? 360.0, "deg"),
pQuantity("instanceCount", opts.instanceCount, "", { isInteger: true }),
pBool("equalSpace", opts.equalSpacing ?? true),
]);
}
function buildOffsetPlaneSelect(opts) {
let base;
if (opts.basePlaneIds && opts.basePlaneIds.length)
base = pQuery("entities", { deterministicIds: opts.basePlaneIds });
else if (opts.basePlaneQuery)
base = pQuery("entities", { queryString: opts.basePlaneQuery });
else
base = pQuery("entities", { deterministicIds: ["JCC"] }); // Front
return featureCall("cPlane", opts.name ?? "Plane", [
pEnum("cplaneType", "CPlaneType", "OFFSET"),
base,
pQuantity("offset", opts.offset ?? 1.0),
pBool("oppositeDirection", false),
]);
}
// --- Assembly feature builders (posted to the assembly /features endpoint) --
function buildAssemblyMate(opts) {
const queries = opts.mateConnectorIds.map((fid) => ({
btType: "BTMFeatureQueryWithOccurrence-157",
path: [],
featureId: fid,
queryData: "",
}));
return {
btType: "BTFeatureDefinitionCall-1406",
feature: {
btType: "BTMMate-64",
featureType: "mate",
name: opts.name ?? "Mate",
suppressed: false,
parameters: [
pEnum("mateType", "Mate type", opts.mateType ?? "FASTENED"),
{
btType: "BTMParameterQueryWithOccurrenceList-67",
queries,
parameterId: "mateConnectorsQuery",
},
],
},
};
}
function buildAssemblyMateConnector(opts) {
return {
btType: "BTFeatureDefinitionCall-1406",
feature: {
btType: "BTMMateConnector-66",
featureType: "mateConnector",
name: opts.name ?? "Mate connector",
suppressed: false,
parameters: [
{
btType: "BTMParameterEnum-145",
enumName: "Origin type",
value: "ON_ENTITY",
parameterId: "originType",
namespace: "",
},
{
btType: "BTMParameterQueryWithOccurrenceList-67",
parameterId: "originQuery",
queries: [
{
btType: "BTMInferenceQueryWithOccurrence-1083",
inferenceType: opts.inferenceType ?? "CENTROID",
path: [opts.occurrenceId],
deterministicIds: [],
},
],
},
],
},
};
}
function buildAssemblyGroup(opts) {
const queries = opts.occurrenceIds.map((oid) => ({
btType: "BTMIndividualOccurrenceQuery-626",
path: [oid],
}));
return {
btType: "BTFeatureDefinitionCall-1406",
feature: {
btType: "BTMMateGroup-65",
featureType: "mateGroup",
name: opts.name ?? "Group",
suppressed: false,
parameters: [
{
btType: "BTMParameterQueryWithOccurrenceList-67",
queries,
parameterId: "occurrencesQuery",
},
],
},
};
}
# onshape (Node CLI)
Command-line automation for [Onshape](https://www.onshape.com) CAD, as an npm package.
Build and inspect parametric models — sketches, extrudes, holes, fillets, chamfers,
shells, booleans, mirrors, patterns — query geometry, read mass properties, drive
assemblies and drawings, and export **STL / STEP / 3MF** straight from your terminal.
This is a Node/TypeScript port of [`onshape-cli`](https://github.com/am-will/onshape-cli)
with **full command parity**: the same command names, the same flags, and the same JSON
contract. It talks directly to the Onshape REST API over HTTP Basic auth and emits JSON.
## Install
```bash
npm install -g onshape # global `onshape` command
# or
npx onshape <command> # run without installing
```
Requires Node ≥ 18. The optional `@napi-rs/keyring` dependency enables OS-keychain
credential storage; without it credentials fall back to a `0600` file.
## Authenticate
Create an API key pair at https://dev.onshape.com → **API keys**, then save it once:
```bash
onshape login # import from env / ~/.claude/mcp.json, or prompt, then save
onshape config show # show saved credentials (secret redacted)
onshape logout # delete saved credentials
```
Credentials resolve in this order: `--access-key`/`--secret-key` flags →
`ONSHAPE_ACCESS_KEY`/`ONSHAPE_SECRET_KEY` env vars → `~/.onshape/credentials.json`
(or `ONSHAPE_CONFIG`) → Linux `$XDG_CONFIG_HOME/onshape/credentials.json` → the
`onshape` block of `~/.claude/mcp.json`. `ONSHAPE_BASE_URL`/`--base-url` overrides the
API base URL.
## Quickstart
```bash
# find a document and part studio
onshape list-documents --limit 5
onshape get-document-summary --doc <documentId>
# target a part studio once
export ONSHAPE_DOC=... ONSHAPE_WS=... ONSHAPE_ELEM=...
# build: sketch -> extrude -> fillet -> export
SK=$(onshape sketch-rectangle --plane Top --corner1 0,0 --corner2 3,2 | jq -r .result.featureId)
EX=$(onshape extrude --sketch "$SK" --depth 0.25 | jq -r .result.featureId)
onshape fillet --feature "$EX" --radius 0.1
onshape export-stl --out bracket.stl
```
Every command prints `{"ok": true, "result": ...}` or
`{"ok": false, "error": ..., "detail": ...}`.
## Selecting geometry
Edges/faces are chosen with a **FeatureScript query**, evaluated server-side. `fillet`,
`chamfer`, `shell`, `boolean`, `mirror`, and the patterns all accept:
| flag | selects |
|---|---|
| `--all` | every edge of every solid body |
| `--feature FID` | every edge created by that feature |
| `--circular` | every circular/arc edge |
| `--query "<FeatureScript>"` | any custom query |
| `--edges id1,id2` | explicit deterministic IDs |
Patterns need a real edge for `--direction-ids`/`--axis-ids` (from `get-edges`).
## Commands
Run `onshape --help` for the grouped list. Categories:
- **Documents & discovery:** `list-documents`, `search-documents`, `get-document`,
`get-document-summary`, `create-document`, `delete-document`, `update-document`,
`get-elements`, `find-part-studios`, `get-workspaces`, `list-versions`,
`create-version`, `get-parts`, `get-features`, `get-feature-specs`,
`get-sketch-info`, `get-body-details`, `get-assembly`
- **Part studio management:** `create-part-studio`, `delete-feature`, `delete-element`,
`add-feature`, `update-feature`, `rollback`, `validate-partstudio`
- **Sketching:** `create-sketch`, `sketch-rectangle`, `sketch-circle`, `sketch-line`,
`sketch-circle-axis`, `sketch-candy-cane-path`
- **Solids & modifiers:** `extrude`, `hole`, `thicken`, `revolve`, `sweep`, `draft`,
`fillet`, `chamfer`, `shell`
- **Patterns & boolean:** `boolean`, `boolean-union`, `mirror`, `linear-pattern`,
`circular-pattern`, `offset-plane`
- **Geometry / measure:** `get-edges`, `find-circular-edges`, `find-edges-by-feature`,
`measure`, `eval-featurescript`, `mass-properties`
- **Variables & configurations:** `get-variables`, `set-variable`, `get-configuration`,
`encode-configuration`
- **Export & images:** `export-stl`, `export`, `thumbnail-info`, `get-thumbnail`,
`shaded-view`
- **Assemblies:** `create-assembly`, `insert-instance`, `get-assembly-features`,
`assembly-add-feature`, `assembly-mate-connector`, `assembly-mate`, `assembly-group`,
`get-bom`, `assembly-mass-properties`, `delete-instance`, `transform-instance`
- **Drawings:** `create-drawing`, `get-drawing-views`, `export-drawing`
- **Feature studios:** `create-feature-studio`, `get-feature-studio`,
`set-feature-studio`, `get-feature-studio-specs`
- **Metadata:** `get-metadata`, `set-metadata`
> Sketch/feature dimensions are in **inches**. Free Onshape accounts can only create
> **public** documents (`create-document --public`; private → HTTP 409). Cross-document
> inserts and drawings reference geometry by **version**, so run `create-version` first.
> `revolve`/`offset-plane` payloads are spec-shaped but can regen-reject — check the
> returned `featureStatus`.
## License
[MIT](../LICENSE) © 2026 William Ryan.
*Not affiliated with or endorsed by Onshape / PTC.*
+37
-2

@@ -9,2 +9,5 @@ "use strict";

const MAX_RETRY_DELAY_MS = 30_000;
// Per-request wall-clock timeout. Without this a stalled/throttled connection
// hangs forever (Node fetch has no default timeout). Override with ONSHAPE_TIMEOUT_MS.
const REQUEST_TIMEOUT_MS = Number(process.env.ONSHAPE_TIMEOUT_MS) || 120_000;
class HttpError extends Error {

@@ -57,2 +60,33 @@ status;

}
/**
* GET raw bytes (e.g. an STL or a thumbnail PNG). Follows Onshape's regional /
* S3 redirects with the Authorization header re-attached on every hop, which
* `fetchWithAuthRedirects` already does. `accept` overrides the Accept header.
*/
async getBinary(path, params, accept = "*/*") {
const url = new URL(path, this.creds.baseUrl);
if (params) {
const search = new node_url_1.URLSearchParams();
for (const [key, value] of Object.entries(params)) {
if (value !== undefined)
search.set(key, String(value));
}
url.search = search.toString();
}
const response = await this.fetchWithAuthRedirects(url, { Accept: accept });
if (!response.ok) {
throw new HttpError(response.status, await responseDetail(response));
}
const buffer = Buffer.from(await response.arrayBuffer());
return { buffer, contentType: response.headers.get("content-type") ?? "", status: response.status };
}
/** GET an absolute URL for raw bytes (already-resolved href, auth re-attached). */
async getBinaryUrl(href, accept = "*/*") {
const response = await this.fetchWithAuthRedirects(new URL(href), { Accept: accept });
if (!response.ok) {
throw new HttpError(response.status, await responseDetail(response));
}
const buffer = Buffer.from(await response.arrayBuffer());
return { buffer, contentType: response.headers.get("content-type") ?? "", status: response.status };
}
async requestJson(url) {

@@ -76,5 +110,6 @@ for (let attempt = 1; attempt <= MAX_READ_ATTEMPTS; attempt += 1) {

const requestHeaders = { ...headers, Authorization: `Basic ${auth}` };
const fetchOnce = (target) => fetch(target, { method, body, headers: requestHeaders, redirect: "manual", signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS) });
let current = url;
for (let hop = 0; hop < 5; hop += 1) {
const response = await fetch(current, { method, body, headers: requestHeaders, redirect: "manual" });
const response = await fetchOnce(current);
if (![301, 302, 303, 307, 308].includes(response.status))

@@ -87,3 +122,3 @@ return response;

}
return fetch(current, { method, body, headers: requestHeaders, redirect: "manual" });
return fetchOnce(current);
}

@@ -90,0 +125,0 @@ }

@@ -82,2 +82,9 @@ "use strict";

}
async getAssembly(documentId, workspaceId, elementId, opts = {}) {
return this.client.get(`/api/v6/assemblies/d/${documentId}/w/${workspaceId}/e/${elementId}`, {
includeMateFeatures: String(opts.includeMateFeatures ?? true),
includeMateConnectors: String(opts.includeMateConnectors ?? true),
includeNonSolids: String(opts.includeNonSolids ?? false),
});
}
async getDocumentSummary(documentId) {

@@ -84,0 +91,0 @@ const document = await this.getDocument(documentId);

@@ -6,2 +6,4 @@ "use strict";

const node_fs_1 = require("node:fs");
const fsvalue_1 = require("./fsvalue");
const round4 = (n) => Math.round(n * 1e4) / 1e4;
class PartStudioManager {

@@ -43,2 +45,37 @@ client;

}
async evaluateFeatureScript(documentId, workspaceId, elementId, script) {
return this.client.post(`/api/v6/partstudios/d/${documentId}/w/${workspaceId}/e/${elementId}/featurescript`, { script });
}
/** Measure solid bodies: bounding box (inches), volume, body count. */
async measure(documentId, workspaceId, elementId) {
const script = "function(context is Context, queries){" +
" var bs = evaluateQuery(context, qAllModifiableSolidBodies());" +
" if (size(bs) == 0) { return { bodies: 0 }; }" +
" var b = evBox3d(context, { topology: qAllModifiableSolidBodies(), tight: true });" +
" var vol = evVolume(context, { entities: qAllModifiableSolidBodies() });" +
" return { bodies: size(bs)," +
" minx: b.minCorner[0]/inch, miny: b.minCorner[1]/inch, minz: b.minCorner[2]/inch," +
" maxx: b.maxCorner[0]/inch, maxy: b.maxCorner[1]/inch, maxz: b.maxCorner[2]/inch," +
" vol_in3: vol/(inch*inch*inch) }; }";
const resp = (await this.evaluateFeatureScript(documentId, workspaceId, elementId, script));
if (!isRecord(resp) || resp.result === null || resp.result === undefined) {
throw new fsvalue_1.FeatureScriptError((0, fsvalue_1.featurescriptMessages)(resp));
}
const d = (0, fsvalue_1.decodeFsValue)(resp.result) || {};
const n = Number(d.bodies ?? 0) || 0;
if (n === 0) {
return { bodies: 0, bbox: { x: 0, y: 0, z: 0, min: [0, 0, 0], max: [0, 0, 0] }, volume_in3: 0 };
}
return {
bodies: n,
bbox: {
x: round4(d.maxx - d.minx),
y: round4(d.maxy - d.miny),
z: round4(d.maxz - d.minz),
min: [round4(d.minx), round4(d.miny), round4(d.minz)],
max: [round4(d.maxx), round4(d.maxy), round4(d.maxz)],
},
volume_in3: round4(Number(d.vol_in3 ?? 0)),
};
}
async updateFeature(documentId, workspaceId, elementId, featureId, feature) {

@@ -45,0 +82,0 @@ return this.client.post(`/api/v9/partstudios/d/${documentId}/w/${workspaceId}/e/${elementId}/features/featureid/${featureId}`, feature);

@@ -9,2 +9,6 @@ "use strict";

exports.buildExtrude = buildExtrude;
exports.buildThicken = buildThicken;
exports.buildSketchFromEntities = buildSketchFromEntities;
exports.buildRectangleSketch = buildRectangleSketch;
exports.buildLineSketch = buildLineSketch;
exports.buildRevolve = buildRevolve;

@@ -36,3 +40,3 @@ exports.buildOffsetPlane = buildOffsetPlane;

}
function circleEntity(id, center, radius) {
function circleEntity(id, center, radius, isConstruction = false) {
return {

@@ -51,5 +55,20 @@ btType: "BTMSketchCurve-4",

},
isConstruction: false,
isConstruction,
};
}
function rectangleEntities(prefix, corner1, corner2) {
const [x1, y1] = corner1;
const [x2, y2] = corner2;
const corners = [
[x1, y1],
[x2, y1],
[x2, y2],
[x1, y2],
];
const segments = [];
for (let i = 0; i < 4; i += 1) {
segments.push(lineEntity(`${prefix}.${i}`, corners[i], corners[(i + 1) % 4]));
}
return segments;
}
function lineEntity(id, start, end, isConstruction = false) {

@@ -242,2 +261,16 @@ const x1 = toMeters(start[0]);

}
function depthQuantity(parameterId, value, variable) {
if (!variable)
return pQuantity(parameterId, value, "in");
return {
btType: "BTMParameterQuantity-147",
isInteger: false,
value,
units: "",
expression: `#${variable}`,
parameterId,
parameterName: "",
libraryRelationType: "NONE",
};
}
function buildExtrude(input) {

@@ -255,3 +288,3 @@ return {

pEnum("operationType", "NewBodyOperationType", input.operationType),
pQuantity("depth", input.depth, "in"),
depthQuantity("depth", input.depth, input.depthVariable),
pBool("oppositeDirection", false),

@@ -263,2 +296,58 @@ pBool("defaultScope", true),

}
/** thicken — mirrors onshape_cli/builders/thicken.py (bare BTMFeature-134). */
function buildThicken(input) {
const expression = input.thicknessVariable ? `#${input.thicknessVariable}` : `${input.thickness} in`;
return {
btType: "BTMFeature-134",
name: input.name,
suppressed: false,
namespace: "",
featureType: "thicken",
parameters: [
{ btType: "BTMParameterEnum-145", enumName: "NewBodyOperationType", value: input.operationType, parameterId: "operationType" },
sketchRegion("entities", input.sketchFeatureId),
{ btType: "BTMParameterBoolean-144", value: input.midplane ?? false, parameterId: "midplane" },
{ btType: "BTMParameterQuantity-147", expression, parameterId: "thickness1" },
{ btType: "BTMParameterBoolean-144", value: input.opposite ?? false, parameterId: "oppositeDirection" },
{ btType: "BTMParameterQuantity-147", expression: "0 in", parameterId: "thickness2" },
],
};
}
/** Generic sketch from a parsed entity list (create-sketch). Matches the
* Python contract: types line/circle/rectangle (arc is not supported). */
function buildSketchFromEntities(input) {
const out = [];
input.entities.forEach((entity, index) => {
const type = String(entity.type ?? "");
const construction = Boolean(entity.construction);
if (type === "line") {
out.push(lineEntity(`e${index}`, point(entity.start), point(entity.end), construction));
}
else if (type === "circle") {
out.push(circleEntity(`e${index}`, point(entity.center), Number(entity.radius), construction));
}
else if (type === "rectangle") {
out.push(...rectangleEntities(`e${index}`, point(entity.corner1), point(entity.corner2)));
}
else if (type === "arc") {
throw new Error("arc entities not supported by this sketch builder yet; use line/circle/rectangle");
}
else {
throw new Error(`Unknown sketch entity type '${type}'. Use line, circle, or rectangle.`);
}
});
return sketch(input.name, planeId(input.plane), out);
}
function buildRectangleSketch(input) {
return sketch(input.name, planeId(input.plane), rectangleEntities("rect", input.corner1, input.corner2));
}
function buildLineSketch(input) {
return sketch(input.name, planeId(input.plane), [lineEntity("line.1", input.start, input.end)]);
}
function point(value) {
if (Array.isArray(value) && value.length === 2 && value.every((v) => Number.isFinite(Number(v)))) {
return [Number(value[0]), Number(value[1])];
}
throw new Error(`Expected a [x, y] point; got ${JSON.stringify(value)}`);
}
function buildRevolve(input) {

@@ -265,0 +354,0 @@ return {

@@ -45,3 +45,11 @@ "use strict";

const partstudio_1 = require("./api/partstudio");
const export_1 = require("./api/export");
const variables_1 = require("./api/variables");
const configurations_1 = require("./api/configurations");
const assemblies_1 = require("./api/assemblies");
const drawings_1 = require("./api/drawings");
const metadata_1 = require("./api/metadata");
const fsvalue_1 = require("./api/fsvalue");
const modeling_1 = require("./builders/modeling");
const advanced_1 = require("./builders/advanced");
const output_1 = require("./output");

@@ -127,2 +135,43 @@ async function main(argv) {

case "mass-properties":
case "create-sketch":
case "sketch-rectangle":
case "sketch-line":
case "hole":
case "thicken":
case "fillet":
case "chamfer":
case "shell":
case "draft":
case "boolean":
case "mirror":
case "linear-pattern":
case "circular-pattern":
case "measure":
case "eval-featurescript":
case "get-variables":
case "set-variable":
case "get-configuration":
case "encode-configuration":
case "export-stl":
case "export":
case "thumbnail-info":
case "get-thumbnail":
case "shaded-view":
case "get-assembly":
case "create-assembly":
case "insert-instance":
case "get-assembly-features":
case "assembly-add-feature":
case "assembly-mate-connector":
case "assembly-mate":
case "assembly-group":
case "get-bom":
case "assembly-mass-properties":
case "delete-instance":
case "transform-instance":
case "create-drawing":
case "get-drawing-views":
case "export-drawing":
case "get-metadata":
case "set-metadata":
await handleReadCommand(parsed);

@@ -254,2 +303,8 @@ return;

const edges = new edges_1.EdgeQuery(client);
const exports = new export_1.ExportManager(client);
const variables = new variables_1.VariableManager(client);
const configurations = new configurations_1.ConfigurationManager(client);
const assemblies = new assemblies_1.AssemblyManager(client);
const drawings = new drawings_1.DrawingManager(client);
const metadata = new metadata_1.MetadataManager(client);
switch (parsed.command) {

@@ -438,8 +493,20 @@ case "list-documents": {

const { doc, ws, elem } = dwe(parsed.options);
(0, output_1.emit)(await addFeatureResult(partstudios, doc, ws, elem, (0, modeling_1.buildRevolve)({
name: stringOption(parsed.options, "name") ?? "Revolve",
sketchFeatureId: requiredOption(parsed.options, "sketch"),
angle: numberOption(parsed.options, "angle", 360),
operationType: stringOption(parsed.options, "op") ?? "NEW",
}), !parsed.options.noValidate));
const axisIds = splitList(stringOption(parsed.options, "axisIds"));
const axisQuery = stringOption(parsed.options, "axis");
const name = stringOption(parsed.options, "name") ?? "Revolve";
const sketchFeatureId = requiredOption(parsed.options, "sketch");
const angle = numberOption(parsed.options, "angle", 360);
const operationType = stringOption(parsed.options, "op") ?? "NEW";
const feature = axisIds.length || axisQuery
? (0, advanced_1.buildRevolveAxis)({
name,
sketchFeatureId,
axisIds: axisIds.length ? axisIds : undefined,
axisQuery,
operationType,
revolveType: stringOption(parsed.options, "type") ?? "FULL",
angle,
})
: (0, modeling_1.buildRevolve)({ name, sketchFeatureId, angle, operationType });
(0, output_1.emit)(await addFeatureResult(partstudios, doc, ws, elem, feature, !parsed.options.noValidate));
return;

@@ -459,6 +526,10 @@ }

const { doc, ws, elem } = dwe(parsed.options);
(0, output_1.emit)(await addFeatureResult(partstudios, doc, ws, elem, (0, modeling_1.buildOffsetPlane)({
const baseIds = splitList(stringOption(parsed.options, "baseIds"));
const basePlane = stringOption(parsed.options, "basePlane");
const basePlaneIds = baseIds.length ? baseIds : basePlane ? [(0, modeling_1.planeId)(basePlane)] : undefined;
(0, output_1.emit)(await addFeatureResult(partstudios, doc, ws, elem, (0, advanced_1.buildOffsetPlaneSelect)({
name: stringOption(parsed.options, "name") ?? "Offset plane",
basePlane: stringOption(parsed.options, "basePlane") ?? "Top",
offset: requiredNumberOption(parsed.options, "offset"),
basePlaneIds,
basePlaneQuery: stringOption(parsed.options, "baseQuery"),
offset: numberOption(parsed.options, "offset", 1.0),
}), !parsed.options.noValidate));

@@ -508,4 +579,415 @@ return;

}
// ---- sketching ----
case "create-sketch": {
const { doc, ws, elem } = dwe(parsed.options);
let entities;
try {
const parsedEntities = JSON.parse(requiredOption(parsed.options, "entities"));
if (!Array.isArray(parsedEntities))
throw new Error("--entities must be a JSON array");
entities = parsedEntities;
}
catch (error) {
throw new output_1.CliError(error instanceof Error ? error.message : String(error), null, 2);
}
(0, output_1.emit)(await addFeatureResult(partstudios, doc, ws, elem, (0, modeling_1.buildSketchFromEntities)({
name: stringOption(parsed.options, "name") ?? "Sketch",
plane: stringOption(parsed.options, "plane") ?? "Front",
entities,
}), !parsed.options.noValidate));
return;
}
case "sketch-rectangle": {
const { doc, ws, elem } = dwe(parsed.options);
(0, output_1.emit)(await addFeatureResult(partstudios, doc, ws, elem, (0, modeling_1.buildRectangleSketch)({
name: stringOption(parsed.options, "name") ?? "Sketch",
plane: stringOption(parsed.options, "plane") ?? "Front",
corner1: parsePointOption(parsed.options, "corner1"),
corner2: parsePointOption(parsed.options, "corner2"),
}), !parsed.options.noValidate));
return;
}
case "sketch-line": {
const { doc, ws, elem } = dwe(parsed.options);
(0, output_1.emit)(await addFeatureResult(partstudios, doc, ws, elem, (0, modeling_1.buildLineSketch)({
name: stringOption(parsed.options, "name") ?? "Sketch",
plane: stringOption(parsed.options, "plane") ?? "Front",
start: parsePointOption(parsed.options, "start"),
end: parsePointOption(parsed.options, "end"),
}), !parsed.options.noValidate));
return;
}
// ---- solids / modifiers ----
case "hole": {
const { doc, ws, elem } = dwe(parsed.options);
(0, output_1.emit)(await addFeatureResult(partstudios, doc, ws, elem, (0, modeling_1.buildExtrude)({
name: stringOption(parsed.options, "name") ?? "Hole",
sketchFeatureId: requiredOption(parsed.options, "sketch"),
depth: requiredNumberOption(parsed.options, "depth"),
operationType: "REMOVE",
depthVariable: stringOption(parsed.options, "depthVar"),
}), !parsed.options.noValidate));
return;
}
case "thicken": {
const { doc, ws, elem } = dwe(parsed.options);
(0, output_1.emit)(await addFeatureResult(partstudios, doc, ws, elem, (0, modeling_1.buildThicken)({
name: stringOption(parsed.options, "name") ?? "Thicken",
sketchFeatureId: requiredOption(parsed.options, "sketch"),
thickness: requiredNumberOption(parsed.options, "thickness"),
operationType: stringOption(parsed.options, "op") ?? "NEW",
}), !parsed.options.noValidate));
return;
}
case "fillet": {
const { doc, ws, elem } = dwe(parsed.options);
(0, output_1.emit)(await addFeatureResult(partstudios, doc, ws, elem, (0, advanced_1.buildFillet)({
name: stringOption(parsed.options, "name") ?? "Fillet",
radius: numberOption(parsed.options, "radius", 0.06),
filletType: stringOption(parsed.options, "type") ?? "EDGE",
...selection(parsed.options),
}), !parsed.options.noValidate));
return;
}
case "chamfer": {
const { doc, ws, elem } = dwe(parsed.options);
(0, output_1.emit)(await addFeatureResult(partstudios, doc, ws, elem, (0, advanced_1.buildChamfer)({
name: stringOption(parsed.options, "name") ?? "Chamfer",
width: numberOption(parsed.options, "width", 0.08),
chamferType: stringOption(parsed.options, "type") ?? "EQUAL_OFFSETS",
angle: optionalNumberOption(parsed.options, "angle"),
...selection(parsed.options),
}), !parsed.options.noValidate));
return;
}
case "shell": {
const { doc, ws, elem } = dwe(parsed.options);
(0, output_1.emit)(await addFeatureResult(partstudios, doc, ws, elem, (0, advanced_1.buildShell)({
name: stringOption(parsed.options, "name") ?? "Shell",
thickness: numberOption(parsed.options, "thickness", 0.125),
faceIds: splitList(stringOption(parsed.options, "faces")),
queryString: stringOption(parsed.options, "query"),
inward: !parsed.options.outward,
}), !parsed.options.noValidate));
return;
}
case "draft": {
const { doc, ws, elem } = dwe(parsed.options);
(0, output_1.emit)(await addFeatureResult(partstudios, doc, ws, elem, (0, advanced_1.buildDraft)({
name: stringOption(parsed.options, "name") ?? "Draft",
angle: numberOption(parsed.options, "angle", 3.0),
neutralPlaneQuery: requiredOption(parsed.options, "neutral"),
faceQuery: requiredOption(parsed.options, "faces"),
}), !parsed.options.noValidate));
return;
}
// ---- patterns / boolean ----
case "boolean": {
const { doc, ws, elem } = dwe(parsed.options);
(0, output_1.emit)(await addFeatureResult(partstudios, doc, ws, elem, (0, advanced_1.buildBoolean)({
name: stringOption(parsed.options, "name") ?? "Boolean",
operationType: stringOption(parsed.options, "op") ?? "UNION",
toolIds: splitList(stringOption(parsed.options, "toolIds")),
toolsQuery: stringOption(parsed.options, "tools"),
targetsQuery: stringOption(parsed.options, "targets"),
keepTools: Boolean(parsed.options.keepTools),
}), !parsed.options.noValidate));
return;
}
case "mirror": {
const { doc, ws, elem } = dwe(parsed.options);
(0, output_1.emit)(await addFeatureResult(partstudios, doc, ws, elem, (0, advanced_1.buildMirror)({
name: stringOption(parsed.options, "name") ?? "Mirror",
patternType: stringOption(parsed.options, "type") ?? "PART",
entitiesQuery: requiredOption(parsed.options, "entities"),
mirrorPlaneIds: splitList(stringOption(parsed.options, "planeIds")),
mirrorPlaneQuery: stringOption(parsed.options, "planeQuery"),
}), !parsed.options.noValidate));
return;
}
case "linear-pattern": {
const { doc, ws, elem } = dwe(parsed.options);
(0, output_1.emit)(await addFeatureResult(partstudios, doc, ws, elem, (0, advanced_1.buildLinearPattern)({
name: stringOption(parsed.options, "name") ?? "Linear Pattern",
patternType: stringOption(parsed.options, "type") ?? "PART",
entitiesQuery: requiredOption(parsed.options, "entities"),
directionIds: splitList(stringOption(parsed.options, "directionIds")),
directionQuery: stringOption(parsed.options, "direction"),
distance: requiredNumberOption(parsed.options, "distance"),
instanceCount: requiredNumberOption(parsed.options, "count"),
opposite: Boolean(parsed.options.opposite),
}), !parsed.options.noValidate));
return;
}
case "circular-pattern": {
const { doc, ws, elem } = dwe(parsed.options);
(0, output_1.emit)(await addFeatureResult(partstudios, doc, ws, elem, (0, advanced_1.buildCircularPattern)({
name: stringOption(parsed.options, "name") ?? "Circular Pattern",
patternType: stringOption(parsed.options, "type") ?? "PART",
entitiesQuery: requiredOption(parsed.options, "entities"),
axisIds: splitList(stringOption(parsed.options, "axisIds")),
axisQuery: stringOption(parsed.options, "axis"),
instanceCount: requiredNumberOption(parsed.options, "count"),
angle: numberOption(parsed.options, "angle", 360.0),
equalSpacing: !parsed.options.noEqualSpacing,
}), !parsed.options.noValidate));
return;
}
// ---- query / measure ----
case "measure": {
const { doc, ws, elem } = dwe(parsed.options);
(0, output_1.emit)(await partstudios.measure(doc, ws, elem));
return;
}
case "eval-featurescript": {
const { doc, ws, elem } = dwe(parsed.options);
const scriptFile = stringOption(parsed.options, "scriptFile");
const script = scriptFile ? (0, featurestudio_1.loadText)(undefined, scriptFile) : stringOption(parsed.options, "script") ?? "";
const resp = (await partstudios.evaluateFeatureScript(doc, ws, elem, script));
if (parsed.options.raw) {
(0, output_1.emit)(resp);
return;
}
const messages = (0, fsvalue_1.featurescriptMessages)(resp);
if (!isRecord(resp) || resp.result === null || resp.result === undefined) {
throw new output_1.CliError(String(messages[0]?.message ?? "FeatureScript evaluation failed"), messages, 1);
}
const out = { value: (0, fsvalue_1.decodeFsValue)(resp.result) };
if (resp.console)
out.console = resp.console;
if (messages.length)
out.warnings = messages;
(0, output_1.emit)(out);
return;
}
// ---- variables ----
case "get-variables": {
const { doc, ws, elem } = dwe(parsed.options);
(0, output_1.emit)(await variables.getVariables(doc, ws, elem));
return;
}
case "set-variable": {
const { doc, ws, elem } = dwe(parsed.options);
(0, output_1.emit)(await variables.setVariable(doc, ws, elem, requiredOption(parsed.options, "name"), requiredOption(parsed.options, "expression"), stringOption(parsed.options, "description")));
return;
}
// ---- configurations ----
case "get-configuration": {
const { doc, ws, elem } = dwe(parsed.options);
(0, output_1.emit)(await configurations.getConfiguration(doc, ws, elem));
return;
}
case "encode-configuration": {
const { doc, elem } = dwe(parsed.options);
const params = loadJsonArray(parsed.options, "params", "paramsFile");
(0, output_1.emit)(await configurations.encodeConfiguration(doc, elem, params));
return;
}
// ---- export / images ----
case "export-stl": {
const { doc, ws, elem } = dwe(parsed.options);
const out = requiredOption(parsed.options, "out");
const written = await exports.exportStl(doc, ws, elem, out, {
binary: !parsed.options.ascii,
resolution: stringOption(parsed.options, "resolution") ?? "medium",
configuration: stringOption(parsed.options, "configuration"),
});
(0, output_1.emit)({ written });
return;
}
case "export": {
const { doc, ws, elem } = dwe(parsed.options);
const out = requiredOption(parsed.options, "out");
const format = stringOption(parsed.options, "format") ?? "STEP";
const written = await exports.exportTranslation(doc, ws, elem, out, {
formatName: format,
elementKind: stringOption(parsed.options, "kind") ?? "partstudios",
configuration: stringOption(parsed.options, "configuration"),
});
(0, output_1.emit)({ written, format });
return;
}
case "thumbnail-info": {
const { doc, ws, elem } = dwe(parsed.options);
(0, output_1.emit)(await exports.thumbnailInfo(doc, ws, elem));
return;
}
case "get-thumbnail": {
const { doc, ws, elem } = dwe(parsed.options);
const out = requiredOption(parsed.options, "out");
const size = stringOption(parsed.options, "size") ?? "600x340";
const written = await exports.getThumbnail(doc, ws, elem, out, { size });
(0, output_1.emit)({ written, size });
return;
}
case "shaded-view": {
const { doc, ws, elem } = dwe(parsed.options);
const out = requiredOption(parsed.options, "out");
const written = await exports.shadedView(doc, ws, elem, out, {
elementKind: stringOption(parsed.options, "kind") ?? "partstudios",
width: numberOption(parsed.options, "width", 600),
height: numberOption(parsed.options, "height", 340),
viewMatrix: stringOption(parsed.options, "viewMatrix"),
showEdges: !parsed.options.noEdges,
configuration: stringOption(parsed.options, "configuration"),
});
(0, output_1.emit)({ written });
return;
}
// ---- assemblies ----
case "get-assembly": {
const { doc, ws, elem } = dwe(parsed.options);
(0, output_1.emit)(await docs.getAssembly(doc, ws, elem));
return;
}
case "create-assembly": {
const { doc, ws } = docWorkspace(parsed.options);
(0, output_1.emit)(await assemblies.createAssembly(doc, ws, requiredOption(parsed.options, "name")));
return;
}
case "insert-instance": {
const { doc, ws, elem } = dwe(parsed.options);
(0, output_1.emit)(await assemblies.insertInstance(doc, ws, elem, {
sourceDocumentId: stringOption(parsed.options, "srcDoc") ?? doc,
sourceElementId: requiredOption(parsed.options, "srcElem"),
partId: stringOption(parsed.options, "part"),
sourceVersionId: stringOption(parsed.options, "srcVersion"),
isAssembly: Boolean(parsed.options.isAssembly),
isWholePartStudio: Boolean(parsed.options.wholeStudio),
configuration: stringOption(parsed.options, "configuration"),
}));
return;
}
case "get-assembly-features": {
const { doc, ws, elem } = dwe(parsed.options);
(0, output_1.emit)(await assemblies.getFeatures(doc, ws, elem));
return;
}
case "assembly-add-feature": {
const { doc, ws, elem } = dwe(parsed.options);
(0, output_1.emit)(await addAssemblyFeature(assemblies, doc, ws, elem, requiredJson(parsed.options)));
return;
}
case "assembly-mate-connector": {
const { doc, ws, elem } = dwe(parsed.options);
(0, output_1.emit)(await addAssemblyFeature(assemblies, doc, ws, elem, (0, advanced_1.buildAssemblyMateConnector)({
name: stringOption(parsed.options, "name") ?? "Mate connector",
occurrenceId: requiredOption(parsed.options, "occurrence"),
inferenceType: stringOption(parsed.options, "inference") ?? "CENTROID",
})));
return;
}
case "assembly-mate": {
const { doc, ws, elem } = dwe(parsed.options);
(0, output_1.emit)(await addAssemblyFeature(assemblies, doc, ws, elem, (0, advanced_1.buildAssemblyMate)({
name: stringOption(parsed.options, "name") ?? "Mate",
mateType: stringOption(parsed.options, "type") ?? "FASTENED",
mateConnectorIds: splitList(requiredOption(parsed.options, "connectors")),
})));
return;
}
case "assembly-group": {
const { doc, ws, elem } = dwe(parsed.options);
(0, output_1.emit)(await addAssemblyFeature(assemblies, doc, ws, elem, (0, advanced_1.buildAssemblyGroup)({
name: stringOption(parsed.options, "name") ?? "Group",
occurrenceIds: splitList(requiredOption(parsed.options, "occurrences")),
})));
return;
}
case "get-bom": {
const { doc, ws, elem } = dwe(parsed.options);
(0, output_1.emit)(await assemblies.getBom(doc, ws, elem, { multiLevel: Boolean(parsed.options.multiLevel) }));
return;
}
case "assembly-mass-properties": {
const { doc, ws, elem } = dwe(parsed.options);
(0, output_1.emit)(await assemblies.massProperties(doc, ws, elem));
return;
}
case "delete-instance": {
const { doc, ws, elem } = dwe(parsed.options);
(0, output_1.emit)(await assemblies.deleteInstance(doc, ws, elem, requiredOption(parsed.options, "node")));
return;
}
case "transform-instance": {
const { doc, ws, elem } = dwe(parsed.options);
const paths = loadJsonArray(parsed.options, "paths", "pathsFile");
const transform = loadJsonArray(parsed.options, "transform", "transformFile");
(0, output_1.emit)(await assemblies.transformOccurrences(doc, ws, elem, paths, transform, { isRelative: !parsed.options.absolute }));
return;
}
// ---- drawings ----
case "create-drawing": {
const { doc, ws } = docWorkspace(parsed.options);
(0, output_1.emit)(await drawings.createDrawing(doc, ws, {
name: requiredOption(parsed.options, "name"),
sourceElementId: requiredOption(parsed.options, "srcElem"),
sourceVersionId: requiredOption(parsed.options, "srcVersion"),
sourceDocumentId: stringOption(parsed.options, "srcDoc"),
partId: stringOption(parsed.options, "part"),
}));
return;
}
case "get-drawing-views": {
const { doc, ws, elem } = dwe(parsed.options);
(0, output_1.emit)(await drawings.getViews(doc, ws, elem));
return;
}
case "export-drawing": {
const { doc, ws, elem } = dwe(parsed.options);
const out = requiredOption(parsed.options, "out");
const format = stringOption(parsed.options, "format") ?? "PDF";
const written = await exports.exportTranslation(doc, ws, elem, out, { formatName: format, elementKind: "drawings" });
(0, output_1.emit)({ written, format });
return;
}
// ---- metadata ----
case "get-metadata": {
const { doc, ws, elem } = dwe(parsed.options);
const part = stringOption(parsed.options, "part");
(0, output_1.emit)(part ? await metadata.getPartMetadata(doc, ws, elem, part) : await metadata.getElementMetadata(doc, ws, elem));
return;
}
case "set-metadata": {
const { doc, ws, elem } = dwe(parsed.options);
const properties = loadJsonArray(parsed.options, "properties", "propertiesFile");
(0, output_1.emit)(await metadata.setElementMetadata(doc, ws, elem, properties, stringOption(parsed.options, "part")));
return;
}
}
}
function selection(options) {
return {
edgeIds: splitList(stringOption(options, "edges")),
queryString: stringOption(options, "query"),
featureId: stringOption(options, "feature"),
selectAll: Boolean(options.all),
circular: Boolean(options.circular),
};
}
function splitList(value) {
if (!value)
return [];
return value
.split(",")
.map((item) => item.trim())
.filter((item) => item.length > 0);
}
function loadJsonArray(options, inlineKey, fileKey) {
let parsed;
try {
parsed = (0, partstudio_1.loadJson)(stringOption(options, inlineKey), stringOption(options, fileKey));
}
catch (error) {
throw new output_1.CliError(error instanceof Error ? error.message : String(error), null, 2);
}
if (!Array.isArray(parsed))
throw new output_1.CliError(`--${inlineKey} must be a JSON array`, null, 2);
return parsed;
}
async function addAssemblyFeature(assemblies, doc, ws, elem, feature) {
const response = await assemblies.addFeature(doc, ws, elem, feature);
const featureId = isRecord(response) && isRecord(response.feature) ? response.feature.featureId ?? null : null;
return { featureId, response };
}
async function addFeatureResult(partstudios, doc, ws, elem, feature, validate) {

@@ -606,3 +1088,3 @@ const response = await partstudios.addFeature(doc, ws, elem, feature);

function printHelp() {
console.log(`onshape
console.log(`onshape — command-line automation for Onshape CAD

@@ -612,47 +1094,52 @@ Usage:

Commands:
login
logout
config set|show|path|clear
list-documents
search-documents
get-document
get-document-summary
create-document
delete-document
update-document
get-elements
find-part-studios
get-workspaces
list-versions
create-version
get-parts
get-features
get-feature-specs
get-sketch-info
get-body-details
create-part-studio
delete-feature
delete-element
create-feature-studio
get-feature-studio
set-feature-studio
get-feature-studio-specs
add-feature
update-feature
rollback
sketch-circle
sketch-circle-axis
sketch-candy-cane-path
extrude
revolve
sweep
offset-plane
boolean-union
validate-partstudio
get-edges
find-circular-edges
find-edges-by-feature
mass-properties
Credentials:
login logout config set|show|path|clear
Documents & discovery:
list-documents search-documents get-document get-document-summary
create-document delete-document update-document get-elements
find-part-studios get-workspaces list-versions create-version
get-parts get-features get-feature-specs get-sketch-info
get-body-details get-assembly
Part studio management:
create-part-studio delete-feature delete-element
add-feature update-feature rollback validate-partstudio
Sketching:
create-sketch sketch-rectangle sketch-circle sketch-line
sketch-circle-axis sketch-candy-cane-path
Solids & modifiers:
extrude hole thicken revolve sweep draft fillet chamfer shell
Patterns & boolean:
boolean boolean-union mirror linear-pattern circular-pattern offset-plane
Geometry / measure:
get-edges find-circular-edges find-edges-by-feature measure
eval-featurescript mass-properties
Variables & configurations:
get-variables set-variable get-configuration encode-configuration
Export & images:
export-stl export thumbnail-info get-thumbnail shaded-view
Assemblies:
create-assembly insert-instance get-assembly-features assembly-add-feature
assembly-mate-connector assembly-mate assembly-group get-bom
assembly-mass-properties delete-instance transform-instance
Drawings:
create-drawing get-drawing-views export-drawing
Feature studios:
create-feature-studio get-feature-studio set-feature-studio get-feature-studio-specs
Metadata:
get-metadata set-metadata
Every command prints {"ok": true, "result": ...} or {"ok": false, "error": ..., "detail": ...}.
`);
}
+2
-2
{
"name": "onshape",
"version": "0.1.4",
"description": "Node.js CLI for Onshape CAD automation with the same JSON contract as onshape-cli.",
"version": "0.2.0",
"description": "Node.js CLI for Onshape CAD automation — full command parity with onshape-cli (same flags + JSON contract).",
"license": "MIT",

@@ -6,0 +6,0 @@ "type": "commonjs",