Skip to content

Drawings and plans

drawing

Shop drawings: dimensioned orthographic views of parts and assemblies.

A part sheet shows the part lying on the bench as in its frame (reference face up, reference edge towards the woodworker) in third-angle projection, folded around the top face and the front edge, each view labelled with the side it shows. Its overall (cut) dimensions are given outermost. See docs/plan.md, M10, for the conventions.

Views are hidden-line projections along the axes of the part frame (or of the assembly), so a model coordinate maps directly to a drawing coordinate. Everything is laid out in paper millimetres on a Sheet at a standard scale chosen to fit an A4 sheet.

View dataclass

View(name: str, toward: Vec3, up: Vec3)

A direction of view: toward points from the object to the viewer.

true_up property

true_up: Vec3

up made square to the direction of view.

axis

axis(axis: int) -> tuple[int, int] | None

Where a model axis runs in the drawing: (0 across or 1 up, its sign), or None when it runs along the direction of view.

lines

lines(
    shape: Shape,
) -> tuple[list[Polyline], list[Polyline]]

The visible and hidden lines of shape in this view, in model units.

Row dataclass

Row(values: list[float])

A row of dimensions along one drawing axis of a view: a chain through values (drawing coordinates, model units), or one overall dimension.

Plot dataclass

Plot(
    view: View,
    cell: tuple[int, int] | None,
    label: str,
    visible: list[Polyline],
    hidden: list[Polyline],
    bounds: tuple[Point2, Point2],
    rows: dict[str, list[Row]] = dict(),
    balloons: list[tuple[Point2, str]] = list(),
    centres: list[tuple[Point2, float]] = list(),
    dims3d: list[tuple[Vec3, Vec3, Vec3, str]] = list(),
    leaders: bool = False,
    flexible: bool = False,
    zoom: Point2 | None = None,
    zoomed: bool = False,
    crop: bool = False,
    side: Side | None = None,
    numbers: list[int] = list(),
    parent: tuple[Plot, Point2] | None = None,
    origin: Point2 = (0.0, 0.0),
    scale: float = 1.0,
    lines: str | None = None,
)

A view of a shape placed on a sheet, with its rows of dimensions.

Its cell is the column and row of the layout grid it goes in, or None for a view placed in the space the grid leaves (see Layout).

at

at(p: Point2) -> Point2

A point in drawing coordinates on the sheet.

extent

extent() -> tuple[float, float, float, float]

How far the plot reaches beyond its outline on paper, with its dimensions and label: left, right, above, below.

title

title() -> str

The label, with the scale when it isn't the sheet's.

band

band(side: str) -> float

The depth on paper of the dimensions (and balloons) on one side, from the outline.

callouts

callouts() -> dict[str, list[tuple[list[Point2], str]]]

The balloons by the side of the view they go on, outside its dimensions: the side cheapest to reach, crossing few rows of dimensions and little of the view. Balloons with the same label (identical features) are one, on leaders to each of them, or for more than MAX_LEADERS, to the one nearest its side.

add_row

add_row(side: str, values: list[float]) -> None

Add a row of dimensions on a side, outside the ones there, unless the same row is already on the opposite side.

weight

weight() -> str

The line weight of the view (a suffix of the style): thinner where its edges are short on paper, so that small details don't fill in.

Layout dataclass

Layout(
    plots: list[Plot],
    notes: Point2 = (0.0, 0.0),
    scale: float = 1.0,
)

Plots on a sheet: the orthographic views in a grid of cells (column, row from the top) from the top left, above a strip for notes and the title block; the others (cell None: an isometric view, details) in the space left, each as near as can be to where it belongs.

fit

fit(page: Point2 = A4) -> Point2

Choose the largest standard scale at which the plots fit the page, then the largest enlarged views and isometric views; place them. Returns the size of the sheet: the page, or taller when even the smallest scale doesn't fit.

Feature dataclass

Feature(
    number: int,
    text: str,
    centre: Vec3,
    opens: list[Side],
    coords: list[tuple[int, float, int | None]],
    holes: list[tuple[int, Vec3, float]],
    label: int = 0,
)

What a cut leaves on a part, for dimensioning.

coords are the positions (part frame, by axis) of the surfaces the cut leaves inside the blank: the planes of shoulders and cheeks, the corners of slanted faces, the axes of holes; for some, the axis of the view that shows them best (along a hole, a slanted face edge-on). opens are the sides of the blank the cut reaches, the side it is cut into first. Identical features (cuts differing only in where they are) share their label, the number they are called out by.

features

features(part: ResolvedPart) -> list[Feature]

The features of a part, one per cut that removes anything, numbered, and labelled by their descriptions without positions (identical ones alike).

part_sheet

part_sheet(
    model: ResolvedModel, part: ResolvedPart
) -> Sheet

A shop drawing of one part: its views with overall and feature dimensions, the features numbered and described, and a title block; for a group of identical parts, with their number and names.

assembly_sheet

assembly_sheet(model: ResolvedModel) -> Sheet

A drawing of the whole assembly: front, top and side views with the overall dimensions (no hidden lines), and an isometric view.

parts_sheet

parts_sheet(model: ResolvedModel) -> Sheet

The parts of an assembly: an exploded isometric view with the parts numbered, and a list of them with their cut sizes.

cover_sheet

cover_sheet(
    model: ResolvedModel, lines: list[tuple[str, str]] = ()
) -> Sheet

A cover for the plans: the name, a large isometric view of the whole thing (no dimensions, no hidden lines), and below it its overall size, parts and materials, then lines (e.g. the date).

sheet

A sheet of paper for drawings: lines, text and circles in paper millimetres.

Coordinates have their origin at the bottom left of the sheet, y up, so that drawing coordinates map onto them without flipping; the SVG is written y down. The items are kept as data, rendered to SVG here and onto a PDF page by woodwright.pdf.

Sheet dataclass

Sheet(
    width: float, height: float, items: list[Item] = list()
)

text

text(
    at: Point2,
    text: str,
    size: float = TEXT,
    anchor: str = "middle",
    angle: float = 0,
    bold: bool = False,
) -> None

Text with its baseline at at, turned angle degrees counterclockwise; anchor start, middle or end.

balloon

balloon(
    center: Point2, label: str, radius: float = 2.6
) -> None

A number in a circle, marking a feature or a part.

dimension

dimension(
    a: Point2,
    b: Point2,
    line_a: Point2,
    line_b: Point2,
    text: str,
    text_at: Point2 | None = None,
) -> None

A linear dimension between a and b (the origins of the extension lines), its dimension line from line_a to line_b, with oblique ticks at the ends and text above its middle (or at text_at).

text_width

text_width(text: str, size: float = TEXT) -> float

An estimate of the width of a line of text (a sans-serif font).

pdf

The plans of an assembly as one PDF, with reportlab: a cover, contents, its drawing sheets in order, each a page of its own size, numbered and bookmarked.

A sheet is drawn in its own millimetres (the canvas scaled to mm, y up as in Sheet), so line widths, dashes and text sizes keep their values; lines stay vectors and text stays text, in the PDF's built-in Helvetica.

The document is laid out by reportlab's Platypus: each sheet is a flowable filling a page template of the sheet's size, text flows in a column on A4; the contents take a second pass for their page numbers.

Page dataclass

Page(title: str, sheet: Sheet)

A sheet in the plans, under a title in the contents and bookmarks.

Source dataclass

Source(name: str, code: str, command: str | None = None)

The code a design was made from, for the appendix of its plans.

Plans dataclass

Plans(
    name: str,
    sheets: list[Page],
    cover: Sheet | None = None,
    cut_list: str | None = None,
    description: str | None = None,
    source: Source | None = None,
)

What goes into the plans of an assembly, in this order: a cover, contents, a description, the drawing sheets, the cut list, and an appendix with the source and the versions it was made with.

draw_sheet

draw_sheet(canvas: Canvas, sheet: Sheet) -> None

Draw a sheet onto the current page of canvas, its bottom left corner at the page's.

write_plans

write_plans(path: Path, plans: Plans) -> None

The plans as a PDF; see Plans for what goes in.

fonts cached

fonts() -> Fonts

The fonts of the text pages: the first TrueType fonts found (registered with reportlab), else the PDF's standard ones (Latin-1 only).

markdown

markdown(text: str) -> list[Flowable]

Paragraphs from a subset of Markdown: headings (#, ##, ###), paragraphs, lists (-, *, 1.), fenced code blocks, and in a line bold, italic, code and links. The text may be indented as a whole (a docstring).

grouping

Identical parts and cuts, for the cut list and the drawings: "4 x leg", "24 x hole ø5".

Two parts are identical when they are of the same kind, from the same stock, laminated the same way, with blanks of the same size, and the shape of one is that of the other turned over in one of the ways a board can be: end for end, face down or both, and for a square section (or a rod) also a quarter turn about its length. The grain stays along the length. Which way round doesn't matter: the woodworker picks the reference face and edge of each piece as it comes.

When only a mirror image of one matches the other, they are a mirror pair (opposite hand): the left and right sides of a drawer. They are grouped too, the mirror images marked.

Cuts of a part are identical when they differ only in where they are (see Cut.offsets): mortises described alike, holes but for their offsets.

PartGroup dataclass

PartGroup(
    name: str,
    parts: list[ResolvedPart],
    mirrored: list[ResolvedPart] = list(),
)

Identical parts. The first is the one drawn and listed, the others are made the same, those in mirrored as its mirror image.

members

members() -> str

The names of the parts, the mirror images after the others: 'leg_1, leg_2; mirror image: leg_3, leg_4'.

group_parts

group_parts(parts: list[ResolvedPart]) -> list[PartGroup]

The parts grouped into identical ones, in the order of their first parts, each group's parts in the given order (but a part not mirrored first).

group_name

group_name(names: list[str]) -> str

The words of the names (between underscores) that all of them have, in order, but numbers: leg for leg_front_left and back_right_leg, top_beam for top_beam_1 and top_beam_2. Without any, the names joined (left_right), or for more than three, the first.

same_part

same_part(a: ResolvedPart, b: ResolvedPart) -> bool | None

Whether part b is identical to a (False), its mirror image (True), or neither (None).

group_cuts

group_cuts(cuts: list[Cut]) -> list[list[Cut]]

The cuts grouped into identical ones, in the order of the first of each.

cut_shape

cut_shape(cut: Cut) -> str

A cut's description without where it lies, if it says so (a hole's offsets).

describe_cuts

describe_cuts(cuts: list[Cut]) -> str

Identical cuts as one: their count and description, and where they lie if they say so: as the rows and columns of a grid if they make one ('24 x hole ø5, ..., in b.top_face, 174 to 526 every 32 from b.left_end; 37 from b.front_edge and b.back_edge'), else each position.

local

local(text: str, part: Part) -> str

A description with the part's sides named without the part, for a list under its name: 'in back_edge' for 'in stile.back_edge'.