Frame and panel door¶
projects.frame_and_panel_door
A door of four raised panels in a mortise-and-tenon frame; outside sizes.
Two stiles, a top, a middle and a bottom rail, and between the rails above and below the middle one a short muntin. A groove runs through along the inside edges of all of them, centered in the thickness, as cut on a table saw. The rails are tenoned into the stiles, the muntins into the rails, the tenons as thick as the grooves are wide and with shoulders as wide as they are deep, so that the grooves run out beside them. At the ends of the stiles, the tenons of the top and the bottom rails have haunches filling the grooves; the muntins' short tenons leave room for each other in the middle rail.
The panels, the grain upright, are raised on the front, their backs
flat: the bevel, as planed by hand, runs from the field right out to the
edges, at the mouths of the grooves (bevel out from the field) as
thick as the grooves are wide, and on into them, getting thinner; the
field of a 14 mm panel lies 1 mm behind the face of a 22 mm frame. They
aren't glued, but float in the grooves: the edges are clearance short
of the bottom across the grain, so the panels can swell, and 1 mm along
it.
Parameters¶
| Parameter | Default |
|---|---|
height |
800 |
width |
500 |
member_width |
65 |
groove_width |
8 |
groove_depth |
10 |
tenon_length |
40 |
muntin_tenon_length |
25 |
bevel |
30 |
clearance |
2 |
stock |
22 mm board, oak |
panel_stock |
14 mm board, oak |
Change them in a design file (see Change an example):
Drawings¶
The plans in one PDF: plans.pdf.
Cut list¶
stile 800 x 65 x 22 mm oak, 2 pieces: stile_left, stile_right
- 3 x mortise 45 x 8 mm, 42 deep, in back_edge
- haunch mortise 10 x 8 mm, 10 deep, in back_edge at right_end
- haunch mortise 10 x 8 mm, 10 deep, in back_edge at left_end
- groove 8 mm, 10 deep, in back_edge
rail 450 x 65 x 22 mm oak, 2 pieces: rail_top, rail_bottom
- haunched tenon 45 x 8 mm, 40 long, haunch 10 long, on left_end
- haunched tenon 45 x 8 mm, 40 long, haunch 10 long, on right_end
- mortise 45 x 8 mm, 27 deep, in back_edge
- groove 8 mm, 10 deep, in back_edge
rail_middle 450 x 65 x 22 mm oak
- tenon 45 x 8 mm, 40 long, on left_end
- tenon 45 x 8 mm, 40 long, on right_end
- mortise 45 x 8 mm, 27 deep, in front_edge
- mortise 45 x 8 mm, 27 deep, in back_edge
- groove 8 mm, 10 deep, in front_edge
- groove 8 mm, 10 deep, in back_edge
muntin 352.5 x 65 x 22 mm oak, 2 pieces: muntin_top, muntin_bottom
- tenon 45 x 8 mm, 25 long, on right_end
- tenon 45 x 8 mm, 25 long, on left_end
- groove 8 mm, 10 deep, in front_edge
- groove 8 mm, 10 deep, in back_edge
panel 320.5 x 168.5 x 14 mm oak, 4 pieces: panel_top_left, panel_top_right, panel_bottom_left, panel_bottom_right
- chamfer 30 x 6 mm on bottom_face_front_edge_arris, running out past front_edge
- chamfer 30 x 6 mm on bottom_face_back_edge_arris, running out past back_edge
- chamfer 30 x 6 mm on bottom_face_left_end_arris, running out past left_end
- chamfer 30 x 6 mm on bottom_face_right_end_arris, running out past right_end
Code¶
From woodwright/examples/projects.py, which imports the names it uses (see From an example to your own design):
def frame_and_panel_door(
height: float = 800,
width: float = 500,
member_width: float = 65,
groove_width: float = 8,
groove_depth: float = 10,
tenon_length: float = 40,
muntin_tenon_length: float = 25,
bevel: float = 30,
clearance: float = 2,
stock: Stock = DOOR_OAK,
panel_stock: Stock = PANEL_OAK,
) -> Assembly:
"""A door of four raised panels in a mortise-and-tenon frame; outside sizes.
Two stiles, a top, a middle and a bottom rail, and between the rails
above and below the middle one a short muntin. A groove runs through
along the inside edges of all of them, centered in the thickness, as
cut on a table saw. The rails are tenoned into the stiles, the muntins
into the rails, the tenons as thick as the grooves are wide and with
shoulders as wide as they are deep, so that the grooves run out beside
them. At the ends of the stiles, the tenons of the top and the bottom
rails have haunches filling the grooves; the muntins' short tenons
leave room for each other in the middle rail.
The panels, the grain upright, are raised on the front, their backs
flat: the bevel, as planed by hand, runs from the field right out to the
edges, at the mouths of the grooves (`bevel` out from the field) as
thick as the grooves are wide, and on into them, getting thinner; the
field of a 14 mm panel lies 1 mm behind the face of a 22 mm frame. They
aren't glued, but float in the grooves: the edges are `clearance` short
of the bottom across the grain, so the panels can swell, and 1 mm along
it.
"""
rail_length = width - 2 * member_width
def corner(rail_end: SideRef, stile_edge: SideRef, position: Position, outer: SideRef) -> Joint:
return MortiseTenon(
tenon=rail_end,
mortise=stile_edge,
position=position,
tenon_length=tenon_length,
haunch=outer,
haunch_length=groove_depth,
tenon_thickness=groove_width,
shoulder=groove_depth,
)
asm, (stile_left, stile_right, rail_top, rail_bottom) = _frame(
"frame_and_panel_door", corner, height, rail_length, member_width, stock
)
# The rails' front edges are at the top.
rail_middle = asm.add(Board("rail_middle", stock, length=rail_length, width=member_width))
middle = (height - member_width) / 2
for rail_end, stile_edge, stile_end in [
(rail_middle.left_end, stile_left.back_edge, stile_left.left_end),
(rail_middle.right_end, stile_right.front_edge, stile_right.left_end),
]:
asm.join(
MortiseTenon(
tenon=rail_end,
mortise=stile_edge,
position=offset(middle, stile_end),
tenon_length=tenon_length,
tenon_thickness=groove_width,
shoulder=groove_depth,
)
)
# Standing like the stiles: their right ends up, back edges to the right.
opening_height = middle - member_width
muntins = []
for name, top, bottom in [
("top", rail_top, rail_middle),
("bottom", rail_middle, rail_bottom),
]:
muntin = asm.add(Board(f"muntin_{name}", stock, length=opening_height, width=member_width))
for end, rail_edge in [
(muntin.right_end, top.back_edge),
(muntin.left_end, bottom.front_edge),
]:
asm.join(
MortiseTenon(
tenon=end,
mortise=rail_edge,
tenon_length=muntin_tenon_length,
tenon_thickness=groove_width,
shoulder=groove_depth,
)
)
muntins.append(muntin)
def into_groove(edge: SideRef, groove: SideRef, position: Position, length: float) -> None:
# The bevelled edge goes in whole, the back of the panel against the
# back of the groove.
asm.join(
TongueAndGroove(
tongue=edge,
groove=groove,
tongue_thickness=groove_width,
tongue_length=length,
tongue_offset=0,
cut_tongue=False,
position=position,
across=offset((stock.thickness - groove_width) / 2),
clearance=groove_depth - length,
)
)
# Standing like the stiles too, their reference faces at the back: the
# front edges to the left, the right ends up.
opening_width = (rail_length - member_width) / 2
edge_reach, end_reach = groove_depth - clearance, groove_depth - 1
for row, muntin, above, under, stile_end in [
("top", muntins[0], rail_top.back_edge, rail_middle.front_edge, "right_end"),
("bottom", muntins[1], rail_middle.back_edge, rail_bottom.front_edge, "left_end"),
]:
for column, stile_groove, muntin_groove, rail_end in [
("left", stile_left.back_edge, muntin.front_edge, "left_end"),
("right", stile_right.front_edge, muntin.back_edge, "right_end"),
]:
panel = asm.add(
Board(f"panel_{row}_{column}", panel_stock, opening_height, opening_width)
)
outer, inner = panel.front_edge, panel.back_edge
if column == "right":
outer, inner = inner, outer
stile_position = offset(member_width, getattr(stile_groove.part, stile_end))
into_groove(outer, stile_groove, stile_position, edge_reach)
into_groove(inner, muntin_groove, CENTERED, edge_reach)
for end, groove in [(panel.right_end, above), (panel.left_end, under)]:
into_groove(end, groove, flush_with(getattr(groove.part, rail_end)), end_reach)
# On the design box, the bevel ends at the mouth of the groove.
for side in (panel.front_edge, panel.back_edge, panel.left_end, panel.right_end):
panel.cut(
Chamfer(
panel.arris(panel.bottom_face, side),
size=bevel,
size_b=panel_stock.thickness - groove_width,
run_out=True,
)
)
return asm
Exported with woodwright 0.9.0 on 2026-10-25.