Skip to main content
Glama
TechHandPro

techhand-print-fab

by TechHandPro
README.md
# techhand-print-fab

Shareable MCP server for original parts: idea → parametric model → STL/3MF, plus FDM notes, Bambu X1 Carbon starting settings, and a rough BOM.

OpenSCAD is the primary model. A CadQuery script is written beside it and is not executed here. The server never starts a printer. TNT is not required.

## Install

Python 3.10+. From a checkout:

```bash
python3 -m pip install -e .
```

That installs the `techhand-print-fab` command and pins `mcp` to the 2.x line (`requirements.txt`). Use `python3 -m pip install -e ".[dev]"` when you also want pytest (`requirements-dev.txt`).

OpenSCAD is optional for the built-in kinds (box, plate, mount plate, cylinder, tube, L-bracket). When `openscad` is on `PATH`, or `OPENSCAD_BIN` points at it, STL/3MF export shells out to it and boolean holes are in the mesh. Without it, those kinds still export from a built-in mesh. The bundled trainer grip files are `custom_scad` and need OpenSCAD to mesh. See the dogfood section.

CadQuery is not a dependency. `model.py` is a script for a machine that has CadQuery.

## Run

stdio (Cursor and most local MCP clients):

```bash
techhand-print-fab
```

Streamable HTTP, for a connector that wants a URL. Default bind is loopback only:

```bash
techhand-print-fab --http --host 127.0.0.1 --port 8765
```

The MCP path is `/mcp`.

Projects live in `FAB_DATA_DIR`, or `~/.local/share/techhand-print-fab` when that is unset. `--data-dir` overrides both.

## Install as an MCP connector

Cursor, after `techhand-print-fab` is on `PATH`. This is the shape in `examples/cursor-mcp.json`:

```json
{
  "mcpServers": {
    "techhand-print-fab": {
      "command": "techhand-print-fab"
    }
  }
}
```

Pin a data directory and an import root (for an existing `.scad` tree such as a grip CAD checkout):

```json
{
  "mcpServers": {
    "techhand-print-fab": {
      "command": "techhand-print-fab",
      "env": {
        "FAB_DATA_DIR": "/home/me/.local/share/techhand-print-fab",
        "FAB_IMPORT_ROOTS": "/path/to/cad-v0"
      }
    }
  }
}
```

From a checkout before the script is on `PATH`, point Python at `src`:

```json
{
  "mcpServers": {
    "techhand-print-fab": {
      "command": "python3",
      "args": ["-m", "techhand_print_fab"],
      "env": {
        "PYTHONPATH": "/path/to/techhand-print-fab/src"
      }
    }
  }
}
```

Grok Bot or any client that speaks Streamable HTTP: run `techhand-print-fab --http --host 127.0.0.1 --port 8765` and point the connector at `http://127.0.0.1:8765/mcp`. Do not bind a public interface unless you have your own auth in front. This server has none.

Nothing in that setup calls TNT.

## Tools

| Tool | What it does |
| --- | --- |
| `fab_create_project` | Local project. Units are millimeters. |
| `fab_list_parts` | Parts already generated. |
| `fab_param_model` | OpenSCAD (`model.scad`) and, by default, a CadQuery script (`model.py`) from params JSON. |
| `fab_export_stl` | Binary STL on disk. |
| `fab_export_3mf` | Geometry-only 3MF. Not a Bambu/Orca project and not a toolpath. |
| `fab_dfm_check` | Wall, hole, overhang, clearance, and 256 mm bed heuristics. |
| `fab_x1c_profile_notes` | Starting notes for PETG, ASA, TPU, PA, and PA-CF. |
| `fab_bom_sketch` | Filament mass and a fastener guess from hole diameters. |

`backend` on `fab_param_model` is `openscad`, `cadquery`, or `both` (default). OpenSCAD stays the primary file whenever it is written.

### Params

`kind` is `box`, `cylinder`, `tube`, `plate`, `l_bracket`, `mount_plate`, or `custom_scad`.

Prismatic parts use a corner at the origin: `+X` length, `+Y` width, `+Z` height. Round parts are centered on Z. An L bracket is a base plate plus an upright on the back edge (`+Y`).

`face` on a hole is `base` (drill along Z) or `upright` (L bracket only; `x_mm` is along the length and `y_mm` is the Z height). Cylinder hole `x_mm` / `y_mm` are offsets from the axis.

Example (`examples/l-bracket.params.json`):

```json
{
  "kind": "l_bracket",
  "length_mm": 40,
  "width_mm": 30,
  "height_mm": 25,
  "thickness_mm": 3,
  "material": "PETG",
  "clearance_mm": 0.3,
  "holes": [
    {"diameter_mm": 3.4, "x_mm": 12, "y_mm": 10, "face": "base"}
  ]
}
```

`custom_scad` takes `scad_body` or `source_path`. `source_path` may be `cad-v0` (the bundled trainer grip) or a `.scad` file or directory under the project folder or `FAB_IMPORT_ROOTS` (`os.pathsep`-separated). A directory becomes one part per file, named `{part_name}-{relative-stem}`, up to 50 files. A relative `include <file.scad>` inside that directory is inlined. Absolute includes, `../`, `use`, and `import()` are rejected.

Call shape:

1. `fab_create_project` with a name.
2. `fab_param_model` with `project_id`, `part_name`, and `params`.
3. `fab_export_stl` / `fab_export_3mf`.
4. `fab_dfm_check`, `fab_x1c_profile_notes`, `fab_bom_sketch` as needed.

`output_path` on export must stay inside the part directory or `FAB_EXPORT_ROOTS`.

## PRINT dogfood: trainer grip CAD v0

Bundled at `src/techhand_print_fab/cad_v0/` and installed with the package. Training grip block only. `source_path` `cad-v0` needs no `FAB_IMPORT_ROOTS` entry.

| File | Slug when `part_name` is `trainer` |
| --- | --- |
| `grip_shell.scad` | `trainer-grip-shell` |
| `grip_shell_left.scad` | `trainer-grip-shell-left` |
| `grip_shell_right.scad` | `trainer-grip-shell-right` |
| `backstrap_insert.scad` | `trainer-backstrap-insert` |
| `laser_clamp.scad` | `trainer-laser-clamp` |
| `spring_seat.scad` | `trainer-spring-seat` |
| `trigger_lever.scad` | `trainer-trigger-lever` |
| `assembly_preview.scad` | `trainer-assembly-preview` |

Left and right shells `include <grip_shell.scad>`. The import step inlines that file. `assembly_preview.scad` is a pose stub (`import_grip` is not a module in this set) and is not the STL target.

1. `fab_create_project` with `name` `Trainer grip v0` and a short description of the original trainer block.
2. `fab_param_model` with that `project_id`, `part_name` `trainer`, `source_path` `cad-v0`, and `params` `{"material": "PETG"}`.
3. `fab_list_parts` returns the eight slugs above.
4. `fab_dfm_check` on `trainer-grip-shell`. Header numbers are read from the file: wall about 2.4 mm, clearance 0.25 mm, box 110 × 32 × 120 mm. `fab_dfm_check` on `trainer-laser-clamp` warns on the 0.15 mm diametral clearance.
5. `fab_export_stl` on `trainer-grip-shell`.

Step 5 needs OpenSCAD. These parts are not a built-in primitive. The server still writes `model.scad`. If `openscad` is missing, the tool returns an error that names OpenSCAD and does not report a mesh or a printer job. Install OpenSCAD, or set `OPENSCAD_BIN`, and call `fab_export_stl` again. `fab_export_3mf` is the same gate. Box, plate, and the other primitive kinds export without OpenSCAD.

Prefer Push stays held. Ticket attach stays the optional `extras/tnt` package.

## Guardrails

The server refuses a 1:1 copy of a proprietary commercial product.

- `reproduction` is `original` (default), `interoperable_fixture`, or `proprietary_clone`.
- `proprietary_clone` is always refused, before any file is written.
- Phrases such as "exact copy", "1:1 clone", "counterfeit", "knock-off", and "copy the commercial product" are refused on names, intent, notes, and imported OpenSCAD.
- An original bracket, a fixture you designed, or geometry from your own measurements is in scope. Saying "1:1 in millimeters" about your own sketch is not a clone request.

Every tool result sets `dry_fire: true` and `printer_dispatched: false`. Export copy says the mesh was written and no printer job was submitted. Profile notes are starting temperatures and habits for a person to type into OrcaSlicer or Bambu Studio. They are not an official Bambu profile, they are not applied to a slicer, and they are not a completed print. Confirm them against the filament datasheet.

DFM numbers assume a 0.4 mm nozzle and a 256 mm X1 Carbon build axis. They do not inspect a sliced gcode file.

## Optional TNT bridge

Default `pip install` of this package does not include ticket attach and does not import a TNT client.

The extra lives in `extras/tnt` and registers `fab_attach_to_ticket` only when both of these are true:

1. `techhand-print-fab-tnt` is installed (entry point group `techhand_print_fab.bridges`).
2. `TECHHAND_FAB_ENABLE_TNT=1` is set when the server starts. If the flag is set and the extra is missing, the process exits instead of silently dropping the tool.

```bash
python -m pip install -e .
python -m pip install -e extras/tnt --no-deps
```

`--no-deps` avoids looking up `techhand-print-fab` on PyPI when you installed the core from this checkout. Once both packages are published, a normal install of `techhand-print-fab-tnt` is enough.

The tool runs `user-tnt attach-fab` (override the binary with `USER_TNT_COMMAND`, split like a command line, not a shell) and writes a JSON payload to stdin:

```json
{
  "action": "attach_fab_artifact",
  "ticket_id": 403,
  "project_id": "...",
  "part_name": "clip",
  "note": "",
  "files": [{"name": "model.stl", "path": "/absolute/model.stl", "bytes": 123}],
  "printer_dispatched": false
}
```

`user-tnt` has to be installed and logged in on that machine. This repo does not ship it. Exit 0 is the only confirmation the bridge reports. It still does not start a printer.

Cursor snippet with the bridge turned on:

```json
{
  "mcpServers": {
    "techhand-print-fab": {
      "command": "techhand-print-fab",
      "env": {
        "TECHHAND_FAB_ENABLE_TNT": "1"
      }
    }
  }
}
```

Leave that variable unset for a TNT-free connector. `fab_attach_to_ticket` will not be in the tool list.

## Development

```bash
python -m pip install -e ".[dev]"
python -m pytest
```

CI runs that on Python 3.12 and does not install OpenSCAD. Tests cover tool schemas, the clone refusal, and STL/3MF export through the built-in mesh, plus a mocked OpenSCAD success path.

## Out of scope

Live printer control, sliced toolpaths, Bambu or CAD vendor connectors, and a hard dependency on TNT.

TDQS

A4/5.0

Scored across 8 tools

Disambiguation5/5

Each tool targets a distinct step in the fabrication workflow: project setup, model generation, mesh export, DFM analysis, printer notes, and BOM estimation. Even the two export tools are clearly differentiated by format and description.

Naming Consistency3/5

The fab_ prefix is consistent, and most names are readable, but conventions are mixed: list_parts, create_project, export_stl, and export_3mf follow verb_noun, while param_model, dfm_check, x1c_profile_notes, and bom_sketch are noun-oriented names. This is a noticeable pattern break, though not chaotic.

Tool Count5/5

Eight tools is well-scoped for a pre-print fabrication helper covering project setup, parametric model generation, exports, DFM checks, printer notes, and BOM sketching. Each tool has a clear role without redundancy.

Completeness4/5

The surface covers the main generation-to-export-to-check workflow well, including STL/3MF output and practical fabrication guidance. Minor gaps exist, such as no delete/update operations for projects or parts, but these are not critical for the stated dry-fire design purpose.

Maintenance

ActivityMaintained
ResponsivenessNo issues