Skip to main content
← API reference

Grain matching (matched sets)

Parts that must be cut side by side from one piece of board.

2D · matchedSets (grain matching)

POST /v1/optimize/2d

{
  "parts": [
    { "id": "DF", "name": "Drawer front", "w": 48.125, "h": 6 },
    { "id": "DOOR", "name": "Door", "w": 24, "h": 30, "qty": 2 },
    { "name": "Shelf", "w": 22.5, "h": 11.25, "qty": 4 }
  ],
  "stock": [{ "w": 96, "h": 48, "qty": 2, "grain": "w" }],
  "options": { "kerf": 0.125 },
  "matchedSets": [
    {
      "id": "base-1",
      "name": "Base cabinet fronts",
      "grain": "w",
      "layout": { "dir": "rows", "lines": [["DF"], ["DOOR", "DOOR"]] }
    }
  ]
}
// sheets[0].parts — the three set members (the four shelves follow them on the sheet)
[
  {
    "name": "Drawer front",
    "x": 0,
    "y": 0,
    "w": 48.125,
    "h": 6,
    "rotated": false,
    "matchedSet": { "id": "base-1", "instance": 1, "position": 1 }
  },
  {
    "name": "Door",
    "x": 0,
    "y": 6.125,
    "w": 24,
    "h": 30,
    "rotated": false,
    "matchedSet": { "id": "base-1", "instance": 1, "position": 2, "hint": "left" }
  },
  {
    "name": "Door",
    "x": 24.125,
    "y": 6.125,
    "w": 24,
    "h": 30,
    "rotated": false,
    "matchedSet": { "id": "base-1", "instance": 1, "position": 3, "hint": "right" }
  }
]

{
  "matchedSets": [
    {
      "id": "base-1",
      "letter": "A",
      "name": "Base cabinet fronts",
      "requested": 1,
      "asBlock": 1,
      "split": 0,
      "unplaced": 0,
      "block": { "w": 48.125, "h": 36.125 },
      "warnings": []
    }
  ]
}

matchedSets[] — request (2D)

Parts cut out of ONE piece of board, side by side or one above the other, exactly one kerf apart, so the figure of the veneer runs on across them (a drawer front over two doors). The set travels through the optimizer as one rectangle — the block — and comes back as its members. Every refusal is a 400 before the quota is reserved; a size mismatch is a warning (matchedSets[].warnings), not an error. At most 24 members per set; a part can be in one set only. Engines heuristic and balanced (which does not rank priority, so under respectStock a set can lose its place there); max refuses a request with sets (400 details.unsupported).

FieldTypeDescription
matchedSetsMatchedSet[]Matched sets (grain matching): groups of parts cut side by side from one piece of board. Send a NON-EMPTY ARRAY of sets (at most 50); members are named by parts[].id. Only a non-empty array switches the feature on: absent, null, [] — and any value that is not an array — is a request without sets, answered exactly as before (no error). With sets the response adds sheets[].parts[].matchedSet on every member and a matchedSets rollup; csv gains Set and Position columns at the end, svg the matched-sets frame group, dxf the position code on LABELS; pdf and labelsPdf draw the members as ordinary parts for now (a warnings line says so; the set marks are in sheets[].parts[].matchedSet, csv and svg). Engine max refuses a non-empty array (400 details.unsupported); an empty one is fine. 2D only: the 1D, wood and nest endpoints ignore this field and parts[].id.
idstring | integer · requiredUnique among the sets; at most 64 characters. Echoed on every member placement and in matchedSets[] of the response (as a string).
namestringOptional label, echoed in the response rollup. Printable characters only.
qtyinteger · default 1How many instances (three identical cabinets → 3). The parts' qty must cover every instance.
grain"w" | "h" | nullThe SET's grain: along the block's width (w) or height (h); it decides the direction of every member (their own grain is not used inside the block). Absent/null = no grain: the block may turn when every member is rotatable.
onMiss"split" | "unplaced" · default "split"When the block fits no stock sheet: split places the members one by one (with a warning), unplaced lists them in unplaced.
installGapnumberThe gap between the members ON THE CABINET (default: the kerf). The align check measures the lines with it; on the sheet the members still stand one kerf apart, and when the kerf is wider a gap: warning says so. Unit-agnostic: pick one unit (mm, inch, …) for every length in the request and every length in the response is in that same unit. Nothing is converted server-side.
layoutMatchedSetLayout · requiredWhere each member sits, as lines of parts[].id values. rows: each line is a row of members side by side, the rows stacked top to bottom ([["DF"], ["DOOR", "DOOR"]] = a drawer front over two doors). cols: each line is a column of members one above the other, the columns side by side. A part may appear more than once in one set. Positions are numbered in reading order: top to bottom, left to right.
layout.dir"rows" | "cols" · requiredrows: each line is a row of members side by side, the rows stacked top to bottom. cols: each line is a column, the columns side by side.
layout.lines(string | integer)[][] · requiredThe members, as parts[].id values — [["DF"], ["DOOR", "DOOR"]] is a drawer front over two doors.

parts[].id · parts[].grainGroup · options.setOutlines — request (2D)

FieldTypeDescription
parts[].idanyThe row's id, so a matchedSets layout can name it. Any JSON value is accepted here. Only an id a set REFERS TO is checked: it must be a non-empty string of printable characters, at most 64 long, or a safe integer (7 and "7" are the same id), and it must be on exactly one part. Every other id is ignored, whatever it holds (a float, an object, null, repeated, long, empty): always without a non-empty matchedSets, and on the rows no set names with one — so an ERP article number is safe to send.
parts[].grainGroupstring · deprecatedDEPRECATED — still accepted and still working exactly as before. Parts sharing a group are kept on one sheet. If a group is too large for one sheet the members are split and a string in warnings names the group. It only keeps them on ONE SHEET: it does not place them next to each other, so it gives no continuous grain across them. For fronts whose figure must run on (a drawer front over two doors) use matchedSets.
options.setOutlinesany · default trueA boolean. With matchedSets and the svg export: draw the dashed frame and the "SET A" badge of every set instance, in ONE separate group (<g id="matched-sets" data-role="annotation">) a laser or CNC import can drop in one move. false leaves that group out (the position codes in the part labels stay). Read only in a request with sets — there a value other than true, false or null is a 400; without sets the field is ignored, whatever it holds.

sheets[].parts[].matchedSet — response

Present ONLY on a placed member of a matched set (never in a request without matchedSets): which set and instance it belongs to and where in it. position counts the members in reading order on the cabinet front — top to bottom, left to right — from 1; with the set's letter it is the code the csv Position column and the svg labels print (A1, A2 …).

FieldTypeDescription
idstringThe set's id, as a string.
instanceintegerWhich instance of the set (1 … qty).
positionintegerThe member's place in reading order, from 1.
hint"left" | "middle" | "right" | "top" | "bottom"Where it sits, when that is unambiguous (a row or column of 2–3 plain members). Absent otherwise.

matchedSets[] — response rollup

One requested matched set: what became of its instances. Present ONLY in a request with sets.

FieldTypeDescription
idstringThe set's id, as a string.
letterstringA, B … in request order — the letter of the csv Set column and the svg frame badge.
namestringThe set's name, when one was sent.
requestedintegerInstances asked for (qty).
asBlockintegerInstances cut as ONE block, members side by side on a sheet.
splitintegerInstances whose block fit no stock sheet, so the members were placed one by one (onMiss: "split") — or, in a material-aware job with NO stock of the set's material, went to unmatchedMaterials like every part of that material.
unplacedintegerInstances whose members are listed in unplaced (onMiss: "unplaced", or no room left on the sheets) — or in unmatchedMaterials when the job has no stock of the set's material.
block{ w, h }The block in the set's own frame: the members plus one kerf between them. Unit-agnostic: pick one unit (mm, inch, …) for every length in the request and every length in the response is in that same unit. Nothing is converted server-side.
warningsstring[]One line per problem, each starting with its code: align: (the lines are not flush), gap: (the kerf is wider than installGap; both judged at 1/64 of your unit or a quarter kerf, whichever is smaller), grain: (the set's grain overrides the own grain of the members it names), split: / unplaced: (the block did not go on a sheet), unmatched: (no stock of the set's material), priority: (it may lose its place under respectStock), grainGroup: (the members are in different grainGroups). Empty when all is well.