A batch-export CLI for Onshape that generates multiple CAD variants from parametric models using property permutations, with parallel processing, local format conversion, and 3D preview generation.
| Requirement | Purpose | Install |
|---|---|---|
| Bun β₯ 1.1 | Runtime & package manager | curl -fsSL https://bun.sh/install \| bash |
| Onshape account | Source CAD models | onshape.com |
| Python 3.7+ (optional) | Local STEP / 3MF conversion | python --version |
| Playwright Chromium (optional) | 3D preview generation | bunx playwright install chromium |
Install Python dependencies for local conversion:
pip install -r requirements.txt
# Clone and install
bun install
# Install + set up Playwright browser (for preview)
bun run setup
The tool needs Onshape API keys. Get them at dev-portal.onshape.com/keys.
Recommended β .env file:
ONSHAPE_ACCESS_KEY=your_access_key_here
ONSHAPE_SECRET_KEY=your_secret_key_here
On first run with no .env, the tool will prompt you to enter credentials and save them automatically.
Credentials are never written back to
config.jsonto prevent accidental secrets in version control.
bun start
On first launch with no models configured, the tool walks you straight into adding your first model.
Select a model to process:
> my-part
another-part
β Add New Model
Exit
| Action | Description |
|---|---|
| π¦ Export | Shows an export preview (total files, skips, new) then runs all jobs |
| π Convert | Batch-converts existing STLs in dist/<model>/STL/ to STEP / 3MF locally |
| πΌοΈ Preview | Renders a 5Γ5 grid PNG of all STL variants using Playwright |
| βοΈ Permutations | Fetches the Onshape config schema and guides you through setting up value arrays |
| βοΈ Edit Details | Set preview appearance (color, metalness, roughness) and transforms (rotation, translation) |
| β Delete Model | Removes model from config.json |
The Add New Model form asks for:
gridfinity-bin)Select βοΈ Permutations on any model to:
10 mm, 20 mm, 30 mm)The Cartesian product of all selected values becomes a named permutation group in config.json.
{
"settings": {
"maxConcurrent": 3
},
"credentials": {},
"models": [
{
"name": "my-part",
"url": "https://cad.onshape.com/documents/<doc>/w/<ws>/e/<elem>",
"formats": ["STL", "STEP", "3MF"],
"propSets": [],
"permutations": [
{
"name": "sizes",
"props": {
"Width": ["50 mm", "75 mm", "100 mm"],
"Height": ["30 mm", "45 mm"],
"HasLid": [true, false]
}
}
],
"rotation": { "x": -90, "y": 0, "z": 0 },
"translation": { "x": 0, "y": 0, "z": 0 },
"style": {
"color": "#2c3e50",
"metalness": 0.8,
"roughness": 0.15
}
}
]
}
| Field | Type | Description |
|---|---|---|
settings.maxConcurrent |
number |
Max parallel export / conversion jobs. Default 3. |
models[].name |
string |
Unique model identifier. Used for output folder and file prefix. |
models[].url |
string |
Onshape part studio URL. |
models[].formats |
string[] |
Export formats: "STL", "STEP", "3MF", "IGES". |
models[].propSets |
object[] |
Static list of pre-defined property combinations. Each object = one export. |
models[].permutations |
PermGroup[] |
Dynamic permutation groups. Each groupβs props are expanded into a Cartesian product. |
models[].rotation |
{x,y,z} |
Rotation in degrees applied to the 3D preview. |
models[].translation |
{x,y,z} |
Translation applied to the 3D preview. |
models[].style.color |
string |
Hex color for preview material. |
models[].style.metalness |
0β1 |
Preview material metalness. |
models[].style.roughness |
0β1 |
Preview material roughness. |
models[].style.emissive |
string |
Emissive color hex. |
models[].style.emissiveIntensity |
number |
Emissive light intensity. |
propSets vs permutationspropSets β a static list where each object is one exact export variant:
"propSets": [
{ "Width": "50 mm", "Height": "30 mm" },
{ "Width": "75 mm", "Height": "45 mm" }
]
permutations β an array of named groups, each containing parameter β values arrays. The tool generates every combination (Cartesian product) of each groupβs values:
"permutations": [
{
"name": "small",
"props": {
"Width": ["50 mm", "75 mm"],
"Height": ["30 mm", "45 mm"]
}
}
]
This produces 2 Γ 2 = 4 export variants from the small group.
Multiple permutation groups let you define independent sets of variants:
"permutations": [
{
"name": "no-lid",
"props": {
"Width": ["50 mm", "75 mm"],
"Height": ["30 mm", "45 mm"]
}
},
{
"name": "with-lid",
"props": {
"Width": ["50 mm", "75 mm"],
"Height": ["30 mm", "45 mm"],
"HasLid": [true]
}
}
]
Both groups are expanded and merged. During export, propSets and all expanded permutation groups are combined into one flat list.
"props": {
"Connector": ["Large", "Small"],
"Height": ["15 mm", "30 mm"],
"HasBottom": [true, false]
}
Generates 2 Γ 2 Γ 2 = 8 combinations:
{ Connector: "Large", Height: "15 mm", HasBottom: true }
{ Connector: "Large", Height: "15 mm", HasBottom: false }
{ Connector: "Large", Height: "30 mm", HasBottom: true }
{ Connector: "Large", Height: "30 mm", HasBottom: false }
{ Connector: "Small", Height: "15 mm", HasBottom: true }
...
| Format | Method | Notes |
|---|---|---|
| STL | Onshape API | Fetched directly |
| STEP | Local Python converter | Converts from STL; requires Python + dependencies |
| 3MF | Local Python converter | Converts from STL; requires Python + trimesh |
| IGES | Onshape API | Fetched directly |
/api/partstudios/.../translations with format and configuration string/api/translations/{id} every 5 seconds until requestState === "DONE"/api/documents/.../externaldata/{id}STEP and 3MF are converted locally from STL using a Python helper script (src/local_converter.py), avoiding Onshape API calls for derived formats. The Python script attempts up to 5 reconstruction strategies (gmsh, trimesh, cadquery, etc.).
Files that already exist on disk are skipped. Pass LOG_LEVEL=silly to see skip messages:
LOG_LEVEL=silly bun start
--convert <folder>Batch-convert all STL files in a folder to STEP and 3MF locally, then exit:
bun start --convert ./dist/my-part/STL
--preview <folder>Generate a preview.png for all STL files in a folder, then exit. Automatically looks up model config (rotation, translation, style) if the path matches a model name:
bun start --preview ./dist/my-part/STL
LOG_LEVEL=sillyShow verbose output including skipped files:
LOG_LEVEL=silly bun start
dist/
βββ my-part/
βββ STL/
β βββ my-part_Width_50-mm_Height_30-mm.stl
β βββ my-part_Width_50-mm_Height_45-mm.stl
β βββ ...
βββ STEP/
β βββ my-part_Width_50-mm_Height_30-mm.step
β βββ ...
βββ 3MF/
β βββ ...
βββ preview.png
<model-name>_<Param1>_<value1>_<Param2>_<value2>...<ext>
50 mm β 50-mm)&, =, ;) β underscores.stl, .step, .3mf)bun run build:win # β build/onshape-exporter.exe
bun run build:linux # β build/onshape-exporter
bun run build:mac # β build/onshape-exporter-mac
The binary includes all JS dependencies. Playwright is loaded dynamically at runtime β install it separately on the target machine if you need preview:
bun add playwright
bunx playwright install chromium
bun run release # patch bump (1.0.0 β 1.0.1)
bun run release minor # minor bump (1.0.0 β 1.1.0)
bun run release major # major bump (1.0.0 β 2.0.0)
bun run release 1.2.3 # exact version
The release script:
package.json and commitslocal_converter.py, requirements.txt, .env.example, README.md, and LICENSE.zip (Windows / macOS) or .tar.gz (Linux)Requires the GitHub CLI (gh) to be installed and authenticated.