CAD-сервис

ИНТЕГРАЦИИ / ДЛЯ СКРИПТОВ И АГЕНТОВ

Одна библиотека.
Интерфейс, API и MCP.

На сайте и в программных запросах используются одни и те же 33 генератора.
Можно искать крепления, менять размеры и получать CAD или пробник для печати.

Скачать OpenAPI 3.1

REST API

GET /api/models?q=…

Поиск по названию, устройству и применению. Семейство можно передать параметром group.

GET /api/models/{id}

Полное описание и схема числовых параметров с исходными значениями.

POST /api/generate/{id}

Строит CAD, STEP, FCStd, STL деталей и сетку просмотра.

POST /api/probe/{id}

Строит пробник с учётом ориентации печати и создаёт 3MF.

GET /api/jobs/{job_id}

Статус queued, running, completed или failed. Готовый результат содержит адреса файлов.

Размеры — в миллиметрах, углы — в градусах. Передавайте только изменённые параметры. Неизвестные ключи и недопустимые значения отклоняются.

Shell / поиск
curl -G --data-urlencode 'q=крышка' 'https://fastening.home.mivx.ru/api/models'
Shell / печатный пробник
curl -X POST 'https://fastening.home.mivx.ru/api/probe/cantilever' \
  -H 'Content-Type: application/json' \
  -d '{"parameters":{"beam_length":32,"clearance":0.3}}'

Задачи и файлы

HTTP 200 возвращает готовый результат. При HTTP 202 сохраните statusUrl и опрашивайте его с учётом retryAfter. Поле status = completed означает успешное завершение; failed содержит сообщение об ошибке. При HTTP 429 очередь занята — повторите запрос позднее.

Результат содержит assets: адрес каталога файлов. В нём находятся demo.step, demo.FCStd и parameters.json, а у пробника — probe.stl и fit-test.3mf. Физическая печать автоматически не запускается.

Результаты хранятся в ограниченном кэше: сохраняйте нужные файлы локально. Готовые исходные пробники уже прошли нарезку. Для новых размеров читайте print.status: passed — серверная нарезка пройдена; project_ready и sliced: false — 3MF подготовлен и требует нарезки в Bambu Studio. Проект сохраняет профиль P2S 0,4 мм / PETG / 0,20 мм, автоматические поддержки и полный завершающий G-код.

JSON / принятая задача
{
  "jobId": "example-g",
  "status": "queued",
  "statusUrl": "/api/jobs/example-g",
  "retryAfter": 1
}
Shell / проверка статуса
curl 'https://fastening.home.mivx.ru/api/jobs/JOB_ID'

MCP для агентов

Подключите Streamable HTTP endpoint https://fastening.home.mivx.ru/mcp. Поддерживаются версия 2026-07-28 с метаданными каждого запроса и версии 2025-11-25 / 2025-06-18 / 2025-03-26 с initialize. Сервер работает без сессий.

list_fasteners

Найти механизм по слову или семейству.

get_fastener

Получить описание, схему параметров и ссылки на файлы.

generate_model

Сгенерировать CAD по числовым параметрам.

generate_probe

Подготовить печатный пробник из того же набора размеров.

get_job

Получить состояние задачи и ссылки на результат.

Также доступны ресурсы fastening://catalog, fastening://skill и шаблоны исходников генераторов. Ответы MCP содержат ссылки и параметры; большие массивы сетки остаются в REST API.

Спецификация транспорта MCP

Shell / список MCP-инструментов
curl -X POST 'https://fastening.home.mivx.ru/mcp' \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -H 'MCP-Protocol-Version: 2026-07-28' \
  -H 'Mcp-Method: tools/list' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientInfo":{"name":"my-agent","version":"1.0.0"},"io.modelcontextprotocol/clientCapabilities":{}}}}'

Готовый skill и Python-клиент

Skill описывает выбор крепления, параметры, ожидание задач, скачивание файлов и границы CAD-проверки. Клиент использует стандартную библиотеку Python, автоматически ждёт завершения задачи и защищает существующие файлы от перезаписи.

Shell / использование клиента
python3 client.py list --query 'защёлка'
python3 client.py show cantilever
python3 client.py probe cantilever --parameters '{"beam_length":32}' --output ./probe
Python-клиент целиком
Python / client.py
Скачать
"""Standard-library client for Fastening Lab queries, CAD and print coupons."""

import argparse
import json
import os
import tempfile
import time
from pathlib import Path
from urllib.error import HTTPError
from urllib.parse import urlencode, urljoin, urlsplit
from urllib.request import Request, urlopen


def request(base, route, body=None):
    url = urljoin(base.rstrip("/") + "/", route)
    if urlsplit(url).netloc != urlsplit(base).netloc:
        raise ValueError("Unexpected API origin")
    data = None if body is None else json.dumps(body).encode()
    req = Request(
        url,
        data=data,
        headers={"Content-Type": "application/json", "Accept": "application/json"},
    )
    try:
        with urlopen(req, timeout=30) as response:
            return json.load(response)
    except HTTPError as error:
        message = error.read().decode(errors="replace")
        raise ValueError(f"HTTP {error.code}: {message[:600]}") from None


def wait(base, result, timeout=900):
    deadline = time.monotonic() + timeout
    while result.get("status") in {"queued", "running"}:
        if time.monotonic() > deadline:
            raise TimeoutError(
                "The CAD job is still running; poll " + result["statusUrl"]
            )
        time.sleep(max(0.5, min(5, result.get("retryAfter", 1))))
        result = request(base, result["statusUrl"])
    if result.get("status") == "failed":
        raise ValueError(result.get("error", "CAD job failed"))
    return result


def download(base, route, output):
    url = urljoin(base.rstrip("/") + "/", route)
    if urlsplit(url).netloc != urlsplit(base).netloc:
        raise ValueError("Unexpected artifact origin")
    with tempfile.TemporaryDirectory(dir=output.parent) as tmp:
        staged = Path(tmp) / output.name
        with urlopen(url, timeout=90) as response, staged.open("wb") as stream:
            expected = response.headers.get("Content-Length")
            received = 0
            while chunk := response.read(1024 * 1024):
                stream.write(chunk)
                received += len(chunk)
            if expected is not None and received != int(expected):
                raise ValueError("Incomplete download; no output file was published")
        os.link(staged, output)


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--base-url", default="https://fastening.home.mivx.ru")
    commands = parser.add_subparsers(dest="command", required=True)
    search = commands.add_parser("list")
    search.add_argument("--query", default="")
    show = commands.add_parser("show")
    show.add_argument("model_id")
    for name in ("generate", "probe"):
        cmd = commands.add_parser(name)
        cmd.add_argument("model_id")
        cmd.add_argument("--parameters", default="{}")
        cmd.add_argument("--output", type=Path)
    args = parser.parse_args()
    if args.command == "list":
        result = request(args.base_url, "/api/models?" + urlencode({"q": args.query}))
    elif args.command == "show":
        result = request(args.base_url, "/api/models/" + args.model_id)
    else:
        result = wait(
            args.base_url,
            request(
                args.base_url,
                f"/api/{args.command}/{args.model_id}",
                {"parameters": json.loads(args.parameters)},
            ),
        )
        if args.output:
            args.output.mkdir(parents=True, exist_ok=True)
            files = (
                ["fit-test.3mf", "probe.stl", "parameters.json"]
                if args.command == "probe"
                else ["demo.step", "demo.FCStd", "parameters.json"]
            )
            if any((args.output / name).exists() for name in files):
                raise FileExistsError(
                    "Output files already exist; choose a new directory"
                )
            for name in files:
                download(
                    args.base_url, result["assets"] + "/" + name, args.output / name
                )
        model = result.pop("model", None)
        if model:
            result.update(model_id=model["id"], parameters=model["parameters"])
    print(json.dumps(result, ensure_ascii=False, indent=2))


if __name__ == "__main__":
    try:
        main()
    except (ValueError, OSError) as error:
        raise SystemExit(str(error)) from None

Что подтверждают проверки

Проверяются геометрия, валидность CAD-тел, обратное чтение STEP и пересечения на контрольных стадиях движения. Это не физическая проверка усилия, удержания или ресурса. Y-Zipper — экспериментальная геометрия. Некоторые металлические детали представлены габаритными моделями.