Laps, Bridles & Dovetails#
Half lap, cross lap, bridle, box joint, through and half-blind dovetail, and sliding dovetail
Names: half_lap cross_lap bridle box_joint dovetail_through dovetail_half_blind sliding_dovetail depth_ratio cheek_thickness depth slope pin_count finger_width socket_depth housing_depth tongue_thickness stopped front_gap index pin avoid on
When to use#
- Members that cross (frames, lattices, a trestle's X):
half_lapwhere at least one member ends at the crossing,cross_lapwhere both run on through it,bridlewhere the end of one member is forked over the other (an open mortise and tenon). - A box or drawer corner (two boards meeting end to face):
box_jointfor fingers,dovetail_throughordovetail_half_blindfor a drawer front. - A shelf or divider captured in a housing (a T, not a corner):
sliding_dovetail.
The marks, join and the layout rule are in joinery-contract. A joint with a tenon is in
joinery-tenons.
Syntax#
joinery half_lap name=<N> { [depth_ratio <0..1>] clearance <mm> }
joinery cross_lap name=<N> { [depth_ratio <0..1>] clearance <mm> }
joinery bridle name=<N> { cheek_thickness <mm> depth <mm> clearance <mm> }
joinery box_joint name=<N> { pin_count <n> | finger_width <mm> clearance <mm> }
joinery dovetail_through name=<N> { slope "1:N" pin_count <n> clearance <mm> }
joinery dovetail_half_blind name=<N> { slope "1:N" pin_count <n> socket_depth <mm> clearance <mm> }
joinery sliding_dovetail name=<N> {
slope "1:N" housing_depth <mm> tongue_thickness <mm> clearance <mm>
[stopped #true front_gap <mm>]
}
Crossing members (half_lap, cross_lap, bridle). The two boards are laid out
through one another at full size; the shared box is the joint. The marks sit on the
cheek, inside the stock, each facing the board that lies over it.
half_lap/cross_lap: each part loses what of the shared box lies on its mark's side, down to the cheek.depth_ratio(default0.5) is the share of the male's thickness it loses and must agree with where its mark puts the cheek: a disagreement is an error, not a guess.clearancewidens the female's notch but never deepens it, so the cheeks stay flush. Ahalf_lapreaches the end of at least one member; two boards crossing mid-length are across_lap.bridle: the female is the fork, open at its end; the male keeps its middle as the tongue.cheek_thicknessis each fork cheek, so the slot isthickness - 2 * cheek_thicknesswide.depthis how far the slot runs in from the female's end and must reach across the male. Both members are the same thickness, face for face, and both marks sit on the mid-plane (half the thickness down).
Corners (box_joint, dovetail_*). The male (finger or tail board) is written
shoulder to shoulder, like a tenoned rail: its end stops at the female's inner face and
its mark sits on that end, facing the female's mark on the broad face it bears on. The
compiler adds the fingers or tails to the male's end and cuts the sockets from the female; the
stock list adds the tails to the male's blank.
- Write the female full length, running to the male's outer face (a dovetail must end there: the tails slide in along the tail board's thickness).
box_joint:pin_countis the male's fingers (2 * pin_count - 1equal positions, both outer ones the male's), orfinger_width, the narrowest finger you want, and the count follows the board (an odd number). One of the two, not both.dovetail_*:slope "1:N"is an enum written as a word (1:8 hardwood, 1:6 softwood; outside 1:4 to 1:12 warns).pin_countcounts pins with the half-pin at each edge, so there is one tail fewer. In place ofpin_count, a graduated schedule: onepin index=<i> width=<mm>row per tail inside arepeat.socket_depth(half-blind) is how deep the sockets run into the front; the rest is the lap. A through dovetail's tails are as long as the pin board is thick.avoid start=<mm> width=<mm> margin=<mm>(RFC-0070) keeps every tail root clear of a feature cut elsewhere on the same, male board —start=/width=are offsets from the joint's own low end, the same zero apin index=row's position already is, not the avoided feature's ownoffset=/start=frame (reuse adado's run-axisstart=to land in the same axis).on=female(a zone on the pin board instead) is refused, not built.
sliding_dovetail is a T: male mark on the shelf's end, female mark on the broad
face it meets, square. The housing runs the male's whole depth, walls leaning outward as they
go deeper; the male is shouldered down to a matching tapered tongue. housing_depth is how
deep it goes and how far the tongue stands, tongue_thickness is the tongue's root width
centred on the male mark. stopped #true with front_gap leaves that much uncut at the end
nearer the project's front (front=, default neg_y) so the housing does not show.
Example#
A half lap, a cross lap and a bridle. The half lap's marks are on the cheek at mid-thickness with opposite normals; the bridle's female member ends where the male crosses it.
project name="Laps" units=mm schema_version=1 {
params { glue_clearance 0.2 }
materials { material Oak type=hardwood }
// Half lap: each board keeps one half of its thickness at the overlap. The mark of
// each sits on the cheek (mid-thickness) and faces the other board.
component LapA role=rail {
stock Oak 400.0 40.0 20.0
mark Cheek x=380.0 y=20.0 z=-10.0 normal=neg_z
}
component LapB role=rail {
stock Oak 400.0 40.0 20.0
mark Cheek x=20.0 y=20.0 z=-10.0 normal=pos_z
}
// Cross lap and bridle: members of one thickness, marks on the mid-plane.
component Bar role=bar {
stock Oak 300.0 60.0 30.0
mark Mid x=30.0 y=30.0 z=-15.0 normal=pos_z
mark MidDown x=30.0 y=30.0 z=-15.0 normal=neg_z
}
component Slat role=slat {
stock Oak 300.0 40.0 20.0
mark Top x=150.0 y=20.0 z=-10.0 normal=pos_z
mark Bottom x=150.0 y=20.0 z=-10.0 normal=neg_z
}
joinery half_lap name=Lap {
depth_ratio 0.5
clearance glue_clearance
}
joinery cross_lap name=Cross {
depth_ratio 0.5
clearance glue_clearance
}
joinery bridle name=Fork {
cheek_thickness 10.0
depth 60.0
clearance glue_clearance
}
assembly Frame {
part Lap_A instance=LapA { by top_sw; pos x=0.0 y=0.0 z=0.0 }
part Lap_B instance=LapB { by top_sw; pos x=360.0 y=0.0 z=0.0 }
join joint=Lap male=Lap_A@Cheek female=Lap_B@Cheek
part Cross_A instance=Slat { by top_sw; pos x=0.0 y=500.0 z=0.0 }
part Cross_B instance=Slat { by center; pos x=150.0 y=520.0 z=-10.0; rot z=90.0 }
join joint=Cross male=Cross_A@Top female=Cross_B@Bottom
part Bridle_A instance=Bar { by top_sw; pos x=0.0 y=1000.0 z=0.0 }
part Bridle_B instance=Bar { by top_sw; pos x=60.0 y=1000.0 z=0.0; rot z=90.0 }
join joint=Fork male=Bridle_A@Mid female=Bridle_B@MidDown
}
}
Corners and the housing: the male's mark is on its end, the female's on a broad face.
project name="Corners" units=mm schema_version=1 {
params { glue_clearance 0.2 }
materials { material Oak type=hardwood }
component Side role=side {
stock Oak 20.0 300.0 100.0
mark Inner x=20.0 y=7.5 z=-50.0 normal=pos_x
}
component Front role=front {
stock Oak 200.0 15.0 100.0
mark End x=0.0 y=7.5 z=-50.0 normal=neg_x
}
joinery box_joint name=Box {
finger_width 20.0
clearance glue_clearance
}
joinery dovetail_through name=Through {
slope "1:8"
pin_count 3.0
clearance glue_clearance
}
joinery dovetail_half_blind name=HalfBlind {
slope "1:8"
pin_count 3.0
socket_depth 12.0
clearance glue_clearance
}
joinery sliding_dovetail name=Housing {
slope "1:6"
housing_depth 7.0
tongue_thickness 10.0
clearance glue_clearance
stopped #true
front_gap 12.0
}
assembly Boxes {
part S1 instance=Side { by top_sw; pos x=0.0 y=0.0 z=0.0 }
part F1 instance=Front { by top_sw; pos x=20.0 y=0.0 z=0.0 }
part S2 instance=Side { by top_sw; pos x=0.0 y=400.0 z=0.0 }
part F2 instance=Front { by top_sw; pos x=20.0 y=400.0 z=0.0 }
part S3 instance=Side { by top_sw; pos x=0.0 y=800.0 z=0.0 }
part F3 instance=Front { by top_sw; pos x=20.0 y=800.0 z=0.0 }
part S4 instance=Side { by top_sw; pos x=0.0 y=1200.0 z=0.0 }
part F4 instance=Front { by top_sw; pos x=20.0 y=1200.0 z=0.0 }
join joint=Box male=F1@End female=S1@Inner
join joint=Through male=F2@End female=S2@Inner
join joint=HalfBlind male=F3@End female=S3@Inner
join joint=Housing male=F4@End female=S4@Inner
}
}
Pitfalls and diagnostics#
- A lap whose
depth_ratiodisagrees with its mark is refused; so is a mark off the shared cheek plane (contact), marks that do not face each other (facing), and members that cross skewed rather than square to one another (faces-overlap: skewed laps are unsupported, RFC-0022). - A
half_lapcrossing mid-length of both members is refused with "this is across_lap"; abridlewhose female does not end where the male crosses it is refused with "a member crossing in its middle is across_lap" (kind). - A bridle needs members the same thickness, face for face (
material), marks half the thickness down (contact),cheek_thicknessleaving a slot (params), anddepthreaching across the male and not running the female's whole length (remaining-wall). - Corners. Refused: the male mark on a broad face, the female mark on an end or edge, the
male's end not wholly on the face, fewer than three fingers, a finger, tail root or pin
under 3 mm, a half-blind socket as deep as the pin board is thick, a box joint with both or
neither of
pin_countandfinger_width, dovetail sockets closed on both sides. Warned: a lap thinner thanmax(3 mm, 15 %), a fractionalpin_count, a slope outside 1:4 to 1:12. slope "1:8"is a word, never a parameter's name. Do not give adovetail_*bothpin_countand apinschedule.- A lipped (overlay) drawer front that runs past the side: rabbet its back face back to the
side at least as deep as the sockets (
bedside_cabinet.kdl). - A groove through the male's end stops at the shoulder; run it on into the tails with
stopped=#truewhere something (a drawer floor) sits in it (blanket_chest.kdl). - Choose the joint a woodworker would use: the validator cannot tell a poor choice (box joints on a corner that needs a mitre) from a good one.
See also#
docs/KDL_AUTHORING.md§8 ("Tenons, laps, bridles and mitres", "Box joints and dovetails", "Sliding dovetail housing"); ADR-0013 (and its lap addendum), ADR-0015 (graduated schedules); RFC-0124 (sliding dovetail).examples/desk.kdl(box_joint),examples/dining_chair.kdl(half_lap),examples/panel_door_and_window.kdl(cross_lap),examples/bedside_cabinet.kdl,examples/blanket_chest.kdl,examples/biedermeier_box.kdl,examples/welsh_dresser.kdl(dovetails),examples/bookcase.kdl(sliding_dovetail).joinery-contract.md,joinery-tenons.md,joinery-frames.md,edge-and-face-cuts.md.
Names#
Every name this page declares, how the example library uses it, and the shortest real use.
half_lap#
joinery kind; value in 4 of 116 examples (4 uses).
Shortest real use, from pierced_splat_side_chair.kdl:
joinery half_lap name=SlipSeatCornerJoint {
depth_ratio 0.5
clearance glue_clearance
}
cross_lap#
joinery kind; value in 3 of 116 examples (3 uses).
Shortest real use, from mid_century_slat_bench.kdl:
joinery cross_lap name=SlatLap {
depth_ratio 0.25
clearance glue_clearance
}
bridle#
joinery kind; not used by any example yet.
box_joint#
joinery kind; value in 6 of 116 examples (7 uses).
Shortest real use, from danish_teak_sideboard.kdl:
joinery box_joint name=CaseCorner {
finger_width finger_w
clearance glue_clearance
}
dovetail_through#
joinery kind; value in 10 of 116 examples (10 uses).
Shortest real use, from arts_crafts_cabinet_on_stand.kdl:
joinery dovetail_through name=CarcaseDovetail {
slope "1:8"
pin_count 5
clearance glue_clearance
}
dovetail_half_blind#
joinery kind; value in 17 of 116 examples (17 uses).
Shortest real use, from welsh_dresser.kdl:
joinery dovetail_half_blind name=DrawerDovetail {
slope "1:6"
pin_count 3.0
socket_depth 10.0
clearance glue_clearance
}
sliding_dovetail#
joinery kind; value in 8 of 116 examples (8 uses).
Shortest real use, from studio_cabinet_on_stand.kdl:
joinery sliding_dovetail name=ShelfDovetail {
slope "1:8"
housing_depth "case_th * 0.4"
tongue_thickness "shelf_th * 0.6"
clearance glue_clear
}
depth_ratio#
node in 7 of 116 examples (7 uses).
Shortest real use, from dining_chair.kdl:
depth_ratio 0.5
cheek_thickness#
not used by any example yet.
depth#
property; node in 59 of 116 examples (122 uses); property in 95 of 116 examples (571 uses); value in 33 of 116 examples (74 uses).
Shortest real use, from glazed_vitrine_whiplash_crest.kdl:
depth 8.0
slope#
word-valued property; node in 23 of 116 examples (35 uses).
Shortest real use, from art_deco_cabinet_on_fuseau_legs.kdl:
slope "1:8"
pin_count#
node in 21 of 116 examples (28 uses).
Shortest real use, from arts_crafts_cabinet_on_stand.kdl:
pin_count 5
finger_width#
node in 4 of 116 examples (5 uses).
Shortest real use, from danish_teak_sideboard.kdl:
finger_width finger_w
socket_depth#
node in 17 of 116 examples (18 uses); value in 1 of 116 examples (1 uses).
Shortest real use, from welsh_dresser.kdl:
socket_depth 10.0
housing_depth#
node in 11 of 116 examples (15 uses); value in 7 of 116 examples (16 uses).
Shortest real use, from pennsylvania_schrank.kdl:
housing_depth 8.0
tongue_thickness#
node in 12 of 116 examples (18 uses); value in 3 of 116 examples (3 uses).
Shortest real use, from dining_table.kdl:
tongue_thickness 10.0
stopped#
property; node in 7 of 116 examples (7 uses); property in 40 of 116 examples (135 uses).
Shortest real use, from biedermeier_chest_of_drawers.kdl:
stopped #true
front_gap#
node in 7 of 116 examples (8 uses); value in 1 of 116 examples (1 uses).
Shortest real use, from danish_teak_sideboard.kdl:
front_gap 10.0
index#
property; property in 1 of 116 examples (1 uses).
Shortest real use, from blanket_chest.kdl:
pin index=i width="i == 2.0 ? 18.0 : (carcass_height - carcass_th) / 8.0"
pin#
node in 19 of 116 examples (35 uses).
Shortest real use, from appalachian_ladder_back_side_chair.kdl:
pin shape=round size=3.0
avoid#
node in 1 of 116 examples (1 uses).
Shortest real use, from blanket_chest.kdl:
avoid start="carcass_height - till_lid_th - till_height" width=till_stock_th
on#
word-valued property; not used by any example yet.