New Project

Joinery Contract#

The two-body contract between joinery and join: marks, clearance, and the kinds table

Names: joinery join male female joint clearance fit butt name grain_check

When to use#

Every time two parts are joined. A joint is declared by the author, never inferred (D8): you name the joint type once with a joinery block, then apply it with join between two marks. The compiler cuts the male feature on one part and the female cavity on the other at the same time. Which kind to write is the next question: use the kinds table below to find the family file.

Syntax#

joinery <kind> name=<Name> {
    <param> <value>...            // kind-specific; see the family file
    clearance <expr>              // female side only, grown by clearance / 2 per face
    fit slip|snug|press           // instead of clearance=: resolve the female material's own
    grain_check off reason="..."  // opt out of the grain-contact error/rating; reason required
}
join joint=<Name> male=<Part>@<Mark> female=<Part>@<Mark>
  • male=/female= are Part@Mark, written unquoted (ADR-0002). The male part receives the protruding feature (tenon, tongue, pin); the female receives the cavity.
  • A reference whose part id contains a repeat interpolation must be quoted because { is structural in KDL: male="TopBoard_{j}@EdgeGlue_N". A literal id stays bare.
  • join lives in an assembly. The joinery block is project-level and reusable by name.
  • repeat inside a joinery body writes a schedule (graduated dovetail pin rows).
  • grain_check off reason="..." opts a joint out of the grain-contact error and the banded strength rating (never the movement warning, a separate concern) — see docs/KDL_AUTHORING.md §8's "Grain contact and the joint-strength rating" and docs/adr/0082-grain-contact-and-joint-strength-rating.md. reason= is required.

Kinds and the file that documents each#

KindKey paramsFile
buttnone (needs no marks)here
dowel pocket_hole biscuit domino edge_gluefastener rowsjoinery-fasteners
mortise_and_tenon round_tenontenon family (pin, wedge, key, through, proud)joinery-tenons
half_lap cross_lap bridle box_joint dovetail_through dovetail_half_blind sliding_dovetaillaps, bridles, box and dovetail jointsjoinery-laps-and-boxes
miter stile_and_railmitres and frame-and-paneljoinery-frames
tongue_and_groovedeclared only; a newer file in INDEX.md may build itsee INDEX.md
hardware_mounthardware <Name>; cuts nothingmaterials-and-hardware

A kind that is declared but not yet cut is resolved, drawn and reported as "not yet synthesised" by ww joins, ww check and the viewer.

The layout rule every cut joint shares (M5, ADR-0013)#

A joint is laid out from its male mark, on the stock face the mark sits on and faces.

  • The two marks' normals must face each other (opposite in world space) on faces that are in contact, and the male mark must lie where the faces overlap. Where the female mark sits along its face is not used.
  • A standard anchor (top_sw) has no facing, so a joint that cuts anything needs marks on both parts. Only butt needs none.
  • clearance goes on the female side only: its hole, slot or mortise is the male one grown by clearance / 2 on every face.
  • Clearance resolution (issue #55). clearance= always wins when given. With neither clearance= nor fit=, or fit= alone: the female material's tolerance_slip=/ tolerance_snug=/tolerance_press= (materials-and-hardware), for the named class or snug by default; else the project's glue_clearance param; else 0.2. clearance= and fit= together is an error. See docs/KDL_AUTHORING.md's "Clearance resolution" for the full chain and the draft per-material starting values.
  • Refused (nothing cut; ww check fails): marks that do not face each other, faces not in contact (an inset door's reveal), faces that do not overlap or are too short for the fastener, a fastener wider than the shared overlap or one that does not fit its stock, fasteners closer than their own size, a hole deeper than the board, holes from opposite faces that meet, and a parameter missing or out of range.
  • Warned: less wood left behind a blind cut than max(3 mm, 15 %) of the board, and a fractional count.
  • A mark on no real stock face fails at join time. Oblique shoulders and tilt=/heading= marks are handled by the tenon family; see components-and-stock.

Example#

project name="Joined Boards" units=mm schema_version=1 {
    params { glue_clearance 0.2 }
    materials { material Oak type=hardwood }
    component Leg role=leg {
        stock Oak 45.0 45.0 700.0
        mark Face x=45.0 y=22.5 z=-100.0 normal=pos_x
    }
    component Rail role=rail {
        stock Oak 400.0 20.0 80.0
        mark End x=0.0 y=10.0 z=-40.0 normal=neg_x
    }
    joinery mortise_and_tenon name=RailJoint {
        tenon_thickness 8.0
        tenon_length 25.0
        clearance glue_clearance
    }
    assembly Frame {
        part Leg_A instance=Leg { by top_sw; pos x=0.0 y=0.0 z=0.0 }
        part Rail_A instance=Rail { by top_sw; pos x=45.0 y=12.5 z=-60.0 }
        join joint=RailJoint male=Rail_A@End female=Leg_A@Face
    }
}

Pitfalls and diagnostics#

  • Write male=Rail_A@End, never Rail_A:End or a quoted string (unless interpolated).
  • join is a mutual contract, not an action on one body; do not order joins or features expecting a sequence. connect is the removed old spelling of join.
  • ww joins lists each join with its gap, whether the marks meet and the normals' dot product (-1.0 is facing squarely); ww check adds the clash and joinery verdicts.
  • A parameter on a joinery block may be a literal or a CEL expression over the project's params; enums (slope "1:8", face "'neg_y'") have per-kind quoting; see the family file.
  • Choose the joint a woodworker would actually use. The validator rejects what cannot be made; it cannot tell a poor choice (end-grain pocket screws into hardwood, a mortise into 6 mm ply) from a good one.
  • Marks that touch but are not joined are not a joint; a join is what cuts.

See also#

  • docs/KDL_AUTHORING.md §8; ADR-0013 (joint framework), ADR-0014 (joint authoring edits), ADR-0002 (Part@Mark).
  • components-and-stock.md (marks), parts-and-assemblies.md (where join sits).
  • joinery-fasteners.md, joinery-tenons.md, joinery-laps-and-boxes.md, joinery-frames.md, materials-and-hardware.md.

Names#

Every name this page declares, how the example library uses it, and the shortest real use.

joinery#

project-level node; node in 109 of 116 examples (440 uses).

Shortest real use, from neo_shaker_cherry_sideboard.kdl:

joinery butt name=BackJoint { }

join#

node in 109 of 116 examples (1962 uses).

Shortest real use, from desk.kdl:

join joint=PullMount male=Face@PullPoint

male#

word-valued property; property in 109 of 116 examples (1962 uses).

Shortest real use, from desk.kdl:

join joint=PullMount male=Face@PullPoint

female#

word-valued property; property in 108 of 116 examples (1918 uses).

Shortest real use, from wedged_post_and_rung_stool.kdl:

join joint=LegJ male=Leg_FL@Top female=Seat@LegFL

joint#

word-valued property; property in 109 of 116 examples (1962 uses).

Shortest real use, from desk.kdl:

join joint=PullMount male=Face@PullPoint

clearance#

node in 102 of 116 examples (333 uses).

Shortest real use, from appalachian_ladder_back_side_chair.kdl:

clearance 0.4

fit#

word-valued property; not used by any example yet.

butt#

joinery kind; value in 20 of 116 examples (21 uses).

Shortest real use, from neo_shaker_cherry_sideboard.kdl:

joinery butt name=BackJoint { }

name#

word-valued property; property in 116 of 116 examples (556 uses).

Shortest real use, from neo_shaker_cherry_sideboard.kdl:

joinery butt name=BackJoint { }

grain_check#

word-valued property; not used by any example yet.

Settings

Region

A region picks sensible defaults; change any of these separately.

Units

In inches, type lengths the way you say them: 34 1/2, 3/4", 1'-2" or 12.7mm. A length that can't be read is flagged, never rounded to something else.

Paper
Date order
Clock