Sheet and fluent handles¶
Sheet is the declared authoring façade. Feature verbs return fluent handles; those handles
are documented here because they are part of normal use even though their Python names begin
with an underscore.
Projection convention¶
Use Sheet(part, projection="first") or build_drawing(part, projection="first")
for first-angle layout and its matching projection symbol. The CLI equivalent is
--projection first. Explicit "third" selects third-angle layout and its symbol;
omitting projection uses third-angle layout and its symbol. Use
projection_symbol=False (CLI: --no-projection-symbol) to omit the symbol for an
embedded illustration; this does not change the projection convention or view layout.
The visibility choice also survives generated-script replay.
View names retain their physical viewing directions in the working part frame. Changing convention changes sheet relationships, not which side of the part a name shows:
| View | Viewed from | Page directions | Third-angle position | First-angle position |
|---|---|---|---|---|
front |
Negative Y | X right, Z up | Reference | Reference |
plan |
Positive Z | X right, Y up | Above front | Below front |
side |
Positive X | Y right, Z up | Right of front | Left of front |
The resolved convention is available as drawing.view_plan.convention. Generated scripts
retain the requested convention. Annotation footprints, scale/page selection and measured
repacking use the same convention. An authored relation contradicting the principal layout
fails before projection; a whole-view pin must preserve the convention's relationships.
Sheet margins and title-block width¶
Set each physical sheet edge independently with margin_left, margin_right,
margin_top, and margin_bottom (millimetres). Omitted edges remain 10 mm. frame=True
draws the border at those margins and reserves a further 6 mm inside it for content.
zones=True adds the zone ruler and implies a frame; each margin must fit its labels.
Sergio's folded-sheet arrangement is an explicit option on A4, A3, or A2:
from draftwright import build_drawing
drawing = build_drawing(
"part.step", page="A3", frame=True,
margin_left=25, margin_right=10, margin_top=10, margin_bottom=10,
title_block_width=175,
)
drawing.export("sergio", formats=("pdf", "svg"))
The same keywords work on Sheet(...) and make_drawing(...). An explicit
title_block_width measures between border centrelines and aligns the block to the right
and bottom sheet margins. The 175 mm block therefore fits the 210 mm folded face with
25 mm left and 10 mm right clearance. Its visible border extends half its 0.15 mm stroke
beyond each centreline. Without an explicit width, the existing widths remain 120 mm on
A4 and 150 mm on larger ISO sheets, with the existing extra 1 mm right/bottom clearance.
Revision defaults also remain unchanged.
The standard title block derives two read-only fields from the settled drawing: UNITS is
mm, matching Draftwright's drawing and export coordinate convention, and FORMAT is the
effective A-series designation (for example A2). A non-standard page states its physical
WIDTHxHEIGHT instead. These are derived after automatic page selection, so they cannot drift
from the sheet that was actually rendered.
CLI parameters carry the same options into rendered drawings and generated scripts:
draftwright part.step --page A3 --frame \
--margin-left 25 --margin-right 10 --margin-top 10 --margin-bottom 10 \
--title-block-width 175 --out sergio --format pdf,svg
Add --script to write a replayable Sheet script. Margins must be finite and nonnegative;
the width must be finite and positive. An explicit sheet too small for its margins and
block is refused. drawing.drawable_bounds exposes the physical drawable rectangle as
(left, bottom, right, top) page coordinates in millimetres.
Dimension text style¶
Title fields and notes at paper size¶
Keep title-block values concise and put longer engineering information in a notes() block.
title_field_overflow identifies text wider or taller than its title-block cell, even when the
text remains inside the page. This is a legibility warning, like overlapping annotations;
text outside the drawable page remains an annotation_out_of_bounds error. The full value
stays rendered; the diagnostic does not truncate it, shrink the font or invent an abbreviation.
lint_summary()["passed"] checks errors only, so inspect warnings and the legibility component
as well. A title warning does not prevent scale recovery from restoring missing measurements.
from build123d import Box
from draftwright import Sheet
sheet = Sheet(
Box(30, 20, 10), page="A4", scale=2,
title="FRAME - DFM REVIEW", number="REVIEW", material="SEE NOTES",
detail_view=False,
)
sheet.authored_dimensions()
envelope = sheet.envelope()
for parameter in envelope.dimension_ids():
sheet.dimension(envelope, parameter)
sheet.notes([
"QUOTATION / DFM REVIEW - NOT RELEASED",
"Material: TITANIUM - GRADE TBD",
"Hinge fit and pin retention: engineering decision required",
], prefer="tr")
drawing = sheet.build()
for issue in drawing.lint():
print(issue.severity, issue.code, issue.message)
page and scale control the sheet and model size. The current annotation preset has a fixed
3 mm nominal paper font size; changing scale does not enlarge notes. Sheet.notes() and
Sheet.table() have no font-size option. text_position and text_orientation below control
dimension style, not note size. A smaller model scale makes notes larger relative to the part;
a smaller page changes how much of the page they occupy. Neither changes their printed size.
For the A2 frame trial, retain A2 and the requested scale unless the full package fits the new
constraints with its required measurements intact; the small box example is not proof that
the frame fits A4.
prefer="tr" ranks available table positions near the top-right corner. The shared placement
solve still measures the whole notes block and checks all occupied regions. If it cannot fit,
table_dropped reports the failure. Split long prose into authored lines or choose an appropriate
page; do not remove engineering content merely to silence a diagnostic. General notes remain
uncredited text: they do not automatically satisfy missing dimensions or unsupported features.
Page selection also measures visible imported general notes before choosing a sheet. Derived sections and details claim their less-flexible space first; notes and tables take remaining regions. The late block placer prefers 6 mm of separation but retries the existing minimum ink clearance when necessary. An automatic section likewise prefers a wider gap from its parent row, and an unreserved detail prefers space around its complete footprint only when that keeps its requested scale. These are readability preferences, not permission to shrink, omit, or overlap required content.
Dimension position and reading direction¶
Position and reading direction are independent drawing-wide settings:
sheet = Sheet(part, text_position="above", text_orientation="horizontal")
# Declare features and dimensions as usual, then build.
drawing = sheet.build()
text_position="inline" (the default) leaves a gap for the value in the dimension
line; "above" keeps the line continuous and offsets the full value and tolerance.
For angular dimensions, above means outside the arc. text_orientation="aligned"
(the default) follows the dimension line or arc tangent; "horizontal" keeps text
horizontal on the sheet. An aligned vertical value reads from the right. Short
spans use outside arrows while retaining their complete text.
The same options are accepted by build_drawing(), make_drawing(),
emit_sheet_script() and generate_sheet_script(). The CLI flags are
--text-position and --text-orientation. Generated scripts retain these settings;
SVG, PDF and DXF export the same resolved ink. Unsupported values raise ValueError.
The choices affect rendering and its placement footprint, not feature measurements
or tolerances. These are explicit rendering choices, not a claim of standards conformity.
Feature-leader region policy¶
Feature callouts may use proved whitespace inside a projected view without exposing raw page
coordinates. The drawing-wide leader_region policy is accepted by Sheet(...),
build_drawing(), make_drawing(), and generated scripts:
sheet = Sheet(part, leader_region="exterior")
# or: build_drawing(part, leader_region="interior")
"auto"is the default and preserves the normal solve: eligible feature families offer interior candidates while retaining their established exterior fallback."interior"requires an interior candidate for an eligible, unconstrained feature. It does not invent interior geometry for unsupported families; those remain exterior. If no proved interior candidate fits, the eligible callout drops and lint reports it."exterior"removes all interior candidates and restores the historical exterior-only feature-leader layout.
The CLI spelling is --leader-region auto|interior|exterior. Generated --script output
retains a non-default selection in its Sheet(...) constructor. A feature carrying an explicit
side="left"|"right"|"above"|"below" remains exterior because authored placement intent is
stronger than the drawing-wide preference. The policy affects feature leaders only—not linear
dimensions, free notes, tables, or view placement—and never supplies coordinates.
The demand-guided annotation layout additionally reserves a blank 6 mm gutter
between the front/plan annotation bands and between the column and side view,
growing toward 12 mm when the selected sheet has spare room. Its facing strips
cannot spend that gutter. estimated-strips retains its established shared
corridors; this change does not silently alter that layout's sheet selection.
For separate linear hole patterns whose pitch marks have the same projected stations and printed value in one view, the renderer keeps one dimension rather than repeating the same pitch beside parallel rows. That mark retains the measurement identities and feature ownership of every covered pattern. Rotated or nonmatching rows remain separate; an unplaced pitch is still reported rather than silently treated as covered.
Acceptable recovered leaders¶
A sheet-wide recovery is a last resort, not permission to send a callout around the page. Draftwright accepts a recovered feature leader only when:
- its tip remains attached to the proved feature in its owning view;
- its label stays on the sheet and outside projected views and settled annotation text;
- its shaft stays out of other views and does not cross settled annotation text or non-crossable strokes; ordinary dimension-line crossings retain the existing documented crossing policy;
- it has at most one routing bend before the normal label shelf, never doubles back, and stays local (each leg no longer than the owning view's diagonal, the complete route no longer than twice that diagonal; a 30 mm floor keeps very small views usable).
If no candidate passes, the callout drops with a lint finding. The length and bend limits are Draftwright readability rules, not ISO- or ASME-prescribed numbers. They prevent a callout such as CTC04's 4×R13 from taking a long U-shaped detour into the next view. Ordinary feature leaders are still solved jointly; these additional limits govern the sheet-wide recovery path.
Through-hole wording¶
The optional argument to a hole handle's through() controls its printed indicator:
hole.through() # default THRU
hole.through("THRU") # explicit wording
hole.through("") # omit the printed indicator
hole.through("THROUGH ALL") # alternative wording
Each declares a through hole and clears any blind depth. Diameter, tolerances, fits and
measurement identities remain unchanged. The empty string is an explicit display omission;
it remains visible in the suppression ledger and does not count as a printed through
qualifier. .depth(4) makes the hole blind and clears the override; a later .through()
returns to the default.
The override is also accepted as through_indicator= by Sheet.hole() and the IR hole()
constructor, including pattern members. Generated scripts preserve the difference between
no override, explicit default wording and an empty string. Callouts and hole tables measure
the selected wording before placement. Compatible holes can share a callout when their
resolved wording, machining specifications and placement constraints agree; original feature
owners and measurement identities remain separate. Removing one owner of a shared callout
rebuilds the survivors through the placement solver.
Feature schedules¶
sheet.schedule([(feature, ("bore.diameter", "location"))], name="holes") declares a measured
table through the same IR/compiler as ordinary dimensions. Features may be fluent handles or
exact owned IR objects. Values, tolerances, units and through/blind wording come from the
compiler. location expands the addressable member coordinates; explicit canonical IDs select
individual components. Use prefer="tr", "tl", "br" or "bl" to rank the existing table
placement candidates.
Schedules establish authored dimension intent. Add sheet.dimension(...) declarations when a
measurement should also appear beside a view. Ordinary table(...) text has no measurement
authority. See shared drawing documents for an executable
recipe, verified cell evidence and the document report contract.
For an automatic pmi="annotate" drawing with multiple long imported thread/knurl
requirements, the engine may instead place a manufacturing requirements table.
The diameter or hole callout keeps its canonical size and a short SEE MFG n reference;
the table carries the complete source-owned manufacturing terms. Its measured footprint
participates in page selection, and the references are used only after the table fits.
If it cannot be placed, full direct callouts remain. Lint reports a short reference whose
source-matched table row is removed or altered. This does not apply to pmi="off" or
replace an authored feature schedule.
An imported datum-scheme sentence is omitted from the sheet only when its whole meaning is proven by placed, source-owned datum symbols on the matching geometry. The original AP242 text and the symbol source IDs remain in the model. A missing symbol, unmatched geometry, or extra instruction keeps the note (or produces a source-coverage lint error if a previously proven symbol is later removed).
Grooves on coaxial bodies¶
When different turned bodies share an axis line, give their steps distinct profile_group
values and use the owning body's value on s.groove(..., profile_group="outer").
The token associates features; it does not specify an annotation position or add a measurement.
Generated scripts preserve this association using Draftwright-owned tokens instead of provider
keys. A groove can still be declared when the authored set omits all of that body's steps.
Build-scoped declaration selectors¶
An editable script can give a feature a declaration identity and later address that intent without a list position:
hole = sheet.hole(diameter=6, at=(0, 0, 0), axis="z").identify("declaration:1")
sheet.dimension(sheet.by_declaration("declaration:1"), "bore.diameter")
The identity follows fluent replacements such as .depth() and identity-preserving list
reorders. Assigning another feature into its public sheet.features slot or deleting it
withdraws the identity; it never transfers to the new occupant. Live IDs must be unique.
Generated scripts use this build-scoped selector to join assessment evidence back to editable
intent. It is not a persistent feature, face or topology ID and makes no correspondence promise
across recognition runs. Recognition occurrence IDs, when present, remain local to the exact
generation run recorded by the declaration.
Declaration-scoped layout overrides¶
An agent editing a generated script can append a bounded layout override without changing the original feature declaration or supplying page coordinates:
options = sheet.layout_options("declaration:57")
check = sheet.validate_layout_override("declaration:57", side="above")
if check["supported"]:
sheet.layout_override("declaration:57", side="above")
lane_options = sheet.layout_options(
"declaration:9", parameter="slot_width.length"
)
lane_check = sheet.validate_layout_override(
"declaration:9", parameter="slot_width.length", lane=3
)
if lane_check["supported"]:
sheet.layout_override(
"declaration:9", parameter="slot_width.length", lane=3
)
Without parameter=, layout_options() reports the declaration's current side and supported
values. With an exact declared parameter it reports dimension-lane capability. A lane is an
integer from 1 through 8: a one-based drafting-spaced rank from that dimension's physical
witness, not a distance. The shared measured-candidate solve may resolve it into proven
whitespace inside or outside the view and records that candidate's region explicitly. The
current lane-capable slice is the linear width/length dimensions of slots. Capability is
declared in one compiler-owned registry so another dimension family is added deliberately, not
by teaching each renderer a private spelling.
The side surface accepts above, below, left, and right;
validate_layout_override() returns
structured invalid_declaration, unsupported_declaration, unsupported_control, or
unsupported_value refusals, plus invalid_control_combination when side and lane addressing
are mixed, without mutating the sheet. Both documents say
requires_build_validation: true: support means that the declaration and vocabulary are valid,
not that the final sheet has enough space. build() and lint() remain the authority for
feasibility and collision-free placement.
layout_override() accepts either keyword-only side, or an exact parameter plus lane, and
rejects duplicate overrides for the same target. Side chooses a feature corridor; lane ranks a
parallel position for one referential dimension. Neither is pinning or priority, and neither moves
an annotation to a caller-supplied coordinate or changes its measurement, tolerance, datum, or
other engineering semantics. Generated scripts
emit the override as a separate line after the identity-bearing declarations. A declared report
records the requested and resolved value under layout.overrides with
intent_class: "layout-only"; lane rows also retain parameter_id. An infeasible lane drops
honestly with the requested lane in the lint message. Omitting the override retains the existing
placement behaviour.
Checking dimension placement rules¶
Call handle.dimension_ids() to find the measurements a declared feature exposes, then
query a measurement before choosing its placement controls:
options = s.dimension_options(hole, "bore.diameter")
# options["placements"] contains pairs accepted by the planner's placement rules.
# None means leave that override unspecified.
check = s.validate_dimension(hole, "bore.diameter", view="plan", side="left")
if check["supported"]:
s.dimension(hole, "bore.diameter", view="plan", side="left")
else:
print(check["issues"], check["options"])
Both queries return versioned JSON-ready dictionaries without recording intent, preparing the
Sheet, recognising geometry or rendering. Use complete view/side pairs: a side supported in one
view may be unavailable in another. The axis argument follows dimension()'s parameter-variant
rules; full parameter ids already name their variant. Location intent currently admits no view
or side override. Invalid references, unsupported controls and unsupported placement pairs have
separate structured refusal codes in validate_dimension(); dimension_options() raises on an
invalid reference.
Hole-size callouts in the plan and side views accept left or right. The overall
envelope height accepts left or right in the front view. These hints can separate
hole sizes from vertical location dimensions without moving annotation coordinates:
sheet.dimension(lower_hole, "bore.diameter", side="left")
sheet.dimension(upper_hole, "bore.diameter", side="left")
sheet.dimension(envelope, "height.length", side="left")
sheet.dimension(lower_hole, "location")
sheet.dimension(upper_hole, "location")
Each side belongs to its named view: the front-view height and side-view hole sizes occupy different boundaries. The layout reserves space for the left height and short step rises together before choosing scale and page. The shared solver places the annotations within those boundaries; generated scripts retain the authored sides. An explicit side is a constraint: an infeasible placement reports the affected measurement instead of silently choosing the opposite boundary. Repair preserves an authored overall-height side, including after a label adjustment.
The explicit single_dimension_placement_rules scope means supported reports acceptance by
the current planner placement rules. Both documents set requires_build_validation: true:
whole-part classification can select a different renderer, so this query does not prove actual
rendered support. For example, a boss on a turned part can use a different diameter renderer
from a boss on a prismatic part. Agreement with other authored dimensions, visibility in an
authored view set, available space, completeness and collision-free placement also require a
build. Build and lint still assess the resulting Sheet. A
query never replaces an existing dimension request. Keep the original feature handle for edits;
these reports do not introduce persistent feature identities or new lane, route or pin controls.
Angular measurements¶
Sheet.angle() derives an included angle from a vertex and two witness points
in model coordinates. Select its canonical included.angle measurement with
dimension(). The engine chooses a true-angle principal view and places the arc,
arrows and complete compiler-owned label through the shared corridor solver.
from math import sqrt
from build123d import Polygon, extrude
from draftwright import Sheet
part = extrude(Polygon((0, 0), (30, 0), (15, 15 * sqrt(3)), align=None), amount=3)
sheet = Sheet(part).authored_dimensions()
angle = sheet.angle(
vertex=(0, 0, 3), first=(15, 0, 3), second=(7.5, 7.5 * sqrt(3), 3),
)
angle.tolerance(0.05, on="included.angle")
sheet.dimension(angle, "included.angle")
drawing = sheet.build()
for finding in drawing.lint():
print(finding.code, finding.message)
The default sector="minor" is the non-reflex angle towards the two witness
points. sector="opposite" selects the vertically opposite sector, extending
both supports through the vertex. It preserves the numerical angle and can keep
an exterior dimension beside its corner. The engine never silently switches
between these sectors or substitutes a supplementary angle.
Reversing witness order preserves the selected angle. For extended supports meeting beyond
a rounded corner, use virtual_vertex=True; this declares the virtual
intersection without asserting that the vertex lies on a physical edge.
Zero-length or collinear rays, oblique planes and unsupported sectors are
rejected. Insufficient page space produces angular_dimension_dropped against
the named measurement. Symmetric tolerances and lower/upper deviation magnitudes
retain degree units, including small deviations when the nominal display is
coarse. Omitting the dimension request suppresses the angle; generated scripts
retain its reference geometry and tolerances.
After building, drawing.dimension(feature, "included.angle", pin=True, priority=2)
uses the shared corridor in live and deferred edits. As with other edits, use an
exact feature from drawing.model(). Measurement comparison records the angular
references, so equal-valued support substitutions cannot appear preserved merely
because the labels match.
Sheet.angle_pattern(*references) declares repeated coplanar corners using
AngularReference values from draftwright.model. Each corner remains independently
addressable as included.angle.member1, included.angle.member2, and so on, in
declaration order. Request every member with dimension() to permit one quantity
label such as 3× 60°. Different member tolerances or side preferences produce
individual labels; omitting one member suppresses that measurement without renumbering
the others. The generated script retains the full member geometry and each request.
After the conservative strip solve, curved dimensions have up to three corner-local radius alternatives at the existing tier spacing. The shared placement stage accepts a shorter radius or recovers a dropped candidate only when its actual lines and label clear fixed and same-batch ink and fit the page. Pinned dimensions retain their solved position.
The lower-level measured_dimension(kind="angular", ...) route retains nominal
author-supplied text. Its explicit AngularReference supplies the same ray
geometry; plain Sheet-authored three-point ref_pts use (first, vertex, second).
Imported PMI requires explicit ordering. Structured tolerances on that raw route
remain unsupported; use the canonical declaration for new authored angles.
Lint compares degree values at their displayed resolution, inspects the visible
angular ink and checks complete canonical labels against the compiler. Physical
critique checks each claimed corner against finite supports in the cached provider
evidence, including members represented by a quantity label. Unprovable correspondence
produces angular_support_unverifiable; contradictory supports produce
angular_support_mismatch.
Automatic drawings derive outside-profile angular requirements from ordered face supports, available since Quiddity 0.2.6. Angles already defined by a recognised chamfer or regular-polygon callout do not add duplicate automatic requirements. This requires the same run's exact face or shared-edge evidence, not matching numerical angles; explicit angle declarations remain available on those corners. Verified profile repetitions can share a quantity label; equal numerical angles alone do not establish a pattern. The requirement audit retains one outcome per physical corner even when several share one mark.
Selecting a hole location component¶
Use location alone to request the usual location set. To select one component of a hole
group or pattern, pair its declared member index with the measured world axis:
options = sheet.dimension_options(holes, "location")
print(options["location_components"]) # valid member/axis pairs and their parameter ids
sheet.dimension(holes, "location", member=1, axis="y")
Member indices start at zero and refer to the declared members tuple. A singleton hole
has member 0. The axis is transverse to the hole's own axis: an X-directed hole can have
Y and Z location dimensions. A bolt-circle pattern also accepts member="centre".
Both selectors are required for a fine request; invalid members or axes are refused.
The existing validate_dimension() query accepts the same selectors.
The coarse pattern request keeps its automatic anchor policy: the member nearest the datum, or the centre of a bolt circle. Adding a fine request selects that extra component without selecting its sibling axis. Repeating a request does not duplicate a measurement. Generated scripts carry the member and axis so removing one location declaration omits that component. Coincident dimensions can share one mark while recording every approved owner.
These indices address one declaration and its generated script. They do not establish correspondence across unrelated recognition runs. The shared solver still chooses where to place the dimension; location selection supplies no page coordinates.
Sheet¶
Comparing measurements after a declaration edit¶
compare_measurements() compares named compiled measurements, including their owner,
parameter, nominal value, tolerance, directional span and rendered claim text. Linear
dimensions also retain their measured path lengths in model units, so exchanging labels
between two spans cannot hide behind an unchanged label multiset. Recorded per-annotation
spans and location components also distinguish equal-valued axes under a coarse parameter
id. These are provenance claims; independent lint must still check physical targets.
Reuse the feature objects in a declaration when editing placement intent:
from dataclasses import replace
from draftwright import build_drawing
from draftwright.audit import compare_measurements
original_model = sheet.model()
edited_model = replace(
original_model,
authored_dimensions=tuple(
replace(request, side="left") if request.role == "height.length" else request
for request in original_model.authored_dimensions
),
)
before = build_drawing(part, model=original_model)
after = build_drawing(part, model=edited_model)
result = compare_measurements(before, after)
print(result["status"], result["lost"], result["changed"], result["unknown"])
The status is preserved, changed or unknown. An unresolved owner or unconfirmed
claim prevents preserved. For an in-place edit, capture
before = drawing.measurement_snapshot() before changing the drawing, then compare
that snapshot with the edited drawing. Snapshots retain process-local feature references.
Separately rebuilt declarations do not acquire correspondence from equal geometry or
inventory order. If the caller knows the correspondence, pass
feature_pairs=((old_feature, new_feature), ...). Each pair must name exact owners in
the respective snapshots and the correspondence must be one-to-one. These are caller
assertions; the comparison checks measurement meaning under them. Output owner numbers
are positions in the before snapshot for presentation, not durable identities.
diff_builds() includes this result as measurement_comparison and detects same-kind
substitutions between shared declared owners. A clear annotation diff can still have
unknown measurement correspondence. Use independent lint_summary()["quality"]
evidence alongside the comparison: named-measurement preservation does not establish
physical completeness, pin preservation or release readiness.
Sheet
¶
Bases: _SheetViewMethods
Reference features, declare their drawing aspects, export.
Each declaration method mirrors a :mod:draftwright.model constructor: pass the
build123d object to read its geometry, or explicit values. :meth:hole returns a
chainable :class:_Hole (.through() / .depth()), :meth:diameter / :meth:step
a :class:_Dim, for their own aspects; :meth:pocket / :meth:slot /
:meth:rectangular_blind_slot / :meth:envelope a :class:_Params
(.tolerance(on=…)) which forwards unknown attributes to the
Sheet so those verbs still chain to any further declaration (preserving their prior
return-Sheet behaviour); the remaining verbs return the Sheet. :meth:build /
:meth:export hand the declared features to the engine with detection skipped.
features
property
¶
The declared IR features — mutable: override, drop or reorder before
:meth:build.
Each feature carries an identity token (#908), so a reorder via
:meth:~_FeatureView.reverse or :meth:~_FeatureView.sort moves every reference
with it — a handle, a tolerance, a GD&T origin, a section, an add_dimension
intent all follow their feature to its new position.
Assignment is not a move. features[i] = f and slice assignment mint a new
identity, because assignment cannot distinguish "move this feature here" from
"put a different feature here" — so references to the displaced feature fail
loudly rather than silently transferring to whatever replaced it. The same holds
for deletion. Use reverse/sort to reorder while keeping references.
view_constraints
property
¶
The immutable pre-projection view request authored on this sheet.
from_part(part, **opts)
classmethod
¶
Seed the declared set from detection (the hybrid mode, ADR 4 (was 0011 §3)): start
from the model the detector recovers, then override specific features (edit the
list via :attr:features, or re-declare) before :meth:build.
This states the automatic dimension source (#874), because that is what asking for
detection means: you have asked for the engine's reading of the part, features and
dimensions alike. It is not the implicit default the breaking change removed — a
Sheet(part) still has to say — it is from_part's own meaning.
Because the choice is from_part's rather than a script line's, adding
dimension(...) declarations overrides it rather than conflicting: detect the
features, then declare exactly which of their measurements to draw. That is the
natural way to take over a detected drawing, and requiring the caller to redeclare
every feature by hand to reach it would be a poor trade (#921). An
explicit auto_dimensions() still conflicts — there the script has said both things.
add(feature)
¶
Register a pre-built IR :class:~draftwright.model.Feature (escape hatch for
the constructors this façade does not surface directly, e.g. PMI).
Adding the exact feature object already in :attr:features returns a handle to
its existing registration. Equal-valued distinct objects remain separate features.
Other inputs raise :class:TypeError before changing the sheet.
Returns a handle, like every declaration verb (#922). It matters here more than it
looks: the ENVELOPE is emitted through this escape hatch rather than through
:meth:envelope, so while add returned the sheet, sheet.dimension(env, "width")
— ADR 4 (was 0016)'s own worked example — could not be written against a generated script.
Naming would have been uniform across the verbs and silently absent for one feature
in the middle of the file, which is worse than being absent everywhere. A raw
ControlFrame or DatumRef may name a handle as its origin; add resolves
and token-binds that provenance exactly like the public GD&T verbs.
dimension(feature=_UNSET, role=_UNSET, *, axis=None, member=None, view=None, side=None, **removed)
¶
dimension(feature, role) — the ADR 4 (was 0016) referential verb. See
:meth:_authored_dimension for the semantics.
The signature is the real one again (#720): the transitional call-shape dispatch to
:meth:measured_dimension was removed at 0.4.0, so dimension means one thing. That
restores most of what the @overload pair provided — the parameter names and the
DimensionIntent return a caller sees (#963) — without the dual shape.
One thing it does NOT restore: **removed means a type checker accepts any keyword
rather than rejecting an unknown one (#720). That is the price of catching the
legacy call at runtime to name its replacement; the alternative is a static error whose
text is about argument counts. Worth revisiting once the break is old news, at which
point **removed can go and the signature becomes exact.
feature/role default to a sentinel rather than being required so that a
keyword-only legacy call (dimension(kind=…, value=…)) reaches the message below
instead of a bare "missing 2 required positional arguments". The old shape never
appeared in a release, so this refusal is the only notice it gets — a documented
break (docs/deprecations.md).
view selects front/plan/side/rear and side selects the corresponding
above/below/left/right corridor where that dimension renderer supports
it. They express authored placement intent, not page coordinates; invalid or
unrenderable pairs fail clearly during planning.
dimension_options(feature, role, *, axis=None, member=None)
¶
Discover view/side pairs accepted by planner placement rules, without building.
Uses the same handle/id resolver as :meth:dimension and the planner's placement
rules. None means omit that override. Pairs are intentional: a side
supported in one view need not be supported in another. The result is JSON-ready.
Scope is one dimension's planner placement rules. Whole-part classification can
select a different renderer, so acceptance does not prove actual rendered support.
The full authored set, chosen views, compound-callout agreement and collision-free
placement also require build validation. Keep the supplied
feature handle to address subsequent edits; the result creates no persistent feature
identity. Invalid feature/measurement references raise as they do in dimension.
The query neither records an intent nor prepares, recognises, or renders the part.
validate_dimension(feature, role, *, axis=None, member=None, view=None, side=None, **unsupported)
¶
Preflight a proposed dimension call without recording or rendering it.
Returns supported, structured issues and the valid options for this
target. supported means accepted by the current planner placement rules;
actual renderer support and whole-sheet feasibility still require build validation.
Existing authored intent is never replaced or modified.
Unknown controls are refused rather than ignored or passed on to a renderer.
measured_dimension(*, kind, value, label, dominant_axis, ref_pts, ref_bbox=None, at=None, axis=None, upper_tol=None, lower_tol=None, lower_bound=None, upper_bound=None, source='sheet', source_kind=None, source_id='', lowering_blockers=(), rendering_blockers=(), cylindrical_refs=(), circular_refs=(), view=None, side=None, angular_reference=None, angular_references=(), angular_member_ids=(), angular_reference_item_groups=())
¶
Declare a drafting dimension from explicit measured values.
Named for what it carries (ADR 4 (was 0016) / #873). dimension is referential on
Drawing today and becomes so on :class:Sheet in #874 — it names a feature and a
role, and the engine reads the value off the geometry. The verb that carries a number of
its own needed a name saying so before that name could be reused. A measured dimension is
the one place a value does NOT come from the part.
This is the concept-shaped Sheet API used by generated AP242 scripts: the source file
may call the record PMI, but the editable script declares a dimension category, value,
label, referenced model points, and optional structured tolerances. For ordinary
geometry-backed edits prefer feature handles such as sheet.hole(...).tolerance(...).
lower_bound and upper_bound are the mutually exclusive alternative to
upper_tol/lower_tol for a limit range. source_id is the external record
identity retained by generated imported-PMI scripts; hand-authored dimensions normally
leave it blank. lowering_blockers carries the explicit reason a supported imported
requirement could not safely enrich a canonical feature parameter;
rendering_blockers carries the source-geometry reason it cannot be drawn truthfully.
view/side select a supported semantic corridor while leaving its actual
position to the normal placement solve.
angular_reference names a model-space vertex, first and second
ray witness, with optional virtual_vertex=True for extended supports.
Its only supported sector is the non-reflex "minor" sector. Supply it as an
AngularReference or mapping; empty ref_pts are filled from that reference.
A Sheet-authored angle with exactly three ref_pts uses the order
(first, vertex, second). Generic imported PMI stations require an explicit
angular reference; their order cannot be inferred from the point count.
Geometric intent is preserved even when angular rendering is unavailable.
Delegates to :func:draftwright.model.declare.measured_dimension (#704), so
build_drawing(model=…) callers can author the same feature without the façade.
of(ref)
¶
A decoratable handle onto an existing feature — the hybrid seam (#463).
ref is a fluent handle this sheet issued, a feature index, a :class:Feature already
in :attr:features (e.g. seeded by :meth:from_part), or the build123d object you
built (matched by ⌀ + in-plane position). Returns the same fluent handle the declaration
verbs do, so you can .fit(...) / .tolerance(...) — and, for a hole, .cbore(...)
— a feature you did not declare from scratch. Raises if the object matches no feature or
is ambiguous.
by_declaration(declaration_id)
¶
Return the live handle carrying declaration_id, or fail if it was withdrawn.
layout_options(declaration_id, *, parameter=None)
¶
Describe the bounded layout-only controls supported by one declaration.
This is a pre-build capability query, not a feasibility promise: the shared layout solve still decides whether the requested corridor can be used on the final sheet.
validate_layout_override(declaration_id, *, side=None, parameter=None, lane=None, **unsupported_controls)
¶
Preflight a layout override without mutating the sheet.
A supported result means the declaration and bounded vocabulary are valid. It still
requires :meth:build to establish geometric feasibility on the composed sheet.
layout_override(declaration_id, *, side=None, parameter=None, lane=None)
¶
Append one bounded declaration-scoped layout override.
side selects a feature corridor. parameter + lane selects a one-based
parallel lane for one declared referential dimension. Neither form accepts page
coordinates; the shared placement solve resolves and validates the physical offset.
hole(obj=None, **kw)
¶
Declare a hole from the tool cylinder you subtracted (or explicit values).
Returns a fluent handle: .through() (default) / .depth(d).
double_d_bore(obj=None, **kw)
¶
Declare a double-D bore from its cutter or explicit major ⌀ and A/F values.
diameter(obj=None, **kw)
¶
Declare an external cylindrical diameter (a boss / OD) — the ⌀ is read off the
object. Returns a handle: chain .tolerance(...) for a ± on the ⌀ (P2a).
boss(obj=None, **kw)
¶
Alias of :meth:diameter — an external cylindrical boss / OD.
polygonal_boss(**kw)
¶
Declare a regular polygonal-prism boss sized across flats and by height.
polygonal_stock(**kw)
¶
Declare whole regular polygonal-prism stock sized A/F and axially.
external_spur_gear(**kw)
¶
Declare one complete metric external spur involute gear requirement.
step(obj=None, **kw)
¶
Declare one axial segment of a turned profile (its OD + length). A model with any
step renders as a turned part. Returns a handle: .tolerance(...) tolerances the
step length by default, .tolerance(..., on="diameter") its OD (P2a).
slot(obj=None, **kw)
¶
Declare a milled slot (width + length, with optional end_radius).
oriented_slot(**kw)
¶
Declare a free-direction rectangular through slot and its source passage.
pocket(obj=None, **kw)
¶
Declare a blind rectangular recess — a floored slot/pocket (width × length ×
depth). From an object the depth axis defaults to the shortest bbox span; pass
depth_axis= for a recess deeper than it is wide.
rectangular_blind_slot(**kw)
¶
Declare a capped, edge-open rectangular U-section slot.
All arguments are explicit because cutter geometry alone cannot establish its open end, terminal wall, or material-opening direction.
round_bottom_blind_slot(**kw)
¶
Declare a capped, edge-open slot with a flat floor and equal round sides.
All arguments are explicit because detached cutter geometry cannot establish the open end, terminal wall, material-opening direction, or round-bottom ownership.
channel(**kw)
¶
Declare a full-span floored channel (wall-to-wall width only).
pad(obj=None, **kw)
¶
Declare a bounded rectangular raised pad (footprint + in-plane location).
Every pad owns its terminal-to-attachment height; a Z profile level measures a
different datum-to-attachment fact and does not replace it. axis= and
direction= preserve all six signed principal orientations.
chamfer(obj=None, **kw)
¶
Declare a chamfer (bevelled edge) — sheet.chamfer(bevel_face) reads axis, legs
and a point on the bevel off the oblique chamfer face, or explicit
sheet.chamfer(axis="z", leg=6, at=(x, y, z)). leg = equal-leg 45° (C{leg});
leg1/leg2 = asymmetric. turned=True declares that axis is the shaft
axis and selects its profile view; generated Sheet programs preserve this flag.
fillet(obj=None, **kw)
¶
Declare a fillet (rounded edge) — sheet.fillet(round_face) reads axis, radius
and a point on the round off the cylindrical blend face, or explicit
sheet.fillet(axis="z", radius=3, at=(x, y, z)). Called out R{radius} (grouped
n× R for equal radii). turned=True declares that axis is the shaft axis
and selects its profile view; generated Sheet programs preserve this flag.
blend(**kw)
¶
Declare a complete straight or circular rolling-ball blend path.
Explicit-only because one detached face cannot prove whole-path ownership or Fillet
precedence. path_kind='circular' additionally requires the centre-line
path_radius; non-principal paths retain axis_direction and receive one radius
callout.
paired_ramp_step(**kw)
¶
Declare a mirror-symmetric paired-ramp step by its axis, equal acute angle, open-to-terminal run length and shared-ridge midpoint. The form is explicit-only: a detached face or cutter cannot prove the paired material-removal topology.
gusset_rib(**kw)
¶
Declare a triangular reinforcing rib or a proven linear/mirror pattern.
angle(**kw)
¶
Declare an included angle from vertex/first/second model-space points.
The value is derived from those rays. Select included.angle with
dimension() and use the usual tolerance, omission and placement controls.
No number or annotation radius is authored.
angle_pattern(*members)
¶
Declare repeated AngularReference corners with independent member IDs.
The parameters are included.angle.member1, member2, etc. An
authored omission retains the other member identities; tolerances may
target one full parameter ID or the whole included-angle family.
hex_pocket(**kw)
¶
Declare a blind regular hex from physical mouth geometry.
circular_channel(**kw)
¶
Declare a cylindrical seat by axis, radius, run and three physical arc points.
circular_blind_step(**kw)
¶
Declare a quarter-cylindrical corner cut by radius, stopped depth, oriented
terminal-to-open centreline and canonical transverse quarter-arc section.
The form is explicit-only because one detached face cannot prove the blind cut.
through_step(**kw)
¶
Declare a rectangular open-profile step spanning its run axis.
The explicit section is (envelope endpoint, concave corner, envelope
endpoint) in the two non-run coordinates. Its two legs are independently
dimensioned in the end-on view; the through run is already owned by the envelope.
flat(obj=None, **kw)
¶
Declare a machined flat on round stock (#148b) — sheet.flat(flat_face) reads the
leader point off the planar flat face (axis= and across= still required), or
fully explicit sheet.flat(axis="z", across=15, at=(x, y, z)). Called out
{across} A/F (across flats — flat-to-flat for a double-D / hex, the D height for a
lone flat).
groove(obj=None, **kw)
¶
Declare a turned / circlip groove on round stock (#148c) — sheet.groove(floor_face)
reads axis, width, diameter and the leader point off the reduced-OD floor face, or fully
explicit sheet.groove(axis="z", width=3, diameter=16, at=(x, y, z)). Called out
{width} WIDE × ø{diameter} (groove width + floor diameter).
plate(obj=None, **kw)
¶
Declare a thin slab's thickness (a base plate / wall / rib) — sheet.plate(slab_box)
reads the thin axis + extent + centre off the slab, or explicit sheet.plate(axis="z",
lo=0, hi=4, u=10, v=5). Only a multi-plate part dimensions plates (a single slab is
the envelope).
rotational(**kw)
¶
Declare a turned body's axial furniture — OD, rotation axis, concentric bores
(#945): sheet.rotational(od=30, bores=(16,), axis="z").
The last recognised kind without a declarative surface, which is why a generated script for a turned part could not declare its dimensions at all (#938).
Keyword-only, unlike its object-capable siblings — see
:func:draftwright.model.rotational for why an object form would disagree with
detection (#950).
step_level(obj=None, **kw)
¶
Declare a prismatic height ladder + step-position shoulders (a rebated / stepped
block) — sheet.step_level(part) reads base / interior levels / the (axis,
position) shoulders / datum off the part, or explicit sheet.step_level(base=0,
levels=(10,), shoulders=(("x", 30),)). A shoulder locates where a step changes
height along a horizontal axis, so a stepped block is fully constrained (#555/#578).
pattern(member, **kw)
¶
Declare a hole pattern (bolt circle / linear array / grid) — build the
member with :func:draftwright.model.hole.
pocket_pattern(member, **kw)
¶
Declare a linear/grid array of identical blind pockets (#841) — build the
representative member with :func:draftwright.model.pocket. Renders as one grouped
N× W × L × D DEEP callout + (n-1)× pitch dim(s), not N competing size dims.
slot_pattern(member, **kw)
¶
Declare a linear/grid array of identical milled slots (#841) — build the
representative member with :func:draftwright.model.slot. Renders as one grouped
N× SLOT W × L leader + (n-1)× pitch dim(s), not N competing size dims.
envelope(obj=None)
¶
Declare the overall bounding dimensions. Defaults to the whole part.
The no-argument form reuses an equal whole-part envelope already seeded by
:meth:from_part; its returned handle therefore decorates and dimensions that
detected feature instead of appending a duplicate. An explicit obj remains a
distinct declaration, including when another envelope is already present.
general_tolerance(designation, *, statement='', source_id='', part21_id='')
¶
Declare the document-wide general tolerance printed in the title block.
default_surface_finish(ra, *, statement='', source_id='', part21_id='')
¶
Declare the document-wide surface finish shown beside the title block.
document_note(text, *, kind, source_id='', part21_id='', on_drawing=True, represented_by_source_ids=())
¶
Declare a document statement, optionally retained as model-only metadata.
datum(letter, ref, *, view=None, side=None)
¶
Declare a datum feature symbol (ISO 5459). ref is a build123d planar face, or
a feature handle / :class:Feature / index for a feature's axis. The target view + strip
side are derived from the geometry; view/side override them (ADR 4 (was 0011) P2c).
finish(ra, ref, *, view=None, side=None)
¶
Declare a surface-finish symbol (ISO 1302, Ra) on ref — a build123d planar face or
a feature. sheet.finish("3.2", top_face); view/side override the strip.
note(text, ref, *, satisfies=(), view=None, side=None)
¶
Declare a manufacturing note (#488) on a leader to ref — a build123d planar
face or a feature. The shop callouts detection can't infer: thread specs
(sheet.note("M3x0.5 TAP", bore)), DEBURR, chip-relief, knurl. Placed like the GD&T
items, clear of the views/title block; view/side override the derived strip.
satisfies may name canonical parameter ids only when ref is a feature; it grants
coverage only when this structured note is placed, never by parsing its prose (#1351).
structured_note(text, ref, *, satisfies=(), view=None, side=None)
¶
Declare a manufacturing note and return its own addressable feature handle.
This bindable spelling is for editors and generated scripts that must identify the
note declaration itself. Placement remains solver-owned; the handle carries no page
coordinates. :meth:note retains its Sheet-returning fluent behavior.
control(ref, *, view=None, side=None)
¶
A GD&T feature-control-frame builder on ref — a feature handle / :class:Feature /
index, or a build123d planar face. Chain one method per ISO 1101 characteristic
(.position(0.1, to="A B") …); each stacks a frame on the target. The target view +
strip are derived from the geometry; view/side override them (ADR 4 (was 0011) P2c.2).
schedule(rows, *, name, prefer='tr')
¶
Declare a measured table from (feature, parameter_ids) rows.
location selects every addressable hole member's transverse distances.
Explicit canonical IDs select individual measurements. Values, units and
tolerances come from the compiler; the existing table solve owns placement.
This declares authored dimension intent, like :meth:dimension.
table(rows, *, prefer='tr', name=None, block_cols=None)
¶
Declare a corner-block data table — positioned at :meth:build by the engine's generic
auto-placer, clear of the views, title block, and annotations (the same machinery as the
hole table), and lint-checked. rows is a sequence of equal-length row sequences (row 0
the header); cells are stringified. prefer ranks the free candidates by the page corner
to sit nearest ("tr"/"tl"/"br"/"bl"); it does not exclude viable interior
or opposite-corner space. A table with no fitting free region records an inspectable
table_dropped lint — it never overlaps. Revision blocks, BOMs and schedules all use
this; :meth:notes is the single-column convenience over it.
notes(lines, *, title='NOTES', number=True, prefer='tr')
¶
Declare a manufacturing NOTES block — a single-column :meth:table of lines with a
title header (None to omit) and optional 1 … auto-numbering::
sheet.notes(["BREAK ALL EDGES 0.3", "DEBURR", "M3x0.5 TAP"])
auto_dimensions()
¶
Soft deprecated (#1043) — prefer :meth:authored_dimensions in new code.
Supported, and not scheduled for removal: existing scripts keep working. But on this surface, authored dimensions are the right default, for three reasons:
- it is what draftwright itself emits —
--scriptwritesauthored_dimensions()with a line per dimension, so the automatic path is the only form the tool never produces; - omission means suppression (ADR 4 (was 0016)) only holds for an authored set. Under an automatic one you cannot express "not that one";
- an authored list is editable text. That is what makes "generate a script, then refine it" work, for a person or a model.
If you want the planner's choices as a starting point, generate them —
draftwright yourmodule:part --script — and edit the result, rather than asking
for them at runtime where they cannot be seen or changed.
Still asks for the planner's automatic set (ADR 4 (was 0016)). A build must say where its
dimensions come from rather than defaulting silently (#874), and this is one of the
two ways to say it. It is also what :meth:add_dimension augments.
build_drawing(part)'s automatic path is unaffected and carries no warning — that
is the detected front door, and being automatic is its whole point.
authored_dimensions()
¶
Declare that the :meth:dimension lines in this script ARE the complete set.
The other half of :meth:auto_dimensions, and until #933 it did not exist: the
authored source was entered implicitly, as a side effect of calling dimension() at
least once. That left one thing unsayable — the complete set is empty — because
absence of dimension(...) lines is also what a script with no source at all looks
like, which _check_dimension_source must reject. So a valid PartModel carrying
authored_dimensions=() built directly but could not be written as a script.
Calling dimension(...) still selects this source on its own; the verb only makes
the choice sayable when there is nothing else to say it. Emitted scripts write it
unconditionally, so an authored script states its source with a verb rather than with
a comment a reader has to trust (ADR 4 (was 0016) / #874).
add_dimension(feature, role, *, axis=None, member=None, view=None, side=None)
¶
Augment the planner's set with one more measurement (ADR 4 (was 0016) / #872).
feature is a declared-feature handle (what :meth:hole, :meth:boss, … return),
an index into :attr:features, or the IR feature itself. role names the
measurement — "bore.diameter", "grid_pitch", … — and carries no number: the
value is read from the geometry, so the size still lives in exactly one place.
Returns a :class:DimensionIntent.
Soft deprecated (#1043) — supported and not scheduled for removal, but it exists
only to augment :meth:auto_dimensions, which is itself discouraged here. To add a
measurement to an authored set, write a :meth:dimension line: same effect, and the
result is visible in the script rather than computed at runtime.
Requesting a measurement the planner already emits is a deliberate no-op, not an error: a script should be able to ask without first knowing the rule set's mind.
axis disambiguates a role a feature carries more than once — today a grid
pattern's two pitches. Omitting it there raises rather than picking one, because
a silent coin toss between the row and column pitch is the kind of wrong a reader
cannot see.
view/side have the same solver-owned placement semantics as on
:meth:dimension.
model()
¶
The IR the engine will draw (detection skipped) — for inspection. Wraps the
declared features into a :class:PartModel without rendering a drawing (#453):
the same wrapping :meth:build hands the engine (part bbox + corner datum + step-
inferred orientation + the P2a decorations), so inspection pays no projection/anno
cost and can't hit a layout/render failure. Wraps the solids body (as :func:_analyse
does), so the bbox/datum match what build() draws even when the part carries
bbox-extending non-solid geometry.
Gated on the dimension source like :meth:build (#874): this is the model the engine
WOULD draw, so a sheet that cannot be built must not hand one out — otherwise
build_drawing(part, model=sheet.model()) is a way around the check, and the two
public surfaces disagree about the same sheet (the #707 class of divergence).
build()
¶
Build the :class:~draftwright.drawing.Drawing — detection skipped; only the
declared features are drawn. Declared corner-block tables (:meth:table/:meth:notes)
are placed last, clear of everything already on the sheet.
export(stem=None, *, formats=('pdf',), dpi=150)
¶
Build the drawing and write the requested formats — a format name or an
iterable from ("svg", "dxf", "pdf", "png"), default PDF (matching the
CLI); return {format: path}. dpi sets the PNG raster resolution.
stem defaults to the drawing number, lower-cased.
formats=None means "unspecified", so it takes this method's PDF default.
:meth:Drawing.export requires an explicit format selection.
take_over(*, dimensions, principal_views, derived_views)
¶
Adopt a detected baseline with explicit, independently chosen sources.
This is the public transition from :meth:from_part's detected features and implicit
automatic dimensions to an editable Sheet request. Principal and derived views are
separate sources (ADR 2 (was 0018)): keeping automatic principal planning while authoring the
complete derived set replaces an inferred section instead of augmenting it; an empty
authored derived set explicitly suppresses every inferred section/detail.
The declaration is atomic and order-independent. Matching declarations made before this call are accepted, while an explicit contradictory source raises without changing any source. The incoherent ADR 2 (was 0018) combination -- authored views with automatic dimensions -- is always refused.
authored_views()
¶
Declare that subsequent :meth:view lines are the complete principal set.
Calling this with no view(...) lines makes the empty authored set explicit. It
remains a request even when a later build finds that no projectable drawing can satisfy
it; absence of the verb retains the behaviourally-compatible automatic default.
auto_views()
¶
Select automatic principal and derived views, optionally augmented by add verbs.
view(name)
¶
Add one view to the complete authored principal/orientation set.
add_view(name)
¶
Require one additional principal/orientation view in an automatic set.
section_view(label, through=None, *, at=None)
¶
Author a named section view through a declared feature or explicit Y cut plane.
add_section_view(label, through=None, *, at=None)
¶
Augment automatic derived views with one named section.
detail_view(label, around)
¶
Author a named detail view around a declared feature.
add_detail_view(label, around)
¶
Augment automatic derived views with one named detail around a declared feature.
section(feature=None, *, at=None)
¶
Request a full section A–A (#841) — the part-level verb behind the auto section.
A section fires automatically only when a Z-axis hole/pattern has a counterbore, spotface, or blind bottom; a blind pocket has no such driving hole, so its floor and depth stay hidden-line-only. This forces a cut so that internal profile reads.
The cut plane is normal to Y. feature — a fluent handle / :class:Feature /
index — cuts through that feature's centre (the natural "section through this
pocket"); at=<y> cuts at an explicit Y; bare section() cuts through the
part centre. The section renders last (its room check clears the right-of-side-view
band), so declare it after the per-feature verbs. Chainable.
detail()
¶
Ensure enlarged detail-view recovery is enabled (#42/#307/#841).
Automatic builds enable it by default; this verb is useful after constructing a
Sheet(..., detail_view=False) or when an emitted declaration should state the
choice explicitly. Adds a magnified crop of the step-height region when warranted
(a no-op otherwise). Chainable. Not feature-targeted — for a blind pocket's
floor/depth prefer :meth:section.
row(*views, gap=None)
¶
Constrain complete view blocks into a left-to-right row.
column(*views, gap=None)
¶
Constrain complete view blocks into a bottom-to-top column.
Authored views and layout¶
View declarations are semantic constraints. They name projections, relationships and whole-view anchors; the layout solve owns the resulting page positions. Authored views must be paired with authored dimensions so a deliberately omitted view cannot strand planner-selected annotations:
This runnable example includes the part, feature declarations, dimension discovery, three principal views and section A–A. The bore's location dimensions are deliberately omitted, and lint keeps those omissions visible.
from build123d import Box, Cylinder, Pos
from draftwright import Sheet
bore_tool = Pos(12, 0, 0) * Cylinder(3, 12)
part = Box(60, 40, 12) - bore_tool
sheet = Sheet(part, page="A3", scale=1, projection="third")
sheet.authored_dimensions().authored_views()
envelope = sheet.envelope(part)
for parameter_id in envelope.dimension_ids():
sheet.dimension(envelope, parameter_id)
bore = sheet.hole(bore_tool)
print(bore.dimension_ids()) # discover this handle's Sheet measurement IDs
sheet.dimension(bore, "bore.diameter")
for name in ("front", "plan", "side"):
sheet.view(name)
sheet.section_view("A", through=bore) # or at=0 for an explicit model-space Y plane
drawing = sheet.build()
issues = drawing.lint()
for issue in issues:
print(issue.severity, issue.code, issue.message)
# drawing.export("build/section", formats=("pdf", "svg"))
On a Sheet handle, dimension_ids() lists dotted measurement IDs; use
sheet.dimension(bore, "bore.diameter"). On a built Drawing, the second argument to
drawing.dimension(envelope_feature, "length", role="width") is a parameter kind,
with role= distinguishing measurements of the same kind. Hole diameter and depth
are callout content: use drawing.callout(bore_feature) for those, not
drawing.dimension(...). These are different vocabularies.
Augmenting automatic views¶
view() and section_view() specify complete authored sets. add_view() and
add_section_view() instead augment an explicitly automatic set. Choose the source
before adding views; an authored set cannot be switched to automatic in place.
This separate, runnable example keeps automatic principal and derived views and adds a section at model-space Y=0. Its dimensions are still explicitly authored:
from build123d import Box
from draftwright import Sheet
part = Box(60, 40, 12)
sheet = Sheet(part, page="A3", scale=1).authored_dimensions()
envelope = sheet.envelope(part)
for parameter_id in envelope.dimension_ids():
sheet.dimension(envelope, parameter_id)
sheet.auto_views()
sheet.add_section_view("A", at=0)
drawing = sheet.build()
issues = drawing.lint()
for issue in issues:
print(issue.severity, issue.code, issue.message)
auto_views() emits SoftDeprecationWarning: it remains supported and has no removal
date. The notice recommends the editable authored surface; it does not mean the
augmentation example is invalid. Automatic dimensions remain supported too, but they
cannot be paired with a complete authored view set.
Sheet.section() is deprecated with removal targeted at 0.6.0. For an authored
workflow replace it with section_view("A", through=feature) or section_view("A", at=y);
for automatic augmentation use auto_views() followed by add_section_view(...).
Drawing.section() is a separate automatic post-build operation and may add no section
when the geometry does not warrant one.
Rear views are explicitly requested. s.view("rear") includes rear in an authored
view set; s.auto_views().add_view("rear") adds it to the automatic baseline.
Rear looks toward the part from +Y with Z upward, so increasing world X runs left
on the page. This physical direction is the same under both projection conventions.
When side is also present, rear sits beyond side in the unfolding direction: left
for first-angle, right for third-angle.
Y-axis hole and hole-pattern callouts, locations and pitch dimensions can use rear, as can overall width and height. For example:
s = Sheet(part, projection="first").authored_dimensions()
hole = s.hole(diameter=6, at=(10, 20, 5), axis="y").through("THRU")
s.dimension(hole, "bore.diameter")
s.dimension(hole, "location")
s.view("rear")
drawing = s.build()
Dimensions still use the shared placement solve. An unsupported measurement/view combination is refused; a required depth extent, for example, needs side. Rear is never added automatically to improve visibility, coverage or hidden lines. Generated scripts preserve an explicit rear request.
view(...), section_view(...) and detail_view(...) define complete authored sets;
omission suppresses a view. add_view(...), add_section_view(...) and
add_detail_view(...) augment an explicitly selected auto_views() source. row(...) and
column(...) are shorthand for whole-view relations. A principal-view handle's pin((x, y))
anchors its projection origin in page millimetres; it never positions an annotation.
A detail around a turned step with an approved step.length uses a profile view
and redraws that length through the shared dimension pass. For example,
s.detail_view("A", around=shoulder).scale(3) retains its measurement identity and
tolerance at three times the sheet scale. Omitted measurements stay omitted. An
authored recovery detail that cannot place its dimension raises an error.
Principal orthographic views share the sheet scale and reject .scale(...). Detail and
isometric handles may carry an independent positive factor. Infeasible relations, scales and
pins raise with their declaration source; unsupported pins on derived or pictorial views are
refused at declaration and constraints are never silently relaxed. The immutable authored
snapshot is available as sheet.view_constraints, while drawing.view_plan is the distinct
resolved result.
Layout advisories are also available as structured findings from Drawing.lint() and
lint(physical=False). legibility_floor_breached reports an explicit scale below the
legibility floor when a legible automatic fit exists; page_fit_uncertain reports an
unsuccessful layout fit; layout_repack_stalled reports unresolved measured-repack
triggers; and scale_fallback_applied reports a computed scale or a completeness-driven
scale fallback. These warnings contribute to the legibility component of lint_summary().
A successful measured fit replaces the earlier estimated fit warning.
Taking over a detected baseline¶
Sheet.from_part(part) retains the detector's feature set and starts with implicit automatic
dimensions. Use take_over(...) to adopt that same Sheet as an explicit editable request without
copying features into a second Sheet:
s = Sheet.from_part(part)
hole = s.of(next(feature for feature in s.features if feature.kind == "hole"))
s.take_over(
dimensions="authored",
principal_views="automatic",
derived_views="authored",
)
s.dimension(hole, "bore.diameter")
s.section_view("A", through=hole)
The three sources are chosen atomically. In this example the principal planner keeps the
automatic front/plan/side/isometric baseline, while the authored derived set replaces inferred
sections. Leaving that authored set empty suppresses inferred derived views; selecting
derived_views="automatic" accepts them. Matching declarations may appear before or after
take_over(...) with the same result. Explicit source contradictions fail without partially
changing the Sheet, and automatic dimensions remain incompatible with authored views under ADR
0018.
To emit editable Python from an adopted Sheet, pass both request halves to the script emitter;
the model carries the feature/dimension semantics and view_constraints carries the independent
view sources and semantic targets:
from draftwright.sheet_emit import emit_sheet_script
source = emit_sheet_script(
s.model(),
"part = make_part()",
"drawing",
title="BRACKET",
number="DWG-001",
view_constraints=s.view_constraints,
)
Refining a staged hole into manufacturing authority¶
A detected hole remains a live feature handle after takeover. Enrich that handle instead of declaring a second, unrelated annotation. Counterbore/spotface tools can supply their geometry; thread and fit are authored manufacturing intent:
s = Sheet.from_part(part, page="A2", scale=1)
stack = s.of(next(feature for feature in s.features if feature.kind == "hole"))
s.take_over(
dimensions="authored",
principal_views="automatic",
derived_views="authored",
)
stack.cbore(collar_tool) # diameter and depth re-read from the tool
stack.thread("M6x1", depth=12).fit("H8")
for parameter_id in stack.dimension_ids():
intent = s.dimension(stack, parameter_id)
if parameter_id == "counterbore.diameter":
intent.format(decimals=2) # 6.35 remains 6.35, still feature-linked
s.section_view("A", through=stack) # replaces inferred derived views
drawing = s.build()
assert not [issue for issue in drawing.lint() if issue.severity != "info"]
drawing.export("quote/grm01", formats=("pdf", "svg", "dxf"))
An explicit tap depth is a real thread.depth parameter: it appears in
dimension_ids(), participates in authored-set suppression and placement, and round-trips
through generated Sheet code. fit("H8") applies only to bore.diameter; it does not leak
onto a counterbore diameter. A plain thread("M6x1") remains valid when no independent depth is
specified.
This surface models the common bore + recess + tap-depth stack in one solver-participating
callout. More general ordered operation stacks remain tracked by issue #1360. Until a physical
requirement has a structured parameter, use a feature-linked note(..., satisfies=(...)) only
for parameter ids the handle actually exposes; free prose does not satisfy coverage.
Generated scripts and editing agents use structured_note(...) when the note declaration
itself needs a stable selector:
note = sheet.structured_note(
"BORE DIAMETER VERIFIED",
stack,
satisfies=("bore.diameter",),
).identify("declaration:note", provenance="structured-note")
same_note = sheet.by_declaration("declaration:note")
It declares the same solver-placed manufacturing note as note(...), but returns the note's
own feature handle rather than returning the Sheet for chaining. The handle controls intent,
not page coordinates. Final reports can therefore link the declaration to its exact placed ink
while separately crediting the physical feature and parameter named by satisfies.
Straight and circular Blend chains¶
When recognition accepts a complete schema-v3 straight or circular rolling-ball Blend path that is not superseded by a dimension-worthy Fillet, it becomes one radius requirement. Declare the same meaning explicitly with the dedicated word:
blend = sheet.blend(
axis="z", # canonical dominant component (x/y/z tie-break)
axis_direction=(0.321394, 0.383022, 0.866025),
radius=0.2,
at=(12.345, -4.5, 6.789), # straight anchor / circular centre in part coordinates
side="convex",
)
sheet.dimension(blend, "blend.radius")
For a straight path, at is a point on its analytic line and axis_direction is the canonical
line direction. A circular path uses path_kind="circular", treats at as its centre and
axis_direction as its normal, and requires path_radius= for the centre-line circle's major
radius. radius remains the rolling-ball radius; side may be "convex" or "concave".
axis must match the same first-maximum x/y/z tie-break used by automatic conversion, so an
explicit declaration has the same exact occurrence identity and view routing as its released
record.
Generated Sheet code preserves every field.
Straight-path radius leaders target curved boundaries of the physical cylindrical patch.
Their at value locates the analytic axis, not the arrow tip. If a declared support is missing
or ambiguous, the engine reports blend_dropped. Physical lint reports
radius_leader_target_mismatch when a placed tip misses its trimmed boundary, or
radius_leader_target_unverifiable when that boundary cannot be established; these findings
lower fidelity. Circular-path toroidal blends retain their separate tangency selection.
These are part-space feature coordinates, never page positions. The annotation placement solve owns the final leader and label coordinates. The word is explicit-only because detached surface geometry cannot prove a complete chain or the provider aggregate's Fillet precedence.
View handle¶
_View
¶
A fluent handle for one semantic whole-view constraint.
Its layout verbs relate or pin the complete view block. They never address a feature annotation, so ADR 2 (was 0014) remains the sole owner of dimension/callout/GD&T coordinates.
left_of(other, *, gap=None)
¶
Keep this whole view block left of other.
right_of(other, *, gap=None)
¶
Keep this whole view block right of other.
above(other, *, gap=None)
¶
Keep this whole view block above other.
below(other, *, gap=None)
¶
Keep this whole view block below other.
align_x(other)
¶
Align this view's projection origin horizontally with other.
align_y(other)
¶
Align this view's projection origin vertically with other.
pin(at)
¶
Pin a principal view's projection origin at (x, y) page millimetres.
scale(factor)
¶
Set an independent detail/orientation scale; principal views reject it.
Dimension intent handle¶
dimension(feature, parameter_id) and add_dimension(feature, parameter_id) return a
referential DimensionIntent. The handle never carries a replacement nominal and never chooses
page coordinates. Use format(decimals=n) to preserve between 0 and 15 decimal places in the
printed nominal while reconciliation, tolerance, suppression and provenance continue to read the
numeric parameter from the feature. Optional view="front|plan|side|rear" and
side="above|below|left|right" arguments select a supported semantic corridor when authored
routing must override the derived default; the normal placement solve still chooses coordinates
and reports capacity/crossing failures. Trailing zeroes are intentional manufacturing display text:
sheet.authored_dimensions()
sheet.dimension(envelope, "width.length").format(decimals=2) # 13.5 prints as 13.50
sheet.dimension(tapped_hole, "bore.diameter", view="plan", side="left")
sheet.dimension(tapped_hole, "location").format(decimals=2) # all selected directions
A location intent's precision applies to all its selected directional values, including side-drilled holes and slots. The policy survives generated-script replay. When several location intents share one coincident mark, give them matching display precision; incompatible printed values raise an actionable error instead of silently choosing one intent's label.
DimensionIntent
¶
The handle :meth:Sheet.add_dimension returns (ADR 4 (was 0016)).
It exposes no coordinate. Optional view= / side= are declared on the verb as
semantic corridor selection; :meth:place can add a bounded relative lane. The engine
still solves and validates the candidate's physical position — a dimension line
references; the engine places.
ADR 2 (was 0012)'s .pin() / .priority() are deliberately absent for now. The engine
already has two spellings of "keep this put" at different layers, and adding a third
that no renderer consumes would ship a chainable verb doing nothing. This handle is
the extension point for them once that concept is converged.
Every other attribute forwards to the owning :class:Sheet, so the declare-then-chain
contract holds (sheet.add_dimension(bore, "depth").hole(...)) despite this
returning a handle rather than the sheet — the same rule :class:_Params follows.
format(*, decimals)
¶
Preserve decimals places in this dimension's printed nominal (#1349).
This is display policy on a referential intent, not a restated label: reconciliation,
tolerance, suppression and provenance continue to use the feature parameter's numeric
value and semantic identity. Automatic dimensions keep their existing formatting
unless their explicit add_dimension intent opts in. For a location intent,
the policy applies to every selected directional value.
place(*, lane)
¶
Prefer a one-based drafting-spaced lane from this dimension's witness.
The value is a relative rank, not a page-space offset. Physical placement and
feasibility remain owned by the shared measured-candidate solve, which may admit
a proven-clear interior or exterior candidate.
Generated scripts normally record the same policy through
:meth:Sheet.layout_override, which also retains declaration evidence.
Hole handle¶
_Hole
¶
Bases: _Nameable
A fluent handle for one declared hole — through vs blind (which changes the callout), and the P2a ± tolerance on its bore ⌀.
through(indicator=None)
¶
Declare through, optionally choosing its printed indicator ('' omits it).
depth(d)
¶
A blind hole d mm deep — adds a depth callout.
tolerance(lo, hi=None, *, source=None, source_ids=(), limit_bounds=None)
¶
A ± tolerance on the bore ⌀: symmetric .tolerance(0.05) (→ ±0.05) or a
limit pair .tolerance(0.0, 0.1) (→ +0.1 -0.0). Generated import scripts use
source / source_ids to retain external requirement provenance; generated
limit-dimension imports also carry their absolute bounds in limit_bounds.
fit(code, *, show='class')
¶
An ISO 286 fit class on the bore ⌀ — .fit("H7") renders ø8 H7 (the class,
default) or, with show="deviation", the signed deviations ø8 +0.015/0 resolved
for the bore's nominal ⌀. Raises for a class/size outside the built-in table (#29).
requirement(value, *, source, source_ids)
¶
Claim this existing bore diameter for external semantic source identities.
This changes no label: the canonical hole callout already prints the nominal value. Generated AP242 scripts use it to retain ownership without adding a duplicate dim.
cbore(obj=None, *, diameter=None, depth=None)
¶
A counterbore on this hole. .cbore(cbore_cyl) reads its ⌀ + depth off the
counterbore tool object (⌀ from the cylindrical face, depth from the part + tool along
the hole axis — no numbers restated), or pass explicit .cbore(diameter=…, depth=…).
An object supplies defaults; explicit kwargs override (#462).
spotface(obj=None, *, diameter=None, depth=None)
¶
A spotface on this hole — same as :meth:cbore but a shallow facing (#462).
countersink(obj=None, *, major=None, angle=None)
¶
A countersink on this hole (a flat-head screw seat, #575). .countersink(cone_tool)
reads the major ⌀ + included angle off the build123d Cone you subtracted, or explicit
.countersink(major=14, angle=90). Renders ⌵ Ø.. × ..° on the callout. An object
supplies defaults; explicit kwargs override (#451).
thread(spec, *, depth=None)
¶
A thread/tap spec folded onto this hole's callout (#764). .thread("M3x0.5")
renders the tap/thread on the bore leader (e.g. ø2.5 THRU M3x0.5) — a structured
aspect that round-trips, so .thread(...).finish(...) gives Ra-on-thread. A
declaration-only aspect (threads are cosmetic, not modelled geometry — no recogniser).
depth= retains an explicit tap depth as the independently addressable
thread.depth measurement (#1360).
finish(ra, *, view=None, side=None)
¶
A surface-finish symbol (Ra) on this hole's bore (ADR 4 (was 0011) P2c). .finish("1.6")
— the roughness text; view/side override the derived strip.
note(text, *, satisfies=(), view=None, side=None)
¶
A manufacturing note on a leader to this hole (#488).
satisfies explicitly names canonical parameter ids from :meth:dimension_ids that
the placed note meets instead of a drawn dimension. Plain prose has no coverage effect;
view/side override the derived strip (#1351).
Diameter and step handle¶
_Dim
¶
Bases: _Nameable
A fluent handle for a declared dimension-bearing feature (a diameter / boss OD, or a
turned step), carrying the P2a .tolerance aspect. default_kind is the parameter a
bare .tolerance(...) targets — "diameter" for an OD, "length" for a step.
tolerance(lo, hi=None, *, on=None, source=None, source_ids=(), limit_bounds=None)
¶
A ± tolerance on this dimension: symmetric .tolerance(0.05) (→ ±0.05) or a
limit pair .tolerance(0.0, 0.1) (→ +0.1 -0.0). on picks the parameter for
a multi-dim feature — a step's "length" (default) vs its "diameter" (OD),
or its canonical parameter id such as "step.length".
source / source_ids retain provenance on generated imported requirements.
fit(code, *, show='class')
¶
An ISO 286 fit class on this feature's ⌀ (always the diameter — a fit is diametral,
so a step's fit is on its OD, not its length). .fit("h6") renders ø12 h6 (the
class, default) or show="deviation" the signed deviations ø12 0/-0.011 resolved
for the nominal ⌀. Raises for a class/size outside the built-in table (#29).
requirement(value, *, on=None, source, source_ids)
¶
Claim a canonical nominal parameter for external source identities.
finish(ra, *, view=None, side=None)
¶
A surface-finish symbol (Ra) on this feature's surface (ADR 4 (was 0011) P2c).
diameter(journal).finish("0.8"); view/side override the derived strip.
note(text, *, satisfies=(), view=None, side=None)
¶
A manufacturing note on a leader to this feature (#488).
diameter(knurl).note("KNURL 0.8 STRAIGHT"); satisfies explicitly names
canonical parameter ids returned by dimension_ids() that the placed note meets
instead of a drawn dimension. Plain prose has no coverage effect; view/side
override the strip (#1351).
knurl(pitch, pattern='STRAIGHT', *, view=None, side=None)
¶
A knurl callout on this diameter (#765) — diameter(shaft).knurl("0.8") →
KNURL 0.8 STRAIGHT, or .knurl("0.8", "DIAMOND"). Named sugar over :meth:note
(canonical formatting + discoverability): knurl is a text callout on a leader, not
modelled geometry, so no IR/render — it flows through the same note path. view/
side override the derived strip.
thread(spec)
¶
An EXTERNAL thread spec appended to this OD's ⌀ callout (#859) — the turned analog of
:meth:_Hole.thread. step(shaft).thread("M3x0.5") renders ø3 M3x0.5; a structured
aspect on the feature, so .thread(...).finish(...) gives Ra-on-thread. Declaration-only
(threads are cosmetic, rarely modelled as geometry — no recogniser).
Multi-parameter handle¶
Circular-blind, paired-ramp and through steps use a multi-parameter handle because their requirements remain separately addressable:
Circular seats open at both run ends use sheet.circular_channel(...). The
centreline gives ordered endpoints on the cylinder axis; section gives three
physical arc points (start, angular midpoint, end) in ascending transverse world axes.
The geometry supports minor, semicircular, and major arcs. The engine anchors its
leader on the curved wall and locates the axis from the stock bounding-box minimum.
In an authored set, request seat_diameter.diameter, seat_run.length,
seat_sweep.angle, and location independently. A location intent covers X, Y, and Z;
its format(decimals=...) applies to all three. Generated scripts retain the full
geometry and these intents.
ramp = sheet.paired_ramp_step(
axis="y",
angle=51.34,
length=25,
at=(10, 7.5, 0), # midpoint of the shared ridge
)
sheet.authored_dimensions()
sheet.dimension(ramp, "ramp_angle.angle")
sheet.dimension(ramp, "ramp_run.length")
circular = sheet.circular_blind_step(
axis="x",
radius=4,
length=25,
centreline=((-5, 15, 10), (20, 15, 10)), # blind terminal → open envelope
section=((11, 10), (15, 10), (15, 6)), # arc endpoint, centre, endpoint
)
sheet.dimension(circular, "circular_step_radius.radius")
sheet.dimension(circular, "circular_step_depth.length")
step = sheet.through_step(
axis="z",
length=20,
at=(12.5, 7.5, 0),
section=((5, 15), (5, 0), (20, 0)),
)
sheet.dimension(step, "through_step_leg.length.x")
sheet.dimension(step, "through_step_leg.length.y")
blind = sheet.rectangular_blind_slot(
axis="z", # capped penetration/run direction
open_sign=-1, # source-envelope mouth along that run
length=20,
width_axis="x",
depth_axis="y",
depth_sign=1, # material-outward U-section opening
width=10,
depth=5,
at=(0, 7.5, 10),
)
sheet.dimension(blind, "rectangular_blind_slot_width.length")
sheet.dimension(blind, "rectangular_blind_slot_length.length")
sheet.dimension(blind, "rectangular_blind_slot_depth.length")
round_bottom = sheet.round_bottom_blind_slot(
axis="z",
open_sign=1,
length=20,
width_axis="x",
depth_axis="y",
depth_sign=1,
radius=3,
flat_width=4,
at=(0, -1.5, 10),
)
sheet.dimension(round_bottom, "round_bottom_blind_slot_length.length")
sheet.dimension(round_bottom, "round_bottom_blind_slot_flat_width.length")
sheet.dimension(round_bottom, "round_bottom_blind_slot_radius.radius")
These declarations are explicit-only. A detached face or cutter cannot prove the aggregate material-removal topology. Placement remains solver-owned; the engine selects the end-on view and positions the compound leader or linear section dimensions.
rectangular_blind_slot(...) is not an alias for slot(...) or pocket(...). Its dedicated
feature retains the open source-envelope end, capped terminal wall and flat-bottomed U-section,
and the solver places one OPEN SLOT width × length × depth DEEP leader carrying all three
compiler-approved measurement identities. In authored-dimension mode, any non-empty subset is
valid and is role-labelled (WIDE, LONG, DEEP) so every requested identity remains visible.
round_bottom_blind_slot(...) is a separate word, not a flag on the rectangular declaration.
Its independent dimensions are capped run length, straight bottom-flat width and equal side radius;
the total opening width and profile depth are derived from the latter two and are not duplicated in
the dimension plan. The solver places one ROUND-BOTTOM OPEN SLOT … leader. Authored subsets remain
role-explicit (LONG, BOTTOM FLAT, R) and never reconstruct an omitted sibling value.
An explicit through-step may use any principal run axis, and automatic detection supports the same three axes. Where an X/Y-run record's two exact physical intervals are already proved by the face-level plus shoulder/plate grammar and the envelope, that established grammar remains the owner. If even one leg is not proved, the aggregate local-leg grammar owns the occurrence and its exact matching lower-level fragments are removed, so one physical requirement reaches the sheet once. Completeness follows the chosen legacy dimensions too: their authored omission, placement drop, or missing ink is not hidden by the ownership choice.
_Params
¶
Bases: _Nameable
A fluent handle for a declared MULTI-parameter feature — a pocket
(width/length/depth), slot (width/length) or envelope (width/height/depth) — whose
parameters share a KIND but have distinct ROLES. .tolerance(..., on=role)
tolerances ONE parameter by role (#746: e.g. a pocket's depth without touching its
width/length); a bare .tolerance(...) (no on) folds onto every parameter of
the feature (the back-compat kind-keyed form). on accepts the full role
("pocket_depth") or its short tail ("depth").
Every other attribute forwards to the owning :class:Sheet, so these verbs stay
chainable (sheet.pocket(...).hole(...).build()) despite returning a handle —
the module's declare-then-chain contract holds (#807).
tolerance(lo, hi=None, *, on=None, source=None, source_ids=(), limit_bounds=None)
¶
A ± tolerance: symmetric .tolerance(0.05) (→ ±0.05) or a limit pair
.tolerance(0.0, 0.1) (→ +0.1 -0.0). on targets one parameter by its
full id or discriminator, or a parameter family by its shared role (on="depth"
on a pocket → a role-keyed decoration); omit on to tolerance every parameter
of the feature alike (the kind-keyed form). source / source_ids retain
provenance on generated imported requirements.
requirement(value, *, on, source, source_ids)
¶
Claim the feature's canonical diameter for external semantic source identities.
note(text, ref=None, *, satisfies=(), view=None, side=None)
¶
A free-text manufacturing note (#841). With no ref the note anchors to THIS feature —
sheet.slot(...).note("5X OBROUND SLOT") — mirroring :meth:_Hole.note / :meth:_Dim.note
(previously this raised, because the forwarded Sheet.note needs a target). An explicit
ref (a face or feature) still forwards to :meth:Sheet.note, preserving the handle's
forwarding contract for sheet.slot(...).note("DEBURR", face). Returns the handle
(chainable). satisfies explicitly names canonical parameter ids from
dimension_ids() that the placed note meets instead of a drawn dimension;
plain prose has no coverage effect. view/side override the derived strip
(#1351).
Axis-aligned through-slots¶
Sheet.slot(...) declares the independently addressable slot_width.length and
slot_length.length measurements. Pass end_radius= only for a stadium/obround slot; this adds
slot_end_radius.radius, rendered as a solver-placed 2× R… leader whose arrow remains normal
to either end arc. Leaving it unset preserves the rectangular-slot grammar. The declared radius
must equal half the width, and generated Sheet scripts retain the field when recognition proved
semicircular ends.
GD&T control builder¶
_Control
¶
A fluent GD&T feature-control-frame builder (ADR 4 (was 0011) P2c.2). One method per ISO 1101 characteristic — each appends a control frame on the same target, so chained calls stack::
sheet.control(bore).position(0.1, to="A B").perpendicularity(0.05, to="A")
to= names the referenced datum letter(s) ("A" / "A B" / ("A", "B"));
diameter= prefixes the zone with ⌀ (the default for position/concentricity);
modifier= a material-condition symbol ("M"/"L"/"P"). The target view + strip
are derived once (from the feature/face) when :meth:Sheet.control runs; view=/side=
there override them.
straightness(tol, *, modifier=None)
¶
Apply straightness tolerance tol; no datum reference is permitted.
flatness(tol, *, modifier=None)
¶
Apply flatness tolerance tol; no datum reference is permitted.
circularity(tol, *, modifier=None)
¶
Apply circularity tolerance tol; no datum reference is permitted.
cylindricity(tol, *, modifier=None)
¶
Apply cylindricity tolerance tol; no datum reference is permitted.
profile_line(tol, *, to=None, modifier=None)
¶
Apply line-profile tolerance tol, optionally relative to datum(s) to.
profile_surface(tol, *, to=None, modifier=None)
¶
Apply surface-profile tolerance tol, optionally relative to datum(s) to.
angularity(tol, *, to=None, modifier=None)
¶
Apply angularity tolerance tol relative to datum(s) to.
perpendicularity(tol, *, to=None, modifier=None)
¶
Apply perpendicularity tolerance tol relative to datum(s) to.
parallelism(tol, *, to=None, modifier=None)
¶
Apply parallelism tolerance tol relative to datum(s) to.
position(tol, *, to=None, diameter=True, modifier=None)
¶
Apply position tolerance tol relative to to; diameter selects the zone.
concentricity(tol, *, to=None, diameter=True, modifier=None)
¶
Apply concentricity tolerance tol relative to to; diameter selects the zone.
symmetry(tol, *, to=None, modifier=None)
¶
Apply symmetry tolerance tol relative to datum(s) to.
circular_runout(tol, *, to=None, modifier=None)
¶
Apply circular-runout tolerance tol relative to datum(s) to.
total_runout(tol, *, to=None, modifier=None)
¶
Apply total-runout tolerance tol relative to datum(s) to.
Imported AP242 nominal-diameter ownership requires absolute agreement within 1e-6
with the canonical feature diameter, using the same rule as planning. A larger mismatch
keeps the original source value and identity in a blocked dimension; it does not replace
the canonical value or crash planning. Generated Sheet scripts retain the blocker, and
authored_dim_source_unresolved reports the withheld source dimension through lint.
Oriented through-slots¶
Sheet.oriented_slot(...) declares a rectangular through-slot with explicit width, long and
run directions and its retained passage geometry. Its two independently addressable
measurements are oriented_slot_width.length and oriented_slot_length.length; generated
Sheet scripts preserve those directions and the passage. The current drawing grammar requires
both run ends to be open and perpendicular to the run. Automatic conversion rejects a source
with a nonzero end-plane gradient, because the IR cannot retain that slope and a flat opening
would put the leader on the wrong physical rim.