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. |
|
page_w, |
page_h
|
sheet size in mm. |
tb_w |
title-block width in mm. |
|
draft |
the shared |
|
look_at |
scaled centroid |
|
dist |
orthographic camera distance in scaled space. |
|
centroid |
unscaled centroid |
|
views |
dict
|
|
items |
list
|
ordered list of annotation objects (mutable). |
part |
the source solid, when known — enables the feature-coverage lint. |
|
assembly |
feature-coverage severity control — |
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 adimension(...)line.- otherwise — a planner rule decided it, and
reasonnames 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 |
required | |
p2
|
second page-coordinate tuple |
required | |
side
|
|
required | |
view
|
|
required | |
draft
|
the drawing's :attr: |
required | |
name
|
optional annotation name for later :meth: |
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: |
None
|
|
**kwargs
|
forwarded to |
{}
|
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 ø / n× / 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
DetailRequesthere, #304/#307); - drain — one
drain_and_reconcileplaces 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_overlapis 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.