New Project

Edge & Face Cuts#

Dado, groove, rabbet, chamfer, roundover, moulding, taper, hole, and trim

Names: dado groove rabbet chamfer roundover moulding taper hole trim edges faces face offset stopped start run_length stop size width depth target_dim start_z end_z target reference at length diameter ovolo cove ogee thumbnail half_round cockbead ramp lambs_tongue top_perimeter bottom_perimeter front_perimeter

When to use#

Material removal on one part, written inside its component (or an inline part) in the part's own frame. Every feature on a part lowers to a single cut: the compiler unions every cutter and cuts once, so write order does not matter. Use this file for housings, panel grooves, edge profiles, tapered legs, holes and slots, and levelling a raked leg to the floor. For an outline cut through the thickness use cut-paths; for joints between two parts use joinery-contract. A veneer face is veneer-and-laminate.

Syntax#

Enum lists (edges, faces) are child nodes taking one or more arguments; KDL v2 has no array literals.

NodeRequiredNotes
dadowidth depth offsetRuns across the grain.
groovewidth depth offsetRuns with the grain. A face the grain runs into (end grain) skips the cut at that face.
rabbetwidth depth + { edges ... } or face= edge=face=/edge= reaches any edge, vertical ones included. Stoppable, square-ended only. bevel-and-rabbet.
bevelangle + { edges ... } or face= edge=Built (RFC-0143). bevel-and-rabbet.
chamfersize + { edges ... }Stoppable (below).
roundoversize + { edges ... }Stoppable; stop=lambs_tongue refused.
mouldingsection= size= + { edges ... }Stoppable (stopped= start= run_length= stop=square|ramp). section= one of ovolo cove ogee thumbnail half_round cockbead; any other word is refused.
taperstart_z end_z target_dim + { faces ... }Full section at start_z, target_dim at end_z, on the named faces.
holediameter depthdepth=0.0 is through. x= y= z= and normal= (the face it is drilled into) place it. length= + axis=(x|y) make a slot.
trimreference=world|assembly normal= at= + { faces ... }The one feature resolved in world or assembly space, after placement.
  • Edge selectors (edges children): top_perimeter, bottom_perimeter, front_perimeter, all; named edges top_n top_s top_e top_w and bottom_n bottom_s bottom_e bottom_w; the four vertical arrises upright_ne upright_nw upright_se upright_sw; the four edges of the front face front_top front_bottom front_e front_w.
  • Face words (face=, faces): pos_x neg_x pos_y neg_y pos_z neg_z, part-local.
  • Placing a dado/groove (ADR-0011). face= is the face it is cut into (default: the broad face; required on a square section). offset= is the distance of the cut's low edge from the low end of its axis (the bottom face for z): always positive, "up from the bottom" or "in from the front or left". stopped=#true with start= (default 10 mm in) and run_length= (default: out through the far end) stops it. Which way it runs is not a property: it comes from the grain. The removed names offset_bottom and offset_x do not exist; the build reports them by name.
  • target=<Name> on a dado/groove (a bare word, e.g. target=panel) names the part the cut captures. It is documentation for the reader and the joint rules (a stile_and_rail frame's grooves, joinery-frames), not geometry: it moves nothing.
  • id=/label= on a dado/groove are accepted syntax (both parse as strings) but nothing in the compiler reads them back: no diagnostic, no cross-reference, no cut-list or drawing output resolves against them (issue #549). They are for the author's own bookkeeping or future-proofing only — a name you can grep your own source for — not yet a real reference mechanism another node can resolve against.
  • Stopped chamfer/roundover (RFC-0025): stopped=#true start= run_length= stop=square|ramp (square default). chamfer alone also takes stop=lambs_tongue. A chamfer or roundover on a vertical arris is refused if the run overlaps a taper on either of its faces.
  • moulding size= is the profile's overall projection, measured as chamfer/roundover's size. Every section is a stepped polygon (16 chords per quarter circle). half_round and cockbead are applied mouldings: author them as their own narrow component (a strip whose stock is the bead's section, profile cut on the faces that show) and join it on; a cut on the host can never leave material standing proud.
  • Most profiles do not follow a cut_path outline: rabbet/bevel/moulding and any edge selector naming individual compass edges ease the blank's straight edges only. On a part with a through cut_path that changes the outline, a perimeter selector (front_perimeter, all) leaves the sawn edges plain and reports "shaped only along the blank's own edges" in the skips. A named edge (top_n) the saw has taken away entirely is skipped as "sawn away" (a profile there would cut air); a named edge the saw only shortened is still shaped along what is left. A true profile along a curved run is not built (RFC-0126 addendum).
  • chamfer's and roundover's own top_perimeter/bottom_perimeter are the exception (RFC-0021, issue #58 Part A): on a part whose outline came from a through, keep="inside" cut_path, these two sweep the real outline instead — arcs and spline-flattened runs included — rather than the box's four compass edges, which such a part does not have at all (three_legged_stool.kdl's rounded-triangle Seat). stopped= is refused there (no single straight edge's own length to measure a run against).
  • How big a profile may be: a chamfer, roundover, moulding or rabbet is cut into the corner of two faces and must stop strictly short of the opposite face of each, otherwise it is skipped ("would remove the whole section", naming the short dimension). So size is not "the strip's thickness": a half-round strip needs thickness greater than size (and width greater than size); size = width / 2 with both long edges moulded is the true semicircle. A strip 6 thick takes size up to just under 6, not 6.
  • Stop a rabbet (or any stopped cut) at a height: there is no "stop at extent" keyword. Measure on the edge's run axis from its low end: stopped=#true start=0.0 run_length=<height> stops it <height> up; for a run measured down from the high end, start=<length - height> and leave run_length out. Omitting start= begins 10mm in.
  • hole with length=/axis= (RFC-0030) is a slot: length is the straight run between the two semicircular caps and axis (x or y) the part-local axis it runs along. Omit length (or 0.0) for a round hole; axis= does nothing then, so a repeat can pass both and ternary only length.
  • id=/label= on a hole are accepted syntax (both parse as strings) but nothing in the compiler reads them back: no diagnostic, no cross-reference, no cut-list or drawing output resolves against them (issue #551, same gap as dado/groove, issue #549). They are for the author's own bookkeeping only — a name you can grep your own source for — not yet a real reference mechanism another node can resolve against.
  • trim (RFC-0024; reference=assembly, #458): normal= is a direction in reference='s own frame naming the side of the plane that is kept; at= is where the plane sits along that axis (a CEL expression, usually derived, "0.0 - leg_drop"). faces names the part's own face the cut is written from. Pick a side face nearly perpendicular to the plane, not the end the trim conceptually shortens: a face leaning past 60 degrees (MAX_LEAN) is refused. reference= is world (the document's own origin) or assembly (the nearest enclosing instance=-placed assembly's own frame; identity, same as world, when the part has no such ancestor).

Example#

// housing across the grain, a panel groove in an edge, a stopped groove
dado face=pos_y width=19.0 depth=8.0 offset=40.0
groove face=pos_y width=6.0 depth=10.0 offset="(100.0 - 6.0) / 2.0"
groove face=pos_y width=10.0 depth=6.0 offset=40.0 stopped=#true start=50.0 run_length=400.0
// edge treatments
chamfer size=3.0 {
    edges top_perimeter
}
roundover size=4.0 stopped=#true start=60.0 run_length=400.0 stop=ramp {
    edges upright_se upright_sw
}
moulding section=ogee size=4.0 {
    edges bottom_perimeter
}
rabbet width=10.0 depth=6.0 {
    edges front_top
}
taper start_z=-200.0 end_z=-600.0 target_dim=60.0 {
    faces pos_x pos_y
}
// a row of holes where only the outer two are slots
repeat i count=4 {
    hole id="Peg_{i}" diameter=10.0 depth=0.0 x="20.0 + (i * 20.0)" y=40.0 z=0.0 length="(i == 0.0 || i == 3.0) ? 16.0 : 0.0" axis=x
}

Pitfalls and diagnostics#

  • Not built features are skipped, never silently ignored. lamination, pocket, spline and the legacy component-level mortise, tenon, dovetail, box_joint (etc.) appear as a skipped entry in the scene JSON and as ww check's warning "lamination not built: ...". Nothing is cut.
  • bevel, rabbet face= edge= and stopped moulding are built; see bevel-and-rabbet. trim reference=assembly is built (#458); only a reference= naming neither world nor assembly is refused.
  • stop=lambs_tongue on a roundover is refused, named. A section= outside the six words is refused with the reason.
  • A feature that never reaches the part (a trim whose face never crosses the plane) is a skip in the build report, not a silent no-op.
  • Padding stock only to give trim something to cut is real waste (ww::lower::wasteful_stock). Size the stock so the shallowest corner lands on the plane.
  • A groove or dado cut into a face the grain runs into (end grain) is skipped.
  • offset is always positive and measured from the low end; do not use negative z here.
  • Features in a repeat body need id="Name_{i}" wherever an id or mark would collide.

See also#

  • docs/KDL_AUTHORING.md §7; ADR-0011 (dado/groove placement); RFC-0024 (trim), RFC-0025 (stopped profiles), RFC-0030 (slots), RFC-0126 (mouldings).
  • examples/three_legged_stool.kdl (trim), examples/desk.kdl (dados), examples/dining_table.kdl.
  • cut-paths.md, veneer-and-laminate.md, joinery-contract.md, components-and-stock.md.

Names#

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

dado#

feature; node in 23 of 116 examples (63 uses).

Shortest real use, from tall_white_cutout_cabinet.kdl:

dado face=pos_x width=shelf_th depth=dd offset=plinth

groove#

feature; node in 40 of 116 examples (145 uses).

Shortest real use, from kumiko_andon_lantern.kdl:

groove face=pos_z width=gw depth=gd offset="(post_s - gw) / 2.0"

rabbet#

feature; node in 24 of 116 examples (56 uses).

Shortest real use, from tall_white_cutout_cabinet.kdl:

rabbet face=pos_x edge=pos_y width=back_th depth=rd

chamfer#

feature; node in 30 of 116 examples (56 uses).

Shortest real use, from adirondack_plank_chair.kdl:

chamfer size=3.0 { edges top_perimeter }

roundover#

feature; node in 42 of 116 examples (70 uses).

Shortest real use, from adirondack_plank_chair.kdl:

roundover size=3.0 { edges top_perimeter }

moulding#

feature; node in 18 of 116 examples (36 uses); value in 2 of 116 examples (4 uses).

Shortest real use, from farmhouse_harvest_table.kdl:

moulding section=thumbnail size=6.0 {
    edges top_s
}

taper#

feature; node in 12 of 116 examples (27 uses); property in 4 of 116 examples (4 uses).

Shortest real use, from deco_side_chair.kdl:

taper start_z=0.0 end_z=seat_z target_dim=leg_dim {
    faces pos_y
}

hole#

feature; node in 23 of 116 examples (29 uses).

Shortest real use, from pennsylvania_schrank.kdl:

hole

trim#

feature; node in 5 of 116 examples (7 uses); value in 1 of 116 examples (1 uses).

Shortest real use, from mid_century_credenza.kdl:

trim reference=world normal=pos_z at=floor_z {
    faces pos_x
}

edges#

word-valued property; node in 71 of 116 examples (189 uses).

Shortest real use, from dining_chair.kdl:

edges top_n

faces#

word-valued property; node in 15 of 116 examples (34 uses).

Shortest real use, from deco_side_chair.kdl:

faces neg_y

face#

property; node in 1 of 116 examples (1 uses); property in 77 of 116 examples (483 uses).

Shortest real use, from blanket_chest.kdl:

face "'neg_y'"

offset#

property; property in 57 of 116 examples (240 uses).

Shortest real use, from deco_console_table.kdl:

stringing face=neg_y material=Ivory offset=8.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

start#

property; property in 40 of 116 examples (136 uses).

Shortest real use, from blanket_chest.kdl:

avoid start="carcass_height - till_lid_th - till_height" width=till_stock_th

run_length#

property; property in 36 of 116 examples (123 uses).

Shortest real use, from tansu_kiri_chest.kdl:

groove face=neg_z width=door_groove_w depth=8.0 offset=track_y stopped=#true start=0.0 run_length=left_w

stop#

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

Shortest real use, from ebonised_incised_cabinet.kdl:

chamfer size=chamfer_sz stopped=#true start=14.0 run_length="post_h - 28.0" stop=lambs_tongue {
    edges upright_sw
}

size#

property; node in 1 of 116 examples (3 uses); property in 73 of 116 examples (198 uses).

Shortest real use, from bookcase.kdl:

size domino_size

width#

property; node in 46 of 116 examples (66 uses); property in 56 of 116 examples (292 uses); value in 22 of 116 examples (38 uses).

Shortest real use, from appalachian_post_and_rung_bench.kdl:

width 20.0

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

target_dim#

property; property in 12 of 116 examples (27 uses).

Shortest real use, from deco_side_chair.kdl:

taper start_z=0.0 end_z=seat_z target_dim=leg_dim {
    faces pos_y
}

start_z#

property; property in 12 of 116 examples (27 uses).

Shortest real use, from deco_side_chair.kdl:

taper start_z=0.0 end_z=seat_z target_dim=leg_dim {
    faces pos_y
}

end_z#

property; property in 12 of 116 examples (27 uses).

Shortest real use, from deco_side_chair.kdl:

taper start_z=0.0 end_z=seat_z target_dim=leg_dim {
    faces pos_y
}

target#

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

Shortest real use, from ebonised_incised_cabinet.kdl:

groove target=panel face=neg_z width=panel_th depth=groove_d offset=panel_off

reference#

word-valued property; property in 5 of 116 examples (7 uses); value in 3 of 116 examples (3 uses).

Shortest real use, from kitchen_collections.kdl:

part Fridge role=reference label="Refrigerator" {
    stock Appliance (in)36 (in)30 (in)68
    by top_sw
    pos x=(in)42 y=0.0 z=0.0
}

at#

property; node in 47 of 116 examples (1024 uses); property in 10 of 116 examples (18 uses).

Shortest real use, from windsor_round_stool.kdl:

at z=-4.0 { circle r=rf }

length#

property; node in 59 of 116 examples (154 uses); property in 50 of 116 examples (234 uses); value in 46 of 116 examples (203 uses).

Shortest real use, from appalachian_ladder_back_side_chair.kdl:

length 26.0

diameter#

property; node in 50 of 116 examples (94 uses); property in 65 of 116 examples (129 uses).

Shortest real use, from appalachian_post_and_rung_bench.kdl:

diameter 9.0

ovolo#

value in 2 of 116 examples (5 uses).

Shortest real use, from jacobean_joined_chest.kdl:

moulding section=ovolo size=rail_ovolo {
    edges front_top
}

cove#

value in 3 of 116 examples (3 uses).

Shortest real use, from pierced_splat_side_chair.kdl:

moulding section=cove size=5.0 {
    edges upright_sw upright_nw upright_se
}

ogee#

value in 9 of 116 examples (14 uses).

Shortest real use, from blanket_chest.kdl:

moulding section=ogee size=4.0 {
    edges top_perimeter
}

thumbnail#

value in 6 of 116 examples (9 uses).

Shortest real use, from farmhouse_harvest_table.kdl:

moulding section=thumbnail size=6.0 {
    edges top_s
}

half_round#

value in 2 of 116 examples (4 uses).

Shortest real use, from william_and_mary_chest_of_drawers.kdl:

moulding section=half_round size=bead_size {
    edges front_e front_w
}

cockbead#

value in 1 of 116 examples (1 uses).

Shortest real use, from federal_chest_of_drawers.kdl:

moulding section=cockbead size=3.0 {
    edges front_perimeter
}

ramp#

not used by any example yet.

lambs_tongue#

value in 5 of 116 examples (15 uses).

Shortest real use, from ebonised_incised_cabinet.kdl:

chamfer size=chamfer_sz stopped=#true start=14.0 run_length="post_h - 28.0" stop=lambs_tongue {
    edges upright_sw
}

top_perimeter#

value in 49 of 116 examples (70 uses).

Shortest real use, from adirondack_plank_chair.kdl:

edges top_perimeter

bottom_perimeter#

value in 19 of 116 examples (21 uses).

Shortest real use, from flat_pack_scale_test_table.kdl:

edges bottom_perimeter

front_perimeter#

value in 11 of 116 examples (11 uses).

Shortest real use, from bedside_cabinet.kdl:

edges front_perimeter

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