Metadata-Version: 2.5
Name: autoassembler
Version: 0.4.1
Summary: Autoassembler Python SDK for AI Process Planning
Project-URL: Homepage, https://autoassembler.com/
Author-email: C-Infinity <info@c-infinity.ai>
License-Expression: MIT
License-File: LICENSE
Keywords: assembly planning,autoassembler,cad,dfa,manufacturing,process planning,sdk
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Scientific/Engineering
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.12
Requires-Dist: httpx>=0.21.2
Requires-Dist: pydantic>=2.5
Requires-Dist: typing-extensions>=4.6.1
Provides-Extra: cli
Requires-Dist: typer>=0.26.1; extra == 'cli'
Description-Content-Type: text/markdown

# AutoAssembler Python SDK

[AutoAssembler](https://autoassembler.com) is [C-Infinity](https://c-infinity.ai)'s foundational AI for
assembly process planning: read a CAD assembly, analyze its geometry, motion and production logic,
and turn that into manufacturing process plans, diff analysis, DFA analysis, work instructions and more.
This package is the official Python client for the AutoAssembler API. From a script or a notebook you can:

- **Upload** a CAD assembly (STEP, PVZ) and get a processed `Assembly` back.
- **Plan** it: create a process plan, assign parts to stations, let the planner work out the
  assembly sequence, then **validate** that every station is physically buildable.
- **Diff** two revisions of an assembly and **adapt** an existing plan to the new one.

> **Status: 0.x.** The surface may still change between minor releases; see
> [Versioning](#versioning). The v4 API is stable, except the operations under `client.alpha`.

## Documentation

- The [Quickstart](#quickstart) below, then the [Examples](#examples) for each task in more depth.
- Every public class and method has a docstring: `help(client.planning)`, `help(mpp.plan)`,
  or your editor's hover.
- `autoassembler --help` for the [command line](#command-line).

## Installation

```sh
pip install autoassembler        # or: uv add autoassembler
pip install 'autoassembler[cli]' # also the `autoassembler` command (see Command line)
```

Requires **Python 3.12+**.

## Authentication

Create an API key in the AutoAssembler app under
**Settings → API keys → Create API key**, then either pass it explicitly, set
`AA_TOKEN`, or log in interactively once:

```python
from autoassembler import AutoAssembler

client = AutoAssembler(token="aa_...")     # explicit (CI, scripts)
client = AutoAssembler()                   # reads AA_TOKEN, or the token stored by login()
```

```python
import autoassembler
_ = autoassembler.login()   # confirm in your browser; stores the token for 30 days
```

`login()` stores tokens in `~/.config/autoassembler/credentials.json`; `logout()` forgets them.
`login()` also returns the token: assign its result as above to avoid displaying it in a
notebook or interactive shell. Do not commit credentials or include them in reports.
`logout()` removes the local copy; it does not revoke the token on the server.
Pass `base_url=` to both `login()` and `AutoAssembler()` for anything but `https://app.autoassembler.com`,
including your own deployment.

## Quickstart

Use your own CAD files in place of the example filenames below; no CAD data is bundled.
These calls upload the files to the configured service and create resources in your account.

```python
from autoassembler import AutoAssembler

client = AutoAssembler()

# Upload two revisions of a design. Processing overlaps on the server; wait for both.
v1, v2 = (op.result() for op in (client.assemblies.upload("gearbox_v1.step"),
                                  client.assemblies.upload("gearbox_v2.step")))

# Plan v1 from scratch: one station with every part, plan it, check it is buildable.
mpp = client.planning.create_mpp(v1, name="Gearbox v1")   # a manufacturing process plan
station = mpp.add_station("Final assembly")
outcome = mpp.plan(station).result()           # complete | partial | failed
verdict = mpp.validate(station).result()       # feasible | infeasible
print(outcome[station].status, verdict[station].status, mpp.viewer_url)

# What changed in v2, and v1's plan carried over to it.
diff = client.assemblies.diff(v1, v2).result()
mpp_v2 = client.planning.adapt(mpp, v2, diff=diff).result()
```

## Examples

Each example below assumes `client = AutoAssembler()` and a valid token (see
[Authentication](#authentication)). Long-running calls take a `timeout` in seconds;
`OperationTimeout` means the work is still running on the server, not that it failed.

### Upload several CAD files

`upload()` returns as soon as the file is transferred, with processing under way on the server.
Start every upload before waiting on any, and the processing overlaps:

```python
from autoassembler import AutoAssembler, OperationFailed, OperationTimeout

client = AutoAssembler()
paths = ["gearbox_v1.step", "gearbox_v2.step", "housing.pvz"]   # .step, .stp or .pvz

ops = [client.assemblies.upload(path) for path in paths]
for path, op in zip(paths, ops):
    try:
        assembly = op.result(timeout=1800)
    except (OperationFailed, OperationTimeout) as error:   # e.g. the file is not an assembly
        print(f"{path}: {error}")
        continue
    print(f"{path} → {assembly.id}  {assembly.viewer_url}")
```

### Work with existing assemblies and plans

Everything you created earlier is reachable by id:

```python
assembly = client.assemblies.get("asm-1")
print(assembly.name, assembly.status)          # processing | ready | failed

for mpp in assembly.plans():                   # every process plan of the assembly
    print(mpp.id, mpp.name)
mpp = client.planning.get_mpp(assembly, "mpp-1")

structure = assembly.structure()               # the processed eBOM
connections = assembly.connections()           # part-to-part connections (liaison graph)
```

### Compare two revisions

Both assemblies must be `ready`. The report's `summary` holds the counts and `details` the
parts behind them:

```python
report = client.assemblies.diff("asm-1", "asm-2").result(timeout=1800)   # handles or ids

summary = report.summary
if summary.total_parts:
    print(f"parts: {summary.total_parts.reference} → {summary.total_parts.target}")
if summary.geometric_diffs:
    print(f"only in v1: {summary.geometric_diffs.reference}")
    print(f"only in v2: {summary.geometric_diffs.target}")
for swap in report.details.swaps or []:
    print(f"swap: {swap.reference_part} → {swap.target_part} ({swap.confidence})")
for moved in report.details.moved_pose_pairs or []:
    print(f"moved: {moved.reference_part}")
```

### Plan, validate and score a process plan

A new plan knows the assembly's parts and has no stations. `add_station` takes every
unassigned part by default, or the subset you pass as `parts=`:

```python
mpp = client.planning.create_mpp(assembly, name="Gearbox — two stations")
print(len(mpp.unassigned_parts), "parts to assign")

subassembly = mpp.add_station("Shaft subassembly", parts=mpp.unassigned_parts[:5])
final = mpp.add_station("Final assembly")               # everything left

outcome = mpp.plan(*mpp.stations).result(timeout=1800)  # one or more stations
for station in mpp.stations:
    status = outcome[station]
    print(station.name, status.status)                  # complete | partial | failed
    for message in status.messages:                     # what a partial plan could not place
        print("  -", message)

verdict = mpp.validate().result(timeout=1800)          # no arguments: every station
for station in mpp.stations:
    if station in verdict:                              # stations with no planned steps are skipped
        print(station.name, verdict[station].status, verdict[station].messages)

if verdict.ok:                                          # every station feasible
    dfa = mpp.evaluate_dfa().result(timeout=1800)       # design-for-assembly scores
    for station_id, report in dfa.items():
        print(mpp.station(station_id).name, report.total_score, report.average_score)

print(mpp.viewer_url)                                   # open the plan in the app
```

`outcome.ok` is true when every station planned `complete`. `evaluate_dfa` needs stations
that are planned and `feasible`.

### Carry a plan over to a new revision

Rather than planning a revision from scratch, adapt the previous revision's plan: stations and
steps carry over, adjusted for what changed.

```python
v1, v2 = client.assemblies.get("asm-1"), client.assemblies.get("asm-2")
plan_v1 = client.planning.get_mpp(v1, "mpp-1")

diff = client.assemblies.diff(v1, v2).result(timeout=1800)
try:
    plan_v2 = client.planning.adapt(plan_v1, v2, diff=diff, name="Gearbox v2").result(timeout=1800)
except OperationFailed as error:
    raise SystemExit(f"adaptation failed: {error.message}")

for station in plan_v2.stations:
    print(station.name, station.status)
```

### Resume an operation from another process

Every `Operation` has a `name`. Store it, and pick the operation up later, from any process,
without starting the work again:

```python
op = mpp.plan(station)
print(op.name)                                   # e.g. "operations/…"; save it somewhere

# later, elsewhere
from autoassembler import AutoAssembler
op = AutoAssembler().operations.handle(saved_name)
if op.done():
    print(op.result())
```

A resumed operation returns the raw API response rather than the typed result
(`PlanOutcome`, `Assembly`, …); refresh the plan or look the assembly up to read it.

### Clean up

```python
mpp.delete()          # a process plan
assembly.delete()     # an assembly (only your own uploads)
```

## Command line

The `cli` extra installs the `autoassembler` command, for shell scripts and agents. Each command
is a thin layer over the same calls (`uvx --from 'autoassembler[cli]' autoassembler` runs it
without installing):

```sh
autoassembler login                                  # once; or set AA_TOKEN
autoassembler status                                 # versions, server, and who you are logged in as
autoassembler assemblies upload gearbox_v1.step gearbox_v2.step    # one `ID  STATUS  FILE` row each
mpp=$(autoassembler plans create asm-1 --name "Gearbox v1")        # creating commands print the id
autoassembler plans add-station asm-1 "$mpp" "Final assembly"
autoassembler plans plan asm-1 "$mpp"                # every station; or name some, by id or name
autoassembler plans validate asm-1 "$mpp" --json     # one JSON document on stdout
autoassembler assemblies diff asm-1 asm-2
autoassembler plans adapt asm-1 "$mpp" asm-2
```

`autoassembler --help` (or `-h`), and the same on any command, lists the rest. Conventions:

- Results go to stdout and progress to stderr. `--json` prints the result as one JSON document,
  with this SDK's field names.
- Exit codes: `0` success; `1` the request ran and the result is a failure (an API error, a failed
  operation, a station that failed to plan, an infeasible station); `2` usage; `3` not
  authenticated; `4` still running when `--timeout` ran out (it keeps running on the server).
- Long-running commands wait (up to `--timeout`, 1800 s by default). `--no-wait` prints the
  operation's name instead, for `autoassembler operations wait NAME` later.
- The server is `https://app.autoassembler.com` unless `--url` or `AA_URL` says otherwise.

## How the SDK is organized

- **Concepts.** `Assembly` (a CAD file the service knows) and `ProcessPlan` (a manufacturing
  process plan for one assembly, made of `Station`s).
- **Actions.** `upload`, `diff`, `plan`, `validate`, `evaluate_dfa`, `adapt`. An action that creates
  a resource lives on a namespace (`client.assemblies.upload`, `client.planning.adapt`); an action
  that acts on an existing resource lives on that resource's handle (`mpp.plan(station)`).
- **Waiting.** Anything that outlives its HTTP request returns an `Operation[T]`.
  `op.result(timeout=None)` polls with backoff and returns `T`, or raises `OperationFailed` /
  `OperationTimeout`; `op.done()` checks without blocking. Start several, then collect:

  ```python
  ops = [client.assemblies.upload(p) for p in cad_files]
  assemblies = [op.result() for op in ops]
  ```

Outcomes use the document's own words: a station's planning status is `pending`, `complete`,
`partial` (a usable partial plan) or `failed`; its validation verdict is `feasible` or
`infeasible`, with the issues behind it; an assembly is `processing`, `ready` or `failed`.

### The full API: `client.api`

The curated layer above is built on a client generated from the v4 OpenAPI spec, available
as `client.api` (`client.api.assemblies.get_status(...)`, `client.api.planning.get_mpp(...)`).
Use it when the vocabulary above does not cover what you need, and
[tell us](#support) what was missing. Every endpoint method carries its own docstring
(`help(client.api.planning.get_mpp)`), and the whole tree is browsable from `help(client.api)`.

### Errors

| Failure | Raised as |
|---|---|
| The server answered with an error status | `ApiError` (`status_code`, `body`, `headers`), or a typed subclass: `BadRequestError`, `UnauthorizedError`, `ForbiddenError`, `NotFoundError`, `ConflictError`, `UnprocessableEntityError` |
| The response did not match the expected schema | `ParsingError` (not an `ApiError`) |
| No response at all (DNS, connection, timeout) | `httpx.HTTPError` subclasses |
| A long-running operation failed or was cancelled | `OperationFailed` (`code`, `message`, `operation`) |
| A long-running operation did not finish in time | `OperationTimeout` (a `TimeoutError`) |

### Async

`AsyncAutoAssembler` currently exposes the generated client (`client.api`) and
`client.operations`; the curated `assemblies` / `planning` namespaces arrive on the async
client in a later release under the same names, with `asyncio.gather` as the natural way to
run work in parallel.

### Advanced

- **Timeouts.** 60 s per request by default: `AutoAssembler(timeout=20.0)` or
  `request_options={"timeout": 5}` per call.
- **Retries.** Requests that fail with 408, 409, 429 or 5xx are retried twice with backoff;
  `request_options={"max_retries": 0}` turns it off per call.
- **Your own `httpx` client**, for proxies or transports:
  `AutoAssembler(httpx_client=httpx.Client(proxy="http://..."))`.
- **Raw responses.** `client.api.<namespace>.with_raw_response.<method>()` returns headers and
  status alongside the parsed data.
- **Context managers.** `with AutoAssembler() as client:` closes the connection pool.

## Compatibility

| | |
|---|---|
| API | AutoAssembler **v4** (`autoassembler.API_VERSION == "v4"`). The client is generated from the v4 OpenAPI spec. |
| Servers | `https://app.autoassembler.com` by default; any AutoAssembler deployment that serves the v4 API via `base_url=`. |

## Versioning

The package follows [semantic versioning](https://semver.org/), independently of the API
version. While 0.x, a minor release may change the surface and a patch release only fixes;
from 1.0, breaking changes need a major release and removals are deprecated for one minor
release first. Pin a minor version (`autoassembler~=0.4.0`) if you need a stable surface
while on 0.x.

### Alpha

Operations the API offers before they are stable sit under `client.alpha` (also
`client.api.alpha`): today `client.alpha.jobs` and `client.alpha.planning.apply_ops`. They are
outside these guarantees: any release may change or remove them. An operation that becomes
stable moves out of `alpha`, to its namespace's usual place.

## Support

Bug reports, feature requests and questions: write to
[info@c-infinity.ai](mailto:info@c-infinity.ai). For a bug, include the SDK version
(`python -c "import autoassembler; print(autoassembler.__version__)"`), your Python version,
and, for a failed operation, its `name`. Do not include API keys or tokens.

## License

MIT © C-Infinity. The license text ships with the package.
