DFM Platform
Manufacturability analysis as an API
A general-purpose manufacturability engine: hand it a STEP / glTF, get back a recognized feature graph (holes, bosses, pockets, fillets, ...), per-vertex thickness / clearance / draft fields, and rule-pack verdicts for whichever process you’re evaluating against — CNC, casting, injection molding, additive, sheet metal, or a rule pack you author yourself.
What it does
Three analysis layers, all from one upload:
Layer 1
Feature recognition
Every face on the part gets a primitive role. Recognized features (holes, bosses, slots, fins, pockets, protrusions, rounds, fillets) are reported with their dimensions and member faces.
Layer 2
Per-vertex measurements
Local wall thickness, clearance to the closest opposite wall, signed draft angle against the chosen pull direction — available per-vertex, mappable back to any face under the cursor.
Layer 3
Rule modules
Declarative rules combine selectors (which features to inspect), metrics (what to measure), and tier ladders (how the value maps to pass / warning / fail). Ship with shipped packs, or author your own.
Use cases
- In-house quoting / procurement. Auto-flag manufacturability issues on inbound RFQs before a human looks at them.
- CAD plug-ins. Run analysis on save and surface findings inline; render the per-face severity over the live model.
- Custom design guidelines. Encode your house DFM rules (“every wall ≥ 2.0 mm; every hole < Ø1.5 mm reviewed by an engineer”) once, evaluate against every design.
- AI agents. The MCP server gives an LLM tools to upload, analyze, and author rule packs in-conversation.
Recognized features
Every POST /dfm/analyze result includes a features.features[] array; each entry has a kind,fid (face ID), and dimensions where applicable. Use related_fids to highlight an entire composite feature on the model.
| Feature kind | Description |
|---|---|
| Wall | Plane-bounded face that forms an outer or inner wall of the part. |
| Slab | Plate-like body region — a thick wall whose two faces are antiparallel planes. |
| Hole | Cylindrical (or conical) inward-pointing feature. Carries diameter, depth, and stages for stepped holes. |
| Boss | Cylindrical (or conical) outward-pointing feature. Carries diameter, height, and base relief geometry. |
| Slot | Wall pair forming a through or blind slot. Carries width, depth, and the bridging floor/ceiling face. |
| Fin | Wall pair forming a thin protrusion. Carries thickness and height. |
| Aggregate inward feature whose member faces line a recess. Members include floor, walls, and any sub-features (holes, bosses) inside. | |
| Protrusion | Aggregate outward feature whose member faces form a raised region attached to the part body. |
| Round | Convex blend (negative bevel) running along an external edge. |
| Fillet | Concave blend (positive radius) running along an internal corner. |
| Bend | Cylindrical region between two planar walls — characteristic of sheet-metal bends. |
Shipped rule modules
Five rule packs ship out of the box, each with one or more configurations (e.g. cnc:5-axis versus cnc:3-axis). Hit GET /dfm/modules to fetch their full specs (tier thresholds, parameters, message templates) suitable for cloning into a custom pack.
CNC Machining
key: cncConfigurations: 3-axis, 5-axisSubtractive milling. Rules govern tool reach, internal radii, undercut access, and hole proportions.
Rules
| solid_body | Solid body | Internal hollows are not machinable — the toolpath cannot reach a sealed inner cavity. |
| setup_count | Required setups | Number of workpiece orientations the configuration needs to reach every face. 3-axis: cardinal-only spindle; 5-axis: tilted access cone widens what one setup covers. |
| min_wall_thickness | Min wall thickness | Walls thinner than the configured floor (chatter / stiffness). |
| min_internal_radius | Internal corner radius | Fillets must exceed tool radius — an end-mill cannot cut a sharper inside corner. |
| tool_clearance | Tool clearance in slots / fins | Slot or fin clearance must admit the tool diameter. |
| undercut_access | Undercut access | 3-axis: any undercut is unmachinable from the chosen pull. 5-axis: undercuts are usually accessible but flagged for setup. |
| tool_inaccessible | Tool-inaccessible region | Face is occluded along every candidate approach axis. 3-axis: hard fail; 5-axis: warning. |
| thread_candidates | Thread candidates | Holes whose diameter matches a standard tap-drill, and bosses whose diameter matches a standard external major diameter — flagged so the operator can confirm intended threads. |
| hole_depth_to_diameter | Hole depth / diameter | Deep holes need peck drilling or special tooling. |
| pocket_aspect | Pocket aspect / tool reach | Tight pockets may not admit the configured tool. |
Casting
key: castingConfigurations: sand, investmentCast metal processes — sand and investment. Rules govern wall thickness, mold release, section uniformity, and internal corner stresses.
Rules
| solid_body | Solid body | Casting requires a single closed shell — internal hollow cavities cannot be reproduced by mold filling. |
| min_wall_thickness | Min wall thickness | Cast walls below process minimum risk short fills and cold shuts. |
| thickness_uniformity | Section uniformity | Large thick→thin transitions cause shrinkage cavities and hot spots. |
| internal_fillet | Internal fillet radius | Sharp internal corners crack on cooling. |
| sharp_internal_corner | Sharp internal corners | Concave edges with no fillet bridging the dihedral are stress risers. |
Injection Molding
key: injectionConfigurations: standard, side-actionPlastic injection molding. The strictest geometric rule-set: uniform walls, mandatory draft, no undercuts without side actions.
Rules
| solid_body | Solid body | Injection requires a single closed shell — internal hollow cavities are not reproducible by the molding cycle. |
| applicability | Applicability — moldable shell | Injection requires a thin-walled shell. FAILs solid bulk parts and warns on highly non-uniform thickness. |
| wall_thickness | Wall thickness uniformity | Walls should sit within tolerance of a single nominal value to avoid sink marks and uneven cooling. |
| draft_required | Draft for ejection | Every face needs positive draft from the pull direction. Undercuts FAIL unless side actions are permitted. |
| no_undercuts | No undercuts (gate) | Yes/no gate independent of draft thresholds. Useful as a quick tooling-cost flag. |
| boss_proportions | Boss h / D | Tall thin bosses warp during cooling. |
| rib_thickness | Rib thickness | Ribs / fins should be ≤ ~60% of wall thickness to avoid sink marks. |
Additive Manufacturing
key: additiveConfigurations: dmls, sla, fdmLayer-wise build processes (DMLS, SLA, FDM). Rules govern min feature size, overhang / self-support angle, and trapped-material risk.
Rules
| min_wall_thickness | Min wall thickness | Process-specific minimum (laser spot / extrusion width / cured-layer thickness). |
| overhang_angle | Overhang / self-support | Faces below the self-support threshold need supports; reverse-facing faces (vs build direction) often FAIL. |
| trapped_volume | Trapped material | Closed cavities trap unfused powder (DMLS) or uncured resin (SLA). Each pocket needs a drain / escape hole. |
| min_feature_size | Min feature size | Features below the process resolution will not print cleanly. |
Sheet Metal
key: sheet-metalConfigurations: mild-steel-1.5mm, aluminium-2.0mm, stainless-1.0mmPunched / bent sheet stock. Rules govern uniform gauge, bend radii, and hole proportions.
Rules
| solid_body | Solid body | Sheet metal requires a single closed shell — there is no process route to produce an internal hollow from sheet stock. |
| applicability | Applicability — sheet shell | Sheet metal requires a constant-thickness, plane-dominated part. FAILs prismatic / cast / printed 3D parts that aren't single-gauge shells. |
| uniform_thickness | Uniform sheet gauge | Sheet metal is constant-thickness. Departure from nominal gauge means the part isn't a single-sheet design. |
| bend_radius | Min inside bend radius | Inside radius < k×sheet thickness cracks during forming. |
| hole_diameter | Min hole diameter | Punched holes need diameter ≥ sheet thickness; smaller holes break punches or require EDM. |
| hole_to_edge | Hole-to-edge distance | Holes too close to an edge tear out during punching. |
Adaptive meshing
By default, all DFM endpoints resolve their meshing knobs automatically. The backend reads the part’s analytic bounding-box diagonal (sub-second, no meshing required) and scales max_edge and curvature so triangle count stays roughly flat regardless of part size — small parts keep mm-scale features sharp, big parts don’t buy a 30-minute mesh time.
# Default: bbox-driven adaptive density
curl -X POST .../analyze -F "file=@part.step" ...
# Override either / both knobs (gmsh max edge mm, curvature elements/2π)
curl -X POST .../analyze -F "file=@part.step" \
-F "max_edge=1.4" -F "curvature=12" .../dfm/quick-info
Fast geometry-only pass. Sub-second on most parts. Metered under dfm.quick_info.
Request
curl -X POST https://digitalmetal.io/api/v1/dfm/quick-info \
-H "X-API-Key: $DM_KEY" \
-F "file=@part.step"Response 200
{
"geometry": {
"volume_cm3": 124.50,
"surface_area_cm2": 84.20,
"bounding_box_mm": { "x": 48.30, "y": 32.10, "z": 21.60 }
},
"glb_url": "https://blob.vercel-storage.com/dfm/.../preview.glb",
"stats": { "n_features": 320, "n_vertices": 38693, "n_triangles": 63960 }
}/dfm/analyze
Submit a full DFM analysis. Metered under dfm.analyze. Returns 202 with a job_id; poll GET /dfm/jobs/{id}, then read GET /dfm/jobs/{id}/result.
Form fields
file— .step / .stp / .gltf / .glb, max 25 MBblob_url+filename— alternative tofile; the backend fetches server-sidequote_id(optional) — attach this analysis to an existing quote rowmax_edge(optional, defaultauto)curvature(optional, defaultauto)pull(optional, defaultcardinal) —cardinal|hemisphere|x,y,zalpha_min(deg, default 0.5) — undercut thresholdalpha_warn(deg, default 3.0) — low-draft warning threshold
Response 202
{ "job_id": "ae91d057fa33", "status": "queued" }/dfm/jobs/{id}
Job status. Poll every 1–2 seconds.
{
"job_id": "ae91d057fa33",
"status": "queued" | "running" | "done" | "error",
"error": null | string,
"filename": "part.step",
"log": [ "...", "...", "..." ]
}/dfm/jobs/{id}/result
Full result: recognized features, every shipped module’s rule output, GLB URLs.
{
"job_id": "ae91d057fa33",
"status": "done",
"features": {
"roles_by_fid": { "face_0007": 1, "face_0008": 4, ... },
"role_names": { "0": "Unclassified", "1": "Hole wall", ... },
"features": [
{ "fid": "face_0007", "kind": "HOLE", "diameter_mm": 4.0,
"depth_mm": 8.0, "related_fids": [...] },
...
],
"manufacturability": {
"pull_dir": [0, 0, 1], "alpha_min_deg": 0.5,
"n_undercuts": 2, "n_low_draft": 4
}
},
"modules": {
"modules": [
{ "module_key": "cnc", "configurations": [
{ "configuration_key": "5-axis",
"result": { "overall_severity": "warning",
"summary": { "pass": 7, "warning": 2, "fail": 0 },
"rules": [ ... ] } } ] },
...
]
},
"artifacts": {
"thickness_glb": "https://...",
"clearance_glb": "https://...",
"features_glb": "https://...",
"draft_glb": "https://...",
"features_json": "https://...",
"modules_json": "https://..."
}
}/dfm/jobs/{id}/recompute
Re-run draft + recognition + modules with a new pull direction. Mesh is cached.
# pick a different pull
curl -X POST .../jobs/ae91d057fa33/recompute \
-H "X-API-Key: $DM_KEY" \
-F "pull=0,0,-1"Custom rule modules
The rule engine is exposed as a declarative grammar — selectors pick a subset of recognized features, a metric is read from each, and a tier ladder maps the metric to pass / warning / fail. Author your own module spec and run it against any completed analysis, no redeploy required.
/dfm/rule-schema
Catalog of selectors, metrics, comparison ops, RHS forms. Read this before authoring.
{
"selector_sets": ["walls", "holes", "bosses", "fillets", "rounds",
"pockets", "slots", "fins", "all_features", ...],
"metrics_per_feature": ["thickness.min", "thickness.mean", "draft.min",
"hole.diameter", "boss.height_over_diameter",
"axis_angle_to_pull", "area_mm2", ...],
"metrics_whole_part": ["part.thickness.cv", "part.undercut_count", ...],
"comparison_ops": ["lt","le","gt","ge","eq","ne"],
"severity_levels": ["pass","warning","fail"],
"rhs_forms": [
{ "shape": "<number>", "doc": "literal" },
{ "shape": "{\"param\":\"<name>\"}", "doc": "configuration parameter" },
{ "shape": "{\"param\":\"<name>\",\"factor\":<x>}", "doc": "param × factor" }
]
}/dfm/modules
Every shipped module spec, suitable for cloning + editing into a custom module.
/dfm/jobs/{id}/modules/eval
Evaluate a caller-supplied module spec against a completed job. Same return shape as a single module entry in GET /jobs/{id}/result.
Request body (application/json)
{
"module": {
"key": "house",
"name": "House DFM rules",
"configurations": {
"default": { "name": "Default", "parameters": { "min_wall_mm": 1.5 } }
},
"rules": [{
"key": "min_wall",
"name": "Minimum wall thickness",
"scope": "per_feature",
"selector": { "set": "walls" },
"metric": "thickness.min",
"tiers": [
{ "level": "fail", "op": "lt", "rhs": { "param": "min_wall_mm", "factor": 0.7 } },
{ "level": "warning", "op": "lt", "rhs": { "param": "min_wall_mm" } },
{ "level": "pass" }
],
"messages": {
"fail": "{fid}: {metric:.2f} mm < {threshold:.2f} mm (severe)",
"warning": "{fid}: {metric:.2f} mm < {threshold:.2f} mm",
"pass": "all {n_inspected} walls ≥ {threshold:.2f} mm"
}
}]
},
"configuration_key": "default",
"overrides": { "min_wall_mm": 2.0 }
}End-to-end example
JOB=$(curl -sS -X POST https://digitalmetal.io/api/v1/dfm/analyze \
-H "X-API-Key: $DM_KEY" -F "file=@part.step" | jq -r .job_id)
while [ "$(curl -sS https://digitalmetal.io/api/v1/dfm/jobs/$JOB \
-H "X-API-Key: $DM_KEY" | jq -r .status)" != "done" ]; do
sleep 2
done
curl -sS https://digitalmetal.io/api/v1/dfm/jobs/$JOB/result \
-H "X-API-Key: $DM_KEY" \
| jq '.modules.modules[] | { module: .module_key,
verdicts: [.configurations[] | { conf: .configuration_key,
severity: .result.overall_severity }] }'Error codes
401— missing / revoked / malformed API key402— metered quota exhausted on the free tier; upgrade to add overage404— job_id not found (or expired from cache)413— file exceeds 25 MB422— unsupported extension / build-volume violation / invalid module spec504— meshing exceeded 5-minute timeout (try a coarsermax_edgeoverride)