kumiki.drawing¶
Flat import path
kumiki/__init__.py re-exports everything on this page via from kumiki import *, so every name below is also available directly as kumiki.Drawing -- you do not need to import from the submodule path shown in the heading above.
kumiki.drawing
¶
What a frame asks to have drawn.
A drawing names itself and the timbers it is of, and never a layout: where the views go on the page, which way their cameras face and at what scale are worked out from the timbers themselves. So a frame says what it wants drawn and never how to draw it, and the same drawing is as right on a small sheet as on a large one.
Measurements hang off the viewport they are drawn in, because a drawing is a projection and a dimension only means anything in the plane it is projected onto. The same two features measured in the front elevation and in the plan view are two dimensions with two numbers, and either may be meaningless while the other is fine.
Drawing
dataclass
¶
Drawing(name: str, timber_paths: Tuple[TimberPath, ...] = (), drawing_id: Optional[DrawingId] = None, measurements: Mapping[str, Tuple[Measure, ...]] = dict())
A drawing the frame asks for: a name, and which timbers it is of.
Timbers are named by path, the same name they carry everywhere else, and by path alone -- which of two timbers sharing a path is not a question a name can answer, and a drawing of "the front left post" should not have to know whether one was made twice. A path naming no timber is not an error either: a drawing of a timber a later edit removed is worth keeping and showing as empty, rather than failing to raise the frame it belongs to.
drawing_id is what an override in the drawings file names, so it has to
survive editing the code around it. It defaults to the name, which is stable
as long as the name is.
measurements
class-attribute
instance-attribute
¶
measurements: Mapping[str, Tuple[Measure, ...]] = field(default_factory=dict)
__post_init__
¶
Source code in kumiki/drawing.py
Measure
dataclass
¶
Measure(anchor_a: FeaturePath, anchor_b: FeaturePath, kind: Optional[MeasurementKind] = None, measure_id: Optional[MeasurementId] = None, placement: Optional[MeasurementPlacement] = None)
A dimension between two features, drawn in one viewport.
TODO identity should include kind as well
TODO should we enforce canonical ordering on anchor_a / anchor_b, we can create a new class CanonicalFeaturePathPair or something
Identity is the anchors, plus measure_id when the same pair is measured
more than once in the same viewport -- deliberately not a position in a
list, so that a measurement generated by an algorithm keeps whatever the
drawings file has said about it when the algorithm next runs and emits a
different number of them. It is scoped to the viewport, since that is where
a measurement lives.
__post_init__
¶
Source code in kumiki/drawing.py
kind_identity
staticmethod
¶
kind_identity(kind: Optional[MeasurementKind]) -> Tuple
A kind in a comparable form, or an empty one for "whichever is natural".
The parts rather than the name, because a name can arrive as an older
one -- angle and projected_angle are the same kind written years
apart, and must not read as two different measurements.
Source code in kumiki/drawing.py
pair_identity
¶
identity
¶
What makes this measurement itself, within its viewport.
The anchors come already in one order (see _canonicalise_anchors), so measuring A to B and measuring B to A are one measurement.
The kind is part of it, because two kinds between one pair are two dimensions and both should show: the horizontal and the vertical between the same two points is an ordinary thing to want. The alternative was making the author mint a measure_id to tell them apart, which is a chore for the common case.
The cost, which the editing flow has to know about: changing a measurement's kind changes its identity. So an override cannot edit a code measurement's kind in place -- it is a different measurement now. Say it as suppressing the original and adding the new one, which is what those two mechanisms are already for.
Source code in kumiki/drawing.py
MeasurementDirection
¶
Bases: Enum
Which direction a distance is taken along.
PERPENDICULAR is the shortest distance and means something in either space. HORIZONTAL and VERTICAL are directions of the sheet, so they exist only when projected -- the solid has no up. The three-dimensional counterpart is a distance along a named direction, which does not exist yet.
MeasurementFeature
¶
Bases: Enum
What a feature behaves as, for the purpose of measuring it.
Four members, but two of them belong to one space each. A face is a PLANE in the solid and becomes either a LINE or an AREA once projected, depending on whether it is seen edge-on. AREA is the projected dead end: a face seen at an angle covers the view, and there is no distance between two things that each cover the view.
That one distinction is the whole of the difference between the two spaces. Face to face angle and perpendicular distance are perfectly good questions in the solid, where both are planes, and meaningless on the sheet, where both are areas.
MeasurementKind
dataclass
¶
MeasurementKind(operation: MeasurementOperation, space: MeasurementSpace, direction: MeasurementDirection = PERPENDICULAR)
What a dimension is measuring.
A structured value rather than one name per combination. The combinations multiply -- every operation needs a projected form and a solid one, and a distance needs a direction -- so spelling each out by hand means a name to invent and keep in sync for each, and the list doubles again when RADIUS or a distance along a named direction arrives.
The name is composed from the parts instead, which is why there is no
mapping to maintain: projected_horizontal_distance is exactly its three
fields, read out.
direction
class-attribute
instance-attribute
¶
direction: MeasurementDirection = MeasurementDirection.PERPENDICULAR
__post_init__
¶
Source code in kumiki/drawing.py
__str__
¶
as_wire
¶
The form a file holds, which says each part rather than naming the whole.
Not the composed name, because one name is ambiguous: angle is what
this calls a solid angle, and is also what every measurement written
before spaces existed calls a projected one. Saying the space outright
costs a few characters and cannot be misread.
Source code in kumiki/drawing.py
from_wire
classmethod
¶
from_wire(value) -> Optional[MeasurementKind]
A kind as read from a file: the structured form, or an older name.
Source code in kumiki/drawing.py
parse
classmethod
¶
parse(text: str) -> MeasurementKind
Read a kind back from its name, or from one of the older names.
The old names were all projected, and aligned and perpendicular both
become a perpendicular distance: between two points the shortest
distance IS the distance, which is why the two collapsed into one.
Where an old name and a new one collide -- angle, which now composes
for a SOLID angle -- the old reading wins, because every file that
contains the word was written meaning the old one. Solid kinds are
written structured (see as_wire), so nothing needs the ambiguous form.
Source code in kumiki/drawing.py
MeasurementOperation
¶
MeasurementPlacement
dataclass
¶
Where a dimension sits, as distinct from what it measures.
Its own object rather than a bare number because placement grows: which
side of the feature the line sits on, where the text goes when it will not
fit between the arrows, whether a witness line is drawn. offset is the
only one of those that exists yet.
None throughout means "wherever the viewport puts it", which is what every measurement written before placement existed means.
MeasurementSpace
¶
Bases: Enum
Whether a measurement is taken on the sheet or in the solid.
A drawing is a projection, so a dimension on one measures what the viewport shows. The same two features also have a relationship in three dimensions, which is a different number and sometimes a different question entirely -- two faces at an angle have an angle between them in the solid, and cover each other on the sheet.
kinds_for
¶
kinds_for(feature_a: MeasurementFeature, feature_b: MeasurementFeature, space: MeasurementSpace, parallel: Optional[bool] = None) -> Tuple[MeasurementKind, ...]
Which kinds a pair admits, best first. Empty when it admits none.
feature_a and feature_b are what the two features behave as in this space -- already projected, if the space is projected. parallel says whether two directions line up, and is only consulted when both are lines or planes, since that is the only pair whose answer depends on it.
The rules are here rather than in the viewer because they are the same rules in both, and two copies of a table is how a table drifts. What the viewer keeps is the projection itself, which needs a camera to work out.