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
automatically selected principal/pictorial views and 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. |
|
scale_decision |
JSON-friendly resolution of an automatic or explicit scale request, including the requested/effective scales and any required placement blockers. |
|
annotation_scheme_decision |
dict[str, object]
|
JSON-friendly corridor comparison and a versioned,
observational profile recommendation from pre-render demand. With
|
view_decision |
dict[str, object]
|
JSON-friendly resolution of automatic principal-view selection.
|
arrangement_decision |
JSON-friendly record of how the sheet arrangement was resolved
(#1130) — |
|
section_decision |
JSON-friendly record of the section A-A outcome (#1190) —
|
|
detail_decisions |
list[dict[str, object]]
|
JSON-friendly outcomes for requested detail views, including model-space crop bounds, physical support evidence, resolved scale, and a named refusal reason. Observational only; requirement lint remains authoritative. |
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 — |
|
reproducible |
default for :meth: |
The constructor also accepts cyls, a precomputed
analyse_cylinders(part) result (cached privately; computed lazily on
first :meth:lint otherwise).
view_plan
property
¶
The resolved view plan (ADR 2 (was 0018)), or None before the views are created.
READ ONLY, and there is no setter: a resolved plan that a caller can rebind is indistinguishable from an authored request, which is the confusion ADR 2 (was 0018 §1) exists to prevent. Editing means converting it into constraints explicitly and resolving again.
working_part
property
¶
The coordinate-coherent compiler/projection solid (read-only).
iso_projection_scale
property
¶
The scale of the final projected isometric view, if one was projected.
recognition_frame
property
¶
The provider caller-to-working frame, or None outside a framed build.
leader_region
property
¶
The resolved feature-leader region policy for this drawing.
pmi_mode
property
¶
The imported PMI presentation policy resolved for this drawing.
general_tolerance_source
property
¶
The document default attached to the title-block tolerance, if any.
default_surface_finish_source
property
¶
The document-wide finish attached to sheet furniture, if any.
document_member
property
¶
Whether this drawing belongs to an imported document.
document_source_annotation_ids
property
¶
Source PMI identifiers already owned by the imported document.
recognition_frame_decision
property
¶
A copy of the explicit framed/raw/refusal selection outcome.
drawable_bounds
property
¶
Physical margins as (left, bottom, right, top) page coordinates in mm.
A frame uses this rectangle; view placement reserves additional clearance inside it.
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 1 (was 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 4 (was 0011)) rather than detected — the public read the annotation pass threads onto its PlacementContext (#639).
set_iso_projection_scale(scale)
¶
Record an isometric projection or reprojection from the projection stage.
attach_document_context(member, source_annotation_ids)
¶
Attach imported-document policy once, before annotation passes run.
coords(view)
¶
Return the :class:ViewCoordinates for a named view.
at(view, x, y, z)
¶
Map a world point to a page point (px, py, 0) in view.
Raises :class:ViewNotPlanned when view is not on the sheet. A bare KeyError
from inside whichever render pass happened to ask first is not a usable answer to
"this drawing does not have that view" — ADR 2 (was 0018 §6) wants an absent view to be a
named result, because view-set selection makes asking for one the normal case rather
than a bug (#1130).
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"/"rear"→ 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 1 (was 0008) IR) — the read surface for semantic edits (#397, ADR 4 (was 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 read
surface for feature-referenced edits.
Read-only — a view of what was built; mutating it does not change the drawing. Experimental: exposes raw IR dataclasses that may still evolve.
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 3 (was 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 4 (was 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".
recognition_evidence()
¶
The run-scoped provider evidence paired with :meth:recognition.
This experimental, read-only view is available for raw automatic recognition and
after the first physical critique of a declared drawing. It is None before that
lazy critique, for a bare drawing, and for the framed path, which has not adopted
the provider's framed-evidence contract. Draftwright never reruns recognition merely to fill
this value. The returned evidence borrows exact faces from the source part, so callers
must not mutate that part while using the evidence view.
recognition_ownership()
¶
Run-local accepted-occurrence ownership captured during detected conversion.
This experimental, read-only ledger is available only when raw automatic recognition
supplied :meth:recognition_evidence. It currently classifies unconditional one-to-one
adapters; singleton/grouped/pattern holes, slots, and pockets; nested countersinks; and
settled ownerless unsupported, deferred, and evidence-only policy. Remaining nested and
classification-only families stay explicitly unclassified. It carries opaque provider
references and therefore cannot be serialized or used as persistent feature identity.
record_section_decision(status, *, reason=None, detail='')
¶
Record what happened to section A–A (#1190).
A public verb rather than an attribute the render pass assigns, so the
annotations layer stays off Drawing internals (ADR 1 (was 0005)) and every outcome
lands in one shape. status is "placed", "skipped" or
"not_warranted"; reason is a stable code for the skipped case.
material_fields()
¶
The per-view filled projected material of this drawing, keyed by id(shape).
The ADR 2 (was 0014) leader routing and the leader_crosses_silhouette critique must
agree on what counts as travelling through the part, so both read this ONE
lowering rather than each deriving the answer from the projected outline. An empty
dict means the part could not be meshed, which callers must read as "no material
known" rather than "clear" — inventing clearance from a failed lowering is how a
silent geometry failure becomes a confident wrong answer.
The tessellation behind it is memoised (including its failure, so an unmeshable part is not retried on every lint), but the per-view fields are reconciled against the CURRENT views on each call, for two reasons:
- This is first called mid-build, by the feature-leader stage — which runs before the section and detail stages. Fields built once would permanently omit every view added afterwards, and leaders in a detail view would silently never be checked for cutting.
- Entries are identity-checked, like
_view_edge_entries' (#143), because a replaced view shape can be collected and itsidreused — which would hand back another view's material.
pending_title_block_box()
¶
The title block's page-space footprint before it has been drawn, or None.
"title_block" sits near the end of _PASS_SEQUENCE, so it is absent from
iter_annotations while strips place — which is why a below/right strip ran
into its region and only the paths asking _title_block_box directly ever
avoided it (#481 did that for GD&T; #1593 was the same gap for dimensions).
Its footprint is deterministic before it exists, so the builder measures it
once and hands it over here rather than annotations/ probing the drawing
(ADR 1 (was 0005 §2): the drawing is not the state bus).
title_block_for(key, factory)
¶
Return the build-owned title block for its page, fields and typography.
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 4 (was 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.
conveyed_by distinguishes a measurement that was WITHHELD from one that was
CONSOLIDATED (#1154): when it is set, the fact is still on the sheet, stated by the
dimension it names. "This is not drawn" and "this is drawn over there" are different
answers, and an audit that flattened them would read a de-duplication as a gap. It
rides the same stable kind@(x,y,z)/axis key as feature, so the two halves of
one consolidation can be matched up without importing IR types. None wherever
nothing takes the fact over — which is NOT the same question as authored: an
authored omission carries a conveyed_by whenever the author's set keeps the
owner, since the author chooses which dimensions are drawn and not where the
geometry states a fact.
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 moved between instances. 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.
A list, because one annotation can draw several independently suppressible
measurements — a compound hole callout renders bore diameter, depth and counterbore
together (ADR 4 (was 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.
These keys describe geometry and have the collision limits of feature_key.
For declaration-local ownership and meaning comparisons, capture
:meth:measurement_snapshot; geometry descriptions alone do not prove correspondence.
measurement_snapshot()
¶
Capture named measurement claims for a declaration-local edit comparison.
Use :func:draftwright.audit.compare_measurements to compare snapshots. Capture
before mutating a drawing. Owner references stay local to this process; separately
rebuilt declarations need an explicit correspondence, never a geometry-key guess.
This read compiles the existing model and reads recorded claim text, without recognition.
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 2 (was 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).
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.
A measured schedule shared by other features requires editing its declared
rows and rebuilding; partial removal refuses before changing any ink.
dimension(feature, param, *, role=None, side=None, 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, exact parameter
id, or discriminator it exposes — a turned step's "length" or a through step's
"through_step_leg.length.x"/"x" (value-only slot geometry is 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= or
an exact parameter id/discriminator to pick one — an ambiguous kind raises rather
than guessing.
view is chosen from the selected principal views ("front"/"plan"/
"side"/"rear") where the span projects non-degenerate — a length along the turning
axis vanishes in its end-on view, so the view follows the geometry. Through-step legs
share their semantic axis end view and natural outside-corner sides. Pass view=
to select a principal explicitly (a non-orthographic view foreshortens the span and is
rejected). An implicit side is "above" except for through-step legs, whose
missing corner selects the natural outside corridor. kwargs forward to the dimension
— except tolerance=, which is folded into the label (see :meth:place_dim),
because helpers discard a forwarded tolerance whenever a label is present.
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/pad-height/circular-blind-step/fillet/blend/paired-ramp/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. 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 room
check carves the view row around whatever is already placed and takes the
leftmost gap that fits, so it needs the occupancy to be complete. The outcome
is recorded on :attr:section_decision either way (#1190).
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.
In live mode, returns one placed name per distinct requested in-plane
ordinate with a real offset. In deferred mode, records the intent and
returns []; the names are created when the context finalizes.
Circular channels also accept this verb: their X/Y/Z offsets locate the seat
axis from the stock bounding-box minimum, and axes may select any subset
of those three coordinates. They use the shared profile corridor solve.
Raises ValueError for an unsupported feature (side-drilled
bores are placed by the auto-pass). A feature with no datum-referenced ref (a
datum-less model or a concentric/on-datum bore) returns []. Live placement
handles this feature alone; automatic/deferred rendering may coalesce truly
coincident ordinates while retaining every semantic owner. 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 4 (was 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 routes width, length, obround end radius and its model-derived datum position, even if fewer intents were recorded. Unsupported-axis turned callouts live-replay and raise the same ValueError as the live verb. Routing uses only-set mode; the auto-pass path is untouched.
Draining empties the list, so repeated calls and exports are no-ops until more intents are recorded. A live-replayed intent is removed only after placement; an error leaves the remaining batch available for a corrected retry.
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"/"rear"), 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 user-positioned free text and return its annotation name.
add_table(rows, *, prefer='tr', name='table', block_cols=None, _source_id=None, _source_ids=(), _features=(), _drop_code='table_dropped', _drop_severity='warning', _cells=(), _left_align_cols=())
¶
Fit a data table in available sheet space and report any placement drop.
add_balloons(view, specs)
¶
Place leadered balloons in the view's reserved halo.
add_hole_table(view='plan', *, prefer='tr', name=None, balloons=True)
¶
Add a hole table and optional matching balloons for a view.
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 2 (was 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.
repair(max_iter=3, *, _initial_issues=None, _on_settled=None)
¶
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_ink_overlap/annotation_overlap— try the shared dimension candidate solver once, with along-span choices and at most one existing stacking tier. Pins, authored sides, annotation membership and confirmed measurements survive; every lint-code/severity component must stay the same or improve, and at least one must improve. An infeasible candidate leaves the findings.
Only engine-built dimensions (carrying placement_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; a clean drawing is returned unchanged.
Wrong-side flips retain their issue-count rollback guard. Rejected ink candidates restore the original items and registry; an exception during candidate critique also restores them before propagating the error.
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
the allowlisted placement codes in ADR 5), 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.