{
  "openapi": "3.1.1",
  "info": {
    "title": "Threadsmith Marker Layout API",
    "version": "1.0.0",
    "summary": "Lay sewing pattern pieces on your fabric width with the grain respected; get length and efficiency.",
    "description": "Every endpoint performs the same calculation as the human page and returns inputs, result, units, assumptions, sources, warnings and computedAt. MCP endpoint: /mcp."
  },
  "servers": [
    {
      "url": "https://threadsmithmarkerlayout.usefulweb.app"
    }
  ],
  "paths": {
    "/api/v1/calculate": {
      "post": {
        "operationId": "calculate",
        "summary": "Lay sewing pattern pieces on your fabric width with the grain respected; get length and efficiency.",
        "description": "Places pattern pieces on a fabric of the width you have, as tightly as a deterministic bottom-left fill can: every piece keeps its grainline along the selvage (turned 180° when allowed, 90° only for cross-grain cutting, never for napped or one-way fabric), pairs get a mirrored copy, and pieces cut on the fold are unfolded on open fabric or put against the fold of folded fabric. You get the marker length — how much fabric to buy — the efficiency (piece area ÷ fabric area), the lower bound, the position and turn of every piece, a picture of the layout and an SVG or DXF of the marker. Pieces come from a JSON list or are drafted on the spot from bust, waist and hip.\n\nMethodology:\n- Every piece is turned so its grainline runs along the selvage (the marker length). Allowed turns: 0° and 180° (only 0° on napped or one-way fabric), plus 90° and 270° when cross-grain cutting is allowed. Pairs get a mirrored copy for every second piece; on folded fabric each placement cuts two layers, so pairs need half the copies, and fold pieces must lie with their fold edge on the fabric fold.\n- Fabric and pieces are rasterised (2, 5 or 10 mm cells). A piece’s footprint is conservative: every cell the polygon (dilated by half the gap) touches is taken, so placed pieces never overlap. Bottom-left fill: each piece goes to the position with the smallest marker end, then the leftmost, then the lowest free spot, over all its allowed turns.\n- Three deterministic orders are tried (by area, by length, by width, largest first) and the shortest marker is kept. The result is verified exactly: no two cutting lines cross or contain each other and every piece lies within the usable width.\n- Efficiency = sum of piece areas ÷ (marker length × usable width). The lower bound (area ÷ width) shows how far any layout could get; real shapes rarely reach it.\n\nLay the pieces out on the real fabric before cutting: check the print direction, stripes and checks (matching is not planned), and buy a little extra for shrinkage and straightening the grain.",
        "tags": [
          "apps"
        ],
        "x-mcp-tool": "threadsmith_marker_layout",
        "x-owner-app": "threadsmith",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ThreadsmithMarkerLayoutInput"
              },
              "examples": {
                "example1": {
                  "summary": "Known optimum: four 50 × 25 cm rectangles on 100 cm fill it completely",
                  "value": {
                    "source": "pieces",
                    "units": "cm",
                    "pieces": "[{\"name\":\"Panel\",\"polygon\":[[0,0],[50,0],[50,25],[0,25]],\"grain\":0,\"quantity\":4}]",
                    "fabricWidth": 100
                  }
                },
                "example2": {
                  "summary": "Skirt and bodice blocks on 140 cm folded fabric",
                  "value": {
                    "source": "blocks",
                    "units": "cm",
                    "blocks": [
                      "skirt",
                      "bodice"
                    ],
                    "bust": 92,
                    "waist": 74,
                    "hip": 100,
                    "fabricWidth": 140,
                    "mode": "folded"
                  }
                },
                "example3": {
                  "summary": "Own pieces: a yoke on the fold and a pair of pocket bags (inches)",
                  "value": {
                    "source": "pieces",
                    "units": "in",
                    "pieces": "[{\"name\":\"Yoke\",\"polygon\":[[0,0],[9,0],[9,4],[0,5]],\"grain\":90,\"quantity\":1,\"fold\":[[0,0],[0,5]]},{\"name\":\"Pocket bag\",\"path\":\"M0 0 L7 0 L7 9 Q3.5 12 0 9 Z\",\"grain\":90,\"quantity\":2,\"pair\":true}]",
                    "fabricWidth": 44,
                    "gap": 0.25
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ThreadsmithMarkerLayoutResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tool": {
      "get": {
        "operationId": "getToolDefinition",
        "summary": "Inputs, output schema and examples for this tool",
        "responses": {
          "200": {
            "description": "Tool definition.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Problem": {
        "type": "object",
        "description": "RFC 9457 problem details. 422 responses include per-field `errors`.",
        "required": [
          "type",
          "title",
          "status",
          "detail"
        ],
        "properties": {
          "type": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "detail": {
            "type": "string"
          },
          "code": {
            "type": "string"
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "field",
                "message"
              ],
              "properties": {
                "field": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "ThreadsmithMarkerLayoutInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "source": {
            "title": "Pieces from",
            "type": "string",
            "enum": [
              "pieces",
              "blocks"
            ],
            "default": "pieces"
          },
          "units": {
            "title": "Units of all lengths",
            "type": "string",
            "enum": [
              "cm",
              "in"
            ],
            "default": "cm"
          },
          "pieces": {
            "title": "Pieces (JSON)",
            "description": "Array of {name, polygon: [[x, y], …] or path: \"M … Z\", grain (degrees, 90 = along +y), quantity, pair (true = half mirrored), fold: [[x1, y1], [x2, y2]]}. The whole result of threadsmith-pattern-block ({\"pieces\": […]}, in mm) is accepted too.",
            "type": "string",
            "maxLength": 400000
          },
          "blocks": {
            "title": "Blocks (when drafting)",
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "skirt",
                "bodice",
                "sleeve",
                "trousers"
              ]
            },
            "default": [
              "skirt"
            ]
          },
          "bust": {
            "title": "Bust / chest girth (when drafting)",
            "type": "number",
            "minimum": 0,
            "maximum": 200
          },
          "waist": {
            "title": "Waist girth (when drafting)",
            "type": "number",
            "minimum": 0,
            "maximum": 200
          },
          "hip": {
            "title": "Hip girth (when drafting)",
            "type": "number",
            "minimum": 0,
            "maximum": 200
          },
          "seamAllowance": {
            "title": "Seam allowance (when drafting)",
            "description": "Empty = 1.5 cm or 5/8 in.",
            "type": "number",
            "minimum": 0,
            "maximum": 5
          },
          "hemAllowance": {
            "title": "Hem allowance (when drafting)",
            "description": "Empty = 4 cm or 1 1/2 in.",
            "type": "number",
            "minimum": 0,
            "maximum": 10
          },
          "fabricWidth": {
            "title": "Usable fabric width",
            "description": "Selvage to selvage minus edges you cannot use. Empty = 140 cm or 54 in.",
            "type": "number",
            "minimum": 0,
            "maximum": 500
          },
          "mode": {
            "title": "Fabric",
            "type": "string",
            "enum": [
              "open",
              "folded"
            ],
            "default": "open"
          },
          "crossGrain": {
            "title": "Allow cross-grain (90°) placement",
            "type": "boolean",
            "default": false
          },
          "napped": {
            "title": "Napped or one-way fabric (no 180° turns)",
            "type": "boolean",
            "default": false
          },
          "gap": {
            "title": "Gap between pieces",
            "description": "Empty = 0.",
            "type": "number",
            "minimum": 0,
            "maximum": 5
          },
          "resolution": {
            "title": "Layout raster",
            "type": "string",
            "enum": [
              "2",
              "5",
              "10"
            ],
            "default": "5"
          },
          "output": {
            "title": "File",
            "type": "string",
            "enum": [
              "svg",
              "dxf"
            ],
            "default": "svg"
          }
        },
        "required": []
      },
      "ThreadsmithMarkerLayoutResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "tool",
          "computedAt",
          "inputs",
          "summary",
          "result",
          "units",
          "assumptions",
          "sources",
          "warnings",
          "presentation",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.0"
          },
          "tool": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "version"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "inputs": {
            "type": "object",
            "description": "Normalised inputs actually used, including applied defaults."
          },
          "summary": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "required": [
              "units",
              "fabricWidth",
              "usableWidth",
              "mode",
              "length",
              "lengthMm",
              "efficiency",
              "lowerBoundMm",
              "pieceAreaCm2",
              "pieces",
              "placements",
              "overlaps",
              "file"
            ],
            "properties": {
              "units": {
                "type": "string",
                "enum": [
                  "cm",
                  "in"
                ]
              },
              "fabricWidth": {
                "type": "number",
                "description": "In the chosen units."
              },
              "usableWidth": {
                "type": "number",
                "description": "Width the pieces are laid on (half on folded fabric), in the chosen units."
              },
              "mode": {
                "type": "string",
                "enum": [
                  "open",
                  "folded"
                ]
              },
              "length": {
                "type": "number",
                "description": "Marker length in the chosen units."
              },
              "lengthMm": {
                "type": "number"
              },
              "efficiency": {
                "type": "number",
                "description": "Piece area ÷ (length × usable width), %."
              },
              "lowerBoundMm": {
                "type": "number",
                "description": "Total piece area ÷ usable width: no layout can be shorter."
              },
              "pieceAreaCm2": {
                "type": "number"
              },
              "pieces": {
                "type": "integer",
                "description": "Pieces placed (copies included)."
              },
              "placements": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "name": {
                      "type": "string"
                    },
                    "copy": {
                      "type": "integer"
                    },
                    "mirrored": {
                      "type": "boolean"
                    },
                    "rotation": {
                      "type": "number"
                    },
                    "x": {
                      "type": "number"
                    },
                    "y": {
                      "type": "number"
                    },
                    "areaCm2": {
                      "type": "number"
                    }
                  }
                }
              },
              "overlaps": {
                "type": "integer",
                "description": "Exact polygon check of the result: overlapping pairs or pieces outside the fabric (0 expected)."
              },
              "file": {
                "type": "object",
                "properties": {
                  "format": {
                    "type": "string"
                  },
                  "filename": {
                    "type": "string"
                  },
                  "bytes": {
                    "type": "integer"
                  },
                  "sha256": {
                    "type": "string"
                  },
                  "artifactUrl": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      }
    }
  }
}