| "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", | ||
| }, | ||
| ], | ||
| }, | ||
| }; | ||
| } |
+115
| # 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 { |
+541
-54
@@ -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", |
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
No README
QualityPackage does not have a README. This may indicate a failed publish or a low quality package.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
136452
93.47%20
66.67%3049
72.65%0
-100%116
Infinity%1
-50%23
15%