Drawing results

build_drawing(...) and Sheet.build() return a Drawing. Its public methods cover inspection, semantic editing, linting, and export.

Drawing

A composable technical drawing — the editable form of :func:make_drawing.

A Drawing holds the projected views, the annotation list, and per-view coordinate helpers. :func:build_drawing returns one pre-populated with the standard 4-view layout and automatic dimensions; you then add or remove annotations, add section/auxiliary views, and finally :meth:export.

Attributes:

Name Type Description
scale

drawing scale factor (e.g. 2.0 for 2:1).

page_w, page_h

sheet size in mm.

tb_w

title-block width in mm.

draft

the shared Draft preset used by the automatic annotations.

look_at

scaled centroid (x, y, z) — the default look_at and a building block for custom view cameras (see :meth:add_view).

dist

orthographic camera distance in scaled space.

centroid

unscaled centroid (x, y, z).

views dict

{name: (visible_compound, hidden_compound_or_None)}.

items list

ordered list of annotation objects (mutable).

part

the source solid, when known — enables the feature-coverage lint.

assembly

feature-coverage severity control — None auto-detects a multi-solid part as an assembly (per-part bores at info), True/False forces it (#69).

The constructor also accepts cyls, a precomputed analyse_cylinders(part) result (cached privately; computed lazily on first :meth:lint otherwise).

registry property

The AnnotationRegistry (identity/build-issue store) — the build handle the passes' PlacementContext references (#639).

coverage property

The CoverageState — referenced by the run's PlacementContext (#639).

box_cache property

The ONE annotation bounding-box memo for this build (#1138).

Placement and lint both measure annotations, and an optimal bounding_box() tessellates (~16 ms for a Leader), so anything measured on both paths is worth measuring once. Sharing one memo makes that hold by construction.

Keep its measured scope in mind before attributing a build's cost to it: on a plate build it holds two entries, on a flange three, all leaders and their callouts. Dimensions are not among them and cannot be — strip_obstacles decomposes anything exposing .segments instead of boxing it whole, and corridor_blockers skips Dimension/SafeDimension outright. The memo is worth tens of milliseconds, not a phase; the leader work in #1138 is what moved the number.

Exposed publicly (not as _ann_box_cache) because the annotations layer is duck-typed against dwg and, per ADR 0005, reads no private Drawing state. Sharing the dict rather than adding a second memo also means lint()'s existing liveness prune — which drops entries for objects no longer on the sheet — covers placement-seeded entries for free; a separate placement cache would have to re-implement that pruning, and a missed prune keeps OCC geometry alive for the drawing's lifetime.

solve_trace property

The opt-in solve-trace recorder threaded through this build (#736), or None (the default — tracing off). See build_drawing(trace=...).

model_declared property

Whether this drawing's model was declared by the caller (ADR 0011) rather than detected — the public read the annotation pass threads onto its PlacementContext (#639).

add_view(name, shape, camera, up, position, *, look_at=None, scaled=False)

DEPRECATED (#817): the raw view projector is now private (:meth:_add_view).

coords(view)

Return the :class:ViewCoordinates for a named view.

set_view_coordinates(view, coords)

DEPRECATED (#817): now private (:meth:_set_view_coordinates).

drop_view_coordinates(view)

DEPRECATED (#817): now private (:meth:_drop_view_coordinates).

at(view, x, y, z)

Map a world point to a page point (px, py, 0) in view.

view_bounds(view)

Return (x_min, y_min, x_max, y_max) of the projected geometry in view, or None if the view is unknown (#28).

The box is the tight bounding box of the placed silhouette — visible plus hidden lines — in page coordinates (mm from the sheet origin), the same space :meth:at returns. Use it to place free-form notes, leader elbows and the like just outside a view without guessing offsets::

x0, y0, x1, y1 = dwg.view_bounds("front")
dwg.note("SEE NOTE 1", (x1 + 5, (y0 + y1) / 2))

features(view='front')

Return detected geometric features in page coordinates for view.

Holes are grouped by machining spec (diameter + depth + cbore) and returned as :class:FeatureInfo objects with count set to the number of identical holes at that spec. Each group's page_pos is the page position of the first hole in the group.

The view determines which holes appear as circles (and are therefore annotatable from that view):

  • "plan" → Z-axis holes
  • "front" → Y-axis holes
  • "side" → X-axis holes

Returns an empty list when no analysis is available or the view name is unrecognised.

model()

The detected PartModel this drawing was built from (ADR 0008 IR) — the read surface for semantic edits (#397, ADR 0001 Amendment 1).

Both input scenarios converge here: a STEP file and a build123d solid both normalise to a solid, are detected once, and produce the same feature model (.features — holes/slots/steps/patterns, .datums, .orientation, .bbox). This is the provenance-agnostic "what is in this drawing and why" — richer than :meth:features (grouped holes, per view) and the future target for feature-referenced edits (#398).

Read-only — a view of what was built; mutating it does not change the drawing. Experimental: exposes the raw IR dataclasses, which may still evolve (a stabilised public projection is deferred to the write surface #398).

Populated for every built drawing, including a manual-mode (auto_dims=False) build — detection runs in the pipeline, not the annotation pass (#398), so a script can dimension detected features even when it suppressed the automatic ones. None only on a bare, unbuilt Drawing.

recognition()

The ADR 0017 recognition inventory used to build this drawing.

This is the geometry-only evidence below the detected/declared :meth:model and drafting policy. It is an experimental, read-only result.

None for a bare Drawing that did not pass through :func:build_drawing, and for a declared build that has not yet been critiqued — that path recognises nothing (ADR 0011 / #1022) and only builds an aggregate when something asks for physical critique. So None here means "nothing has needed recognition yet", never "this part has no features".

attach_part_model(model)

DEPRECATED (#817): now private (:meth:_attach_part_model).

suppressions()

Every measurement the compiler considered and did not approve, and why.

The audit read (#996). A finished drawing shows what was drawn; this shows what was not, separated into the two cases that mean opposite things:

  • authored — the script's own omission, under ADR 0016's rule that an authored set means omission is suppression. Recoverable by adding a dimension(...) line.
  • otherwise — a planner rule decided it, and reason names which.

The second is the one worth auditing. A rule that fires where it should not produces a drawing that is silently under-defined and lints clean, which is how #997's square rule generated four separate issue reports without any of them naming the cause. An absent dimension is only defensible if something can say which rule removed it; this is that something.

Returns plain dicts so a harness, a script or an LLM can diff two builds without importing IR types. feature is a stable key, not just the type name: a bare "HoleFeature" made two holes indistinguishable, so a diff could not say which one lost its location, or whether a suppression had moved between instances (Codex

996 r1). The key is kind@(x,y,z)/axis, which survives a rebuild because it is

derived from the geometry rather than from list position.

measurement_keys(name)

Which measurements the annotation name draws — possibly none (#1002).

The mirror of :meth:suppressions and deliberately the SAME row shape — {"feature": <stable key>, "parameter_id": ...} — so a drawn measurement and a suppressed one are directly comparable. Without it the two halves of the audit could only be joined by matching an engine-assigned annotation name against a parameter id by substring, which attributed losses to unrelated suppressions (Codex #1001 r1).

A list, because one annotation can draw several independently suppressible measurements — a compound hole callout renders bore diameter, depth and counterbore together (ADR 0016 / #886). Empty means the renderer recorded nothing, not that the annotation measures nothing. Which renderers record it is enforced by the ratchet in tests/test_audit_differential.py; treat presence as exact and absence as unknown.

Exact within a build. Across two builds the key cannot match directly, because feature_key embeds coordinates and scalars a differential deliberately changes — draftwright.audit joins on the feature's kind instead.

attach_solve_trace(trace)

DEPRECATED (#817): now private (:meth:_attach_solve_trace).

place_dim(p1, p2, side, view, draft, *, name=None, slot=8.0, feature=None, **kwargs)

Deprecated low-level page-coordinate dimension escape hatch.

Add a :class:~build123d_drafting.helpers.Dimension that stacks cleanly with the auto-generated dimensions by delegating to the same strip-allocation system (:class:Strip) that :func:build_drawing uses internally.

Parameters:

Name Type Description Default
p1

first page-coordinate tuple (px, py, 0) — use :meth:at to convert world coordinates.

required
p2

second page-coordinate tuple (px, py, 0).

required
side

"above", "below", "left", or "right".

required
view

"front", "plan", or "side".

required
draft

the drawing's :attr:draft preset.

required
name

optional annotation name for later :meth:remove / replace.

None
slot

strip slot depth (mm); the perpendicular space reserved per dim.

8.0
feature

optional source IR feature to attribute this dim to, so :meth:drop / :meth:annotations_of can find it (#398).

None
**kwargs

forwarded to Dimension (e.g. label=, tolerance=).

{}

Deprecated for normal editable scripts: prefer :meth:dimension for feature-backed linear dimensions and :meth:locate for feature-backed location dimensions. Both support pin=True in deferred/finalize mode and can participate in the shared layout solve.

Uses the single-position strip carve, not the ADR-0009 collect-then-solve path the automatic placers use — fine for adding a dimension into free space, but it does not re-solve the strip or dedup against existing dims (#396). Prefer :meth:dimension for a feature-referenced edit.

Falls back to a fixed slot offset when the strip is full or when no layout analysis is available (e.g. when auto_dims=False was not used with :func:build_drawing).

The @deprecated (PEP 702) decorator both emits the runtime DeprecationWarning and lets type checkers/IDEs flag call sites statically (#817).

add(obj, name=None, view=None, feature=None)

DEPRECATED (#817): the raw placement primitive is now private (:meth:_add). Use the placement verbs — :meth:callout/:meth:dimension/:meth:note/:meth:add_table/ :meth:add_balloons — which route through the solve; :meth:note is the door for free text. The public wrapper remains one release for compatibility.

The @deprecated (PEP 702) decorator both emits the runtime DeprecationWarning and lets type checkers/IDEs flag call sites statically (#817).

remove(name)

Remove a previously named annotation. Raises KeyError if absent.

annotations_of(feature)

{name: object} for every annotation rendered for feature (#398).

feature is an IR feature from :meth:model (dwg.model().features[i]). Matched by value, so the exact object is not required. Empty if the feature has no annotations (or its render pass does not yet tag provenance — coverage grows as passes are migrated).

drop(feature)

Remove every annotation rendered for feature (#398) — the semantic curation verb: "stop dimensioning this feature". Returns the removed names.

Use a feature from :meth:model: dwg.drop(dwg.model().features[0]). Removing a feature's callout/centre-mark/size-dims is a page-level edit; call :func:finalize_drawing afterwards (when available) to recompose the sheet.

dimension(feature, param, *, role=None, side='above', view=None, name=None, pin=False, priority=0.0, **kwargs)

Add a dimension for feature's param, attributed to the feature (#398e).

The feature-referenced add verb: pair to :meth:drop. feature is an IR feature from :meth:model; param is a linear parameter kind it exposes — a turned step's "length" or a slot's "length"/"width" (which the feature carries as value-only geometry, derived here via :meth:_derive_span). The dimension is placed into free strip space and tagged with feature, so :meth:drop / :meth:annotations_of find it. Returns the annotation name.

A feature may expose several params of one kind (an envelope's width/height/depth, or a slot's slot_width/slot_length, are all "length"); pass role= to pick one — a bare kind matching more than one raises rather than guessing.

view is chosen automatically as the orthographic view ("front"/"plan"/ "side") where the span projects non-degenerate — a length along the turning axis vanishes in its end-on view, so the view follows the geometry. Pass view= to force one of those three (a non-orthographic view foreshortens the span and is rejected). side defaults to "above"; kwargs forward to the dimension. In deferred mode, pin=True anchors the dimension at its natural slot coordinate inside the shared corridor solve, and priority= controls over-capacity survival. Live placement still uses the single-position escape hatch and pins only the placed annotation name.

Raises ValueError if the feature has no such param, the kind is ambiguous, or view is not orthographic. A hole's "diameter"/"depth" are leader callouts, not linear dimensions, so they raise here — a callout add verb is a separate mechanism, tracked apart from this one.

callout(feature, *, view=None, name=None)

Add a ø leader callout for feature (#414/#419) — the callout half of the feature-referenced add surface, symmetric with :meth:drop.

Where :meth:dimension draws a linear dim, callout draws a leader: for a hole/pattern, the ø / / through-or-depth / counterbore callout (the same text the auto-pass builds), placed beside the feature's end-on view (view defaults to it); for a turned step/boss, the ø… diameter leader in the row below (X-turned) or column left of (Z-turned) the front view. Tagged with feature so :meth:drop / :meth:annotations_of find it. Returns the annotation name.

Raises ValueError if feature exposes no callout (use :meth:dimension for a linear param). A machined-feature callout (pocket/fillet/flat/chamfer/groove) is auto-named and placed in its characteristic view by the kind's renderer, so view=/name= are unsupported for those kinds and raise ValueError rather than being silently ignored (Codex #811). Placed reasonably, not via the auto-pass's whole-set solve (byte-identity is not a goal, #400 Ph2) — :meth:repair tidies the rest. A step/boss diameter that finds no room returns "" (a warning-level drop, like the auto-pass), rather than raising, so a reconstruction script never aborts.

overall_height()

Add the part's overall height — the one dimension with no feature to name.

Every other add verb takes a feature, because every other dimension belongs to one. The overall height usually does too: a model with an EnvelopeFeature carries a height parameter, and dimension(env, "length", role="height") is the verb for it.

A model WITHOUT one still gets an overall height — the compiler falls back to the bounding box, which is a decision only the compiler may make (_compile_overall_height). There is then no feature to record an intent against, so an intent-level script had no way to say "and the 46 mm overall height", and a generated script replayed without it, silently and lint-clean (#889).

This verb is that line. It is deliberately NOT "draw it whenever the compiler approves one": auto_dims=False means the verbs are the whole drawing, so a dimension nobody recorded must not appear — record-then-finalize has to equal placing live.

Returns the placed names (empty when the compiler withholds the height — a Z-turned part whose step chain already tiles it, or an X/Y rotational OD that conveys it).

furniture(feature, *, view=None)

Add a hole/pattern's non-dimensional sheet furniture (#419) — centre marks (every member) plus a pattern's centre-cross (bolt circle) or pitch/grid dims.

The geometric marks a feature carries that no other verb emits: where :meth:callout draws the ø leader and :meth:locate the position dims, furniture draws the centre marks and pattern furniture. feature is a hole/pattern from :meth:model; view defaults to its end-on view. Each mark is tagged with feature so :meth:drop / :meth:annotations_of find it. Returns the placed names (varies by pattern kind — a bolt circle emits a centre-cross, a linear/grid array a pitch dim).

Raises ValueError if feature is not a hole/pattern (use :meth:dimension).

rotational(feature)

Add a rotational part's turned furniture (#424/#426) — the overall OD dimension, the axis centrelines, and any concentric-bore leaders.

The editable handle for the whole-model rotational renderer: where the per-feature verbs place callouts/locations, rotational draws the furniture the auto-pass synthesises for a part's RotationalFeature (a turned / cylindrical body). feature is the rotational feature from :meth:model. Placed by the shared :func:render_rotational — the same whole-model renderer the auto-pass runs, so a script-reconstructed drawing is byte-identical to the direct build (no only= subset, no positional-naming seam: the renderer names its own outputs dim_od / centerline_* / ldr_*). Returns [].

section()

Add the automatic full section A–A (#420) — the section half of the editable surface.

Part-level, unlike the per-feature verbs: a section fires when a Z-axis hole/pattern has a counterbore, spotface, or blind bottom (its internal profile is hidden-line-only in every ortho view), cutting through the densest qualifying row. Takes no argument (the auto A–A) and is not feature-tagged or :meth:drop-compatible — a section is atomic, so it is dropped by commenting the call. Returns the placed annotation names, or [] when no section is warranted or there is no room. Call it after the per-feature verbs — the section's room check clears whatever is already placed right of the side view.

locate(feature, *, axes=None, pin=False)

Add datum-referenced X/Y position dimensions for a Z-axis hole/pattern (#418) — the location half of the feature-referenced add surface.

Distinct from :meth:dimension (a feature's own intrinsic linear params): a location dim measures the datum → feature-centre offset, which no feature exposes as a parameter. feature is a hole/pattern from :meth:model; axes selects the in-plane axes (default both — "x" above the plan view, "y" above the side view). pin=True marks the placed dimensions as deliberate user edits: in deferred mode they still flow through the shared corridor solve, but survive/dedup as high-priority candidates and pin themselves once placed (#511). Each dim is tagged with feature so :meth:drop / :meth:annotations_of find it. Returns the placed names (0–2 — one per axis with a real offset).

Raises ValueError if feature is not a Z-axis hole/pattern (side-drilled bores are placed by the auto-pass). A feature with no datum-referenced ref (a datum-less model, a concentric/on-datum bore, or a ref deduped against a sibling) returns []. Placed reasonably, not via the auto-pass's corridor solve (byte-identity is not a goal, #400 Ph2).

deferred()

Record add-verb calls as placement intents, then batch-solve on exit (#426).

Inside the with block the add verbs (:meth:callout/:meth:locate/ :meth:furniture/:meth:dimension/:meth:section) record their intent instead of placing it live; on normal exit :meth:finalize drains them through the auto-pass's own solvers, so a reconstruction reaches auto-pass placement quality (crossing-free locations, the priority-drop callout solve, the turned diameter/step-length set-solves) rather than greedy live placement. This is the record-then-finalize surface the generated --script builds on.

finalize() runs on normal exit only — if the block raises, the recorded intents are left intact (finalize is skipped) so the error surfaces cleanly and a retry can re-drain. Restores the prior _defer_intents on exit. Idempotent: a later :meth:export (which also finalizes) no-ops once the intents are drained.

Do not nest deferred() blocks: finalize() drains the whole recorded list on every exit, so an inner block would place the outer block's still-pending intents early. One block per reconstruction (what the --script emitter does).

finalize()

Drain the recorded placement intents (#426).

When the drawing was built in deferred mode (_defer_intents), the add verbs recorded :class:~draftwright.intents.Intent\s instead of placing. This drains them, routing what it can through the auto-pass's own solvers — in the auto-pass's own ORDER: the drain stages are keyed by the orchestrator's canonical _PASS_SEQUENCE and executed by the shared run_stages (#699 slice b), so the two build paths cannot silently diverge in sequencing. The routed stages:

  • reserve_section — a recorded section's cutting-plane row is reserved first so the callout carve sees it as an obstacle (Coupling A);
  • live_replay — furniture, non-routed dimensions, and axes-restricted locates replay in recorded order (pop-after-success);
  • hole_callouts — hole/pattern ø callouts through _annotate_holes — the real priority-drop / central-bore-anchoring solve;
  • locations / height_ladder / step_positions / slots / user_dims — the register-only stages queue into the SHARED corridor (a slot position coincident with a hole location collapses to one dim, #345; pin/priority user dims join as first-class candidates, ADR 0012);
  • detail_request — when detail recovery is enabled (the automatic default, persisted on BuildState) and the ladder stage recorded a "step"/"illegible" escalation, the prismatic step-height detail is queued, exactly as the auto pass gates it (#661);
  • diameters / step_lengths — the X/Z-turned set-solves place immediately, before the drain, exactly as the auto-pass runs them (a crowded X-turned head queues its enlarged DetailRequest here, #304/#307);
  • drain — one drain_and_reconcile places every queued candidate (crossing-free, deduped, monotone ladder) + the #690 label reconciliation;
  • section — renders after the drained furniture exists (its room check clears the side view's right);
  • details — every queued detail request resolves through the one generic detailer, after the drain + section so it avoids everything placed (#661 — pre-fix the finalize path never resolved the queue, so the edit path produced no detail views);
  • tabulate — dense-scattered plan holes escalate to the hole table + balloon ring via _maybe_tabulate_holes — last, so it sees the section + title block as obstacles. The density gate counts all analysis holes, so this is a full-reconstruction escalation (a partial hand-edit still tabulates the full count, #434); the escalations live only on the per-run ctx, so a repeat batch starts clean (#639).

A slot records two size dims (slot_width/slot_length) on one feature; routing the feature also regenerates its model-derived datum position dim, so finalize places a superset of the recorded slot intents (auto-pass parity by design — commenting one of a slot's two lines still routes the feature). An unsupported-axis (Y-turned) step/boss callout live-replays, so it surfaces the same ValueError the live verb raises. Only only-set routing is used here; the auto-pass path is untouched.

Idempotent (draining empties the list; a repeat call — or export() then export_pdf() — no-ops) and a no-op when nothing was recorded (the live/auto-pass path), so export() calls it unconditionally. Resilient: a live-replayed intent is removed only after it places, so a verb that raises surfaces the error and leaves the rest recorded. A record → finalize → record-more → finalize sequence drains each batch (#428 review).

annotations()

Return {name: type_name} for every named annotation (#27).

Lets a script introspect what is already on the drawing before adding more — e.g. if "dim_width" not in dwg.annotations() — so it can do incremental edits without risking a silent name-collision replace. Unnamed annotations are omitted; iterate :attr:items for those.

iter_annotations()

Iterate (name, annotation object) for every named annotation.

The encapsulated read path for production code (lint, sheet, sections, renderers): use this instead of reaching into dwg._named directly so the registry stays the single owner of annotation identity (#241).

view_of(name)

The owning orthographic view for name ("front"/"plan"/"side"), or None — instead of reading dwg._anno_view directly (#241).

annotations_in_view(view)

Yield (name, annotation object) for the named annotations owned by view — the common filter-by-view read (#241).

get_annotation(name)

Return the named annotation object, or None if no such name (#27).

note(text, at, *, view=None, rotation=0.0, name=None, align=None)

Add a free-form text note at page position at(x, y) in mm from the sheet origin, the space :meth:at / :meth:view_bounds return (#817).

A note is user-positioned free text ("SEE NOTE 1", a general-tolerance line): it carries no feature and is not part of the placement solve, so — unlike :meth:callout / :meth:dimension, which the solve places — you give the position. Pass view to fold it into that view's block for the cross-view repack; rotation (degrees) and align (a build123d Align pair, default centred on at) are forwarded to the note. Returns the annotation name. This is the public door for free text — the raw Note object + low-level placement primitive are internal.

add_table(rows, *, prefer='tr', name='table', block_cols=None)

Add a generic data table, placed in a free corner (#93).

rows is a list of equal-length string tuples (rows[0] is the header). The table is positioned by :func:fit_box clear of the views, title block, and existing annotations; prefer is the page corner to sit nearest. Returns the table annotation, or None if it has no rows or will not fit (recorded as table_dropped lint). Gear-data, BOM, and revision tables all go through here; :meth:add_hole_table is the hole-specific convenience built on it.

add_balloons(view, specs)

Place a leadered balloon for each (tag, j, hole) in specs, fitted into the halo the layout reserved around the view (#111).

Public verb over the :mod:draftwright.annotations.balloons render pass (#699: the pass lives in the render layer; this owner method threads the build state in). Each hole is assigned to a reserved band — left, right, top or bottom — by a global max-cardinality/min-cost assignment (#516), each band is spread with the 1D strip solver, and a :class:Leader runs from the hole rim to each glyph.

add_hole_table(view='plan', *, prefer='tr', name=None, balloons=True)

Add a hole table for view's holes, placed in a free corner (#93).

One row per hole spec-group — TAG | ⌀ | DEPTH | QTY with tags A, B, … — placed via :meth:add_table. With balloons (the default) a circled tag is added at each hole keyed to its row. The table carries covers_diameters so the coverage lint counts the tabulated holes as dimensioned. Returns the table, or None when view has no holes or it will not fit.

pin(name)

Pin a named annotation so the engine never moves it (#89).

A deliberate placement — by you or an AI — must win over automatic layout. :meth:repair will not re-place a pinned annotation, and the constraint solver (ADR 0003) treats it as fixed. Pinning fixes the position, not existence: :meth:remove and :meth:clear_annotations still apply. Raises KeyError if name is not a known annotation. Returns self for chaining.

unpin(name)

Release a pin so the engine may move name again (#89). Returns self; a no-op if name was not pinned.

clear_annotations(keep=('title_block',))

DEPRECATED (#817): now private (:meth:_clear_annotations).

repair(max_iter=3)

Close the lint→repair loop: act on violations, don't only report them.

After the greedy initial placement, re-place the dimensions behind the mechanically-clear violations and re-lint, bounded to max_iter passes:

  • dim_inside_part — the offset is on the wrong side; flip it once. annotation_overlap is intentionally not repaired here anymore: the corridor/strip solvers own primary placement, and a fixed-step nudge would reintroduce a second placement policy.

Only engine-built dimensions (carrying _dw_spec) are re-placeable; leaders, callouts and standards-judgement issues (e.g. missing_principal_dimension) are left for the caller. Each side flip is attempted at most once and overlap pushes only move outward, so the loop terminates and a clean drawing is returned unchanged.

A pass that would net-increase the issue count (e.g. an overlap push that shoves a label out of frame on a tight sheet) is rolled back and the loop stops, so :meth:repair never makes a drawing worse.

Returns self for chaining.

lint(*, physical=True)

Lint all annotations against all views; returns the list of issues.

When :attr:part is set, also runs :func:lint_feature_coverage. Build-time drops recorded via :meth:_record_build_issue are included.

physical=False asks for the placement critique only — geometry/standards checks over what is on the sheet — and skips the feature-coverage half that needs a recognition inventory of the solid. That is what the repair loop wants (it acts on dim_inside_part and nothing else, ADR 0002), and on a declared build it is the difference between exporting a drawing and recognising the part to no purpose (#1022). The default stays the full critique: a caller asking "is this drawing right?" means both halves.

lint_summary()

Aggregate :meth:lint into a JSON-friendly diagnostic summary.

Gives a non-interactive caller (a script, or an LLM via the API) structured diagnostics and independently inspectable components without rendering the SVG:

  • passed — no error-severity issues;
  • score — legacy coarse 0–1 diagnostic heuristic (see _SCORE_*);
  • diagnostic_score — the same value under its honest name;
  • quality — separable completeness, restraint, and legibility components. No composite drawing-quality score is manufactured (#1127);
  • errors / warnings / infos — counts by severity;
  • by_code — per-check counts;
  • geometry_issues — count of standards/geometry-correctness issues as opposed to pure layout (see _GEOMETRY_AWARE_CODES);
  • issues — the full list, each as a plain dict.
  • pmi — when source PMI exists, source-to-render stage counts derived from the extraction report, final IR, annotation registry, and structured placement drops.

export(out=None, *, formats=None, svg=None, dxf=None, dpi=150)

Lint, then write the requested output formats; return {format: path}.

formats is a format name or an iterable from ("svg", "dxf", "pdf", "png"). PDF renders from the SVG and PNG from the PDF, so the SVG/PDF are written as intermediates and removed when not themselves requested. dpi sets the PNG raster resolution.

Omitting formats — or passing None, which is indistinguishable from omitting it — does not default to ("pdf",); that is :meth:Sheet.export's default. Here it selects the deprecated legacy path below, which writes SVG + DXF and returns a tuple.

Legacy (deprecated in 0.3.1, removed in 0.5.0): the boolean svg=/dxf= keywords — and calling export() with no formats — select those two vector formats and return the old (svg_path, dxf_path) tuple. Prefer formats=[...] (the dict API); export_pdf is likewise superseded by export(formats=("pdf",)).

Both legacy shapes now warn (#987). They were listed under "Deprecated" in the v0.3.1 changelog and then said nothing at runtime for four minor releases, which made the planned 0.5.0 removal a silent break — and invisible to tests/test_deprecation_dates.py, which can only scan things that warn. A deprecation nobody is warned about is documentation, not a deprecation.

export_pdf(out=None)

Deprecated — use export(out, formats=("pdf",))["pdf"]. Renders a PDF (svglib + reportlab) with the draftwright metadata + clickable title-block link.