kumiki.joints.workshop.shavings¶
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.shavings -- you do not need to import from the submodule path shown in the heading above.
kumiki.joints.workshop.shavings
¶
Joint shavings helpers - re-exports from all shavings submodules.
DEFAULT_FEATURE_TEST_TOLERANCES
module-attribute
¶
DEFAULT_FEATURE_TEST_TOLERANCES = FeatureTestTolerances()
FEATURE_GROUP_PAIRS
module-attribute
¶
FEATURE_GROUP_PAIRS: dict = {FeatureGroup.SHOULDER_PLANE: frozenset({FeatureGroup.PERFECT, FeatureGroup.ROUGH, FeatureGroup.TENON}), FeatureGroup.PERFECT: frozenset({FeatureGroup.SHOULDER_PLANE, FeatureGroup.CROSSING}), FeatureGroup.ROUGH: frozenset({FeatureGroup.SHOULDER_PLANE, FeatureGroup.CROSSING}), FeatureGroup.TENON: frozenset({FeatureGroup.SHOULDER_PLANE}), FeatureGroup.CROSSING: frozenset({FeatureGroup.PERFECT, FeatureGroup.ROUGH}), FeatureGroup.NONE: frozenset()}
SomeTimberFeature
module-attribute
¶
SomeTimberFeature = Union[TimberFeature, TimberFace, TimberEnd, TimberLongFace, TimberEdge, TimberLongEdge, TimberShortEdge, TimberCenterline, TimberLongFaceCenterline, TimberCenterplane]
SomeTimberCenterline
module-attribute
¶
SomeTimberCenterline = Union[TimberCenterline, TimberLongFaceCenterline]
LongEdgeOrCenterline
module-attribute
¶
LongEdgeOrCenterline = Union[TimberLongEdge, TimberCenterline, TimberLongFaceCenterline]
LONG_TIMBER_FEATURES
module-attribute
¶
LONG_TIMBER_FEATURES: frozenset = frozenset([(feature.to) for feature in TimberLongFace] + [(feature.to) for feature in TimberLongEdge] + [(feature.to) for feature in TimberCenterplane] + [(feature.to) for feature in TimberCenterline] + [(feature.to) for feature in TimberLongFaceCenterline])
LocatedTimberFeature
module-attribute
¶
TimberLike
module-attribute
¶
TimberLike = Union[Timber, MeshTimber, RoundTimber, RegularPolygonTimber, Board]
IMPERFECT_TIMBER_WARNING
module-attribute
¶
IMPERFECT_TIMBER_WARNING = 'timber is imperfect (does not match perfect timber within), this joint currently does not supporting maknig relief cuts beyond the perfect timber within so the joint may not actually fit'
Ticket
dataclass
¶
Bases: ABC
Base ticket shared by all ticket categories.
The category is represented by the concrete subclass rather than an enum field.
hierarchical identifier using '/' as separator.
e.g. "posts/frontleft", "door/boards/1" The last segment is the display name; preceding segments are folder names.
kumiki_id
class-attribute
instance-attribute
¶
TimberTicket
dataclass
¶
TimberTicket(path: str = UNNAMED_TICKET_PATH, material: Optional[str] = None, reference_features: tuple[TimberFeature, ...] = (), tags: tuple[TimberTag, ...] = ())
Bases: Ticket
Ticket metadata for physical timber members.
reference_features
class-attribute
instance-attribute
¶
reference_features: tuple[TimberFeature, ...] = ()
__post_init__
¶
primary_reference_edge
¶
primary_reference_edge() -> Optional[LongEdgeOrCenterline]
The highest-priority reference feature that is a line, or None.
An arris, the timber's axis, or a long face's centerline -- whichever of those comes first in reference_features. Faces and center planes are skipped rather than turned into one: a drawing's reference edge is a line, and this reports the line the author asked for rather than inventing one from two faces they might not have meant as a pair.
Returned as the narrow type, so it goes straight into measuring's locate_edge or locate_long_face_centerline.
Source code in kumiki/ticket.py
with_member
¶
with_member(member: Union[Member, str]) -> Self
Return a copy in this member role, replacing whatever role it had.
with_tags cannot do this: a second role is an error, not an addition.
Source code in kumiki/ticket.py
AccessoryTicket
dataclass
¶
JointTicket
dataclass
¶
Drawing
dataclass
¶
Drawing(name: str, timbers: Sequence[PerfectTimberWithin] = (), drawing_id: Optional[DrawingId] = None, viewports: Optional[Sequence[Viewport]] = None, page: Optional[Page] = None, measurements: Mapping[ViewportId, Sequence[Measure]] = dict())
A drawing: a name, the timbers it draws, and its viewports.
measurements
class-attribute
instance-attribute
¶
measurements: Mapping[ViewportId, Sequence[Measure]] = field(default_factory=dict)
__post_init__
¶
Source code in kumiki/drawings/drawing.py
walk
¶
walk() -> Iterator[Tuple[ViewportId, Viewport]]
Yield every viewport with its id, depth first, parents before children.
Source code in kumiki/drawings/drawing.py
measurements_by_viewport
¶
measurements_by_viewport() -> Dict[str, Tuple[Measure, ...]]
Return the drawing's measurements, keyed by viewport id.
Source code in kumiki/drawings/drawing.py
leaves
¶
leaves() -> Iterator[Tuple[ViewportId, Viewport]]
Return every leaf viewport (one with a camera) with its id.
get_viewport_id
¶
get_viewport_id(viewport: Viewport) -> ViewportId
Return the id of viewport (its position in the tree).
Source code in kumiki/drawings/drawing.py
viewport_at
¶
viewport_at(viewport_id: ViewportId) -> Optional[Viewport]
Return the viewport with id viewport_id, or None.
Source code in kumiki/drawings/drawing.py
AssemblyFreedom
dataclass
¶
AssemblyFreedom(translations: Tuple[TranslationDof, ...] = (), rotations: Tuple[RotationDof, ...] = ())
The freedom shape for ONE member within ONE joint.
Currently a set of independent half/full intervals from 0 in R6. Future richer shapes (e.g. requires twisting while moving, or move-then-twist sequences) should extend this class rather than being encoded by callers.
translation
staticmethod
¶
translation(direction: Direction3D, freed_after: Numeric) -> AssemblyFreedom
A single half-interval translational freedom along direction.
Source code in kumiki/assembly.py
bidirectional_translation
staticmethod
¶
bidirectional_translation(direction: Direction3D, freed_after: Numeric) -> AssemblyFreedom
A full-interval translational freedom: two opposite half DOFs.
Source code in kumiki/assembly.py
combine
staticmethod
¶
combine(f1: AssemblyFreedom, f2: AssemblyFreedom) -> AssemblyFreedom
Union of the DOFs of two freedoms (a member that can escape either way).
Source code in kumiki/assembly.py
AssemblyJoint
dataclass
¶
AssemblyJoint(name: str, members: Mapping[int, JointMemberSpec])
One joint in the assembly graph.
members must contain an entry for EVERY member participating in the
joint.
AssemblyMember
dataclass
¶
AssemblyMember(key: int, name: str, position: V3, bbox: Optional[AxisAlignedBoundingBox] = None)
AssemblySolution
dataclass
¶
JointMemberSpec
dataclass
¶
JointMemberSpec(freedom: Optional[AssemblyFreedom] = None, ordering: Ordering = Ordering())
One member's participation in one joint.
A None freedom means "unspecified" and the connection is treated as rigid (the member is dragged along whenever the joint moves).
Ordering
dataclass
¶
Extraction position: compared lexicographically; smaller = out earlier.
suborder expresses sequencing required WITHIN a joint (locking
accessories at -1 pop before members at 0) and is authored by the joint
cut functions; order is the frame-level plan set via Joint.with_order.
label
¶
AssemblyBoundingBox
dataclass
¶
AssemblyBoundingBox(min_x: float, max_x: float, min_y: float, max_y: float, min_z: float, max_z: float)
Axis-aligned box in GLOBAL space; used only by the Phase 4 clear-out.
PerfectTimberWithin
dataclass
¶
Bases: ABC
Base class for all timber types in the timber framing system (immutable)
This is an abstract base class (ABC) to prevent direct instantiation. All timbers contain a perfect rectangular timber within their rough bounding box.
Note: Use create_timber() factory function to construct timber instances from length_direction and width_direction vectors. Subclasses are frozen to ensure immutability after construction.
Alternatively, if you already have a Transform object, you can construct a timber directly by passing: Timber(length, size, transform, ticket)
Attributes:
| Name | Type | Description |
|---|---|---|
length |
Numeric
|
Length of the timber along its centerline axis |
size |
V2
|
Cross-sectional size (width, height) of the perfect timber within |
transform |
Transform
|
Position and orientation in global coordinates |
ticket |
TimberTicket
|
Ticket for this timber (used for rendering/debugging) |
ticket
class-attribute
instance-attribute
¶
ticket: TimberTicket = field(default_factory=TimberTicket)
__post_init__
¶
Source code in kumiki/timber.py
get_orientation_global
¶
get_orientation_global() -> Orientation
get_bottom_position_global
¶
get_bottom_position_global() -> V3
Get the bottom position (center of bottom cross-section) in global coordinates from the transform.
get_length_direction_global
¶
get_length_direction_global() -> Direction3D
Get the length direction vector in global coordinates from the orientation matrix
Source code in kumiki/timber.py
get_width_direction_global
¶
get_width_direction_global() -> Direction3D
Get the width direction vector in global coordinates from the orientation matrix
Source code in kumiki/timber.py
get_height_direction_global
¶
get_height_direction_global() -> Direction3D
Get the height direction vector in global coordinates from the orientation matrix
Source code in kumiki/timber.py
get_face_direction_global
¶
get_face_direction_global(face: SomeTimberFace) -> Direction3D
Get the world direction vector for a specific face of this timber.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
face
|
SomeTimberFace
|
The face to get the direction for (can be TimberFace, TimberEnd, or TimberLongFace) |
required |
Returns:
| Type | Description |
|---|---|
Direction3D
|
Direction vector pointing outward from the specified face in world coordinates |
Source code in kumiki/timber.py
get_corner_position_global
¶
get_corner_position_global(corner: TimberCorner) -> V3
Get the position of a corner in global coordinates.
Source code in kumiki/timber.py
get_size_index_in_long_face_normal_axis
¶
get_size_index_in_long_face_normal_axis(face: TimberLongFace) -> int
Get the index of the size in the direction normal to the specified face.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
face
|
TimberLongFace
|
The long face to get the size index for (RIGHT/LEFT or FRONT/BACK) |
required |
Returns:
| Type | Description |
|---|---|
int
|
Index into self.size: 0 (width) for RIGHT/LEFT, 1 (height) for FRONT/BACK |
Source code in kumiki/timber.py
get_size_in_face_normal_axis
¶
get_size_in_face_normal_axis(face: SomeTimberFace) -> Numeric
Get the size of the timber in the direction normal to the specified face.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
face
|
SomeTimberFace
|
The face to get the size for (can be TimberFace, TimberEnd, or TimberLongFace) |
required |
Returns:
| Type | Description |
|---|---|
Numeric
|
The timber's extent along the axis normal to the given face: self.length for |
Numeric
|
TOP/BOTTOM, self.size[0] (width) for RIGHT/LEFT, self.size[1] (height) for FRONT/BACK |
Source code in kumiki/timber.py
get_rough_size_in_face_normal_axis
¶
get_rough_size_in_face_normal_axis(face: SomeTimberFace) -> Numeric
Get the full rough size of the timber in the direction normal to the specified face.
For long faces this returns the sum of the two half-sizes (e.g. right + left for RIGHT or LEFT). For end faces (TOP/BOTTOM) this returns the length.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
face
|
SomeTimberFace
|
The face to get the size for (can be TimberFace, TimberEnd, or TimberLongFace) |
required |
Source code in kumiki/timber.py
get_half_rough_size_in_face_normal_axis
¶
get_half_rough_size_in_face_normal_axis(face: SomeTimberFace) -> Numeric
Get the rough half-size of the timber from the centerline to the specified face.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
face
|
SomeTimberFace
|
A long face (RIGHT, LEFT, FRONT, or BACK). TOP/BOTTOM will raise ValueError since length has no asymmetry concept. |
required |
Returns:
| Type | Description |
|---|---|
Numeric
|
The half-size from centerline to the specified face. |
Source code in kumiki/timber.py
get_nominal_size_in_face_normal_axis
¶
get_nominal_size_in_face_normal_axis(face: SomeTimberFace) -> Numeric
get_half_nominal_size_in_face_normal_axis
¶
get_half_nominal_size_in_face_normal_axis(face: SomeTimberFace) -> Numeric
get_size_in_direction_2d
¶
Get the size of the timber's cross-section measured along an arbitrary 2D direction.
The direction is in the timber's local cross-section plane where x is the width axis and y is the height axis. Returns the total extent (support width) of the rectangular cross-section projected onto that direction.
For axis-aligned directions this matches get_size_in_face_normal_axis.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
direction
|
V2
|
A 2D direction vector (x=width, y=height) in local cross-section space. Does not need to be normalized. |
required |
Returns:
| Type | Description |
|---|---|
Numeric
|
The size of the cross-section measured along the given direction. |
Source code in kumiki/timber.py
get_size_in_direction_3d
¶
get_size_in_direction_3d(direction: Direction3D) -> Numeric
Get the size of the timber measured along an arbitrary 3D direction in global space.
Transforms the direction into the timber's local frame and computes the total extent (support width) of the rectangular prism projected onto that direction.
For axis-aligned directions this matches get_size_in_face_normal_axis.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
direction
|
Direction3D
|
A 3D direction vector in global coordinates. Does not need to be normalized. |
required |
Returns:
| Type | Description |
|---|---|
Numeric
|
The size of the timber measured along the given direction. |
Source code in kumiki/timber.py
get_closest_oriented_face_from_global_direction
¶
get_closest_oriented_face_from_global_direction(target_direction: Direction3D) -> TimberFace
Find which face of this timber best aligns with the target direction.
The target_direction should point "outwards" from the desired face (not into it).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
target_direction
|
Direction3D
|
Direction vector to match against |
required |
Returns:
| Type | Description |
|---|---|
TimberFace
|
The TimberFace that best aligns with the target direction |
Source code in kumiki/timber.py
get_closest_oriented_long_face_from_global_direction
¶
get_closest_oriented_long_face_from_global_direction(target_direction: Direction3D) -> TimberLongFace
Find which long face of this timber best aligns with the target direction.
The target_direction should point "outwards" from the desired face (not into it).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
target_direction
|
Direction3D
|
Direction vector to match against |
required |
Returns:
| Type | Description |
|---|---|
TimberLongFace
|
The TimberLongFace that best aligns with the target direction |
Source code in kumiki/timber.py
get_closest_oriented_end_face_from_global_direction
¶
get_closest_oriented_end_face_from_global_direction(target_direction: Direction3D) -> TimberEnd
Find which end face of this timber best aligns with the target direction.
The target_direction should point "outwards" from the desired end face (not into it).
Returns:
| Type | Description |
|---|---|
TimberEnd
|
The TimberEnd that best aligns with the target direction |
Source code in kumiki/timber.py
get_inside_face_from_footprint
¶
get_inside_face_from_footprint(footprint: Footprint) -> TimberFace
Get the inside face of this timber relative to the footprint.
This method finds which face of the timber is oriented toward the interior of the footprint by: 1. Finding the nearest boundary of the footprint to the timber's centerline 2. Getting the inward normal of that boundary 3. Finding which timber face best aligns with that inward direction
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
footprint
|
Footprint
|
The footprint to determine inside/outside orientation |
required |
Returns:
| Type | Description |
|---|---|
TimberFace
|
The TimberFace that points toward the inside of the footprint |
Source code in kumiki/timber.py
get_outside_face_from_footprint
¶
get_outside_face_from_footprint(footprint: Footprint) -> TimberFace
Get the outside face of this timber relative to the footprint.
This method finds which face of the timber is oriented toward the exterior of the footprint by: 1. Finding the nearest boundary of the footprint to the timber's centerline 2. Getting the inward normal of that boundary 3. Finding which timber face best aligns with the opposite (outward) direction
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
footprint
|
Footprint
|
The footprint to determine inside/outside orientation |
required |
Returns:
| Type | Description |
|---|---|
TimberFace
|
The TimberFace that points toward the outside of the footprint |
Source code in kumiki/timber.py
get_transform_matrix
¶
get_transform_matrix() -> Matrix
Get the 4x4 transformation matrix for this timber
Source code in kumiki/timber.py
project_global_point_onto_timber_face_global
¶
project_global_point_onto_timber_face_global(global_point: V3, face: SomeTimberFace) -> V3
Project a point from global coordinates onto the timber's face and return result in global coordinates.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
global_point
|
V3
|
The point to project in global coordinates (3x1 Matrix) |
required |
face
|
SomeTimberFace
|
The face to project onto (can be TimberFace, TimberEnd, or TimberLongFace) |
required |
Source code in kumiki/timber.py
get_perfect_size
¶
get_perfect_size() -> V2
Returns the perfect cross sectional size of the timber.
The perfect size is the cross sectional size of the perfect timber within.
can_be_extended_for_joints
¶
Returns True if the timber can be extended when cutting joints.
Returns:
| Type | Description |
|---|---|
bool
|
True if the timber can be extended when cutting joints. |
get_rough_half_sizes
abstractmethod
¶
Returns the rough half-sizes of the timber measured from the centerline.
The rough bounding box is defined by four half-sizes measured from the centerline in each direction. This allows the rough timber to be non-coaxial with the perfect timber within (useful for square rule layout).
Returns:
| Type | Description |
|---|---|
Tuple[V2, V2]
|
Tuple of two V2s: - width_halves: V2(right_half, left_half) — half-sizes in the width dimension - height_halves: V2(front_half, back_half) — half-sizes in the height dimension |
Source code in kumiki/timber.py
get_nominal_half_sizes
¶
get_rough_size
¶
get_rough_size() -> V2
Returns the rough cross sectional size of the timber.
The rough size is the total cross sectional size defined by the rough half-sizes. For a perfect timber, this matches the perfect size. For an imperfect timber, this may differ and represents the intended bounding box for joint layout and intersection tests.
Source code in kumiki/timber.py
get_perfect_timber_within_csg_local
¶
get_perfect_timber_within_csg_local(with_feature_labels: bool = True) -> RectangularPrism
Returns the perfect rectangular prism CSG in local coordinates.
This represents the perfect timber within as a CSG object -- the idealized, finished-dimension bounding box (self.size), not the rough/as-sawn stock boundary. All timber types have a perfect rectangular prism that bounds their actual geometry.
Built once, at construction.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
with_feature_labels
|
bool
|
False to leave the override labels off, giving a copy of the same prism whose faces carry the primitive's own default names instead. For a caller with a second copy of this timber in one tree, where two features of one name on one plane would be one surface declared twice. The two centerplanes are declared features of their own rather than overrides, and stay either way. |
True
|
Returns:
| Type | Description |
|---|---|
RectangularPrism
|
RectangularPrism in local coordinates (relative to timber's bottom position) |
Source code in kumiki/timber.py
csg_label_name
classmethod
¶
What this kind of timber is called in a CSG label.
Derived from the class name -- "board", "round_timber" -- so a new timber type names itself without anyone remembering to add it here.
Source code in kumiki/timber.py
csg_label
classmethod
¶
csg_label(*qualifiers: str) -> CutCSGLabel
Label for one of this timber's own CSG shapes.
A classmethod so the name follows the derived class -- a Board's rough extended prism reads "board (rough, extended)", not "timber (...)".
Source code in kumiki/timber.py
get_actual_csg_local
¶
get_actual_csg_local(with_feature_labels: bool = True) -> CutCSG
Returns the actual CSG geometry for this timber.
For the base PerfectTimberWithin class, this returns the perfect rectangular prism. Subclasses override this to return different geometries (cylinder, mesh, etc.).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
with_feature_labels
|
bool
|
False to leave the override labels off -- see get_perfect_timber_within_csg_local. |
True
|
Returns:
| Type | Description |
|---|---|
CutCSG
|
CutCSG representing the actual geometry in local coordinates |
Source code in kumiki/timber.py
get_extended_actual_csg_local
¶
get_extended_actual_csg_local(extend_bot: bool, extend_top: bool, with_feature_labels: bool = True) -> CutCSG
Returns the actual CSG geometry extended to infinity at specified ends.
For the base PerfectTimberWithin class, this returns a rectangular prism using the perfect timber within size, optionally extended to infinity.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
extend_bot
|
bool
|
If True, extend to -infinity at bottom (z=0) |
required |
extend_top
|
bool
|
If True, extend to +infinity at top (z=length) |
required |
with_feature_labels
|
bool
|
False to leave the override labels off, so the faces carry the primitive's default names. This geometry is often a copy of something already in the tree -- a cutter, a relief bound -- and one surface declared twice puts two features of one name on one plane, which derives the same edge twice. |
True
|
Returns:
| Type | Description |
|---|---|
CutCSG
|
CutCSG representing the extended geometry in local coordinates |
Source code in kumiki/timber.py
get_extended_perfect_csg_local
¶
get_extended_perfect_csg_local(extend_bot: bool, extend_top: bool, with_feature_labels: bool = True) -> CutCSG
Returns the PERFECT (finished-dimension) CSG geometry extended to infinity at specified ends -- always self.get_perfect_size(), regardless of any rough/actual sizing a subclass's get_extended_actual_csg_local may use instead. Unlike get_extended_actual_csg_local, this is not overridden per-subclass: every timber type's perfect timber within is a rectangular prism (see get_perfect_timber_within_csg_local), so one implementation suffices for all of them.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
extend_bot
|
bool
|
If True, extend to -infinity at bottom (z=0) |
required |
extend_top
|
bool
|
If True, extend to +infinity at top (z=length) |
required |
with_feature_labels
|
bool
|
False to leave the override labels off -- see get_extended_actual_csg_local. The centerplanes stay. |
True
|
Returns:
| Type | Description |
|---|---|
CutCSG
|
CutCSG representing the extended geometry in local coordinates |
Source code in kumiki/timber.py
get_extended_rough_csg_with_perfect_features_local
¶
get_extended_rough_csg_with_perfect_features_local(extend_bot: bool = False, extend_top: bool = False) -> CutCSG
The rough body (get_extended_actual_csg_local), carrying every perfect plane it lacks: the drawn timber, and the one its features are solved on.
A long face lying on a rough face is already that face, named rptw.. Every other one is added as a PlaneFeature named ptw. -- its own carrier, so the solver measures to it -- and both centerplanes as MidplaneFeatures between whichever planes are the perfect ones.
Raises NotImplementedError for a rough body that cannot carry features.
Source code in kumiki/timber.py
rough_face_tags
¶
rough_face_tags() -> Tuple[FeatureOverride, ...]
This timber's names for its rough prism: rptw.* wherever it lies on the perfect timber within.
Source code in kumiki/timber.py
is_face_perfect
¶
is_face_perfect(face: TimberFace) -> bool
Check if the specified face of the timber is perfect (matches the perfect timber within).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
face
|
TimberFace
|
The TimberFace to check |
required |
Source code in kumiki/timber.py
is_perfect_timber
¶
Check if this timber's actual geometry matches its rough bounding box.
Returns True when the rough half-sizes are symmetric and equal to half the perfect timber within size.
Returns:
| Type | Description |
|---|---|
bool
|
True if the timber is a perfect timber, False otherwise |
Source code in kumiki/timber.py
get_imperfect_fringe_csg_local
¶
get_imperfect_fringe_csg_local(with_feature_labels: bool = True) -> CutCSG
Returns the CSG (local coordinates) of the region where this timber's actual geometry sticks out beyond its perfect-timber-within boundary, i.e. actual minus perfect.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
with_feature_labels
|
bool
|
False to leave the override labels off both shapes. A caller using this as a cutter wants the geometry, not a second copy of names the timber's own body already carries (see PerfectTimberWithin.get_extended_actual_csg_local). |
True
|
Source code in kumiki/timber.py
Timber
dataclass
¶
Bases: PerfectTimberWithin
Rectangular timber which may or may not be perfect.
Inherits all attributes and methods from PerfectTimberWithin
- length: Length of the timber
- size: Cross-sectional size (width, height)
- transform: Position and orientation
- name: Optional name
rough_half_sizes
class-attribute
instance-attribute
¶
from_perfect_timber_within
staticmethod
¶
from_perfect_timber_within(perfect_timber: PerfectTimberWithin, rough_half_sizes: Optional[Tuple[V2, V2]] = None) -> Timber
Create a Timber instance from a PerfectTimberWithin instance.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
perfect_timber
|
PerfectTimberWithin
|
An instance of PerfectTimberWithin |
required |
rough_half_sizes
|
Optional[Tuple[V2, V2]]
|
Optional asymmetric half-sizes from centerline |
None
|
Source code in kumiki/timber.py
get_rough_half_sizes
¶
Returns the rough half-sizes of the timber.
If rough_half_sizes is set, returns that. Otherwise returns symmetric half-sizes derived from the perfect timber within size.
Returns:
| Type | Description |
|---|---|
Tuple[V2, V2]
|
Tuple of (V2(right_half, left_half), V2(front_half, back_half)) |
Source code in kumiki/timber.py
get_actual_csg_local
¶
get_actual_csg_local(with_feature_labels: bool = True) -> CutCSG
Returns the actual CSG geometry for this timber.
For Timber, this returns a rectangular prism using the rough half-sizes, offset from the centerline when the half-sizes are asymmetric.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
with_feature_labels
|
bool
|
False to leave the override labels off -- see PerfectTimberWithin.get_extended_actual_csg_local. |
True
|
Returns:
| Type | Description |
|---|---|
CutCSG
|
RectangularPrism representing the actual geometry in local coordinates |
Source code in kumiki/timber.py
get_extended_actual_csg_local
¶
get_extended_actual_csg_local(extend_bot: bool, extend_top: bool, with_feature_labels: bool = True) -> CutCSG
Returns the actual CSG geometry extended to infinity at specified ends.
For Timber, this returns a rectangular prism using the rough half-sizes, offset from the centerline when the half-sizes are asymmetric.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
extend_bot
|
bool
|
If True, extend to -infinity at bottom (z=0) |
required |
extend_top
|
bool
|
If True, extend to +infinity at top (z=length) |
required |
with_feature_labels
|
bool
|
False to leave the override labels off -- see PerfectTimberWithin.get_extended_actual_csg_local. |
True
|
Returns:
| Type | Description |
|---|---|
CutCSG
|
CutCSG representing the extended geometry in local coordinates |
Source code in kumiki/timber.py
Board
dataclass
¶
Bases: PerfectTimberWithin
Boards are perfect timbers with board-specific semantics
Boards are structurally identical to perfect timbers but carry additional semantics: - the "length" of the board runs in the Z direction so the TOP and BOTTOM faces are referred to as the "ends" of the board - the "width" of the board runs in the X direction so the LEFT and RIGHT faces are referred to as the "sides" of the board - the "thickness" of the board runs in the Y direction so the FRONT and BACK faces are the same as the "faces" of the board
Like timbers, we assume the grain is always running in the length direction.
Note that you can end cut along the length direction but not in the other directions so you must ensure the board dimensions are large enough to incorporate the cuts
get_rough_half_sizes
¶
Returns the rough half-sizes of the board.
For Board, these are symmetric halves of the perfect timber within size.
Returns:
| Type | Description |
|---|---|
Tuple[V2, V2]
|
Tuple of (V2(right_half, left_half), V2(front_half, back_half)) |
Source code in kumiki/timber.py
get_extended_actual_csg_local
¶
get_extended_actual_csg_local(extend_bot: bool, extend_top: bool, with_feature_labels: bool = True) -> CutCSG
Returns the actual CSG geometry extended to infinity at specified ends.
For Board, this returns a rectangular prism using the perfect timber within size.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
extend_bot
|
bool
|
If True, extend to -infinity at bottom (z=0) |
required |
extend_top
|
bool
|
If True, extend to +infinity at top (z=length) |
required |
with_feature_labels
|
bool
|
False to leave the override labels off -- see PerfectTimberWithin.get_extended_actual_csg_local. |
True
|
Returns:
| Type | Description |
|---|---|
CutCSG
|
CutCSG representing the extended geometry in local coordinates |
Source code in kumiki/timber.py
RoundTimber
dataclass
¶
Bases: PerfectTimberWithin
Cylindrical timber (e.g., logs, poles)
Round timbers have a circular cross-section centered on the centerline. The rough bounding box is a square that contains the circle, but the actual geometry is a cylinder.
is_perfect_timber
¶
from_perfect_timber_within
staticmethod
¶
from_perfect_timber_within(perfect_timber: PerfectTimberWithin, diameter: Optional[Numeric] = None) -> RoundTimber
Create a Timber instance from a PerfectTimberWithin instance.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
perfect_timber
|
PerfectTimberWithin
|
An instance of PerfectTimberWithin |
required |
diameter
|
Optional[Numeric]
|
Optional diameter for the round timber, if None, then the diagonal of the perfect_timber.size is used to compute the diameter. |
None
|
Source code in kumiki/timber.py
get_rough_half_sizes
¶
Returns the rough half-sizes of the round timber.
For round timbers, this is a symmetric square bounding box using the diameter.
Returns:
| Type | Description |
|---|---|
Tuple[V2, V2]
|
Tuple of (V2(d/2, d/2), V2(d/2, d/2)) |
Source code in kumiki/timber.py
get_actual_csg_local
¶
get_actual_csg_local(with_feature_labels: bool = True) -> CutCSG
Returns the actual CSG geometry for this timber.
For RoundTimber, this returns a Cylinder with the specified diameter.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
with_feature_labels
|
bool
|
Accepted so that callers can pass it to any timber. A cylinder's round surface declares its own features and carries no override labels, so there is nothing here for it to leave off. |
True
|
Returns:
| Type | Description |
|---|---|
CutCSG
|
Cylinder representing the actual geometry in local coordinates |
Source code in kumiki/timber.py
get_extended_actual_csg_local
¶
get_extended_actual_csg_local(extend_bot: bool, extend_top: bool, with_feature_labels: bool = True) -> CutCSG
Returns the actual CSG geometry extended to infinity at specified ends.
For RoundTimber, this returns a Cylinder optionally extended to infinity.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
extend_bot
|
bool
|
If True, extend to -infinity at bottom (z=0) |
required |
extend_top
|
bool
|
If True, extend to +infinity at top (z=length) |
required |
with_feature_labels
|
bool
|
Accepted so that callers can pass it to any timber; a cylinder carries no override labels to leave off. |
True
|
Returns:
| Type | Description |
|---|---|
CutCSG
|
Cylinder representing the extended geometry in local coordinates |
Source code in kumiki/timber.py
MeshTimber
dataclass
¶
Bases: PerfectTimberWithin
Timber represented by an arbitrary mesh geometry
This timber type uses a mesh CSG to represent complex or irregular timber geometries that cannot be represented by simple primitives.
TODO: Add mesh_csg field and override get_actual_csg_local()
get_rough_half_sizes
¶
can_be_extended_for_joints
¶
get_extended_actual_csg_local
¶
get_extended_actual_csg_local(extend_bot: bool, extend_top: bool, with_feature_labels: bool = True) -> CutCSG
Returns the actual CSG geometry extended to infinity at specified ends.
For MeshTimber, this returns a rectangular prism using the perfect timber within size (the bounding box). Note: MeshTimber cannot be extended for joints.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
extend_bot
|
bool
|
If True, extend to -infinity at bottom (z=0) |
required |
extend_top
|
bool
|
If True, extend to +infinity at top (z=length) |
required |
with_feature_labels
|
bool
|
False to leave the override labels off -- see PerfectTimberWithin.get_extended_actual_csg_local. |
True
|
Returns:
| Type | Description |
|---|---|
CutCSG
|
CutCSG representing the extended geometry in local coordinates |
Source code in kumiki/timber.py
RegularPolygonTimber
dataclass
¶
Bases: PerfectTimberWithin
Timber with regular polygonal cross-section
This timber type has a polygonal (non-rectangular) cross-section that is extruded along the length axis. Examples include hexagonal or octagonal timbers.
The polygon is inscribed in a circle with radius equal to half the minimum dimension of the rough bounding box.
is_perfect_timber
¶
get_rough_half_sizes
¶
Returns the rough half-sizes of the polygon timber.
For polygon extrusion timbers, these are symmetric halves of the rectangular bounding box.
Returns:
| Type | Description |
|---|---|
Tuple[V2, V2]
|
Tuple of (V2(w/2, w/2), V2(h/2, h/2)) |
Source code in kumiki/timber.py
get_actual_csg_local
¶
get_actual_csg_local(with_feature_labels: bool = True) -> CutCSG
Returns the actual CSG geometry for this timber.
For RegularPolygonTimber, this returns a ConvexPolygonExtrusion with the specified number of sides.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
with_feature_labels
|
bool
|
Accepted so that callers can pass it to any timber. This shape declares its own features and carries no override labels, so there is nothing here for it to leave off. |
True
|
Returns:
| Type | Description |
|---|---|
CutCSG
|
ConvexPolygonExtrusion representing the actual geometry in local coordinates |
Source code in kumiki/timber.py
get_extended_actual_csg_local
¶
get_extended_actual_csg_local(extend_bot: bool, extend_top: bool, with_feature_labels: bool = True) -> CutCSG
Returns the actual CSG geometry extended to infinity at specified ends.
For RegularPolygonTimber, this returns a ConvexPolygonExtrusion optionally extended to infinity.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
extend_bot
|
bool
|
If True, extend to -infinity at bottom (z=0) |
required |
extend_top
|
bool
|
If True, extend to +infinity at top (z=length) |
required |
with_feature_labels
|
bool
|
Accepted so that callers can pass it to any timber; this shape carries no override labels to leave off. |
True
|
Returns:
| Type | Description |
|---|---|
CutCSG
|
ConvexPolygonExtrusion representing the extended geometry in local coordinates |
Source code in kumiki/timber.py
CutTimber
¶
CutTimber(timber: PerfectTimberWithin, cuts: Optional[List[Cutting]] = None, joints: Optional[List[Joint]] = None)
A timber with cuts applied to it.
Create a CutTimber from a Timber.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
timber
|
PerfectTimberWithin
|
The timber to be cut |
required |
cuts
|
Optional[List[Cutting]]
|
List of cuts to apply (default: empty list) |
None
|
joints
|
Optional[List[Joint]]
|
Joints this timber participates in (default: empty list). Populated by the from_joints constructors. Anything asking "which joint produced this cut?" reads it, so a CutTimber built by hand simply cannot answer that -- which is the honest outcome, since by hand there is no joint to name. |
None
|
Source code in kumiki/timber.py
resolve_joint_path
¶
Which of this timber's joints a name refers to.
A list, for the same reason Frame.resolve_timber_path returns one: two identical joints on one timber -- both ends of a brace -- share a name, and pretending a name means one joint quietly picks whichever was cut first. Where it does match several the reference stops being stable, so it warns.
Counted over this timber's cuts, in order, which is what the cut labels and the CSG paths below them are numbered by.
Source code in kumiki/timber.py
from_joints
classmethod
¶
from_joints(timber: PerfectTimberWithin, joints: List[Joint]) -> CutTimber
Build a CutTimber for timber by collecting every Cutting across joints
whose Cutting.timber is this exact timber (matched by identity -- the same
matching Frame.from_joints uses to merge cuttings for a timber across the
whole frame).
Useful when a joint function needs "this timber's actual body so far" (e.g.
cut_free_house_joint's housed_timbers) but the timber has cuts from more than
one joint (e.g. a corner miter plus a roundover decoration): rather than
manually picking which Joint.cuttings key belongs to which timber (easy to
mix up -- see cuttings["timberA"] vs cuttings["timberB"]), this collects
every relevant cutting automatically, in the order joints are given.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
timber
|
PerfectTimberWithin
|
The timber to build a CutTimber for |
required |
joints
|
List[Joint]
|
Joints to search for cuttings on |
required |
Returns:
| Type | Description |
|---|---|
CutTimber
|
CutTimber wrapping |
Source code in kumiki/timber.py
get_extended_rough_csg_with_perfect_features_local
¶
get_extended_rough_csg_with_perfect_features_local() -> CutCSG
The rough timber, carrying every perfect feature, with each end that has an end cut extended to infinity: what is drawn and solved, before the cuts. Built once, at construction.
Source code in kumiki/timber.py
get_extended_perfect_csg_local
¶
get_extended_perfect_csg_local() -> CutCSG
The perfect timber within, with each end that has an end cut extended to infinity. Built once, at construction.
render_perfect_timber_within_with_cuts_csg_local
¶
render_perfect_timber_within_with_cuts_csg_local() -> CutCSG
The perfect timber within with every cut subtracted. Built once, at construction.
Only for showing the piece without its rough stock: the viewer's perfect mesh of a timber whose stock is not its perfect box. What is drawn, measured, picked and solved is the rough tree carrying every perfect feature, render_timber_with_cuts_csg_local.
Source code in kumiki/timber.py
get_perfect_timber_within_bounding_box_prism
¶
get_perfect_timber_within_bounding_box_prism() -> RectangularPrism
Get the bounding box prism for this timber cropped based on its end cuts if any, otherwise the original perfet timber within box is produced. The bounding box is aligned with the timber's orientation.
Uses PerfectTimberWithin size to determine the cross-sectional size of the bounding box. Uses the end cuts (maybe_top_end_cut and maybe_bottom_end_cut) to determine the extent of the timber along its length. For skewed end cuts, finds where the plane intersects the four long edges of the timber and takes the max/min.
Returns:
| Name | Type | Description |
|---|---|---|
RectangularPrism |
RectangularPrism
|
The bounding box for the cut timber in global coordinates |
Source code in kumiki/timber.py
get_rough_bounding_box_prism
¶
get_rough_bounding_box_prism() -> RectangularPrism
Get the bounding box prism for this timber's ROUGH (as-sawn) cross-section, cropped in length the same way as get_perfect_timber_within_bounding_box_prism (the most restrictive end cut across every Cutting on this timber -- the frame's aggregated outer length trims, not each joint's own internal cut geometry).
Unlike the perfect-timber-within box, the rough box may be off-center from the timber's centerline (see get_rough_half_sizes -- e.g. for square-rule layout) and is generally larger than the perfect/finished size.
Returns:
| Name | Type | Description |
|---|---|---|
RectangularPrism |
RectangularPrism
|
The rough bounding box for the cut timber, in global coordinates |
Source code in kumiki/timber.py
get_bounding_box_prism
¶
get_bounding_box_prism() -> RectangularPrism
DEPRECATED_approximate_bounding_prism
¶
DEPRECATED_approximate_bounding_prism() -> RectangularPrism
TODO someday we want a fully analytical solution for this, but for now this is sufficient for our needs.
Get the bounding box prism for this timber including all its cuts. The bounding box is aligned with the timber's orientation.
Uses a hybrid approach: analytical methods for simple cases (HalfSpace cuts), and sampling for complex CSG operations. Works with all CSG types and orientations.
Returns:
| Name | Type | Description |
|---|---|---|
RectangularPrism |
RectangularPrism
|
The bounding box for the cut timber in global coordinates |
Source code in kumiki/timber.py
2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 | |
Accessory
dataclass
¶
Bases: ABC
Base class for joint accessories like wedges, drawbores, etc.
ticket
class-attribute
instance-attribute
¶
ticket: AccessoryTicket = field(default_factory=AccessoryTicket, kw_only=True)
assembly_freedom
class-attribute
instance-attribute
¶
assembly_freedom: Optional[AssemblyFreedom] = field(default=None, kw_only=True)
assembly_ordering
class-attribute
instance-attribute
¶
get_csg_local
abstractmethod
¶
get_csg_local() -> CutCSG
Generate CSG representation of the accessory in local space.
The local space is defined by the accessory's orientation and position, where the CSG is generated at the origin with identity orientation.
Returns:
| Name | Type | Description |
|---|---|---|
CutCSG |
CutCSG
|
The CSG representation of the accessory in local space |
Source code in kumiki/timber.py
PegShape
¶
Peg
dataclass
¶
Bases: Accessory
Represents a peg used in timber joinery (e.g., draw bore pegs, komisen).
The peg is stored in GLOBAL SPACE with absolute position and orientation. In identity orientation, the peg points in the +Z direction, with the insertion end at the origin.
By convention, the origin of the peg is on the mortise face that the peg is going into. This is why there are 2 lengths parameters, one for how deep the peg goes past the mortise face, and one for how far the peg sticks out of the mortise face.
Attributes:
| Name | Type | Description |
|---|---|---|
transform |
Transform
|
Transform (position and orientation) of the peg in global space |
size |
Numeric
|
Size/diameter of the peg (for square pegs, this is the side length) |
shape |
PegShape
|
Shape of the peg (SQUARE or ROUND) |
forward_length |
Numeric
|
How far the peg reaches in the forward direction (into the mortise) |
stickout_length |
Numeric
|
How far the peg "sticks out" in the back direction (outside the mortise) |
get_csg_local
¶
get_csg_local() -> CutCSG
Generate CSG representation of the peg in local space.
The peg is centered at the origin with identity orientation, extending from -stickout_length to forward_length along the Z axis.
Returns:
| Name | Type | Description |
|---|---|---|
CutCSG |
CutCSG
|
The CSG representation of the peg |
Source code in kumiki/timber.py
WedgeShape
dataclass
¶
Wedge
dataclass
¶
Bases: Accessory
Represents a wedge used in timber joinery (e.g., wedged tenons).
The wedge is stored in local space of a timber. In identity orientation, the pointy end of the wedge goes in the length direction of the timber.
The profile of the wedge (trapezoidal shape) is in the Y axis (height in Y). The width of the wedge is in the X axis. The origin (0,0) is at the bottom center of the longer side of the triangle.
Visual representation (looking at wedge from the side):
+z
_ <- tip width
/ \ \
/ \ \ +y
-x /____\ <- base width
↑
origin
get_csg_local
¶
get_csg_local() -> CutCSG
Generate CSG representation of the wedge in local space.
The wedge is created using a polyline extrusion (ConvexPolygonExtrusion) with a trapezoidal profile in the XZ plane. The base is at z=0 with base_width, and the tip is at z=length with tip_width. The extrusion extends along Y from -height/2 to height/2.
The polygon profile is a trapezoid in the XZ plane: - Base at z=0 with width = base_width (centered at x=0) - Tip at z=length with width = tip_width (centered at x=0)
The transform is rotated so that +Y goes to +Z (rotation around X axis by +90°).
Returns:
| Name | Type | Description |
|---|---|---|
CutCSG |
CutCSG
|
The CSG representation of the wedge |
Source code in kumiki/timber.py
2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 | |
CSGAccessory
dataclass
¶
Bases: Accessory
Generic accessory represented as local-space positive CSG plus a global transform.
Sticker
dataclass
¶
FrameWarning
¶
Bases: UserWarning
A problem with a frame's design that still builds, e.g. a joint whose timbers don't touch.
Joint
dataclass
¶
jointAccessories
class-attribute
instance-attribute
¶
jointAccessories: Dict[str, Accessory] = field(default_factory=dict)
__post_init__
¶
Source code in kumiki/timber.py
is_decorative
¶
with_order
¶
with_order(order: Union[int, Mapping[str, int], Iterable[Tuple[Union[str, PerfectTimberWithin, Accessory], int]]]) -> Joint
Return a copy of this joint with assembly order(s) assigned.
Assembly freedoms and suborders are authored by the cut functions; the order is the frame-level plan and is assigned here, after cutting (smaller order = extracted earlier during disassembly).
with_order(n): sets order=n on every cutting and accessory, keeping their suborders, so intra-joint sequencing (peg pops before the tenon slides) is preserved within step n.
with_order({key: n, ...}) or with_order([(member, n), ...]): sets Ordering(n, 0) on each named member — referenced by cutting/accessory string key, or by the timber / accessory object itself (a timber reference applies to every cutting holding it; use the pair-list form for object references, which are unhashable). Unnamed members keep their current ordering. Raises ValueError for unknown references, or when the new orderings break the strict precedence the cut function expressed via suborders (any member pair previously strictly ordered must remain strictly ordered).
Assign orders BEFORE building the Frame: this rebuilds the member objects (dataclasses.replace, preserving timber references), so a Frame built earlier would still hold the previous orderings.
Source code in kumiki/timber.py
2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 2618 2619 2620 2621 2622 2623 2624 2625 2626 2627 2628 2629 2630 2631 2632 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650 2651 2652 2653 2654 2655 2656 2657 2658 2659 2660 2661 2662 2663 2664 2665 2666 2667 2668 2669 2670 2671 2672 2673 2674 2675 2676 2677 2678 2679 2680 2681 2682 2683 2684 2685 2686 2687 2688 2689 2690 2691 | |
Frame
dataclass
¶
Represents a complete timber frame structure with all cut timbers and accessories.
In traditional timber framing, a 'frame' is the complete structure ready for raising. This class encapsulates all the timbers that have been cut with their joints, plus any accessories like pegs, wedges, or drawbores.
Attributes:
| Name | Type | Description |
|---|---|---|
cut_timbers |
List[CutTimber]
|
List of CutTimber objects representing all timbers in the frame |
accessories |
List[Accessory]
|
List of Accessory objects (already in global space) |
name |
Optional[str]
|
Optional name for this frame (e.g., "Oscar's Shed", "Main Frame") |
accessories
class-attribute
instance-attribute
¶
accessories: List[Accessory] = field(default_factory=list)
source_joints
class-attribute
instance-attribute
¶
footprints
class-attribute
instance-attribute
¶
footprints: List[Footprint] = field(default_factory=list)
kiwari
class-attribute
instance-attribute
¶
kiwari: Optional[Kiwari] = field(default=None, compare=False)
__post_init__
¶
resolve_timber_path
¶
resolve_timber_path(path: TimberPath) -> List[ResolvedTimberPath]
Which timbers a name refers to, in this frame.
A list, because a name may match several: paths are not required to be unique, and pretending one always means one timber would quietly pick whichever came first. Where it does match several, the reference stops being stable -- each is then told apart by the order the frame built them, so inserting another above them moves every reference below. That is worth saying out loud rather than discovering later, so it warns.
Source code in kumiki/timber.py
timber_paths
¶
timber_paths() -> List[TimberPath]
Every name in the frame, in order, duplicates included.
cut_timber_of
¶
cut_timber_of(timber: PerfectTimberWithin) -> Optional[CutTimber]
The cut timber holding this exact timber object, or None.
resolved_timber_path_of
¶
resolved_timber_path_of(timber: PerfectTimberWithin) -> Optional[ResolvedTimberPath]
This exact timber's path in this frame, with its occurrence among timbers sharing the name.
Source code in kumiki/timber.py
cut_timber_at
¶
cut_timber_at(path: ResolvedTimberPath) -> Optional[CutTimber]
The cut timber a resolved path names in this frame, or None.
Source code in kumiki/timber.py
from_joints
classmethod
¶
from_joints(joints: List[Joint], additional_unjointed_timbers: Optional[List[PerfectTimberWithin]] = None, name: Optional[str] = None, kiwari: Optional[Kiwari] = None) -> Frame
Create a Frame from a list of joints and optional additional unjointed timbers.
This constructor extracts all cut timbers and accessories from the joints, and combines cut timbers that share the same underlying timber reference.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
joints
|
List[Joint]
|
List of Joint objects |
required |
additional_unjointed_timbers
|
Optional[List[PerfectTimberWithin]]
|
Optional list of PerfectTimberWithin objects that don't participate in any joints (default: empty list) |
None
|
name
|
Optional[str]
|
Optional name for the frame |
None
|
kiwari
|
Optional[Kiwari]
|
The numbers the frame was built from, if it takes any |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
Frame |
Frame
|
A new Frame object with merged cut timbers and collected accessories |
Raises:
| Type | Description |
|---|---|
ValueError
|
If two timbers with the same name but same underlying timber have different references (indicates a bug) |
Source code in kumiki/timber.py
2843 2844 2845 2846 2847 2848 2849 2850 2851 2852 2853 2854 2855 2856 2857 2858 2859 2860 2861 2862 2863 2864 2865 2866 2867 2868 2869 2870 2871 2872 2873 2874 2875 2876 2877 2878 2879 2880 2881 2882 2883 2884 2885 2886 2887 2888 2889 2890 2891 2892 2893 2894 2895 2896 2897 2898 2899 2900 2901 2902 2903 2904 2905 2906 2907 2908 2909 2910 2911 2912 2913 2914 2915 2916 2917 2918 2919 2920 2921 2922 2923 2924 2925 2926 2927 2928 2929 2930 2931 2932 2933 2934 2935 2936 2937 2938 2939 2940 2941 2942 2943 2944 2945 2946 2947 2948 2949 2950 2951 2952 2953 2954 2955 2956 2957 2958 2959 2960 2961 2962 2963 2964 2965 2966 2967 2968 2969 | |
get_bounding_box
¶
Get the axis-aligned bounding box for the entire frame in global coordinates.
This computes the bounding box by getting the bounding prism for each cut timber and finding the global min/max coordinates that enclose all of them.
Returns:
| Type | Description |
|---|---|
tuple[V3, V3]
|
tuple[V3, V3]: (min_corner, max_corner) where each is a 3x1 Matrix representing the minimum and maximum corners of the axis-aligned bounding box in global coordinates |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the frame contains no cut timbers |
Source code in kumiki/timber.py
2971 2972 2973 2974 2975 2976 2977 2978 2979 2980 2981 2982 2983 2984 2985 2986 2987 2988 2989 2990 2991 2992 2993 2994 2995 2996 2997 2998 2999 3000 3001 3002 3003 3004 3005 3006 3007 3008 3009 3010 3011 3012 3013 3014 3015 3016 3017 3018 3019 3020 3021 3022 3023 3024 3025 3026 3027 3028 3029 3030 3031 3032 3033 3034 3035 3036 3037 3038 3039 3040 3041 3042 3043 3044 3045 | |
KumikiArrangementError
¶
Bases: ValueError
Raised when a timber arrangement or joint parameter fails a validation check.
Unlike AssertionError, this survives python -O and is safe for callers to
catch specifically when handling invalid joint/arrangement configurations.
Footprint
dataclass
¶
A support class representing the footprint of the structure in the XY plane
__post_init__
¶
Validate corners. Args: corners: Tuple of points defining the corners, last point connects to first
Source code in kumiki/footprint.py
sides
¶
Returns a list of sides (line segments) connecting consecutive corners.
Returns:
| Type | Description |
|---|---|
List[Tuple[V2, V2]]
|
List of tuples, each containing two points (start, end) representing a side |
Source code in kumiki/footprint.py
is_valid
¶
Checks if the footprint is valid. A valid footprint has at least 3 corners and no intersecting sides.
Returns:
| Type | Description |
|---|---|
bool
|
True if valid, False otherwise |
Source code in kumiki/footprint.py
contains_point
¶
contains_point(point: V2) -> bool
Check if a point is contained within the footprint boundary using ray casting algorithm.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
point
|
V2
|
2D point to check |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if point is inside or on the boundary, False otherwise |
Source code in kumiki/footprint.py
nearest_corner
¶
Find the nearest corner to a given point.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
point
|
V2
|
2D point to measure from |
required |
Returns:
| Type | Description |
|---|---|
Tuple[int, V2]
|
Tuple of (index, corner) where index is the corner index and corner is the V2 point |
Source code in kumiki/footprint.py
nearest_boundary
¶
Find the nearest side (line segment) to a given point.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
point
|
V2
|
2D point to measure from |
required |
Returns:
| Type | Description |
|---|---|
Tuple[int, Tuple[V2, V2], Numeric]
|
Tuple of (index, side, distance) where: - index is the side index - side is a tuple (start_corner, end_corner) - distance is the perpendicular distance to the side |
Source code in kumiki/footprint.py
get_inward_normal
¶
get_inward_normal(side_index: int) -> Direction3D
Get the inward-pointing normal vector for a boundary side.
The inward normal is perpendicular to the boundary side and points toward the interior of the footprint.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
side_index
|
int
|
Index of the boundary side |
required |
Returns:
| Type | Description |
|---|---|
Direction3D
|
Direction3D representing the normalized 3D inward normal vector |
Source code in kumiki/footprint.py
nearest_boundary_from_line
¶
Find the nearest boundary side to a given line segment.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
line_start
|
V2
|
Start point of the line segment (2D) |
required |
line_end
|
V2
|
End point of the line segment (2D) |
required |
Returns:
| Type | Description |
|---|---|
Tuple[int, Tuple[V2, V2], Numeric]
|
Tuple of (index, side, distance) where: - index is the side index - side is a tuple (start_corner, end_corner) - distance is the minimum distance between the line segment and the boundary |
Source code in kumiki/footprint.py
FootprintLocation
¶
Line
dataclass
¶
Line(direction: Direction3D, point: V3)
Represents an oriented, infinite line with origin in 3D space.
CarrierBarrel
dataclass
¶
CarrierBarrel(axis: FeatureKey, radius: Numeric)
A cylinder's barrel around the CarrierLine at axis on the same primitive.
CarrierMap
¶
CarrierMap(carriers: Dict[CarrierRef, Carrier], canonical: Dict[CarrierRef, CarrierRef])
Every primitive carrier mapped to the solving carrier whose columns it uses.
Source code in kumiki/csg/carriers.py
union
staticmethod
¶
union(maps: Sequence[CarrierMap]) -> CarrierMap
Return one map combining maps, which must cover distinct primitives.
Source code in kumiki/csg/carriers.py
canonical
¶
canonical(ref: CarrierRef) -> CarrierRef
carrier
¶
carrier(ref: CarrierRef) -> Carrier
solving_carriers
¶
solving_carriers() -> List[CarrierRef]
carrier_rows
¶
carrier_rows(ref: CarrierRef) -> List[Row]
Return one unit row per unknown of ref's solving carrier.
Source code in kumiki/csg/carriers.py
unknowns
¶
__contains__
¶
__contains__(ref: CarrierRef) -> bool
CarrierRef
dataclass
¶
CarrierRef(owner: CutCSG, local: FeatureKey)
Midplane
dataclass
¶
Midplane(front: CarrierRef, back: CarrierRef)
The plane midway between two planar carriers, e.g. a timber's centerplane between two opposite faces.
AxisAlignedBoundingBox
dataclass
¶
Axis-aligned bounding box of a CSG. A None bound is unbounded that way.
When is_empty, the solid has no points and the bounds are meaningless; check it first.
FeatureCategory
¶
Bases: Enum
Kind of place on a primitive's boundary a default feature names, shared by every primitive.
Paired with an index where a primitive has several (index 0 for one-offs).
PrismFace
¶
Bases: Enum
Face of a RectangularPrism; indices match TimberFace.
ExtrusionCap
¶
CylinderPart
¶
CSGFeatureType
¶
Bases: Enum
Kind of geometry a feature names: face, curved face, edge or point.
Measurements dispatch on the pair of types.
TODO consider adding DERIVED_POINT and DERIVED_FACE here
FeatureTestTolerances
dataclass
¶
How close a point must be to count as on a feature, per feature type.
Faces need only the mesh-vs-analytic gap; edges and points need more, since they are snapped to. A viewport usually scales these by world units per pixel.
for_type
¶
for_type(feature_type: CSGFeatureType) -> Numeric
Return the tolerance for feature_type.
Source code in kumiki/csg/cutcsg.py
__mul__
¶
__mul__(factor: Numeric) -> FeatureTestTolerances
Return these tolerances scaled by factor (e.g. world units per pixel).
Source code in kumiki/csg/cutcsg.py
__rmul__
¶
__rmul__(factor: Numeric) -> FeatureTestTolerances
__truediv__
¶
__truediv__(divisor: Numeric) -> FeatureTestTolerances
uniform
staticmethod
¶
uniform(eps: Numeric) -> FeatureTestTolerances
exact
staticmethod
¶
exact() -> FeatureTestTolerances
Return analytic tolerances, for geometry that was never triangulated.
FeatureGroup
¶
Bases: Enum
Which features a feature may derive an edge with:
SHOULDER_PLANE with PERFECT, ROUGH and TENON
PERFECT with SHOULDER_PLANE and CROSSING
ROUGH with SHOULDER_PLANE and CROSSING
TENON with SHOULDER_PLANE
CROSSING with PERFECT and ROUGH
NONE with nothing
- PERFECT: the perfect timber within's faces, arrises, corners and centerplanes, and rough faces lying on it (rptw.*).
- ROUGH: the rough body's faces.
- SHOULDER_PLANE: a joint's shoulder plane. TENON: a joint's tenon faces.
- CROSSING: the faces a cross lap removes.
PERFECT and ROUGH don't pair with themselves: a timber's own arrises and corners are declared. The order is the pick preference, best first.
FeatureMarkingStatus
¶
FeatureMarkingSpec
dataclass
¶
How to mark a feature on a drawing, when not the default.
mark_relative_to names the feature to dimension it from: the nearest feature of that name in the
CSG tree. Auto dimensioning prefers that measurement; naming each other makes a symmetric pair.
mark
class-attribute
instance-attribute
¶
Datum
¶
FeatureNature
¶
Bases: Enum
Where a feature lives, which sets how far it reaches:
- REAL: on the surface (a face, an arris); cropped by the cuts.
- AUXILIARY: in a void (a bore's axis); bounded by its own extent.
- PERFECT_REFERENCE: inside the material (the perfect timber within); cropped to the solid.
FeatureProperties
dataclass
¶
Metadata every feature carries.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
group
|
which features this one may derive edges with (FeatureGroup). NONE by default. |
required | |
priority
|
lower wins when several features claim a point. |
required | |
nature
|
FeatureNature. Only REAL features are tested against the triangulated result. |
required | |
marking_override
|
how to mark it on a drawing. |
required | |
purpose
|
what it is for (FeaturePurpose). |
required | |
datum
|
how much to measure from it (Datum). |
required |
marking_override
class-attribute
instance-attribute
¶
marking_override: Optional[FeatureMarkingSpec] = None
CSGFeature
dataclass
¶
Bases: ABC
A feature on a CutCSG's boundary, or a non-real one (e.g. a cylinder's axis).
Doesn't know its owner; methods take the owner node to place it in the owner's space.
properties
class-attribute
instance-attribute
¶
properties: FeatureProperties = field(default_factory=FeatureProperties)
feature_key
¶
feature_key() -> Optional[FeatureKey]
feature_type
abstractmethod
¶
feature_type() -> CSGFeatureType
locate_simple_unbounded
¶
locate_simple_unbounded(owner: CutCSG) -> Optional[LocatedFeatureGeometry]
Return the feature's unbounded Plane, Line or Point in owner's space.
None for a curved surface (a barrel, a lofted side, a curved path side).
Source code in kumiki/csg/cutcsg.py
declared_ends
¶
solve_recipe
¶
Return the carriers of owner that produce this feature, or None.
Source code in kumiki/csg/cutcsg.py
is_derived
¶
group_rank
¶
Return this feature's group's pick preference rank (derived features take their better parent's).
test_point_unbounded
abstractmethod
¶
Return whether point is on this feature's surface, ignoring its bounds.
owner places the feature. collect_feature_hits gates real features on the boundary.
Source code in kumiki/csg/cutcsg.py
FaceFeature
dataclass
¶
EdgeFeature
dataclass
¶
Bases: CSGFeature
A straight edge or axis. Locates to a Line.
feature_type
¶
feature_type() -> CSGFeatureType
PointFeature
dataclass
¶
CurvedFaceFeature
dataclass
¶
Bases: CSGFeature
A curved face. Locates to nothing: no plane, line or point describes it.
feature_type
¶
feature_type() -> CSGFeatureType
ProgrammableFaceFeature
dataclass
¶
Bases: _ProgrammableFeature, FaceFeature
ProgrammableEdgeFeature
dataclass
¶
Bases: _ProgrammableFeature, EdgeFeature
ProgrammablePointFeature
dataclass
¶
Bases: _ProgrammableFeature, PointFeature
DerivedEdgeFeature
dataclass
¶
Bases: EdgeFeature
The edge where two planar face features meet. Owned by their shared_ancestor.
test_point_unbounded
¶
Return whether point is on the edge's line (tested directly, since near-parallel faces make face tests unreliable).
Source code in kumiki/csg/cutcsg.py
locate_simple_unbounded
¶
declared_ends
¶
Return the edge's ends as bounded by its parent faces' corners, or None if they can't bound it.
Exact along a face's own axes (every arris of a box); otherwise an over-estimate.
TODO make it exact for oblique lines
Source code in kumiki/csg/cutcsg.py
solve_recipe
¶
derive
staticmethod
¶
derive(a: OwnedFeatureHit, b: OwnedFeatureHit, any_groups: bool = False) -> Optional[DerivedEdgeFeature]
Return the edge where a and b meet, or None.
None if either isn't a face, their groups can't meet (unless any_groups), either face isn't there
(e.g. an infinite prism's cap), or their planes are parallel.
Source code in kumiki/csg/cutcsg.py
is_derived
¶
group_rank
¶
DerivedPointFeature
dataclass
¶
Bases: PointFeature
The point where an edge feature crosses a face feature.
is_derived
¶
group_rank
¶
test_point_unbounded
¶
Return whether point is on the vertex itself.
Source code in kumiki/csg/cutcsg.py
locate_simple_unbounded
¶
solve_recipe
¶
derive
staticmethod
¶
derive(a: OwnedFeatureHit, b: OwnedFeatureHit) -> Optional[DerivedPointFeature]
Return the point where a and b cross, or None.
None unless they are one edge and one face whose groups can meet, both are there, and the line pierces the plane at one point (not lying in it).
Source code in kumiki/csg/cutcsg.py
HalfSpaceFeature
dataclass
¶
Bases: FaceFeature
The boundary plane of a HalfSpace.
feature_key
¶
feature_key() -> Optional[FeatureKey]
locate_simple_unbounded
¶
Source code in kumiki/csg/cutcsg.py
test_point_unbounded
¶
SimpleRectangularPrismFeature
dataclass
¶
Bases: FaceFeature
One of the six faces of a RectangularPrism, named by PrismFace.
feature_key
¶
feature_key() -> Optional[FeatureKey]
locate_simple_unbounded
¶
Source code in kumiki/csg/cutcsg.py
corners
¶
Return the face's four corners, or None if unbounded.
Source code in kumiki/csg/cutcsg.py
test_point_unbounded
¶
Source code in kumiki/csg/cutcsg.py
PlaneFeature
dataclass
¶
Bases: FaceFeature
An extra plane on a primitive, with its own solver carrier. Not real by default.
normal . x = offset in the owner's frame (before its transform).
normal
class-attribute
instance-attribute
¶
properties
class-attribute
instance-attribute
¶
properties: FeatureProperties = field(default_factory=lambda: FeatureProperties(nature=FeatureNature.AUXILIARY))
locate_simple_unbounded
¶
Source code in kumiki/csg/cutcsg.py
solve_recipe
¶
Source code in kumiki/csg/cutcsg.py
test_point_unbounded
¶
Source code in kumiki/csg/cutcsg.py
MidplaneFeature
dataclass
¶
Bases: FaceFeature
The plane midway between two parallel planes of its owner. Not real by default.
Each of between is a feature name or a default FeatureKey. Solved from those two planes; locates
to None if they aren't parallel.
between
class-attribute
instance-attribute
¶
between: Tuple[Union[str, FeatureKey], Union[str, FeatureKey]] = ('', '')
properties
class-attribute
instance-attribute
¶
properties: FeatureProperties = field(default_factory=lambda: FeatureProperties(nature=FeatureNature.AUXILIARY))
locate_simple_unbounded
¶
Source code in kumiki/csg/cutcsg.py
solve_recipe
¶
Source code in kumiki/csg/cutcsg.py
test_point_unbounded
¶
Source code in kumiki/csg/cutcsg.py
SimpleRectangularPrismEdgeFeature
dataclass
¶
Bases: EdgeFeature
An arris of a RectangularPrism, named by its two faces.
faces
class-attribute
instance-attribute
¶
__post_init__
¶
Source code in kumiki/csg/cutcsg.py
of
classmethod
¶
of(name: str, first: PrismFace, second: PrismFace, properties: Optional[FeatureProperties] = None) -> SimpleRectangularPrismEdgeFeature
Return the arris between two faces, given in either order.
Source code in kumiki/csg/cutcsg.py
feature_key
¶
feature_key() -> Optional[FeatureKey]
solve_recipe
¶
test_point_unbounded
¶
Source code in kumiki/csg/cutcsg.py
locate_simple_unbounded
¶
Return the line the two faces meet in, or None.
Source code in kumiki/csg/cutcsg.py
declared_ends
¶
Return the two prism corners the arris runs between, or None if the prism is unbounded there.
Source code in kumiki/csg/cutcsg.py
SimpleRectangularPrismVertexFeature
dataclass
¶
Bases: PointFeature
A corner of a RectangularPrism, named by its three faces; numbered to match TimberCorner.
faces
class-attribute
instance-attribute
¶
faces: Tuple[PrismFace, PrismFace, PrismFace] = (PrismFace.BOTTOM, PrismFace.RIGHT, PrismFace.FRONT)
__post_init__
¶
Source code in kumiki/csg/cutcsg.py
of
classmethod
¶
of(name: str, *faces: PrismFace, properties: Optional[FeatureProperties] = None) -> SimpleRectangularPrismVertexFeature
Return the corner of three faces, given in any order.
Source code in kumiki/csg/cutcsg.py
feature_key
¶
feature_key() -> Optional[FeatureKey]
solve_recipe
¶
locate_simple_unbounded
¶
test_point_unbounded
¶
CylinderAxisFeature
dataclass
¶
Bases: EdgeFeature
The centre line of a Cylinder.
solve_recipe
¶
locate_simple_unbounded
¶
declared_ends
¶
Return the axis's ends at the cylinder's caps, or None if either is infinite.
Source code in kumiki/csg/cutcsg.py
test_point_unbounded
¶
Return whether point is on the axis line (ends not checked).
Source code in kumiki/csg/cutcsg.py
SimpleCylinderCapFeature
dataclass
¶
Bases: FaceFeature
An end cap of a Cylinder; part is TOP or BOTTOM.
feature_key
¶
feature_key() -> Optional[FeatureKey]
locate_simple_unbounded
¶
Source code in kumiki/csg/cutcsg.py
test_point_unbounded
¶
Source code in kumiki/csg/cutcsg.py
SimpleCylinderBarrelFeature
dataclass
¶
Bases: CurvedFaceFeature
The curved side of a Cylinder.
feature_key
¶
feature_key() -> Optional[FeatureKey]
test_point_unbounded
¶
Source code in kumiki/csg/cutcsg.py
SimpleConvexPolygonExtrusionFeature
dataclass
¶
Bases: FaceFeature
A side (points[key] to points[key+1]) or end cap of a ConvexPolygonExtrusion.
feature_key
¶
feature_key() -> Optional[FeatureKey]
locate_simple_unbounded
¶
Source code in kumiki/csg/cutcsg.py
test_point_unbounded
¶
Source code in kumiki/csg/cutcsg.py
SimpleLoftFeature
dataclass
¶
Bases: FaceFeature
A side or end cap of a ConvexPolygonSimpleLoft. Sides are planar only for a pure taper.
feature_key
¶
feature_key() -> Optional[FeatureKey]
locate_simple_unbounded
¶
Source code in kumiki/csg/cutcsg.py
test_point_unbounded
¶
Source code in kumiki/csg/cutcsg.py
ConvexProfileEdgeFeature
dataclass
¶
Bases: EdgeFeature
An edge of a ConvexPolygonExtrusion or ConvexPolygonSimpleLoft.
faces is a side and the next side (a long edge), or a side and a cap (a profile edge).
feature_key
¶
feature_key() -> Optional[FeatureKey]
solve_recipe
¶
Source code in kumiki/csg/cutcsg.py
test_point_unbounded
¶
Source code in kumiki/csg/cutcsg.py
locate_simple_unbounded
¶
Source code in kumiki/csg/cutcsg.py
declared_ends
¶
Return the edge's two ends, or None if either is at an infinite end.
FeatureSource
¶
FeatureOverride
dataclass
¶
Overrides the name and properties of a default feature.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
the default, e.g. prism_face_key(PrismFace.RIGHT). |
required | |
name
|
new name; None keeps the default's. |
required | |
properties
|
replaces the default's properties; None keeps them. |
required |
apply
¶
apply(default: CSGFeature) -> CSGFeature
Return the default feature at key with this override applied.
Source code in kumiki/csg/cutcsg.py
HasFeatures
dataclass
¶
Mixin for primitives that declare their own features.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
feature_overrides
|
overrides of this shape's defaults, at most one per key. |
required | |
extra_features
|
keyless features with their own geometry (a bore's axis, a reference plane). |
required |
Both are validated on construction.
feature_overrides
class-attribute
instance-attribute
¶
feature_overrides: Sequence[FeatureOverride] = field(default=(), kw_only=True)
extra_features
class-attribute
instance-attribute
¶
extra_features: Sequence[CSGFeature] = field(default=(), kw_only=True)
__post_init__
¶
default_features
¶
default_features() -> Dict[FeatureKey, CSGFeature]
get_declared_features
¶
get_declared_features(source: FeatureSource = BOTH) -> List[CSGFeature]
Return the features this node declares (defaults with overrides applied, plus extras), cached per node.
Source code in kumiki/csg/cutcsg.py
OwnedFeatureHit
dataclass
¶
Bases: Generic[F_co]
A feature with the node that owns it.
feature_type
¶
feature_type() -> CSGFeatureType
locate_simple_unbounded
¶
locate_simple_unbounded() -> Optional[LocatedFeatureGeometry]
CSGParity
¶
Bases: Enum
Whether a node adds material to the result (ADDITIVE) or removes it (SUBTRACTIVE).
Cylinder
dataclass
¶
Bases: HasFeatures, CutCSG
A cylinder, optionally infinite at either end.
The axis runs through position along axis_direction, from start_distance to end_distance;
None for either makes that end infinite.
position
class-attribute
instance-attribute
¶
default_features
¶
default_features() -> Dict[FeatureKey, CSGFeature]
Return its two caps and the barrel.
Source code in kumiki/csg/cutcsg.py
carriers
¶
carriers() -> Dict[FeatureKey, Carrier]
Return the axis, the barrel around it, and a plane for each finite cap.
Source code in kumiki/csg/cutcsg.py
has_carrier
¶
has_carrier(key: FeatureKey) -> bool
__repr__
¶
contains_point
¶
Return whether point is inside or on the boundary.
Source code in kumiki/csg/cutcsg.py
is_point_on_boundary
¶
Return whether point is on the barrel or a finite cap.
Source code in kumiki/csg/cutcsg.py
get_outward_normal
¶
get_outward_normal(point: V3, eps: Optional[Numeric] = None) -> Optional[Direction3D]
get_outward_normals
¶
get_outward_normals(point: V3, eps: Optional[Numeric] = None) -> List[Direction3D]
get_aabb
¶
get_aabb() -> AxisAlignedBoundingBox
Source code in kumiki/csg/cutcsg.py
get_convex_hull
¶
Return a hexagonal prism circumscribing the cylinder, capped at its finite ends. It contains the cylinder.
Source code in kumiki/csg/cutcsg.py
exact_line_spans
¶
exact_line_spans(line: Line, seed: Span, tolerance: float, removing: bool) -> Optional[List[Span]]
Solved exactly: the barrel as a quadratic along the line, the caps as planes.
Source code in kumiki/csg/cutcsg.py
angles_around_line
¶
angles_around_line(at: ndarray, direction: ndarray, basis: Tuple[ndarray, ndarray], tolerance: float) -> Optional[AngleSpans]
Solved exactly: the caps, and half a turn about the barrel's tangent plane where at is on it.
Source code in kumiki/csg/cutcsg.py
line_crossings
¶
Return the parameters along the line where it crosses the barrel and the finite caps.
Source code in kumiki/csg/cutcsg.py
plane_sides
¶
Cut exactly: an ellipse (or a strip, for a plane along the axis), then the caps.
Source code in kumiki/csg/cutcsg.py
SolidsAtPoint
¶
Caches contains_point and is_point_on_boundary of one point against several solids.
Answers: any_holds, any_encloses, any_on_surface, outward_normals, average_outward_normal, close_around_it.
Source code in kumiki/csg/cutcsg.py
any_holds
¶
any_encloses
¶
any_on_surface
¶
outward_normals
¶
outward_normals() -> List[Optional[Direction3D]]
Return each on-surface solid's outward normal at the point.
every_outward_normal
¶
every_outward_normal() -> Optional[List[Direction3D]]
Return each face normal of the single solid the point is on the surface of; None if several or none.
Source code in kumiki/csg/cutcsg.py
average_outward_normal
¶
average_outward_normal(negated: bool = False) -> Optional[Direction3D]
Return the mean outward normal (negated if negated), or None if they cancel or are unknown.
Source code in kumiki/csg/cutcsg.py
close_around_it
¶
Return whether the surfaces at the point enclose it, so it is inside their union.
E.g. a point on the shared face of two touching prisms. True when no direction leaves every surface. Exact for one or two surfaces; False if any normal is unknown.
Source code in kumiki/csg/cutcsg.py
ConvexPolygonExtrusion
dataclass
¶
Bases: HasFeatures, CutCSG
A convex polygon in the local XY plane extruded along local Z, optionally infinite at either end.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
points
|
the polygon's (x, y) points in order; must be convex. |
required | |
transform
|
the polygon's position and orientation. |
required | |
start_distance, end_distance
|
extent along local Z; None for infinite. |
required |
transform
class-attribute
instance-attribute
¶
default_features
¶
default_features() -> Dict[FeatureKey, CSGFeature]
Return its two caps, a side per profile edge, and their edges.
Source code in kumiki/csg/cutcsg.py
display_name
classmethod
¶
get_bottom_position
¶
get_bottom_position() -> V3
Return the profile origin at start_distance. Raises ValueError if infinite.
Source code in kumiki/csg/cutcsg.py
get_top_position
¶
get_top_position() -> V3
Return the profile origin at end_distance. Raises ValueError if infinite.
Source code in kumiki/csg/cutcsg.py
__repr__
¶
is_valid
¶
Return whether it has at least 3 points, a convex polygon, and end > start when both are finite.
Source code in kumiki/csg/cutcsg.py
carriers
¶
carriers() -> Dict[FeatureKey, Carrier]
has_carrier
¶
has_carrier(key: FeatureKey) -> bool
contains_point
¶
Return whether point is inside or on the boundary.
Source code in kumiki/csg/cutcsg.py
is_point_on_boundary
¶
Return whether point is on a cap or a side.
Source code in kumiki/csg/cutcsg.py
get_outward_normal
¶
get_outward_normal(point: V3, eps: Optional[Numeric] = None) -> Optional[Direction3D]
get_outward_normals
¶
get_outward_normals(point: V3, eps: Optional[Numeric] = None) -> List[Direction3D]
get_aabb
¶
get_aabb() -> AxisAlignedBoundingBox
Source code in kumiki/csg/cutcsg.py
get_convex_hull
¶
Source code in kumiki/csg/cutcsg.py
TimberFeature
¶
Bases: Enum
Everything a timber has that can be pointed at, in one enum.
The values carry weight in two directions and so are not free to change:
- the narrower enums below share them, and every converter moves between
the two by value (
TimberFace(self.value)), so a member and its counterpart must always agree; - 1..6 are pinned to cutcsg.PrismFace, and the ORDER of the face and edge members is pinned to a prism's default SIDE and ARRIS indices, so that "arris.5" and BOTTOM_FRONT_EDGE pick out the same line. cutcsg cannot say so itself (timber imports cutcsg), so TestDefaultOrderFollowsTimber- Feature pins it from the test side.
That second constraint reads member NAMES -- a prism side is a member
ending _FACE, an arris one ending _EDGE -- so a new member must not end
in either unless it really is one. RIGHT_FACE_CENTERLINE is safe;
RIGHT_CENTERLINE_FACE would quietly break it.
feature
¶
feature() -> TimberFeature
face
¶
face() -> TimberFace
Convert to TimberFace. Values 1-6 map to faces.
Source code in kumiki/timber_features.py
end
¶
end() -> TimberEnd
Convert to TimberEnd. Values 1-2 map to ends.
Source code in kumiki/timber_features.py
long_face
¶
long_face() -> TimberLongFace
Convert to TimberLongFace. Values 3-6 map to long faces.
Source code in kumiki/timber_features.py
edge
¶
edge() -> TimberEdge
Convert to TimberEdge. Values 8-19 map to edges.
Source code in kumiki/timber_features.py
centerline
¶
centerline() -> TimberCenterline
Convert to TimberCenterline. Value 30 maps to CENTERLINE.
The timber's own axis only. A long face's centerline is a different enum -- see long_face_centerline and the note on TimberCenterline.
Source code in kumiki/timber_features.py
long_face_centerline
¶
long_face_centerline() -> TimberLongFaceCenterline
Convert to TimberLongFaceCenterline. Values 31-34 map to long face centerlines.
Source code in kumiki/timber_features.py
center_plane
¶
center_plane() -> TimberCenterplane
Convert to TimberCenterplane. Values 28-29 map to center planes.
Source code in kumiki/timber_features.py
long_faces_it_rests_on
¶
long_faces_it_rests_on() -> Tuple[TimberLongFace, ...]
The long faces this feature is a surface of, or an edge of.
A long face rests on itself; an arris on the two faces that meet at it. Everything else rests on none -- a centerline or a center plane is intrinsic to the perfect timber within and has no face to belong to, and a short feature is not a long one at all.
What this is for: a face is the one kind of feature whose position depends on the ROUGH timber matching the perfect one. Asking a feature which faces it rests on is asking which of those agreements it needs.
Source code in kumiki/timber_features.py
long_edge
¶
long_edge() -> TimberLongEdge
Convert to TimberLongEdge. Values 8-11 map to long edges.
Source code in kumiki/timber_features.py
short_edge
¶
short_edge() -> TimberShortEdge
Convert to TimberShortEdge. Values 12-19 map to short edges.
Source code in kumiki/timber_features.py
corner
¶
corner() -> TimberCorner
Convert to TimberCorner. Values 20-27 map to corners.
Source code in kumiki/timber_features.py
TimberCorner
¶
Bases: Enum
TimberLongFaceCenterline
¶
Bases: Enum
The line down the middle of one long face, running the timber's length.
Named for the face it lies on, as TimberLongFace is. Geometrically it is where a center plane meets that face, and the pairing crosses over: the RIGHT face's centerline lies in the FRONT_BACK plane, since that is the plane the RIGHT face is not parallel to.
Only the four LONG faces have one. An end face has two centerlines rather than one, neither running the timber's length, and they move with every end cut -- a separate enum's problem, if anyone ever wants them.
long_face
property
¶
long_face: TimberLongFace
The long face this centerline runs down the middle of.
center_plane
property
¶
center_plane: TimberCenterplane
The center plane this centerline lies in.
The crossed-over pairing: a RIGHT or LEFT face's centerline lies in the FRONT_BACK plane, and a FRONT or BACK face's in the RIGHT_LEFT plane.
TimberCenterplane
¶
Bases: Enum
One of the two planes that bisect the timber along its length.
Named for the pair of long faces it lies between, matching the RIGHT_FRONT_EDGE convention. Each contains the centerline and has its normal along one of the timber's local cross-section axes: RIGHT_LEFT's normal is the width axis, FRONT_BACK's the height axis.
Unlike a face, a center plane is not a surface -- it is a datum, and it exists whether or not the timber is perfect, which is what makes it somewhere to measure from when no face is.
long_faces
property
¶
long_faces: Tuple[TimberLongFace, TimberLongFace]
The two long faces this plane lies between.
TimberEdge
¶
Bases: Enum
canonical_line_from_corner
¶
canonical_line_from_corner() -> Tuple[TimberCorner, TimberFace]
Returns canonical way to express a line from an edge. The line is defined by starting from the TimberCorner and pointing in the direction of the returned TimberFace's outward normal.
For long edges the line starts at the bottom corner and points toward TOP. For short edges the direction follows cross(long_face_normal, end_outward).
Source code in kumiki/timber_features.py
long_edge
¶
long_edge() -> TimberLongEdge
Convert to TimberLongEdge. Values 8-11 map to long edges.
Source code in kumiki/timber_features.py
short_edge
¶
short_edge() -> TimberShortEdge
Convert to TimberShortEdge. Values 12-19 map to short edges.
Source code in kumiki/timber_features.py
TimberLongEdge
¶
Bases: Enum
long_faces
property
¶
long_faces: Tuple[TimberLongFace, TimberLongFace]
The two long faces that meet at this arris, in the order it is named.
TimberShortEdge
¶
Bases: Enum
long_face
property
¶
long_face: TimberLongFace
Get the TimberLongFace associated with this short edge.
StickoutReference
¶
Bases: Enum
Defines how stickout is measured relative to timber connection points.
Stickout measured from centerline of the timber (default)
joined timber | | |||===== created timber | |
Stickout measured from inside face of the timber
joined timber | | | |===== created timber | |
Stickout measured from outside face of the timber
joined timber | | |====== created timber | |
Stickout
¶
Defines how much a timber extends beyond connection points.
For symmetric stickout, set stickout1 = stickout2. For asymmetric stickout, use different values. Default is no stickout (0, 0) from CENTER_LINE.
StickoutReference modes:
Stickout measured from centerline of the joined timber
joined timber | | |||===== created timber | |
Stickout measured from inside face of the joined timber
joined timber | | | |===== created timber | |
Stickout measured from outside face of the joined timber
joined timber | | |====== created timber | |
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
stickout1
|
Extension beyond the first connection point (default: 0) |
required | |
stickout2
|
Extension beyond the second connection point (default: 0) |
required | |
stickoutReference1
|
How stickout1 is measured (default: CENTER_LINE) |
required | |
stickoutReference2
|
How stickout2 is measured (default: CENTER_LINE) |
required |
Examples:
Symmetric stickout from centerline¶
s = Stickout.symmetric(scalar(1, 5)) # Both sides extend 0.2m from centerline
No stickout¶
s = Stickout.nostickout() # Both sides are 0
Asymmetric stickout¶
s = Stickout(scalar(1, 10), scalar(2, 5)) # Left extends 0.1m, right extends 0.4m from centerline
Stickout from outside faces¶
s = Stickout(scalar(1, 10), scalar(1, 5), StickoutReference.OUTSIDE, StickoutReference.OUTSIDE)
stickoutReference1
class-attribute
instance-attribute
¶
stickoutReference1: Optional[StickoutReference] = None
stickoutReference2
class-attribute
instance-attribute
¶
stickoutReference2: Optional[StickoutReference] = None
__post_init__
¶
Set default stickout references if not provided.
Source code in kumiki/construction.py
symmetric
classmethod
¶
symmetric(value: Numeric, reference: Optional[StickoutReference] = None) -> Stickout
Create a symmetric stickout where both sides extend by the same amount.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
Numeric
|
The stickout distance for both sides |
required |
reference
|
Optional[StickoutReference]
|
How stickout is measured (default: CENTER_LINE) |
None
|
Returns:
| Type | Description |
|---|---|
Stickout
|
Stickout instance with stickout1 = stickout2 = value |
Source code in kumiki/construction.py
DoubleButtJointTimberArrangement
¶
Two butt timbers meeting a single receiving timber.
Arrangements: - Opposing: butt_timber_1 and butt_timber_2 point in opposite cardinal directions (antiparallel, like spokes from either side of the receiving timber). - Orthogonal: butt_timber_1 and butt_timber_2 point in perpendicular cardinal directions (90° apart, like an L at the receiving timber).
front_face_on_butt_timber_1
class-attribute
instance-attribute
¶
front_face_on_butt_timber_1: Optional[TimberLongFace] = None
check_face_aligned
¶
Return None if all timbers are face-aligned with the receiving timber, else an error message.
Source code in kumiki/construction.py
check_face_aligned_cardinal_and_opposing_butts
¶
Return None if: - all timbers are face-aligned, - each butt timber's length direction is orthogonal to the receiving timber (cardinal), - butt_timber_1 and butt_timber_2 are in different cardinal directions, and - the pair approaches from opposite directions (antiparallel), accounting for which end of each timber is used.
Source code in kumiki/construction.py
check_face_aligned_and_orthogonal_butts
¶
Return None if all timbers are face-aligned and the two butt timbers are orthogonal to each other (length directions perpendicular), else an error message.
Source code in kumiki/construction.py
check_perfection
¶
Return None if all timbers are perfect, else an error message.
Source code in kumiki/construction.py
TripleButtJointTimberArrangement
¶
Three butt timbers meeting a single receiving timber.
main_butt_timber_1 and main_butt_timber_2 form an opposing pair (antiparallel). awk_timber is the third butt timber pointing in a third cardinal direction.
check_face_aligned
¶
Return None if all butt timbers are face-aligned with the receiving timber, else an error message.
Source code in kumiki/construction.py
check_face_aligned_cardinal_and_opposing_butts
¶
Return None if: - all timbers are face-aligned, - each butt timber's length direction is orthogonal to the receiving timber (cardinal), - all three butt timbers are in different cardinal directions, and - main_butt_timber_1 and main_butt_timber_2 are antiparallel (pointing towards each other).
Source code in kumiki/construction.py
check_perfection
¶
Return None if all timbers are perfect, else an error message.
Source code in kumiki/construction.py
QuadrupleButtJointTimberArrangement
¶
Four butt timbers meeting a single receiving timber, covering all four cardinal directions.
main_butt_timber_1 and main_butt_timber_2 form one opposing pair (antiparallel). awk_1 and awk_2 form the second opposing pair (antiparallel, on the perpendicular axis).
check_face_aligned
¶
Return None if all butt timbers are face-aligned with the receiving timber, else an error message.
Source code in kumiki/construction.py
check_face_aligned_cardinal_and_opposing_butts
¶
Return None if: - all timbers are face-aligned, - each butt timber's length direction is orthogonal to the receiving timber (cardinal), - all four butt timbers are in different cardinal directions, and - main_butt_timber_1/main_butt_timber_2 are antiparallel and awk_1/awk_2 are antiparallel.
Source code in kumiki/construction.py
check_perfection
¶
Return None if all timbers are perfect, else an error message.
Source code in kumiki/construction.py
CrossCapJointTimberArrangement
¶
A butting post timber "capped" by two crossed timbers.
check_face_aligned_and_orthogonal
¶
Source code in kumiki/construction.py
check_perfection
¶
Return None if all timbers are perfect, else an error message.
Source code in kumiki/construction.py
SpliceJointTimberArrangement
¶
front_face_on_timber1
class-attribute
instance-attribute
¶
front_face_on_timber1: Optional[TimberLongFace] = None
check_face_aligned_and_parallel_axis
¶
Return None if timbers are face-aligned and have parallel length axes, else an error message.
Source code in kumiki/construction.py
check_perfection
¶
Return None if both timbers are perfect, else an error message.
Source code in kumiki/construction.py
CornerJointTimberArrangement
¶
front_face_on_timber1
class-attribute
instance-attribute
¶
front_face_on_timber1: Optional[TimberLongFace] = None
compute_normalized_timber_cross_product
¶
compute_normalized_timber_cross_product() -> Direction3D
Compute the normalized cross product of timber1 and timber2 length directions.
Source code in kumiki/construction.py
is_timber2_left_of_timber1
¶
returns true if timber2 is to the left of timber1 when looking down the length of timber1 and standing on the front face of timber1
Source code in kumiki/construction.py
check_plane_aligned
¶
Return None if timbers are plane-aligned and front face is in plane, else an error message.
Source code in kumiki/construction.py
check_face_aligned_and_orthogonal
¶
Return None if timbers are face-aligned and orthogonal, else an error message.
Source code in kumiki/construction.py
check_perfection
¶
Return None if both timbers are perfect, else an error message.
Source code in kumiki/construction.py
CrossJointTimberArrangement
¶
front_face_on_timber1
class-attribute
instance-attribute
¶
front_face_on_timber1: Optional[TimberLongFace] = None
compute_normalized_timber_cross_product
¶
compute_normalized_timber_cross_product() -> Direction3D
Compute the normalized cross product of timber1 and timber2 length directions.
Source code in kumiki/construction.py
check_plane_aligned
¶
Return None if timbers are plane-aligned and front face is in plane, else an error message.
Source code in kumiki/construction.py
check_face_aligned_and_orthogonal
¶
Return None if timbers are face-aligned and orthogonal, else an error message.
Source code in kumiki/construction.py
check_perfection
¶
Return None if both timbers are perfect, else an error message.
Source code in kumiki/construction.py
BraceJointTimberArrangement
¶
front_face_on_timber1
class-attribute
instance-attribute
¶
front_face_on_timber1: Optional[TimberLongFace] = None
check_perfection
¶
Return None if all timbers are perfect, else an error message.
Source code in kumiki/construction.py
ButtJointBoardArrangement
¶
front_face_on_butt_timber
class-attribute
instance-attribute
¶
front_face_on_butt_timber: Optional[TimberFace] = None
__post_init__
¶
check_orthogonal
¶
Return None if timbers are orthogonal, else an error message. Timbers are orthogonal in this arrangement if butt_timber_face is parallel to some face on the receiving_timber
Source code in kumiki/construction.py
check_face_aligned
¶
Return None if timbers are face-aligned, else an error message.
Source code in kumiki/construction.py
check_perfection
¶
Return None if both timbers are perfect, else an error message.
Source code in kumiki/construction.py
PanelBoardArrangement
¶
check_parallal_coplanar_and_same_thickness
¶
Return None if all boards are parallel (same orientation), share the same thickness, and are coplanar (all measured against boards[0]), else an error message describing the first violation found.
Source code in kumiki/construction.py
ExtendedTimberArrangement
¶
Just a list of timbers, with no real restriction until check functions are called...
check_plane_aligned
¶
check_parallel
¶
Return None if all timbers have parallel length directions (measured against timbers[0]), else an error message.
Source code in kumiki/construction.py
check_face_aligned
¶
Return None if all timbers are face-aligned (measured against timbers[0]), else an error message.
Source code in kumiki/construction.py
check_coaxial_face_aligned_and_same_size
¶
Return None if all timbers share the same centerline (coaxial), are face-aligned, and have the same cross-sectional size, else an error message describing the first violation found (measured against timbers[0]).
"Same size" is checked along matching GLOBAL directions rather than local (width, height) indices: a face-aligned timber may be rotated 90 degrees about the shared centerline relative to timbers[0], in which case its local width/height are swapped relative to global space even though its physical cross-section matches.
Source code in kumiki/construction.py
UnsignedPlane
dataclass
¶
UnsignedPlane(normal: Direction3D, point: V3)
Bases: Plane
Same as Plane but the sign on the normal should be ignored.
__repr__
¶
from_transform_and_direction
staticmethod
¶
from_transform_and_direction(transform: Transform, direction: Direction3D) -> UnsignedPlane
Create an unsigned plane from a transform and a direction.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
transform
|
Transform
|
Transform defining the position and orientation |
required |
direction
|
Direction3D
|
Direction in the transform's local coordinate system |
required |
Returns:
| Type | Description |
|---|---|
UnsignedPlane
|
UnsignedPlane with normal in global coordinates and point at transform position |
Source code in kumiki/geometry.py
Marking
dataclass
¶
DistanceFromFace
dataclass
¶
Bases: Marking
Represents a distance from a face on a timber with + being AWAY from the face.
locate
¶
locate() -> UnsignedPlane
DistanceFromPointIntoFace
dataclass
¶
Bases: Marking
Represents a distance from a point into a face on a timber with + being INTO the timber (that is the negative face normal direction is the + axis of the measurement) If the point is not supplied, the center of the face is used.
locate
¶
locate() -> Point
Convert the distance from a point into a face to a Point
Returns:
| Type | Description |
|---|---|
Point
|
Point at the specified distance from the starting point |
Source code in kumiki/measuring.py
DistanceFromLongEdgeOnFace
dataclass
¶
Bases: Marking
Represents a distance from a long edge on a timber with + being onto the face from the edge.
locate
¶
locate() -> Line
Convert the distance from a long edge to a line on the specified face.
Returns a line parallel to the edge, on the given face, at the specified distance from the edge. The distance is measured along the face plane, perpendicular to the edge direction. Positive distance means moving in the direction of the "other" face's normal (the face that defines the edge together with self.face).
Returns:
| Type | Description |
|---|---|
Line
|
Line parallel to the edge at the specified distance on the face |
Source code in kumiki/measuring.py
PointFromCornerInFaceDirection
dataclass
¶
Bases: Marking
Point on an edge in a given direction.
locate
¶
locate() -> Point
Source code in kumiki/measuring.py
DistanceFromCornerAlongEdge
dataclass
¶
Bases: Marking
Distance along a timber edge from a reference end (corner) to an intersection or closest point. Positive means into the timber from the end.
locate
¶
locate() -> Point
Source code in kumiki/measuring.py
PlaneFromEdgeInDirection
dataclass
¶
ButtJointShoulderResult
¶
Result of computing a butt joint shoulder plane and its associated marking space.
Attributes:
| Name | Type | Description |
|---|---|---|
shoulder_plane |
Plane
|
The shoulder plane (normal points from mortise centerline toward tenon). |
butt_direction |
Direction3D
|
Direction the butt timber is pointing into the receiving timber. |
marking_space |
Space
|
Located where tenon centerline intersects the shoulder plane, oriented with: +X = shoulder_plane.normal (from mortise centerline toward tenon) +Y = caller-provided up_direction (orthogonalized) +Z = derived via right-hand rule |
ButtJointCSGParts
¶
Representation of the geometry components that make up a butt joint. They are combined by unioning the positive parts, then differencing the negative parts.
DovetailTenonGeometeryResult
¶
Bases: NamedTuple
Result of computing the geometry for a dovetail tenon.
Attributes:
| Name | Type | Description |
|---|---|---|
tenon_csg |
CSG representing the tenon shape to be cut from the butt timber. |
|
mortise_csg |
CSG representing the mortise shape to be cut from the receiving timber. |
DovetailTenonWedgeAccessoryParameters
¶
Bases: NamedTuple
Parameters for an optional wedge accessory for a dovetail tenon.
__
| \ wedge_tip_stickout
| ____________________ <- wedge_small_height measured at this line | | | | ___ | \ wedge_back_extra_length |_
Attributes:
| Name | Type | Description |
|---|---|---|
wedge_from_receiving_timber_side |
bool
|
If true, the wedge is designed to be cut from the receiving timber and inserted from that side. If false, the wedge is designed to be cut from the tenon timber and inserted from the tenon side. We must have tenon_depth + receiving_timber_extra_depth > the matching width on the receivingtimber for this to work |
wedge_angle |
Numeric
|
The angle of the wedge taper. 0 means a rectangular wedge, X means a wedge with an X angle taper. |
wedge_extra_height |
Numeric
|
Extra height added to the wedge. The small height of the wedge is calculated as dovetail_depth + wedge_extra_height. The reason for this is that a minimum size of dovetail_depth is required for the joint to physically assemble. |
wedge_from_receiving_timber_side
class-attribute
instance-attribute
¶
TuskTenonGeometryResult
¶
Bases: NamedTuple
Result of computing the geometry for a tusk tenon's crosswise locking key.
Attributes:
| Name | Type | Description |
|---|---|---|
tenon_hole_negative_csg |
CutCSG
|
The crosswise hole cut through the (through-)tenon that the tusk key slides into. |
mortise_clearance_negative_csg |
Optional[CutCSG]
|
Extra clearance cut into the receiving (mortise) timber, only present when its rough stock still surrounds the tenon at the tusk hole's position (None otherwise). |
tusk_accessory_csg |
CSGAccessory
|
The tusk key itself. |
PegPositionSpace
¶
Bases: Enum
Which timber's coordinate space to use when interpreting peg positions and orientations.
SimplePegParameters
¶
Parameters for simple pegs in mortise and tenon joints.
Attributes:
| Name | Type | Description |
|---|---|---|
shape |
PegShape
|
Shape specification for the peg (from PegShape enum) |
peg_positions |
List[Tuple[Numeric, Numeric]]
|
List of (distance_from_shoulder, distance_from_centerline) tuples - First value: distance along length axis measured from shoulder of tenon - Second value: distance in perpendicular axis measured from center |
peg_position_space |
Tuple[PegPositionSpace, PegPositionSpace]
|
Controls which timber's coordinate system is used to interpret each component of peg_positions. A tuple of (shoulder_axis_space, lateral_axis_space). - shoulder_axis_space (first element): controls distance_from_shoulder direction. TENON = along tenon length axis. MORTISE = along mortise length axis. - lateral_axis_space (second element): controls distance_from_centerline direction. TENON = perpendicular to peg face normal and tenon length axis. MORTISE = along mortise length axis. |
size |
Numeric
|
Peg diameter (for round pegs) or side length (for square pegs) |
depth |
Optional[Numeric]
|
Depth measured from mortise face where peg goes in (None means all the way through the mortise timber) |
tenon_hole_offset |
Numeric
|
Offset distance of the hole in the tenon towards the shoulder so that the peg tightens the joint up. You should usually set this to 1-2mm |
peg_orientation |
Tuple[PegPositionSpace, Numeric]
|
Controls which timber's face axes the peg cross-section is aligned to, plus an optional CCW rotation around the drill axis. A tuple of (space, ccw_rotation_angle). - space: TENON = align peg Y axis with the tenon length axis. MORTISE = align peg Y axis with the mortise length axis. - ccw_rotation_angle: counter-clockwise rotation (in radians) around the drill axis applied on top of the face-aligned basis. 0 = no rotation. |
stickout_length |
Optional[Numeric]
|
Length the peg protrudes beyond the mortise entry face. If None, the peg sticks out by half its depth. |
peg_position_space
class-attribute
instance-attribute
¶
peg_position_space: Tuple[PegPositionSpace, PegPositionSpace] = (PegPositionSpace.TENON, PegPositionSpace.TENON)
peg_orientation
class-attribute
instance-attribute
¶
peg_orientation: Tuple[PegPositionSpace, Numeric] = (PegPositionSpace.TENON, scalar(0))
PegPositionResult
¶
Computed geometry for a single peg, all positions and orientations in global space.
Attributes:
| Name | Type | Description |
|---|---|---|
tenon_face_position_global |
V3
|
Center of the peg hole on the tenon face (no draw-bore offset). |
tenon_face_position_with_offset_global |
V3
|
Center of the peg hole on the tenon face, shifted toward the shoulder by tenon_hole_offset for draw-bore tightening. |
mortise_entry_position_global |
V3
|
Center of the peg hole on the mortise entry face. |
orientation_global |
Orientation
|
Orientation of the peg (Z-axis = drill direction into the timber). |
peg_depth |
Numeric
|
Depth of the peg hole (full chord through the mortise, or explicit depth). |
stickout_length |
Numeric
|
Length the peg protrudes beyond the mortise entry face. |
Cutting
dataclass
¶
A set of cuts on a timber (to create a joint, for example), defined by a CSG object representing the volume to be removed.
The CSG object represents the volume to be REMOVED from the timber (negative CSG), in LOCAL coordinates (relative to timber.bottom_position).
maybe_top_end_cut_distance_from_bottom
class-attribute
instance-attribute
¶
maybe_top_end_cut_distance_from_bottom: Optional[Numeric] = None
maybe_bottom_end_cut_distance_from_bottom
class-attribute
instance-attribute
¶
maybe_bottom_end_cut_distance_from_bottom: Optional[Numeric] = None
label
class-attribute
instance-attribute
¶
label: CutCSGLabel = field(default_factory=CutCSGLabel.NoLabel)
assembly_freedom
class-attribute
instance-attribute
¶
assembly_freedom: Optional[AssemblyFreedom] = None
__post_init__
¶
Source code in kumiki/timber.py
get_negative_csg_local
¶
get_negative_csg_local() -> Optional[CutCSG]
Get the complete negative CSG including end cuts. Built once, at construction.
Returns the union of negative_csg with any end cuts that are defined, or None when this cutting removes nothing at all.
Source code in kumiki/timber.py
make_end_cut_distance_from_bottom
staticmethod
¶
make_end_cut_distance_from_bottom(timber: PerfectTimberWithin, end: TimberEnd, distance_from_end_to_cut: Numeric) -> Numeric
Convert distance-from-end to cut-plane distance from timber bottom.
Source code in kumiki/timber.py
Matrix
¶
Immutable: _data is set once at construction and never written to
again (enforced both by omitting __setitem__ and by marking the
underlying numpy buffer read-only), matching the frozen dataclasses
(Transform/Orientation/Axis) that hold Matrix-typed fields elsewhere in
this module -- without this, some_frozen_transform.position[0] = 5
would silently succeed despite the dataclass being frozen.
Source code in kumiki/rule.py
det
¶
cross
¶
equals
¶
equals(other: Matrix, tolerance: Optional[float] = None) -> bool
Elementwise approximate equality (tolerates float noise from trig/sqrt).
Source code in kumiki/rule.py
norm
¶
tolist
¶
__getitem__
¶
Source code in kumiki/rule.py
__iter__
¶
__len__
¶
__mul__
¶
__rmul__
¶
__truediv__
¶
__add__
¶
__radd__
¶
__sub__
¶
__rsub__
¶
__neg__
¶
__eq__
¶
Transform
dataclass
¶
Transform(position: V3, orientation: Orientation)
Represents a 3D transformation with position and orientation. Encapsulates both translation and rotation for objects in 3D space.
identity
classmethod
¶
identity() -> Transform
Create an identity transform at origin with identity orientation.
local_to_global
¶
Convert a point from local coordinates to global world coordinates.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
local_point
|
V3
|
A point in local coordinates |
required |
Returns:
| Type | Description |
|---|---|
V3
|
The same point in global world coordinates |
Source code in kumiki/rule.py
global_to_local
¶
Convert a point from global world coordinates to local coordinates.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
global_point
|
V3
|
A point in global world coordinates |
required |
Returns:
| Type | Description |
|---|---|
V3
|
The same point in local coordinates |
Source code in kumiki/rule.py
local_to_global_direction
¶
Rotate a direction from local to global coordinates. Directions are not translated.
global_to_local_direction
¶
Rotate a direction from global to local coordinates. Directions are not translated.
numeric_local_to_global
¶
Convert local to global using numeric (Float) math. For hot paths like CSG.
numeric_global_to_local
¶
Convert global to local using numeric (Float) math. For hot paths like CSG.
to_global_transform
¶
Convert this transform to global coordinates relative to a parent transform.
invert
¶
invert() -> Transform
Return the inverse of this transform.
For a transform T that converts local to global (global = T * local), the inverse converts global to local (local = T^-1 * global).
Source code in kumiki/rule.py
__mul__
¶
Compose two transforms: result = self * other.
This applies other first, then self. Equivalent to: global = self.local_to_global(other.local_to_global(local))
Source code in kumiki/rule.py
to_local_transform
¶
Convert this transform to local coordinates relative to a parent transform.
rotate_around_axis
¶
Rotate this transform counterclockwise around an axis and return the new transform.
The axis can be positioned anywhere in space (not just through the origin). Uses Rodrigues' rotation formula after translating to make the axis pass through origin.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
axis
|
Axis
|
Axis with position and direction to rotate around |
required |
radians
|
Numeric
|
Angle to rotate in radians (counterclockwise when looking along axis direction) |
required |
Returns:
| Type | Description |
|---|---|
Transform
|
New Transform with rotated position and orientation |
Source code in kumiki/rule.py
Comparison
¶
Bases: Enum
Enum for safe comparison operations
Orientation
dataclass
¶
Represents a 3D rotation using a 3x3 rotation matrix. I guess we never slerp and don't care about memory usage so apparently we're using matrices to implement this class.
matrix
class-attribute
instance-attribute
¶
__post_init__
¶
Convert to Matrix and validate that the matrix is 3x3.
Source code in kumiki/rule.py
multiply
¶
multiply(other: Orientation) -> Orientation
Multiply this orientation with another orientation. Returns a new Orientation representing the combined rotation.
Source code in kumiki/rule.py
invert
¶
invert() -> Orientation
Return the inverse of this orientation. For rotation matrices, the inverse is the transpose.
flip
¶
flip(flip_x: bool = False, flip_y: bool = False, flip_z: bool = False) -> Orientation
Return the orientation with the given axes flipped.
Source code in kumiki/rule.py
__mul__
¶
__mul__(other: Orientation) -> Orientation
__repr__
¶
rotate_right
classmethod
¶
rotate_right() -> Orientation
Rotate right: +X axis rotates to -Y axis (clockwise around Z)
rotate_left
classmethod
¶
rotate_left() -> Orientation
Rotate left: +X axis rotates to +Y axis (counterclockwise around Z)
from_angle_axis
classmethod
¶
from_angle_axis(radians: Numeric, axis: Direction3D) -> Orientation
Create an orientation from an angle-axis rotation (Rodrigues' formula).
Source code in kumiki/rule.py
identity
staticmethod
¶
identity() -> Orientation
from_z_and_y
staticmethod
¶
from_z_and_y(z_direction: Direction3D, y_direction: Direction3D) -> Orientation
Create an Orientation from z and y direction vectors. Computes x = y × z to complete the right-handed coordinate system.
Source code in kumiki/rule.py
from_z_and_x
staticmethod
¶
from_z_and_x(z_direction: Direction3D, x_direction: Direction3D) -> Orientation
Create an Orientation from z and x direction vectors. Computes y = z × x to complete the right-handed coordinate system.
Source code in kumiki/rule.py
from_x_and_y
staticmethod
¶
from_x_and_y(x_direction: Direction3D, y_direction: Direction3D) -> Orientation
Create an Orientation from x and y direction vectors. Computes z = x × y to complete the right-handed coordinate system.
Source code in kumiki/rule.py
from_axis_angle
staticmethod
¶
from_axis_angle(axis: Direction3D, radians: Numeric) -> Orientation
Create an Orientation representing a rotation around an axis by an angle. Uses Rodrigues' rotation formula.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
axis
|
Direction3D
|
Direction vector to rotate around (will be normalized) |
required |
radians
|
Numeric
|
Angle to rotate in radians |
required |
Returns:
| Type | Description |
|---|---|
Orientation
|
Orientation object representing the rotation |
Source code in kumiki/rule.py
from_euleryZYX
staticmethod
¶
from_euleryZYX(yaw: Numeric, pitch: Numeric, roll: Numeric) -> Orientation
Create an Orientation from Euler angles using ZYX rotation sequence.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
yaw
|
Numeric
|
Rotation around Z-axis (radians) |
required |
pitch
|
Numeric
|
Rotation around Y-axis (radians) |
required |
roll
|
Numeric
|
Rotation around X-axis (radians) |
required |
Returns:
| Type | Description |
|---|---|
Orientation
|
Orientation object with combined rotation matrix |
The rotation sequence is: 1. Yaw (Z-axis rotation) 2. Pitch (Y-axis rotation) 3. Roll (X-axis rotation)
Source code in kumiki/rule.py
facing_west
staticmethod
¶
facing_west() -> Orientation
Horizontal timber with top face up. This is the IDENTITY orientation.
- Length: +X (local) = -X (west) in global
- Width: +Y (local) = -Y (south) in global
- Facing: +Z (up)
Source code in kumiki/rule.py
facing_east
staticmethod
¶
facing_east() -> Orientation
Horizontal timber with top face up. 180° rotation around Z axis from facing_west.
- Length: +X (local) = +X (east) in global
- Width: +Y (local) = +Y (north) in global
- Facing: +Z (up)
Source code in kumiki/rule.py
facing_north
staticmethod
¶
facing_north() -> Orientation
Horizontal timber with top face up. 90° counterclockwise rotation around Z axis from facing_west.
- Length: +X (local) = +Y (north) in global
- Width: +Y (local) = -X (west) in global
- Facing: +Z (up)
Source code in kumiki/rule.py
facing_south
staticmethod
¶
facing_south() -> Orientation
Horizontal timber with top face up. 90° clockwise rotation around Z axis from facing_west.
- Length: +X (local) = -Y (south) in global
- Width: +Y (local) = +X (east) in global
- Facing: +Z (up)
Source code in kumiki/rule.py
pointing_up
staticmethod
¶
pointing_up() -> Orientation
Vertical timber with LENGTH pointing upward (+Z). This is the same as pointing_forward.
- Length (local +X) → +Z (up) in global
- Width (local +Y) → +Y (north) in global
- Facing (local +Z) → -X (west) in global
Source code in kumiki/rule.py
pointing_down
staticmethod
¶
pointing_down() -> Orientation
Vertical timber with LENGTH pointing downward (-Z).
- Length (local +X) → -Z (down) in global
- Width (local +Y) → +Y (north) in global
- Facing (local +Z) → +X (east) in global
Source code in kumiki/rule.py
pointing_forward
staticmethod
¶
pointing_forward() -> Orientation
Vertical timber with LENGTH pointing upward (+Z). Identical to pointing_up.
- Length (local +X) → +Z (up) in global
- Width (local +Y) → +Y (north) in global
- Facing (local +Z) → -X (west) in global
Source code in kumiki/rule.py
pointing_backward
staticmethod
¶
pointing_backward() -> Orientation
Vertical timber with LENGTH pointing upward (+Z), rotated 180° from pointing_forward.
- Length (local +X) → +Z (up) in global
- Width (local +Y) → -Y (south) in global
- Facing (local +Z) → +X (east) in global
Source code in kumiki/rule.py
pointing_left
staticmethod
¶
pointing_left() -> Orientation
Vertical timber with LENGTH pointing upward (+Z), rotated 90° CCW from pointing_forward.
- Length (local +X) → +Z (up) in global
- Width (local +Y) → -X (west) in global
- Facing (local +Z) → -Y (south) in global
Source code in kumiki/rule.py
pointing_right
staticmethod
¶
pointing_right() -> Orientation
Vertical timber with LENGTH pointing upward (+Z), rotated 90° CW from pointing_forward.
- Length (local +X) → +Z (up) in global
- Width (local +Y) → +X (east) in global
- Facing (local +Z) → +Y (north) in global
Source code in kumiki/rule.py
Plane
dataclass
¶
Plane(normal: Direction3D, point: V3)
Represents an oriented, infinite plane with origin in 3D space.
__repr__
¶
from_transform_and_direction
staticmethod
¶
from_transform_and_direction(transform: Transform, direction: Direction3D) -> Plane
Create a plane from a transform and a direction.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
transform
|
Transform
|
Transform defining the position and orientation |
required |
direction
|
Direction3D
|
Direction in the transform's local coordinate system |
required |
Returns:
| Type | Description |
|---|---|
Plane
|
Plane with normal in global coordinates and point at transform position |
Source code in kumiki/geometry.py
FeaturePurpose
¶
CutCSGLabel
dataclass
¶
A CSG node's optional name.
CutCSG
dataclass
¶
Bases: ABC
Base class of CSG nodes.
label
class-attribute
instance-attribute
¶
label: CutCSGLabel = field(default_factory=CutCSGLabel.NoLabel, kw_only=True)
__init_subclass__
¶
__repr__
abstractmethod
¶
display_name
classmethod
¶
get_declared_features
¶
get_declared_features(source: FeatureSource = BOTH) -> List[CSGFeature]
Return the features this node declares. Empty for compound nodes.
carriers
abstractmethod
¶
carriers() -> Dict[FeatureKey, Carrier]
Return this node's solver carriers by key.
TODO decide whether to drop NEVER_MARK features' carriers, or tag OPTIONAL ones
has_carrier
¶
has_carrier(key: FeatureKey) -> bool
collect_feature_hits
¶
collect_feature_hits(point: V3, tolerances: FeatureTestTolerances) -> List[OwnedFeatureHit]
Return every declared feature in this subtree that point is on, each at its type's tolerance.
Real features also require point on this node's boundary; non-real ones don't.
Source code in kumiki/csg/cutcsg.py
find_all_features
¶
find_all_features(point: V3, test_tolerances: Optional[FeatureTestTolerances] = None) -> List[OwnedFeatureHit]
Return every feature at point, best first: declared ones in this subtree plus derived edges
and points.
Faces are paired into edges at the EDGE tolerance, and declared edges crossed with faces at the POINT tolerance, so edges and points can be picked from further away than their faces. Derived duplicates are dropped at the end.
Source code in kumiki/csg/cutcsg.py
find_first_feature
¶
find_first_feature(point: V3, test_tolerances: Optional[FeatureTestTolerances] = None) -> Optional[OwnedFeatureHit]
Return the best feature at point (see _sort_feature_hits), or None.
Source code in kumiki/csg/cutcsg.py
contains_point
abstractmethod
¶
is_point_on_boundary
abstractmethod
¶
get_outward_normal
abstractmethod
¶
get_outward_normal(point: V3, eps: Optional[Numeric] = None) -> Optional[Direction3D]
Return the outward normal at boundary point point, or None. Undefined off the boundary.
get_outward_normals
¶
get_outward_normals(point: V3, eps: Optional[Numeric] = None) -> List[Direction3D]
Return the outward normal of every face meeting at boundary point point (several on an edge or corner).
Shapes that can't tell return get_outward_normal's one.
Source code in kumiki/csg/cutcsg.py
get_aabb
abstractmethod
¶
get_aabb() -> AxisAlignedBoundingBox
get_convex_hull
¶
is_exactly_convex_hull
¶
exact_line_spans
¶
exact_line_spans(line: Line, seed: Span, tolerance: float, removing: bool) -> Optional[List[Span]]
Return where line runs inside this primitive, or None if it can't be computed exactly.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
line
|
Line
|
the line, in this primitive's space. |
required |
seed
|
Span
|
the (low, high) stretch of the line to search, as parameters along its unit direction. |
required |
tolerance
|
float
|
how far outside the surface still counts as inside. |
required |
removing
|
bool
|
True when this primitive is being subtracted. A line lying on the surface counts as inside either way; this flips the comparison so that holds for a cut too. |
required |
Returns the parameter spans within seed that are inside. Default: clip seed by the convex
hull, when the primitive is exactly its hull.
Source code in kumiki/csg/cutcsg.py
angles_around_line
¶
angles_around_line(at: ndarray, direction: ndarray, basis: Tuple[ndarray, ndarray], tolerance: float) -> Optional[AngleSpans]
Return the directions around a line, at one point on it, in which this primitive has material.
Looking along the line at at, the primitive fills some angles around it; they say whether the
line runs inside it (all), off it (none), along a flat face (half a turn) or along an edge.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
at
|
ndarray
|
the point on the line (numpy, this primitive's space). |
required |
direction
|
ndarray
|
the line's unit direction (numpy). |
required |
basis
|
Tuple[ndarray, ndarray]
|
two unit vectors square to |
required |
tolerance
|
float
|
how far off a face still counts as on it. |
required |
Returns (low, high) angle spans in radians within [0, 2pi], or None if it can't be computed exactly. Default: from the convex hull's faces, when the primitive is exactly its hull.
Source code in kumiki/csg/cutcsg.py
line_crossings
¶
Return the parameters along the line origin + t * direction where it crosses this primitive's
surfaces: the places the material around the line can change.
origin and unit direction are numpy, in this primitive's space. Default: where the line
crosses each face plane of the convex hull.
Source code in kumiki/csg/cutcsg.py
plane_sides
¶
Return where this primitive cuts the plane of frame, as (front, back) regions within seed.
front is where it has material just on the side the plane's normal points to, back just on the other side (see cross_sections.Sides). Default: from the convex hull. Raises cross_sections.CannotCrossSection if the primitive can't be cut.
Source code in kumiki/csg/cutcsg.py
EmptyCSG
dataclass
¶
Bases: CutCSG
A solid with no points.
display_name
classmethod
¶
__repr__
¶
carriers
¶
carriers() -> Dict[FeatureKey, Carrier]
contains_point
¶
is_point_on_boundary
¶
get_outward_normal
¶
get_outward_normal(point: V3, eps: Optional[Numeric] = None) -> Optional[Direction3D]
get_aabb
¶
get_aabb() -> AxisAlignedBoundingBox
get_convex_hull
¶
HalfSpace
dataclass
¶
Bases: HasFeatures, CutCSG
An infinite half space: all points P with P · normal >= offset.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
normal
|
points into the half space. |
required | |
offset
|
the plane's signed distance from the origin along |
required |
display_name
classmethod
¶
__repr__
¶
carriers
¶
carriers() -> Dict[FeatureKey, Carrier]
has_carrier
¶
has_carrier(key: FeatureKey) -> bool
contains_point
¶
Return whether P · normal >= offset.
Source code in kumiki/csg/cutcsg.py
is_point_on_boundary
¶
Return whether P · normal == offset.
Source code in kumiki/csg/cutcsg.py
get_outward_normal
¶
get_outward_normal(point: V3, eps: Optional[Numeric] = None) -> Optional[Direction3D]
get_convex_hull
¶
Source code in kumiki/csg/cutcsg.py
RectangularPrism
dataclass
¶
Bases: HasFeatures, CutCSG
A prism with rectangular cross-section, optionally infinite at either end. Parameterized like a Timber.
The cross-section (size = width along local X, height along local Y) is centred at the
transform's position; the prism runs from start_distance to end_distance along local Z.
None for either distance makes that end infinite.
transform
class-attribute
instance-attribute
¶
default_features
¶
default_features() -> Dict[FeatureKey, CSGFeature]
display_name
classmethod
¶
__post_init__
¶
Source code in kumiki/csg/cutcsg.py
get_bottom_position
¶
get_bottom_position() -> V3
Return the centre of the start end. Raises ValueError if it is infinite.
Source code in kumiki/csg/cutcsg.py
get_top_position
¶
get_top_position() -> V3
Return the centre of the far end. Raises ValueError if it is infinite.
Source code in kumiki/csg/cutcsg.py
__repr__
¶
equals_prism
¶
equals_prism(other: RectangularPrism) -> bool
Return whether every parameter equals other's.
Source code in kumiki/csg/cutcsg.py
carriers
¶
carriers() -> Dict[FeatureKey, Carrier]
has_carrier
¶
has_carrier(key: FeatureKey) -> bool
Return whether key is one of this prism's carriers: a side, a finite end, or an extra plane.
Source code in kumiki/csg/cutcsg.py
contains_point
¶
Return whether point is inside or on the boundary.
Source code in kumiki/csg/cutcsg.py
is_point_on_boundary
¶
Return whether point is on the boundary.
Source code in kumiki/csg/cutcsg.py
get_outward_normal
¶
get_outward_normal(point: V3, eps: Optional[Numeric] = None) -> Optional[Direction3D]
get_outward_normals
¶
get_outward_normals(point: V3, eps: Optional[Numeric] = None) -> List[Direction3D]
get_aabb
¶
get_aabb() -> AxisAlignedBoundingBox
Source code in kumiki/csg/cutcsg.py
get_convex_hull
¶
Source code in kumiki/csg/cutcsg.py
SolidUnion
dataclass
¶
Bases: CutCSG
Union of children: points in any child.
display_name
classmethod
¶
__post_init__
¶
__repr__
¶
carriers
¶
carriers() -> Dict[FeatureKey, Carrier]
contains_point
¶
is_point_on_boundary
¶
Return whether point is on some child's boundary and not enclosed by the union.
Source code in kumiki/csg/cutcsg.py
get_outward_normal
¶
get_outward_normal(point: V3, eps: Optional[Numeric] = None) -> Optional[Direction3D]
Return the average outward normal of the children point is on the boundary of, or None.
Source code in kumiki/csg/cutcsg.py
get_outward_normals
¶
get_outward_normals(point: V3, eps: Optional[Numeric] = None) -> List[Direction3D]
get_aabb
¶
get_aabb() -> AxisAlignedBoundingBox
Source code in kumiki/csg/cutcsg.py
Intersection
dataclass
¶
Bases: CutCSG
Intersection of left and right: points in both.
__post_init__
¶
__repr__
¶
carriers
¶
carriers() -> Dict[FeatureKey, Carrier]
contains_point
¶
is_point_on_boundary
¶
Source code in kumiki/csg/cutcsg.py
get_outward_normal
¶
get_outward_normal(point: V3, eps: Optional[Numeric] = None) -> Optional[Direction3D]
Return the outward normal at point, averaged where both sides' surfaces meet.
Source code in kumiki/csg/cutcsg.py
get_outward_normals
¶
get_outward_normals(point: V3, eps: Optional[Numeric] = None) -> List[Direction3D]
Source code in kumiki/csg/cutcsg.py
get_aabb
¶
get_aabb() -> AxisAlignedBoundingBox
Source code in kumiki/csg/cutcsg.py
Difference
dataclass
¶
Bases: CutCSG
base minus every solid in subtract.
__post_init__
¶
__repr__
¶
carriers
¶
carriers() -> Dict[FeatureKey, Carrier]
contains_point
¶
Return whether point is in base and not strictly inside any subtracted solid (nor on a flush cut).
Source code in kumiki/csg/cutcsg.py
is_point_on_boundary
¶
Return whether point is in the difference and on the base's surface or a cut's wall.
Source code in kumiki/csg/cutcsg.py
get_outward_normal
¶
get_outward_normal(point: V3, eps: Optional[Numeric] = None) -> Optional[Direction3D]
Return the base's normal at point, else the average negated normal of the cuts there; None if unknown.
Source code in kumiki/csg/cutcsg.py
get_outward_normals
¶
get_outward_normals(point: V3, eps: Optional[Numeric] = None) -> List[Direction3D]
Return the base's normals at point, plus the negated normals of cuts through it not parallel to one.
Source code in kumiki/csg/cutcsg.py
ConvexPolygonSimpleLoft
dataclass
¶
Bases: HasFeatures, CutCSG
A solid lofted between two convex polygons in parallel planes, vertex i to vertex i.
Both profiles must be convex, with the same point count and winding, and every side planar (no
twist; see is_valid). Profiles lie in the local XY plane at bottom_points_z_pos and
top_points_z_pos along local Z; both must be finite.
transform
class-attribute
instance-attribute
¶
default_features
¶
default_features() -> Dict[FeatureKey, CSGFeature]
Return its two caps, a side per profile edge, and their edges.
Source code in kumiki/csg/cutcsg.py
display_name
classmethod
¶
__repr__
¶
is_valid
¶
Return whether both profiles are convex with 3+ matching points, top is above bottom, and every side is planar.
Source code in kumiki/csg/cutcsg.py
carriers
¶
carriers() -> Dict[FeatureKey, Carrier]
has_carrier
¶
has_carrier(key: FeatureKey) -> bool
contains_point
¶
Return whether point is inside or on the boundary.
Source code in kumiki/csg/cutcsg.py
is_point_on_boundary
¶
Return whether point is on the boundary.
Source code in kumiki/csg/cutcsg.py
get_outward_normal
¶
get_outward_normal(point: V3, eps: Optional[Numeric] = None) -> Optional[Direction3D]
get_outward_normals
¶
get_outward_normals(point: V3, eps: Optional[Numeric] = None) -> List[Direction3D]
get_aabb
¶
get_aabb() -> AxisAlignedBoundingBox
Source code in kumiki/csg/cutcsg.py
get_convex_hull
¶
is_exactly_convex_hull
¶
plane_sides
¶
Source code in kumiki/csg/cutcsg.py
TimberFace
¶
Bases: Enum
get_direction
¶
get_direction() -> Direction3D
Get the direction vector for this face in world coordinates.
Source code in kumiki/timber_features.py
is_perpendicular
¶
is_perpendicular(other: TimberFace) -> bool
Check if two faces are perpendicular to each other.
Perpendicular face pairs (orthogonal axes): - X-axis faces (RIGHT, LEFT) <-> Y-axis faces (FRONT, BACK) - X-axis faces (RIGHT, LEFT) <-> Z-axis faces (TOP, BOTTOM) - Y-axis faces (FRONT, BACK) <-> Z-axis faces (TOP, BOTTOM)
Source code in kumiki/timber_features.py
get_opposite_face
¶
get_opposite_face() -> TimberFace
Get the opposite face (the face on the opposite side of the timber).
Opposite pairs: - TOP <-> BOTTOM - RIGHT <-> LEFT - FRONT <-> BACK
Source code in kumiki/timber_features.py
rotate_about
¶
rotate_about(face: TimberFace) -> TimberFace
Rotate this face by 90 degrees about face's outward-normal axis
(a quarter turn using the right-hand rule around that normal).
If this face IS the rotation axis (self == face or self == face.get_opposite_face()), it lies on the axis and is unaffected by the rotation, so it is returned unchanged.
Source code in kumiki/timber_features.py
TimberEnd
¶
Bases: Enum
TimberLongFace
¶
Bases: Enum
is_perpendicular
¶
is_perpendicular(other: TimberLongFace) -> bool
Check if two long faces are perpendicular to each other.
Perpendicular face pairs: - RIGHT <-> FRONT, RIGHT <-> BACK - LEFT <-> FRONT, LEFT <-> BACK
Source code in kumiki/timber_features.py
rotate_left
¶
rotate_left() -> TimberLongFace
Rotate the long face left (90 degrees counter-clockwise).
TimberCenterline
¶
Bases: Enum
The timber's own axis, and nothing else.
One member, and it stays that way. Half the library uses this enum as a
SENTINEL TYPE rather than as a value -- Union[TimberLongFace,
TimberCenterline] in construction.py means "a face, or else measure from
the axis", and measuring.locate_edge branches on isinstance(edge,
TimberCenterline) to return the axis line. A second member would make
every one of those quietly wrong instead of raising, which is why a long
face's centerline is TimberLongFaceCenterline and not a member here.
TODO consider renaming this to TimberAxis. That is what it means, it is what stops anyone reading the name as "any centerline" and adding to it, and it would let SomeTimberCenterline be the name for the group.
ArrangementNames
¶
Bases: Enum
identifies each of the timbers in the various arrangements, we just use one enum for convenience but we really only want to refer to timbers specific to a certain arrangement when using this class
ButtJointTimberArrangement
¶
front_face_on_butt_timber
class-attribute
instance-attribute
¶
front_face_on_butt_timber: Optional[TimberLongFace] = None
top_face_on_butt_timber
class-attribute
instance-attribute
¶
top_face_on_butt_timber: Optional[TimberLongFace] = None
compute_normalized_timber_cross_product
¶
compute_normalized_timber_cross_product() -> Direction3D
Compute the normalized cross product of the butt timber and receiving timber length directions.
Source code in kumiki/construction.py
compute_arrangement_acute_angle
¶
compute_arrangement_acute_angle() -> Numeric
Compute the angle between the 2 timbers
Source code in kumiki/construction.py
check_plane_aligned
¶
Return None if timbers are plane-aligned and front/top face are in plane, else an error message.
Source code in kumiki/construction.py
check_face_aligned_and_orthogonal
¶
Return None if timbers are face-aligned, else an error message.
Unlike check_plane_aligned, this does not validate front_face_on_butt_timber: callers that use this (e.g. drop-in dovetail/housed butt joints) already apply their own, looser front-face validation suited to non-coplanar orthogonal arrangements.
Source code in kumiki/construction.py
check_perfection
¶
Return None if both timbers are perfect, else an error message.
Source code in kumiki/construction.py
CrossJointScribeReliefConfig
dataclass
¶
Configuration for cross joint relief "Scribe" here means one timber is scribed onto the other and completely cut away
cross_timber_1
staticmethod
¶
ButtJointScribeReliefConfig
dataclass
¶
Configuration for butt joint relief "Scribe" here means one timber is scribed onto the other and completely cut away
butt_timber
staticmethod
¶
NotchFrom
¶
ButtJointNotchReliefConfig
dataclass
¶
Configuration for butt joint relief via chop_butt_joint_shoulder_notch_relief_4sided.
Unlike ButtJointScribeReliefConfig (which scribes one timber's whole imperfect body onto the other), this relieves only the material near the inset shoulder using the 4-sided frustum notch -- see chop_butt_joint_shoulder_notch_relief_4sided.
Attributes:
| Name | Type | Description |
|---|---|---|
notch_from |
NotchFrom
|
Which plane the notch is measured from. - Shoulder (default): the notch is anchored to the actual (possibly inset) shoulder plane. The only value cut_mortise_and_tenon_joint itself supports. - Face: only supported by cut_mortise_and_tenon_joint_on_plane_aligned_timbers / _on_face_aligned_timbers. The joint is still fit at the real (inset) shoulder (via the default scribe-based housing cut), but the notch relief itself is anchored to the mortise entry face -- as if mortise_shoulder_inset were 0 -- so it reads as starting at the timber's outer face regardless of how deep the shoulder is actually inset. |
SpliceJointScribeReliefConfig
dataclass
¶
Configuration for splice joint relief "Scribe" here means one timber is scribed onto the other and completely cut away
timber1
staticmethod
¶
CornerJointScribeReliefConfig
dataclass
¶
Configuration for corner joint relief "Scribe" here means one timber is scribed onto the other and completely cut away
timber1
staticmethod
¶
DoubleButtJointScribeReliefConfig
dataclass
¶
Configuration for double butt joint relief.
first_timber_to_be_scribed is scribed first, then
second_timber_to_be_scribed is scribed onto the remaining timber.
with_order
staticmethod
¶
with_order(first_timber_to_be_scribed: ArrangementNames, second_timber_to_be_scribed: ArrangementNames)
Create a DoubleButtJointScribeReliefConfig from the order of which timbers to be scribed
Source code in kumiki/joints/workshop/shavings/relief.py
TripleButtJointScribeReliefConfig
dataclass
¶
Configuration for triple butt joint relief.
first_timber_to_be_scribed is scribed first, then
second_timber_to_be_scribed, then third_timber_to_be_scribed.
with_order
staticmethod
¶
with_order(first_timber_to_be_scribed: ArrangementNames, second_timber_to_be_scribed: ArrangementNames, third_timber_to_be_scribed: ArrangementNames)
Create a TripleButtJointScribeReliefConfig from the order of which timbers to be scribed
Source code in kumiki/joints/workshop/shavings/relief.py
QuadrupleButtJointScribeReliefConfig
dataclass
¶
Configuration for quadruple butt joint relief.
first_timber_to_be_scribed is scribed first, then
second_timber_to_be_scribed, third_timber_to_be_scribed, and
fourth_timber_to_be_scribed.
with_order
staticmethod
¶
with_order(first_timber_to_be_scribed: ArrangementNames, second_timber_to_be_scribed: ArrangementNames, third_timber_to_be_scribed: ArrangementNames, fourth_timber_to_be_scribed: ArrangementNames)
Create a QuadrupleButtJointScribeReliefConfig from the order of which timbers to be scribed
Source code in kumiki/joints/workshop/shavings/relief.py
CrossCapJointScribeReliefConfig
dataclass
¶
Configuration for cross-cap joint relief.
first_timber_to_be_scribed is scribed first, then
second_timber_to_be_scribed is scribed onto the remaining timber.
with_order
staticmethod
¶
with_order(first_timber_to_be_scribed: ArrangementNames, second_timber_to_be_scribed: ArrangementNames)
Create a CrossCapJointScribeReliefConfig from the order of which timbers to be scribed
Source code in kumiki/joints/workshop/shavings/relief.py
BraceJointScribeReliefConfig
dataclass
¶
Configuration for brace joint relief.
The 2 braced timbers are always scribed onto the brace timber.
with_order
staticmethod
¶
with_order(first_timber_to_be_scribed: ArrangementNames, second_timber_to_be_scribed: ArrangementNames)
Create a BraceJointScribeReliefConfig from the order of which timbers to be scribed
Source code in kumiki/joints/workshop/shavings/relief.py
DropinButtJointSweepScribeReliefConfig
dataclass
¶
Configuration for drop in butt joint relief
The butting (drop in) timber is always scribed onto the receiving timber
Actually the butting timber and its entire swept volume in the drop-in path are scribed onto the receiving timber
ShoulderReliefCSGGeometry
dataclass
¶
CSG geometry produced by the shoulder notch relief functions.
receiving_timber_notch_negative_CSG: cut applied to the receiving (mortise) timber, expressed in that timber's local frame.butting_timber_relief_negative_CSG: cut applied to the butting (tenon) timber, expressed in that timber's local frame.Noneonly when no relief geometry is necessary (currently always populated).
orientation_pointing_towards_face_sitting_on_face
¶
orientation_pointing_towards_face_sitting_on_face(towards_face: TimberFace, sitting_face: TimberFace) -> Orientation
Returns a marking orientation with +z toward towards_face and +y pointing into the timber from sitting_face.
Marking transforms use a convention where, for transforms sitting on a timber face, +y points into the timber. This helper builds that orientation from two perpendicular faces.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
towards_face
|
TimberFace
|
The face the orientation's +z axis should point toward. |
required |
sitting_face
|
TimberFace
|
The face the orientation is sitting on; its outward normal becomes -y. |
required |
Returns:
| Type | Description |
|---|---|
Orientation
|
Orientation with +z pointing toward towards_face and +y pointing into the timber. |
Raises:
| Type | Description |
|---|---|
AssertionError
|
If towards_face and sitting_face are not perpendicular. |
Source code in kumiki/joints/workshop/shavings/shavings.py
scribe_face_plane_onto_centerline
¶
scribe_face_plane_onto_centerline(face: TimberFace, face_timber: TimberLike) -> UnsignedPlane
Mark the face plane on a timber.
Returns the plane defined by the face on face_timber. This plane can then be measured onto another timber's centerline to find shoulder plane positions in various butt joints.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
face
|
TimberFace
|
The face on face_timber to mark |
required |
face_timber
|
TimberLike
|
The timber whose face defines the plane |
required |
Returns:
| Type | Description |
|---|---|
UnsignedPlane
|
UnsignedPlane representing the face plane. This can be measured onto a centerline using |
UnsignedPlane
|
mark_distance_from_end_along_centerline() to find intersection points. |
Example
Mark the plane for timber_b's FRONT face¶
face_plane = scribe_face_plane_onto_centerline( ... face=TimberFace.FRONT, ... face_timber=timber_b ... )
Then measure onto timber_a's centerline¶
marking = mark_distance_from_end_along_centerline(face_plane, timber_a) shoulder_distance = measurement.distance
Source code in kumiki/joints/workshop/shavings/shavings.py
locate_pat_shoulder_plane_from_centerline_to_reference_face
¶
locate_pat_shoulder_plane_from_centerline_to_reference_face(shoulder_timber: TimberLike, reference_timber: TimberLike, reference_face: TimberFace) -> Plane
Compute a shoulder plane on shoulder_timber using a face plane on reference_timber.
This helper assumes a plane-aligned arrangement. It scribes the reference face plane onto the shoulder timber centerline, then returns the timber cross-section plane at that mark (normal = shoulder timber length direction).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
shoulder_timber
|
TimberLike
|
Timber receiving the shoulder plane. |
required |
reference_timber
|
TimberLike
|
Timber that owns the reference face. |
required |
reference_face
|
TimberFace
|
Face on |
required |
Returns:
| Type | Description |
|---|---|
Plane
|
Plane perpendicular to |
Source code in kumiki/joints/workshop/shavings/shavings.py
scribe_centerline_onto_centerline
¶
scribe_centerline_onto_centerline(timber: TimberLike) -> Line
Mark the centerline of a timber.
Returns the Line representing the timber's centerline. This line can then be measured onto another timber's centerline to find closest points between skew centerlines.
This is useful for positioning timbers relative to each other, especially in complex 3D joints where centerlines may be skew (non-intersecting, non-parallel).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
timber
|
TimberLike
|
The timber whose centerline to mark |
required |
Returns:
| Type | Description |
|---|---|
Line
|
Line representing the timber's centerline. This can be measured onto another |
Line
|
timber's centerline using mark_distance_from_end_along_centerline() to find closest points. |
Example
Mark the centerline of timber_b¶
centerline_b = scribe_centerline_onto_centerline(timber_b)
Then measure onto timber_a's centerline¶
measurement_a = mark_distance_from_end_along_centerline(centerline_b, timber_a) dist_a = measurement_a.distance
Source code in kumiki/joints/workshop/shavings/shavings.py
check_timber_overlap_for_splice_joint_is_sensible
¶
check_timber_overlap_for_splice_joint_is_sensible(timberA: TimberLike, timberB: TimberLike, timberA_end: TimberEnd, timberB_end: TimberEnd) -> Optional[str]
Check if two timbers overlap in a sensible way for a splice joint.
A sensible splice joint configuration requires: 1. The joint ends are pointing in opposite directions (anti-parallel) 2. The joint end planes either touch each other or go past each other 3. The joint end planes have not gone so far past each other that they reach the opposite end of the other timber
ASCII diagram of a sensible splice joint: A |==================| <- timberA_end timberB_end -> |==================| B
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
timberA
|
TimberLike
|
First timber in the splice joint |
required |
timberB
|
TimberLike
|
Second timber in the splice joint |
required |
timberA_end
|
TimberEnd
|
Which end of timberA is being joined (TOP or BOTTOM) |
required |
timberB_end
|
TimberEnd
|
Which end of timberB is being joined (TOP or BOTTOM) |
required |
Returns:
| Type | Description |
|---|---|
Optional[str]
|
Optional[str]: None if the configuration is sensible, otherwise a string explaining why the configuration fails the sensibility check |
Example
error = check_timber_overlap_for_splice_joint_is_sensible( ... gooseneck, receiving, TimberEnd.BOTTOM, TimberEnd.TOP ... ) if error: ... print(f"Joint configuration error: {error}")
Source code in kumiki/joints/workshop/shavings/shavings.py
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 | |
chop_timber_end_with_prism
¶
chop_timber_end_with_prism(timber: TimberLike, end: TimberEnd, distance_from_end_to_cut: Numeric, label: CutCSGLabel = CutCSGLabel('timber_end_prism_cut'), is_shoulder: bool = False) -> RectangularPrism
Create a RectangularPrism CSG for chopping off material from a timber end (in local coordinates).
Creates a CSG prism in the timber's local coordinate system that starts at distance_from_end_to_cut from the timber end and extends to infinity in the timber length direction. The prism has the same cross-section size as the timber.
This is useful when you need a volumetric cut that exactly matches the timber's cross-section (e.g., for CSGCut objects in compound cuts).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
timber
|
TimberLike
|
The timber to create a chop prism for |
required |
end
|
TimberEnd
|
Which end to chop from (TOP or BOTTOM) |
required |
distance_from_end_to_cut
|
Numeric
|
Distance from the end where the cut begins |
required |
is_shoulder
|
bool
|
The prism's face at the cut is a joint's shoulder plane. |
False
|
Returns:
| Name | Type | Description |
|---|---|---|
RectangularPrism |
RectangularPrism
|
A CSG prism in local coordinates representing the material beyond distance_from_end_to_cut from the end, extending to infinity |
Example
Chop everything beyond 2 inches from the top of a timber¶
chop_prism = chop_timber_end_with_prism(my_timber, TimberEnd.TOP, scalar(2))
This creates a semi-infinite prism starting 2 inches from the top¶
Source code in kumiki/joints/workshop/shavings/shavings.py
chop_timber_end_with_half_plane
¶
chop_timber_end_with_half_plane(timber: TimberLike, end: TimberEnd, distance_from_end_to_cut: Numeric, label: CutCSGLabel = NoLabel(), is_shoulder: bool = False) -> HalfSpace
Create a HalfSpace CSG for chopping off material from a timber end (in local coordinates).
Creates a half-plane cut in the timber's local coordinate system, perpendicular to the timber's length direction, positioned at distance_from_end_to_cut from the specified end. The half-plane removes everything beyond that distance.
This is simpler and more efficient than a prism-based cut when you just need a planar cut perpendicular to the timber's length (e.g., for simple butt joints or splice joints).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
timber
|
TimberLike
|
The timber to create a chop half-plane for |
required |
end
|
TimberEnd
|
Which end to chop from (TOP or BOTTOM) |
required |
distance_from_end_to_cut
|
Numeric
|
Distance from the end where the cut plane is positioned |
required |
label
|
CutCSGLabel
|
What the resulting plane is called in the CSG tree. Left to the caller because an end-chop means something different in every joint that makes one; the top_end_cut / bottom_end_cut names belong to a Cutting's own maybe-end-cuts, not to every plane of this shape. |
NoLabel()
|
is_shoulder
|
bool
|
The plane is a joint's shoulder plane. |
False
|
Returns:
| Name | Type | Description |
|---|---|---|
HalfSpace |
HalfSpace
|
A half-plane in local coordinates that removes material beyond distance_from_end_to_cut from the end |
Example
Chop everything beyond 2 inches from the top of a timber¶
chop_plane = chop_timber_end_with_half_plane(my_timber, TimberEnd.TOP, scalar(2))
This creates a half-plane 2 inches from the top, removing everything beyond¶
Source code in kumiki/joints/workshop/shavings/shavings.py
chop_lap_on_timber_end
¶
chop_lap_on_timber_end(lap_timber: TimberLike, lap_timber_end: TimberEnd, lap_timber_face: TimberFace, lap_length: Numeric, lap_shoulder_position_from_lap_timber_end: Numeric, lap_depth: Numeric, label: CutCSGLabel = CutCSGLabel('lap_cut')) -> Tuple[CutCSG, HalfSpace]
Create CSG cuts for a lap joint between two timber ends.
Creates material removal volumes for both timbers in a lap joint configuration where one timber (top lap) has material removed from one face, and the other timber (bottom lap) has material removed from the opposite face so they interlock.
lap_timber_face
v |--------| lap_length
╔════════════════════════╗ - ║face_lap_timber ║ | lap_depth ║ ╔════════╝ - ║ ║ ║ ║ ╚═══════════════╝ ^ lap_shoulder_position_from_lap_timber_end
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lap_timber
|
TimberLike
|
The timber that will have material removed from the specified face |
required |
lap_timber_end
|
TimberEnd
|
Which end of the top lap timber is being joined |
required |
lap_timber_face
|
TimberFace
|
Which face of the top lap timber to remove material from |
required |
lap_length
|
Numeric
|
Length of the lap region along the timber length |
required |
lap_shoulder_position_from_lap_timber_end
|
Numeric
|
Distance from the timber end to the shoulder (inward) |
required |
lap_depth
|
Numeric
|
Depth of material to remove (measured from lap_timber_face) |
required |
Returns:
| Type | Description |
|---|---|
CutCSG
|
Tuple of (lap_prism, end_cut_half_plane) representing material to remove from the timber |
HalfSpace
|
Both CSGs are in local coordinates of the timber |
Example
Create a half-lap joint¶
top_lap, top_end_cut = chop_lap_on_timber_end( ... timber_a, TimberEnd.TOP, ... TimberFace.BOTTOM, lap_length=4, lap_depth=2, shoulder_pos=1 ... )
Source code in kumiki/joints/workshop/shavings/shavings.py
409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 | |
chop_lap_on_timber_ends
¶
chop_lap_on_timber_ends(top_lap_timber: TimberLike, top_lap_timber_end: TimberEnd, bottom_lap_timber: TimberLike, bottom_lap_timber_end: TimberEnd, top_lap_timber_face: TimberLongFace, lap_length: Numeric, top_lap_shoulder_position_from_top_lap_shoulder_timber_end: Numeric, lap_depth: Numeric, label: CutCSGLabel = CutCSGLabel('lap_cut')) -> Tuple[Tuple[CutCSG, HalfSpace], Tuple[CutCSG, HalfSpace]]
Create CSG cuts for a lap joint between two timber ends.
Creates material removal volumes for both timbers in a lap joint configuration where one timber (top lap) has material removed from one face, and the other timber (bottom lap) has material removed from the opposite face so they interlock.
top_lap_timber_face
v |--------| lap_length
╔════════════════════════╗╔══════╗ - ║face_lap_timber ║║ ║ | lap_depth ║ ╔════════╝║ ║ - ║ ║╔════════╝ ║ ║ ║║ timberB ║ ╚═══════════════╝╚═══════════════╝ ^ top_lap_shoulder_position_from_top_lap_shoulder_timber_end
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
top_lap_timber
|
TimberLike
|
The timber that will have material removed from the specified face |
required |
top_lap_timber_end
|
TimberEnd
|
Which end of the top lap timber is being joined |
required |
bottom_lap_timber
|
TimberLike
|
The timber that will have material removed from the opposite face |
required |
bottom_lap_timber_end
|
TimberEnd
|
Which end of the bottom lap timber is being joined |
required |
top_lap_timber_face
|
TimberLongFace
|
Which face of the top lap timber to remove material from |
required |
lap_length
|
Numeric
|
Length of the lap region along the timber length |
required |
top_lap_shoulder_position_from_top_lap_shoulder_timber_end
|
Numeric
|
Distance from the timber end to the shoulder (inward) |
required |
lap_depth
|
Numeric
|
Depth of material to remove (measured from top_lap_timber_face) |
required |
Returns:
| Type | Description |
|---|---|
Tuple[CutCSG, HalfSpace]
|
Tuple of ((top_lap_prism, top_end_cut), (bottom_lap_prism, bottom_end_cut)) |
Tuple[CutCSG, HalfSpace]
|
Each tuple contains the lap CSG and end cut HalfSpace for that timber |
Tuple[Tuple[CutCSG, HalfSpace], Tuple[CutCSG, HalfSpace]]
|
All CSGs are in local coordinates of their respective timbers |
Example
Create a half-lap joint¶
(top_lap, top_end), (bottom_lap, bottom_end) = chop_lap_on_timber_ends( ... timber_a, TimberEnd.TOP, ... timber_b, TimberEnd.BOTTOM, ... TimberFace.BOTTOM, lap_length=4, lap_depth=2, shoulder_pos=1 ... )
Source code in kumiki/joints/workshop/shavings/shavings.py
550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 | |
chop_profile_on_timber_face
¶
chop_profile_on_timber_face(timber: TimberLike, end: TimberEnd, face: TimberFace, profile: Union[List[V2], List[List[V2]]], depth: Numeric, profile_y_offset_from_end: Numeric = scalar(0), label: CutCSGLabel = CutCSGLabel('profile_cut')) -> Union[SolidUnion, ConvexPolygonExtrusion]
Create a CSG extrusion of a profile (or multiple profiles) on a timber face. See the diagram below for understanding how to interpret the profile in the timber's local space based on the end and face arguments.
end
timber v ^ ╔════════════════════════╗ -x ║face ║< (0,profile_y_offset_from_end) of the profile +y -> ╚════════════════════════╝ +x v
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
timber
|
TimberLike
|
The timber to create a profile for |
required |
end
|
TimberEnd
|
Which end to create the profile on (determines the origin and rotation of the profile) |
required |
face
|
TimberFace
|
Which face to create the profile on (determines the origin, rotation, and extrusion direction of the profile) |
required |
profile
|
Union[List[V2], List[List[V2]]]
|
Either a single profile (List[V2]) or multiple profiles (List[List[V2]]). Multiple profiles are provided as a convenience for creating non-convex shapes by unioning multiple convex polygon extrusions. |
required |
depth
|
Numeric
|
Depth to extrude the profile through the timber's face |
required |
profile_y_offset_from_end
|
Numeric
|
Offset in the Y direction (along timber length from end). The profile will be translated by -profile_y_offset_from_end, so the origin (0,0) in profile coordinates corresponds to (0, profile_y_offset_from_end) in the timber's end-face coordinate system. |
scalar(0)
|
Returns:
| Type | Description |
|---|---|
Union[SolidUnion, ConvexPolygonExtrusion]
|
CutCSG representing the extruded profile(s) in the timber's local coordinates. |
Union[SolidUnion, ConvexPolygonExtrusion]
|
If multiple profiles are provided, returns a SolidUnion of all extruded profiles. |
Notes
- The profile is positioned at the intersection of the specified end and face
- Profile coordinates: X-axis points into timber from end, Y-axis across face, origin at (0,0) on face
- The extrusion extends inward from the face by the specified depth
- For non-convex shapes, provide multiple profiles (List[List[V2]]) which will be individually extruded and unioned together
- Each individual profile uses ConvexPolygonExtrusion, so complex non-convex shapes should be decomposed into multiple convex profiles
Source code in kumiki/joints/workshop/shavings/shavings.py
693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 | |
draw_gooseneck_polygon_NONCONVEX
¶
draw_gooseneck_polygon_NONCONVEX(length: Numeric, small_width: Numeric, large_width: Numeric, head_length: Numeric) -> List[V2]
Returns the non-convex gooseneck profile as a single polygon (for reference/visualization).
The gooseneck shape has a narrow neck that widens into a trapezoidal head. This polygon is non-convex and cannot be used directly with chop_profile_on_timber_face. Use draw_gooseneck_polygon_CONVEX (aliased as draw_gooseneck_polygon) for actual cutting.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
length
|
Numeric
|
Total length of the gooseneck shape along the profile Y-axis. |
required |
small_width
|
Numeric
|
Width of the neck (the narrow portion). |
required |
large_width
|
Numeric
|
Width of the head (the wide trapezoid base; must be > small_width). |
required |
head_length
|
Numeric
|
Length of the trapezoidal head portion. |
required |
Returns:
| Type | Description |
|---|---|
List[V2]
|
List of 2D points forming the non-convex gooseneck polygon (counter-clockwise). |
Source code in kumiki/joints/workshop/shavings/shavings.py
draw_gooseneck_polygon_CONVEX
¶
draw_gooseneck_polygon_CONVEX(length: Numeric, small_width: Numeric, large_width: Numeric, head_length: Numeric) -> List[List[V2]]
Returns the gooseneck profile decomposed into convex polygons for use with chop_profile_on_timber_face.
The non-convex gooseneck shape is split into two convex polygons — a neck rectangle and a head trapezoid — whose union gives the full gooseneck. This is the format required by chop_profile_on_timber_face (List[List[V2]]).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
length
|
Numeric
|
Total length of the gooseneck shape along the profile Y-axis. |
required |
small_width
|
Numeric
|
Width of the neck (the narrow portion). |
required |
large_width
|
Numeric
|
Width of the head (the wide trapezoid base; must be > small_width). |
required |
head_length
|
Numeric
|
Length of the trapezoidal head portion. |
required |
Returns:
| Type | Description |
|---|---|
List[List[V2]]
|
List of two convex polygon point lists: [neck_rectangle, head_trapezoid]. |
Source code in kumiki/joints/workshop/shavings/shavings.py
solve_assembly
¶
solve_assembly(members: Sequence[AssemblyMember], joints: Sequence[AssemblyJoint], clearout_clearance: float = _DEFAULT_CLEAROUT_CLEARANCE, should_cancel: Optional[Callable[[], bool]] = None) -> Optional[AssemblySolution]
Solve the disassembly sequence for an abstract assembly graph.
Returns None when no member has any freedom. On an unsolvable ordering the already-solved steps (including the failing ordering's earlier substeps) are returned with an AssemblyFailure — it never raises for unsolvability.
Raises ValueError for joints referencing unknown member keys.
Source code in kumiki/assembly.py
1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 | |
compute_timber_orientation
¶
compute_timber_orientation(length_direction: Direction3D, width_direction: Direction3D) -> Orientation
Compute the orientation matrix from length and width directions
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
length_direction
|
Direction3D
|
Direction vector for the length axis as 3D vector, the +length direction is the +Z direction |
required |
width_direction
|
Direction3D
|
Direction vector for the width axis as 3D vector, the +width direction is the +X direction |
required |
Returns:
| Type | Description |
|---|---|
Orientation
|
Orientation object representing the timber's orientation in 3D space |
Source code in kumiki/timber.py
create_timber
¶
create_timber(length: Numeric, size: V2, bottom_position: V3, length_direction: Direction3D, width_direction: Direction3D, ticket: Optional[Union[TimberTicket, str]] = None) -> Timber
Factory function to create a Timber with computed orientation from direction vectors
This is the main way to construct Timber instances. It takes direction vectors and computes the proper orientation matrix automatically.
AGENT NOTE: AVOID this function if possible, prefer methods like join_timber, attach_timber, create_*_timber_on_footprint, or even create_axis_aligned_timber, which are more robust and easier to use.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
length
|
Numeric
|
Length of the timber |
required |
size
|
V2
|
Cross-sectional size (width, height) as 2D vector, width is the X dimension (left to right), height is the Y dimension (front to back) |
required |
bottom_position
|
V3
|
Position of the bottom point (center of cross-section) as 3D vector |
required |
length_direction
|
Direction3D
|
Direction vector for the length axis as 3D vector, the +length direction is the +Z direction |
required |
width_direction
|
Direction3D
|
Direction vector for the width axis as 3D vector, the +width direction is the +X direction |
required |
ticket
|
Optional[Union[TimberTicket, str]]
|
Optional ticket for this timber (can be TimberTicket object or string name, used for rendering/debugging) |
None
|
Returns:
| Type | Description |
|---|---|
Timber
|
Timber instance with computed orientation |
Source code in kumiki/timber.py
did_end_cuts_extend_timber
¶
did_end_cuts_extend_timber(timber: PerfectTimberWithin, cuts: List[Cutting]) -> bool
Check if any end cuts extend beyond the timber's original bounds.
An end cut extends beyond if: - Top cut: The cutting plane is at z > timber.length (cuts beyond the top) - Bottom cut: The cutting plane is at z < 0 (cuts beyond the bottom)
In local coordinates, HalfSpace end cuts are defined with: - Top cuts: normal pointing up (+Z), offset at the cut location - Bottom cuts: normal pointing down (-Z), offset at the cut location (negative value)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
timber
|
PerfectTimberWithin
|
The timber being cut |
required |
cuts
|
List[Cutting]
|
List of cuts on the timber |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if any end cut extends beyond the timber's original length |
Source code in kumiki/timber.py
warn_joints_whose_timber_extended_rough_prisms_dont_touch
¶
warn_joints_whose_timber_extended_rough_prisms_dont_touch(joints: Iterable[Joint]) -> None
A FrameWarning for each joint whose timber's extended prisms don't touch. This is just a permissive backstop. Instead, joints should do more specific checks but that's currently not the case for all joints.
Source code in kumiki/timber.py
make_compound_joint
¶
make_compound_joint(joints: List[Joint], ticket: JointTicket) -> Joint
Create a compound joint that combines multiple joints together.
The cuttings and accessories from all joints are merged into a single Joint object. Numeric suffixes are added to accessory and cutting keys if there are conflicts. The tickets of the input joints are ignored.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
joints
|
List[Joint]
|
List of Joint objects to combine |
required |
ticket
|
JointTicket
|
JointTicket for the compound joint |
required |
Source code in kumiki/timber.py
require_check
¶
add_milestone
¶
Emit a milestone marker for the viewer loading screen.
Writes a JSON protocol message to the real stdout pipe so the viewer extension can display progress during script execution. No-ops when not running inside the Kigumi extension (checks KIGUMI_VIEWER_MILESTONES environment variable).
Source code in kumiki/timber.py
solve_frame_assembly
¶
solve_frame_assembly(frame: Frame, should_cancel: Optional[Callable[[], bool]] = None) -> Optional[AssemblySolution]
Solve the disassembly sequence for a frame's source joints.
Adapts the frame into the abstract assembly graph of kumiki/assembly.py — one AssemblyMember per distinct timber/accessory (keyed by ticket kumiki_id, positioned at the timber centroid) and one AssemblyJoint per source joint — then delegates to solve_assembly.
Returns None when no member of any source joint has an assembly freedom.
Source code in kumiki/timber.py
3080 3081 3082 3083 3084 3085 3086 3087 3088 3089 3090 3091 3092 3093 3094 3095 3096 3097 3098 3099 3100 3101 3102 3103 3104 3105 3106 3107 3108 3109 3110 3111 3112 3113 3114 3115 3116 3117 3118 3119 3120 3121 3122 3123 3124 3125 3126 3127 3128 3129 3130 3131 3132 3133 3134 3135 3136 3137 3138 3139 3140 3141 3142 3143 3144 3145 3146 3147 3148 3149 3150 3151 3152 3153 3154 3155 3156 3157 3158 3159 3160 3161 3162 3163 3164 3165 3166 3167 3168 3169 3170 3171 3172 3173 | |
intersect_line_plane
¶
The point where a line crosses a plane, or None if it crosses in no single point.
None covers three cases that all mean "no point here": either argument missing (a caller passing through a locate() that declined), the line parallel to the plane, and the line lying IN the plane. The last is the one that turns up constantly when deriving points -- an edge lies in both of the faces that formed it -- and it is geometrically a whole shared line rather than a point, so it is not an intersection this can describe.
Source code in kumiki/geometry.py
intersect_planes
¶
The infinite line where two planes meet, or None if they never do.
None covers three cases that all mean "no line here": either plane missing (a caller passing through a locate() that declined), the planes parallel, and the planes coincident. Coincident planes are geometrically a whole shared plane rather than a line, so they are not an intersection this can describe -- that relation is worth capturing separately, since two coincident faces is exactly the rough-matches-perfect test, but it is not an edge.
The returned direction is normalised; the returned point is the point on the line closest to the origin.
Source code in kumiki/geometry.py
lines_are_coincident
¶
Whether two lines are the same infinite line.
Not "do they cross": two lines meeting at a point are different lines. This answers whether two features NAME the same geometry, which is what deciding between duplicate features needs.
Source code in kumiki/geometry.py
perpendicular_axes
¶
Two unit axes at right angles to a direction, and to each other.
Which two does not matter, so long as neither is parallel to the direction: the world axis it leans on least is the safe one to start from.
Source code in kumiki/geometry.py
planes_are_coincident
¶
Whether two planes are the same infinite plane, facing either way.
Source code in kumiki/geometry.py
planes_are_parallel
¶
Whether two planes never meet in a line (parallel, or the same plane).
points_are_coincident
¶
Whether two points are the same point.
Source code in kumiki/geometry.py
unit_vector
¶
A vector scaled to length one, or left alone if it has barely any.
safe_normalize_vector, under its own name. Kept as a thin alias because "unit_vector" is what the geometry here is doing, and because dividing by anything merely above zero -- which is what this used to do -- turns the cross product of two nearly parallel edges into a direction made of rounding noise.
Source code in kumiki/geometry.py
memo_by_identity
¶
Memoize compute(obj, *args) per live obj (by identity) and hashable args.
Source code in kumiki/csg/identity_memo.py
merge_coincident_planes
¶
merge_coincident_planes(carriers: Dict[CarrierRef, Carrier], rank: Optional[Mapping[CarrierRef, int]] = None) -> Dict[CarrierRef, CarrierRef]
Map each carrier to its solving carrier: coincident planes (facing either way) share the one ranked
lowest in rank (unranked count as 0), else the first; others map to themselves.
Source code in kumiki/csg/carriers.py
arris_against_cap
¶
arris_against_cap(side: int, sides: int, end: bool) -> FeatureKey
Return the key of the arris where side side meets cap cap.
corner_on_cap
¶
corner_on_cap(vertex: int, vertices: int, end: bool) -> FeatureKey
Return the key of vertex index of the start or end profile.
default_feature_name
¶
default_feature_name(key: FeatureKey) -> str
Return the deterministic name of a default feature, e.g. "arris.5".
side_key
¶
side_key(index: int) -> FeatureKey
feature_groups_intersect
¶
feature_groups_intersect(a: FeatureGroup, b: FeatureGroup) -> bool
measured_by_default
¶
measured_by_default(purpose: FeaturePurpose) -> bool
Return whether features serving purpose are solved for by default (not rough stock or its relief).
feature_hit_sort_key
¶
feature_hit_sort_key(hit: OwnedFeatureHit) -> Tuple
Return a hit's sort key (see _sort_feature_hits).
Source code in kumiki/csg/cutcsg.py
owner_label_trails
¶
owner_label_trails(root: CutCSG) -> Dict[int, Tuple[str, ...]]
Return every node under root, keyed by id, with its label path (shoulder, shoulder#1, ...).
Source code in kumiki/csg/cutcsg.py
shared_ancestor
¶
Return the deepest node under some_common_ancestor containing both first and second, or None.
Source code in kumiki/csg/cutcsg.py
derive_edge_hits
¶
derive_edge_hits(some_common_ancestor: CutCSG, face_hits: List[OwnedFeatureHit]) -> List[OwnedFeatureHit]
Return the edges formed by pairs of face_hits, each owned by the pair's shared ancestor.
O(n^2) in the number of hits.
Source code in kumiki/csg/cutcsg.py
derive_point_hits
¶
derive_point_hits(some_common_ancestor: CutCSG, edge_hits: List[OwnedFeatureHit], face_hits: List[OwnedFeatureHit]) -> List[OwnedFeatureHit]
Return the points where an edge in edge_hits crosses a face in face_hits, each owned by the pair's shared ancestor.
An edge lying in the face's plane gives nothing.
Source code in kumiki/csg/cutcsg.py
prism_face_key
¶
prism_face_key(face: PrismFace) -> FeatureKey
Return the default key of a RectangularPrism face.
prism_arris_key
¶
prism_arris_key(first: PrismFace, second: PrismFace) -> FeatureKey
Return the default key of the arris between two faces, in either order.
Source code in kumiki/csg/cutcsg.py
prism_corner_key
¶
prism_corner_key(*faces: PrismFace) -> FeatureKey
Return the default key of the corner of three faces, in any order.
Source code in kumiki/csg/cutcsg.py
convex_profile_edge_features
¶
convex_profile_edge_features(sides: int) -> Dict[FeatureKey, CSGFeature]
Return the default edges of a convex extrusion or loft with sides sides, in key order:
side n against side n+1, then each side against the start cap, then the end cap.
Source code in kumiki/csg/cutcsg.py
measured_against
¶
measured_against(properties: FeatureProperties, partner: str, datum: Datum = NEUTRAL) -> FeatureProperties
Return properties marked relative to the feature named partner (FeatureMarkingSpec):
auto dimensioning prefers measuring between them. Two naming each other are a symmetric pair.
Source code in kumiki/csg/cutcsg.py
shoulder_override
¶
shoulder_override(key: FeatureKey, name: Optional[str] = None, group: FeatureGroup = NONE) -> FeatureOverride
Return an override marking the default at key as a shoulder plane, in group group.
Source code in kumiki/csg/cutcsg.py
has_face_plane_carrier
¶
has_face_plane_carrier(csg: HasFeatures, key: FeatureKey) -> bool
Return whether face_plane_carriers(csg) has key, locating only that one face.
Source code in kumiki/csg/cutcsg.py
face_plane_carriers
¶
face_plane_carriers(csg: HasFeatures) -> Dict[FeatureKey, Carrier]
Return a CarrierPlane per default face of csg with a plane, and per extra PlaneFeature, by key.
Not cached: a solve keeps the carriers it uses in its CarrierMap.
Source code in kumiki/csg/cutcsg.py
hit_of_kind
¶
hit_of_kind(hit: OwnedFeatureHit[CSGFeature], kind: Type[K]) -> Optional[OwnedFeatureHit[K]]
Return the hit typed as kind, or None if its feature isn't one.
point_tests_memo
¶
Context manager caching point tests (_POINT_TESTS) until the block ends.
Source code in kumiki/csg/cutcsg.py
csg_children
¶
Return the direct children of csg; empty for a primitive.
Source code in kumiki/csg/cutcsg.py
map_primitives
¶
map_primitives(csg: CutCSG, change: Callable[[HasFeatures], CutCSG]) -> CutCSG
Return csg with change applied to every feature-declaring primitive.
Source code in kumiki/csg/cutcsg.py
csg_children_with_parity
¶
csg_children_with_parity(csg: CutCSG, parity: CSGParity = ADDITIVE) -> List[Tuple[CutCSG, CSGParity]]
Return the children of csg with their parity, in csg_children order. Only a Difference's subtract children invert.
Source code in kumiki/csg/cutcsg.py
walk_csg_with_parity
¶
walk_csg_with_parity(root: CutCSG, parity: CSGParity = ADDITIVE) -> Iterator[Tuple[CutCSG, CSGParity]]
Yield every node under root, including it, with its parity.
Source code in kumiki/csg/cutcsg.py
carrier_map
¶
carrier_map(root: CutCSG) -> CarrierMap
Return the CarrierMap of every primitive carrier in the tree, coincident planes merged.
Source code in kumiki/csg/cutcsg.py
make_finite_rectangular_prism_from_half_space
¶
make_finite_rectangular_prism_from_half_space(half_space: HalfSpace, size_of_space: Numeric, depth_of_space: Numeric) -> RectangularPrism
Return a finite prism standing in for half_space near its boundary.
Its start face lies on the boundary plane, it runs depth_of_space into the half space, and its
square size_of_space cross-section is centred where the normal through the origin meets the plane.
Source code in kumiki/csg/cutcsg.py
assert_distinct_children
¶
assert_distinct_children(kind: str, children: Sequence[CutCSG]) -> None
Raise if the same CSG object appears twice among children.
Source code in kumiki/csg/cutcsg.py
translate_profile
¶
translate_profiles
¶
point_in_convex_profile
¶
Return whether a 2D point is inside a convex profile, wound either way.
Source code in kumiki/csg/cutcsg.py
decompose_simple_polygon_into_convex_pieces
¶
Split a simple polygon of (u, v) points into convex quads and triangles, by horizontal bands.
Within each band between vertex v's, the active edges' crossings are paired left to right (even-odd). pathcsg.decompose_path_into_convex_pieces is the same sweep over a FancyPath.
Source code in kumiki/csg/cutcsg.py
translate_csg
¶
Return a copy of csg translated by translation.
Source code in kumiki/csg/cutcsg.py
create_axis_aligned_timber
¶
create_axis_aligned_timber(bottom_position: V3, length: Numeric, size: V2, length_direction: TimberFace, width_direction: Optional[TimberFace] = None, ticket: Optional[Union[TimberTicket, str]] = None) -> Timber
Creates an axis-aligned timber using TimberFace to reference directions in the world coordinate system.
AGENT NOTE: Prefer methods like join_timber, attach_face/plane_aligned_timber, create_*_timber_on_footprint, which are more robust and easier to use.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bottom_position
|
V3
|
Position of the bottom point of the timber |
required |
length
|
Numeric
|
Length of the timber |
required |
size
|
V2
|
Cross-sectional size (width, height) |
required |
length_direction
|
TimberFace
|
Direction for the timber's length axis |
required |
width_direction
|
Optional[TimberFace]
|
Optional direction for the timber's width axis. If not provided, defaults to RIGHT (+X) unless length_direction is RIGHT, in which case TOP (+Z) is used. |
None
|
ticket
|
Optional[Union[TimberTicket, str]]
|
Optional ticket for this timber (can be Ticket object or string name, used for rendering/debugging) |
None
|
Returns:
| Type | Description |
|---|---|
Timber
|
New timber with the specified axis-aligned orientation |
Source code in kumiki/construction.py
create_vertical_timber_on_footprint_corner
¶
create_vertical_timber_on_footprint_corner(footprint: Footprint, corner_index: int, length: Numeric, location_type: FootprintLocation, size: V2, ticket: Optional[Union[TimberTicket, str]] = None) -> Timber
Creates a vertical timber (post) on a footprint boundary corner.
The post is positioned on an orthogonal boundary corner (where two boundary sides are perpendicular) according to the location type:
Location types: - INSIDE: Post has one vertex of bottom face on the boundary corner, with 2 edges aligned with the 2 boundary sides, post extends inside the boundary - OUTSIDE: Post positioned with opposite vertex on the boundary corner, extends outside - CENTER: Post center is on the boundary corner, with 2 edges parallel to boundary sides
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
footprint
|
Footprint
|
The footprint to place the timber on |
required |
corner_index
|
int
|
Index of the boundary corner |
required |
length
|
Numeric
|
Length of the vertical timber (height) |
required |
location_type
|
FootprintLocation
|
Where to position the timber relative to the boundary corner |
required |
size
|
V2
|
Timber size (width, depth) as a 2D vector |
required |
ticket
|
Optional[Union[TimberTicket, str]]
|
Optional ticket for this timber (can be Ticket object or string name, used for rendering/debugging) |
None
|
Returns:
| Type | Description |
|---|---|
Timber
|
Timber positioned vertically on the footprint boundary corner |
Source code in kumiki/construction.py
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 | |
create_vertical_timber_on_footprint_side
¶
create_vertical_timber_on_footprint_side(footprint: Footprint, side_index: int, distance_along_side: Numeric, length: Numeric, location_type: FootprintLocation, size: V2, ticket: Optional[Union[TimberTicket, str]] = None) -> Timber
Creates a vertical timber (post) positioned at a point along a footprint boundary side.
The post is placed at a specified distance along the boundary side from the starting corner.
Location types: - INSIDE: One edge of bottom face lies on boundary side, center of edge at the point, post extends inside - OUTSIDE: One edge of bottom face lies on boundary side, center of edge at the point, post extends outside - CENTER: Center of bottom face is on the point, 2 edges of bottom face parallel to boundary side
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
footprint
|
Footprint
|
The footprint to place the timber on |
required |
side_index
|
int
|
Index of the boundary side (from corner[side_index] to corner[side_index+1]) |
required |
distance_along_side
|
Numeric
|
Distance from the starting corner along the side (0 = at start corner) |
required |
length
|
Numeric
|
Length of the vertical timber (height) |
required |
location_type
|
FootprintLocation
|
Where to position the timber relative to the boundary side |
required |
size
|
V2
|
Timber size (width, depth) as a 2D vector |
required |
ticket
|
Optional[Union[TimberTicket, str]]
|
Optional ticket for this timber (can be Ticket object or string name, used for rendering/debugging) |
None
|
Returns:
| Type | Description |
|---|---|
Timber
|
Timber positioned vertically at the specified point on the footprint boundary side |
Source code in kumiki/construction.py
270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 | |
create_horizontal_timber_on_footprint
¶
create_horizontal_timber_on_footprint(footprint: Footprint, corner_index: int, location_type: FootprintLocation, size: V2, length: Optional[Numeric] = None, ticket: Optional[Union[TimberTicket, str]] = None) -> Timber
Creates a horizontal timber (mudsill) on the footprint boundary side.
The mudsill runs from corner_index to corner_index + 1 along the boundary side. With the face ends of the mudsill timber starting/ending on the footprint corners.
Location types: - INSIDE: One edge of the timber lies on the boundary side, timber is on the inside - OUTSIDE: One edge of the timber lies on the boundary side, timber is on the outside - CENTER: The centerline of the timber lies on the boundary side
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
footprint
|
Footprint
|
The footprint to place the timber on |
required |
corner_index
|
int
|
Index of the starting boundary corner |
required |
location_type
|
FootprintLocation
|
Where to position the timber relative to the boundary side |
required |
size
|
V2
|
Timber size (width, height) as a 2D vector |
required |
length
|
Optional[Numeric]
|
Length of the timber (optional; if not provided, uses boundary side length) |
None
|
ticket
|
Optional[Union[TimberTicket, str]]
|
Optional ticket for this timber (can be Ticket object or string name, used for rendering/debugging) |
None
|
Returns:
| Type | Description |
|---|---|
Timber
|
Timber positioned on the footprint boundary side |
Source code in kumiki/construction.py
stretch_timber
¶
stretch_timber(timber: Timber, end: TimberEnd, overlap_length: Numeric, extend_length: Numeric) -> Timber
Creates a new timber extending the original timber by a given length.
The original timber is conceptually discarded and replaced with a new timber that is the original timber plus the extension.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
end
|
TimberEnd
|
The end of the timber to extend |
required |
overlap_length
|
Numeric
|
Length of timber to overlap with existing timber |
required |
extend_length
|
Numeric
|
Length of timber to extend beyond the end of the original timber (does not include the overlap length) |
required |
Source code in kumiki/construction.py
split_timber
¶
split_timber(timber: Timber, distance_from_bottom: Numeric, ticket1: Optional[Union[TimberTicket, str]] = None, ticket2: Optional[Union[TimberTicket, str]] = None) -> Tuple[Timber, Timber]
Split a timber into two timbers at the specified distance from the bottom.
The original timber is conceptually discarded and replaced with two new timbers: - The first timber extends from the original bottom to the split point - The second timber extends from the split point to the original top
Both timbers maintain the same cross-sectional size and orientation as the original. You will often follow this with a splice joint to join the two timbers together.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
timber
|
Timber
|
The timber to split |
required |
distance_from_bottom
|
Numeric
|
Distance along the timber's length where to split (0 < distance < timber.length) |
required |
ticket1
|
Optional[Union[TimberTicket, str]]
|
Optional ticket for the bottom timber (defaults to "{original_name}_bottom") |
None
|
ticket2
|
Optional[Union[TimberTicket, str]]
|
Optional ticket for the top timber (defaults to "{original_name}_top") |
None
|
Returns:
| Type | Description |
|---|---|
Timber
|
Tuple of (bottom_timber, top_timber) where: |
Timber
|
|
Tuple[Timber, Timber]
|
|
Example
If a timber has length 10 and is split at distance 3: - bottom_timber has length 3, same origin as original - top_timber has length 7, origin at distance 3 from original origin
Source code in kumiki/construction.py
attach_timber
¶
attach_timber(original_timber: TimberLike, size: V2, attached_timber_direction: Direction3D, attached_timber_length: Numeric, attached_timber_opposite_length: Numeric = scalar(0), attached_timber_width_direction: Optional[Direction3D] = None, attached_timber_end_that_points_towards_original_timber: TimberEnd = BOTTOM, original_timber_end_to_measure_from_for_length_position: TimberEnd = BOTTOM, length_position_measurement: Numeric = scalar(0), lateral_offset: Numeric = scalar(0), ticket: Optional[Union[TimberTicket, str]] = None)
NOTE this function is perhaps not so useful in practice, it's mainly here for completeness. Perhaps there are some cases where it's a better alternative to create_timber
Creates a timber that is attached to original_timber.
The original timber is referred to as "original_timber" and the new timber as "attached_timber".
Positioning¶
The attached timber's attached_timber_end_that_points_towards_original_timber end position is length_position_measurement away from
original_timber_end_to_measure_from_for_length_position and then lateral_offset away from the centerline of the original timber,
measured in the direction
length axis of the original timber CROSS length axis of the created attached timber
Orientation¶
The attached timber's orientation is such that its length axis is in the attached_timber_direction direction
and its right face best aligns with attached_timber_right_direction.
if attached_timber_right_direction is None then the direction of the TOP face of the original timber is used instead.
Returns:
| Type | Description |
|---|---|
|
The new attached timber, face-aligned with and positioned relative to the original timber. |
Source code in kumiki/construction.py
537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 | |
attach_plane_aligned_timber
¶
attach_plane_aligned_timber(original_timber: TimberLike, size: V2, original_timber_long_face_that_attached_timber_points_to: TimberLongFace, attached_timber_angle: Numeric, attached_timber_length_or_target: Union[Numeric, TimberLike], attached_timber_stickout: Stickout = nostickout(), attached_timber_end_that_points_towards_original_timber: TimberEnd = BOTTOM, original_timber_end_to_measure_from_for_length_position: TimberEnd = BOTTOM, attached_timber_long_face_to_measure_to_for_length_position: Union[TimberLongFace, TimberCenterline] = CENTERLINE, length_position_measurement: Numeric = scalar(0), original_timber_face_to_measure_from_for_lateral_position: Union[TimberFace, TimberCenterline] = CENTERLINE, attached_timber_long_face_to_measure_to_for_lateral_position: Union[TimberLongFace, TimberCenterline] = CENTERLINE, lateral_position_measurement: Numeric = scalar(0), ticket: Optional[Union[TimberTicket, str]] = None) -> Timber
Creates a timber that is plane-aligned with and attached to original_timber at an angle.
Generalizes :func:attach_face_aligned_timber: the attached timber's length axis lies in the
plane spanned by the original timber's length axis and the normal of
original_timber_long_face_that_attached_timber_points_to (the face it points out of), making
an angle of attached_timber_angle with the original timber's length axis. The attached
timber stays plane-aligned with the original (two of its long faces remain parallel to the
original's lateral faces). attach_face_aligned_timber is the attached_timber_angle == pi/2
(perpendicular) case.
attached_timber_end_that_points_towards_original_timber chooses which end of the attached
timber sits on the original-timber side; note that this flips the realized angle to
pi - attached_timber_angle.
Extents¶
attached_timber_length_or_target places the target end (the end pointing away from the
original timber):
- a numeric length extends the timber along its (tilted) length axis, measured from the
original timber's centerline.
- a timber extends the attached timber until its centerline just touches the target timber's
reference feature selected by attached_timber_stickout.stickoutReference2, taken on the
target's silhouette projected onto the plane spanned by the original timber's length axis
and the attach direction: its CENTER_LINE, or the near (INSIDE) / far (OUTSIDE) boundary of
the silhouette (for a target plane-aligned with that plane these are its long faces; for a
rotated target, its projected corner edges). If the target's centerline is parallel to the
lateral axis it projects to a single point, which is dropped perpendicularly onto the
attached timber's length axis. stickout2 then extends the target end beyond that
feature. stickout2 is ignored (with a warning if set) when a numeric length is given
instead.
attached_timber_stickout places the start end (the end that attaches to the original
timber): the start end is where the attached timber's centerline just touches the original
timber's feature selected by stickoutReference1 — its CENTER_LINE (default), the INSIDE
face (the face the attached timber points out of), or the OUTSIDE face (the opposite face) —
extended by stickout1 beyond it.
Everything else follows attach_face_aligned_timber:
- the length-position is measured along the original timber's length axis from
original_timber_end_to_measure_from_for_length_position to
attached_timber_long_face_to_measure_to_for_length_position (or orthogonally to its centerline).
- the lateral-position is measured along the lateral axis from
original_timber_face_to_measure_from_for_lateral_position to
attached_timber_long_face_to_measure_to_for_lateral_position (or orthogonally to its centerline).
All measurements are taken from the perfect timber within of the original and attached timber.
original_timber_long_face_that_attached_timber_points_to chosen on the wrong side (e.g. the
target actually lies opposite the face's outward normal) would otherwise solve for a
non-positive attached timber length. Rather than failing in that case, this automatically
retries with the opposite long face (RIGHT<->LEFT or FRONT<->BACK) and, if that succeeds,
emits a warning and uses it instead. Only fails if BOTH the requested face and its opposite
produce a non-positive length.
Returns:
| Type | Description |
|---|---|
Timber
|
The new attached timber, plane-aligned with and positioned relative to the original timber. |
Source code in kumiki/construction.py
625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 | |
attach_face_aligned_timber
¶
attach_face_aligned_timber(original_timber: TimberLike, size: V2, original_timber_long_face_that_attached_timber_points_to: TimberLongFace, attached_timber_length_or_target: Union[Numeric, TimberLike], attached_timber_stickout: Stickout = nostickout(), attached_timber_end_that_points_towards_original_timber: TimberEnd = BOTTOM, original_timber_end_to_measure_from_for_length_position: TimberEnd = BOTTOM, attached_timber_long_face_to_measure_to_for_length_position: Union[TimberLongFace, TimberCenterline] = CENTERLINE, length_position_measurement: Numeric = scalar(0), original_timber_face_to_measure_from_for_lateral_position: Union[TimberFace, TimberCenterline] = CENTERLINE, attached_timber_long_face_to_measure_to_for_lateral_position: Union[TimberLongFace, TimberCenterline] = CENTERLINE, lateral_position_measurement: Numeric = scalar(0), ticket: Optional[Union[TimberTicket, str]] = None) -> Timber
Creates a timber that is face-aligned with and attached to original_timber.
The original timber is referred to as "original_timber" and the new timber as "attached_timber". The attached timber runs perpendicular to the chosen long face of the original timber and is fully face-aligned with it.
All measurements are taken from the perfect timber within of the original and attached timber
Orientation¶
The attached timber's length axis runs along the normal of
original_timber_long_face_that_attached_timber_points_to (the face it "points to" / sticks
out of). attached_timber_end_that_points_towards_original_timber chooses which end
(TOP/BOTTOM) of the attached timber sits on the original-timber side. If the chosen face turns
out to be on the wrong side of the target, this automatically retries with its opposite long
face instead of failing -- see :func:attach_plane_aligned_timber.
The attached timber's height and width axis orientation are determined by: - attached_timber_long_face_to_measure_to_for_lateral_position - attached_timber_long_face_to_measure_to_for_length_position so that these faces are parallel to the features on original_timber that they are measured from.
Extents¶
attached_timber_length_or_target places the far end of the attached timber: either a
numeric length measured from the original timber's centerline, or a timber to extend to (up
to the feature selected by attached_timber_stickout.stickoutReference2, plus
stickout2 beyond it). attached_timber_stickout places the near end relative to the
original timber's feature selected by stickoutReference1 (CENTER_LINE by default),
extended by stickout1 beyond it. See :func:attach_plane_aligned_timber for details.
Positioning¶
The attached timber's position is such that the distance between
original_timber_end_to_measure_from_for_length_position and attached_timber_long_face_to_measure_to_for_length_position
is length_position_measurement,
and the distance between
original_timber_face_to_measure_from_for_lateral_position and attached_timber_long_face_to_measure_to_for_lateral_position
is lateral_position_measurement.
Returns:
| Type | Description |
|---|---|
Timber
|
The new attached timber, face-aligned with and positioned relative to the original timber. |
Source code in kumiki/construction.py
951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 | |
join_timbers
¶
join_timbers(timber1: PerfectTimberWithin, timber2: PerfectTimberWithin, location_on_timber1: Numeric, location_on_timber2: Optional[Numeric] = None, lateral_offset: Numeric = scalar(0), stickout: Stickout = nostickout(), size: Optional[V2] = None, orientation_width_vector: Optional[Direction3D] = None, ticket: Optional[Union[TimberTicket, str]] = None) -> Timber
Joins two timbers by creating a connecting timber from centerline to centerline.
This function creates a timber that connects the centerline of timber1 to the centerline of timber2. The joining timber's length direction goes from timber1 to timber2, and its position can be laterally offset from this centerline-to-centerline path.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
timber1
|
PerfectTimberWithin
|
First timber to join (start point) |
required |
timber2
|
PerfectTimberWithin
|
Second timber to join (end point) |
required |
location_on_timber1
|
Numeric
|
Position along timber1's length where the joining timber starts |
required |
location_on_timber2
|
Optional[Numeric]
|
Optional position along timber2's length where the joining timber ends. If not provided, uses the same Z-height as location_on_timber1. |
None
|
lateral_offset
|
Numeric
|
Lateral offset of the joining timber perpendicular to the direct centerline-to-centerline path. The offset direction is determined by the cross product of timber1's length direction and the joining direction. Defaults to scalar(0) (no offset). |
scalar(0)
|
stickout
|
Stickout
|
How much the joining timber extends beyond each connection point (both sides). Always measured from centerlines in this function. Defaults to Stickout.nostickout() if not provided. |
nostickout()
|
size
|
Optional[V2]
|
Optional size (width, height) of the joining timber. If not provided, determined from timber1's size based on orientation. |
None
|
orientation_width_vector
|
Optional[Direction3D]
|
Optional width direction hint for the created timber in global space. Will be automatically projected onto the normal plane of the length axis of the created timber. This is useful for specifying orientation like "face up" for rafters. If not provided, uses timber1's length direction projected onto the perpendicular plane. If the provided vector is parallel to the joining direction, falls back to timber1's width direction. |
None
|
ticket
|
Optional[Union[TimberTicket, str]]
|
Optional ticket for this timber (can be Ticket object or string name, used for rendering/debugging) |
None
|
Returns:
| Type | Description |
|---|---|
Timber
|
New timber connecting timber1 and timber2 along their centerlines |
Source code in kumiki/construction.py
1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 | |
join_plane_aligned_on_plane_aligned_timbers
¶
join_plane_aligned_on_plane_aligned_timbers(timber1: PerfectTimberWithin, timber2: PerfectTimberWithin, location_on_timber1: Numeric, location_on_timber2: Numeric, stickout: Stickout, size: V2, lateral_offset_from_timber1: Numeric = scalar(0), feature_to_mark_on_joining_timber: Optional[TimberFeature] = None, orientation_long_face_on_timber1: Optional[TimberLongFace] = None, orientation_long_face_on_timber2: Optional[TimberLongFace] = RIGHT, ticket: Optional[Union[TimberTicket, str]] = None) -> Timber
Joins two plane-aligned timbers with a connecting timber that lies in their shared plane.
Deprecated: This function's own signature is the main reason for the deprecation --
the lateral-offset/feature-marking/orientation-long-face parameters are hard to reason
about together. attach_plane_aligned_timber covers the same cases with a signature
that, while still not simple, is a bit easier to follow. Prefer it for new code.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
timber1
|
PerfectTimberWithin
|
First timber to join |
required |
timber2
|
PerfectTimberWithin
|
Second timber to join (plane-aligned with timber1) |
required |
location_on_timber1
|
Numeric
|
Position along timber1's length where the joining timber attaches |
required |
location_on_timber2
|
Numeric
|
Position along timber2's length where the joining timber attaches |
required |
stickout
|
Stickout
|
How much the joining timber extends beyond each connection point |
required |
size
|
V2
|
Cross-sectional size (width, height) of the joining timber |
required |
lateral_offset_from_timber1
|
Numeric
|
Lateral offset, in the axis perpendicular to the shared plane, from feature_to_mark_on_joining_timber. Defaults to scalar(0). |
scalar(0)
|
feature_to_mark_on_joining_timber
|
Optional[TimberFeature]
|
Optional feature on the created timber to use as the reference for the lateral offset. It is intended for you to use the locate_face or locate_long_edge functions to create a plane or line on a timber. If not provided, uses the centerline. If a plane is provided, the "origin" of the plane is used for longitudinal positioning (i.e. location_on_timber1). In the case of locate_face, the origin aligns with the center of the created timber. |
None
|
orientation_long_face_on_timber1
|
Optional[TimberLongFace]
|
Optional long face of timber1, on the shared plane, to orient against. If None, an arbitrary face of timber1 on the shared plane is used. |
None
|
orientation_long_face_on_timber2
|
Optional[TimberLongFace]
|
The long face on the created timber that will align with orientation_long_face_on_timber1. Defaults to TimberLongFace.RIGHT. |
RIGHT
|
ticket
|
Optional[Union[TimberTicket, str]]
|
Optional ticket for this timber (can be Ticket object or string name, used for rendering/debugging) |
None
|
Returns:
| Type | Description |
|---|---|
Timber
|
New timber that joins timber1 and timber2, lying in their shared plane |
Source code in kumiki/construction.py
1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 | |
join_face_aligned_on_face_aligned_timbers
¶
join_face_aligned_on_face_aligned_timbers(timber1: PerfectTimberWithin, timber2: PerfectTimberWithin, location_on_timber1: Numeric, stickout: Stickout, size: V2, lateral_offset_from_timber1: Numeric = scalar(0), feature_to_mark_on_joining_timber: Optional[TimberFeature] = None, orientation_face_on_timber1: Optional[TimberFace] = None, ticket: Optional[Union[TimberTicket, str]] = None) -> Timber
Joins two face-aligned timbers with a perpendicular timber.
Deprecated: This function's own signature is the main reason for the deprecation --
the lateral-offset/feature-marking/orientation-face parameters are hard to reason
about together. attach_face_aligned_timber covers the same cases with a signature
that, while still not simple, is a bit easier to follow. Prefer it for new code.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
timber1
|
PerfectTimberWithin
|
First timber to join |
required |
timber2
|
PerfectTimberWithin
|
Second timber to join (face-aligned with timber1) |
required |
location_on_timber1
|
Numeric
|
Position along timber1's length where the joining timber attaches |
required |
stickout
|
Stickout
|
How much the joining timber extends beyond each connection point |
required |
size
|
V2
|
Cross-sectional size (width, height) of the joining timber |
required |
lateral_offset_from_timber1
|
Numeric
|
Lateral offset from timber1's centerline reference. Defaults to scalar(0). |
scalar(0)
|
feature_to_mark_on_joining_timber
|
Optional[TimberFeature]
|
Optional feature on the create timber to use as the reference for the lateral offset. It is intended for you to use the locate_face or locate_long_edge functions to create a plane or line on a timber. If not provided, uses the centerline. If a plane is provided, the "origin" of the plane is used for longitudinal positioning (i.e. location_on_timber1). In the case of locate_face, the origin aligns with the center of the created timber. |
None
|
orientation_face_on_timber1
|
Optional[TimberFace]
|
Optional face of timber1 to orient against. If provided, the width direction of the created timber will align with this face on timber1. If not provided, uses timber1's length direction projected onto the perpendicular plane. |
None
|
ticket
|
Optional[Union[TimberTicket, str]]
|
Optional ticket for this timber (can be Ticket object or string name, used for rendering/debugging) |
None
|
Returns:
| Type | Description |
|---|---|
Timber
|
New timber that joins timber1 and timber2 |
Source code in kumiki/construction.py
1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 | |
closest_stations
¶
closest_stations(a: Optional[Line], b: Optional[Line], eps: Optional[float] = None) -> Optional[Tuple[float, float]]
How far along each line the two come nearest each other.
Two stations, one per line, each a signed distance from that line's point along its direction. None when either argument is missing or the lines are parallel, which includes the same line twice -- parallel lines come nearest everywhere at once, so no one pair of stations describes it.
Source code in kumiki/geometry.py
get_point_on_feature
¶
get_point_on_feature(feature: Union[UnsignedPlane, Plane, Line, Point], timber: PerfectTimberWithin) -> V3
Get a point on a feature.
Source code in kumiki/measuring.py
locate_face
¶
locate_face(timber: PerfectTimberWithin, face: SomeTimberFace) -> Plane
Measure a face on a timber, returning a Plane centered on the face pointing outward.
The plane's normal points OUT of the timber (away from the timber's interior), and the plane's point is positioned at the center of the face surface.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
timber
|
PerfectTimberWithin
|
The timber to measure |
required |
face
|
SomeTimberFace
|
The face to measure |
required |
Returns:
| Type | Description |
|---|---|
Plane
|
Plane with normal pointing outward from the face and point at the face center |
Example
plane = locate_face(timber, TimberFace.RIGHT)
plane.normal points in +X direction (outward from RIGHT face)¶
plane.point is at the center of the RIGHT face surface¶
Source code in kumiki/measuring.py
locate_edge
¶
locate_edge(timber: PerfectTimberWithin, edge: EdgeOrCenterline) -> Line
Measure any edge or centerline on a timber, returning a Line along it.
For TimberCenterline.CENTERLINE: direction = timber length direction, point at mid-length center. For TimberEdge values: uses canonical_line_from_corner to get the starting corner and direction face, then computes the global position and direction.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
timber
|
PerfectTimberWithin
|
The timber to measure |
required |
edge
|
EdgeOrCenterline
|
Which edge or centerline to measure |
required |
Returns:
| Type | Description |
|---|---|
Line
|
Line representing the edge in global coordinates |
Source code in kumiki/measuring.py
locate_long_edge
¶
locate_long_edge(timber: PerfectTimberWithin, edge: TimberLongEdge) -> Line
Measure a long edge on a timber. Thin wrapper around locate_edge.
locate_short_edge
¶
locate_short_edge(timber: PerfectTimberWithin, edge: TimberShortEdge) -> Line
Measure a short edge on a timber. Thin wrapper around locate_edge.
locate_long_face_centerline
¶
locate_long_face_centerline(timber: PerfectTimberWithin, centerline: TimberLongFaceCenterline) -> Line
Measure the centerline of one long face, returning a Line along it.
The line runs the timber's length down the middle of that face, which is the face's center point carried along the length direction.
Kept out of locate_edge and out of EdgeOrCenterline on purpose. The marking functions that take an EdgeOrCenterline -- see mark_distance_from_corner_along_edge_by_intersecting_plane -- measure from a CORNER along the line, and a face's centerline passes through no corner, so widening that union would offer an answer that cannot be given.
Source code in kumiki/measuring.py
locate_center_plane
¶
locate_center_plane(timber: PerfectTimberWithin, plane: TimberCenterplane) -> UnsignedPlane
Measure a center plane on a timber, returning the plane it lies in.
Unsigned, because a bisector has no outward side: the two long faces it sits between are equally far away in either direction, so a signed normal would be picking one arbitrarily and inviting a caller to read meaning into the choice.
Source code in kumiki/measuring.py
locate_position_on_centerline_from_bottom
¶
locate_position_on_centerline_from_bottom(timber: PerfectTimberWithin, distance: Numeric) -> Point
Measure a position at a specific point along the timber's centerline, measured from the bottom.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
timber
|
PerfectTimberWithin
|
The timber to measure on |
required |
distance
|
Numeric
|
Distance along the timber's length direction from the bottom position |
required |
Returns:
| Type | Description |
|---|---|
Point
|
Point on the timber's centerline at the specified distance from bottom |
Source code in kumiki/measuring.py
locate_position_on_centerline_from_top
¶
locate_position_on_centerline_from_top(timber: PerfectTimberWithin, distance: Numeric) -> Point
Measure a position at a specific point along the timber's centerline, measured from the top.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
timber
|
PerfectTimberWithin
|
The timber to measure on |
required |
distance
|
Numeric
|
Distance along the timber's length direction from the top position |
required |
Returns:
| Type | Description |
|---|---|
Point
|
Point on the timber's centerline at the specified distance from top |
Source code in kumiki/measuring.py
locate_bottom_center_position
¶
locate_bottom_center_position(timber: PerfectTimberWithin) -> Point
Measure the position of the center of the bottom cross-section of the timber.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
timber
|
PerfectTimberWithin
|
The timber to measure on |
required |
Returns:
| Type | Description |
|---|---|
Point
|
Point at the center of the bottom cross-section |
Source code in kumiki/measuring.py
locate_top_center_position
¶
locate_top_center_position(timber: PerfectTimberWithin) -> Point
Measure the position of the center of the top cross-section of the timber.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
timber
|
PerfectTimberWithin
|
The timber to measure on |
required |
Returns:
| Type | Description |
|---|---|
Point
|
Point at the center of the top cross-section |
Source code in kumiki/measuring.py
locate_into_face
¶
locate_into_face(distance: Numeric, face: SomeTimberFace, timber: PerfectTimberWithin) -> UnsignedPlane
Measure a distance from a face on a timber.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
distance
|
Numeric
|
How far into the timber, along the face's inward normal, to place the plane |
required |
face
|
SomeTimberFace
|
The face to measure from (can be TimberFace, TimberEnd, or TimberLongFace) |
required |
timber
|
PerfectTimberWithin
|
The timber to measure on |
required |
Returns:
| Type | Description |
|---|---|
UnsignedPlane
|
UnsignedPlane parallel to the face, offset |
UnsignedPlane
|
normal. This is the inverse of mark_distance_from_face_in_normal_direction: if |
UnsignedPlane
|
feature = locate_into_face(d, face, timber), then |
UnsignedPlane
|
mark_distance_from_face_in_normal_direction(feature, timber, face).distance == d |
Source code in kumiki/measuring.py
locate_plane_from_centerline_in_direction
¶
locate_plane_from_centerline_in_direction(timber: PerfectTimberWithin, direction: Direction3D) -> Plane
mark_distance_from_face_in_normal_direction
¶
mark_distance_from_face_in_normal_direction(feature: Union[UnsignedPlane, Plane, Line, Point], timber: PerfectTimberWithin, face: SomeTimberFace) -> DistanceFromFace
Mark a feature onto a face on a timber.
Returns a DistanceFromFace measurement representing the distance from the face to the feature, measured INTO the timber. Positive means the feature is inside the timber (deeper than the face surface). Negative means the feature is outside the timber (shallower than the face surface).
This is the inverse of locate_into_face: If feature = locate_into_face(d, face, timber), then mark_distance_from_face_in_normal_direction(feature, timber, face).distance = d
Source code in kumiki/measuring.py
mark_distance_from_corner_along_edge_by_intersecting_plane
¶
mark_distance_from_corner_along_edge_by_intersecting_plane(plane: Union[UnsignedPlane, Plane], timber: PerfectTimberWithin, edge: Union[TimberLongEdge, TimberShortEdge, EdgeOrCenterline], end: TimberEnd) -> DistanceFromCornerAlongEdge
Mark onto an edge by intersecting a plane, returning a DistanceFromCornerAlongEdge.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
plane
|
Union[UnsignedPlane, Plane]
|
the plane to intersect with |
required |
timber
|
PerfectTimberWithin
|
the timber whose edge we're intersecting |
required |
edge
|
Union[TimberLongEdge, TimberShortEdge, EdgeOrCenterline]
|
the edge to intersect with (TimberLongEdge, TimberShortEdge, TimberEdge, or TimberCenterline) |
required |
end
|
TimberEnd
|
the end of the timber to mark from |
required |
Returns:
| Type | Description |
|---|---|
DistanceFromCornerAlongEdge
|
DistanceFromCornerAlongEdge with the signed distance from the end to the |
DistanceFromCornerAlongEdge
|
intersection. Positive means into the timber from the end. |
Source code in kumiki/measuring.py
mark_distance_from_corner_along_edge_by_finding_closest_point_on_line
¶
mark_distance_from_corner_along_edge_by_finding_closest_point_on_line(line: Line, timber: PerfectTimberWithin, edge: Union[TimberLongEdge, TimberShortEdge, EdgeOrCenterline], end: TimberEnd) -> DistanceFromCornerAlongEdge
Mark onto an edge by finding the closest point to a line, returning a DistanceFromCornerAlongEdge.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
line
|
Line
|
The line feature to mark from |
required |
timber
|
PerfectTimberWithin
|
The timber whose edge we're marking to |
required |
edge
|
Union[TimberLongEdge, TimberShortEdge, EdgeOrCenterline]
|
The edge to mark to (TimberLongEdge, TimberShortEdge, TimberEdge, or TimberCenterline) |
required |
end
|
TimberEnd
|
Which end of the timber to mark from |
required |
Returns:
| Type | Description |
|---|---|
DistanceFromCornerAlongEdge
|
DistanceFromCornerAlongEdge with the signed distance from the end to the closest point. |
Source code in kumiki/measuring.py
mark_distance_from_end_along_centerline
¶
mark_distance_from_end_along_centerline(feature: Union[UnsignedPlane, Plane, Line, Point], timber: PerfectTimberWithin, end: TimberEnd = BOTTOM) -> DistanceFromPointIntoFace
Mark a feature onto the centerline of a timber.
Returns a DistanceFromPointIntoFace measurement representing the distance from the specified end of the timber to the intersection/closest point on the centerline.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
feature
|
Union[UnsignedPlane, Plane, Line, Point]
|
The feature to mark (Plane, Line, Point, etc.) |
required |
timber
|
PerfectTimberWithin
|
The timber whose centerline we're marking to |
required |
end
|
TimberEnd
|
Which end of the timber to mark from (defaults to BOTTOM) |
BOTTOM
|
Returns:
| Type | Description |
|---|---|
DistanceFromPointIntoFace
|
DistanceFromPointIntoFace with distance from the specified end to where the feature intersects/is closest |
DistanceFromPointIntoFace
|
to the centerline. Positive means into the timber from the end. The point is set to the end's |
DistanceFromPointIntoFace
|
centerline position. |
Source code in kumiki/measuring.py
mark_plane_from_edge_in_direction
¶
mark_plane_from_edge_in_direction(plane: Union[UnsignedPlane, Plane], timber: PerfectTimberWithin, edge: EdgeOrCenterline) -> PlaneFromEdgeInDirection
Mark a plane onto a timber edge, returning the direction and signed distance from the edge to the plane.
This is the inverse of locate_plane_from_edge_in_direction: if p = locate_plane_from_edge_in_direction(timber, edge, dir, dist), then mark_plane_from_edge_in_direction(p, timber, edge) returns (dir, dist).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
plane
|
Union[UnsignedPlane, Plane]
|
The plane to mark (its normal becomes the direction) |
required |
timber
|
PerfectTimberWithin
|
The timber whose edge we're measuring from |
required |
edge
|
EdgeOrCenterline
|
Which edge to measure from |
required |
Source code in kumiki/measuring.py
get_rough_support_distance_from_centerline
¶
get_rough_support_distance_from_centerline(timber: PerfectTimberWithin, direction: V2) -> Numeric
distance from cross-section centerline to support plane of the actual timber dimensions in direction
Source code in kumiki/timber_shavings.py
get_rough_support_distance
¶
get_rough_support_distance(timber: PerfectTimberWithin, position_from_bottom: V3, direction: V3) -> Numeric
distance from a 3D local position to support plane of the actual timber dimensions in direction
Source code in kumiki/timber_shavings.py
get_perfect_support_distance
¶
get_perfect_support_distance(timber: PerfectTimberWithin, position_from_bottom: V3, direction: V3) -> Numeric
distance from a 3D local position to support plane of the perfect timber dimensions in direction
Source code in kumiki/timber_shavings.py
find_opposing_face_on_another_timber
¶
find_opposing_face_on_another_timber(reference_timber: PerfectTimberWithin, reference_face: TimberLongFace, target_timber: PerfectTimberWithin) -> TimberFace
Find the opposing face on another timber. Assumes that the target_timber has a face parallel to the reference face on the reference_timber.
Source code in kumiki/timber_shavings.py
create_peg_going_into_face
¶
create_peg_going_into_face(timber: Timber, face: TimberLongFace, distance_from_bottom: Numeric, distance_from_centerline: Numeric, peg_size: Numeric, peg_shape: PegShape, forward_length: Numeric, stickout_length: Numeric) -> Peg
Create a peg that goes into a specified long face of a timber.
The peg is created in the local space of the timber, with the insertion end at the timber's surface and pointing inward perpendicular to the face.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
timber
|
Timber
|
The timber to insert the peg into |
required |
face
|
TimberLongFace
|
Which long face the peg enters from (RIGHT, LEFT, FRONT, or BACK) |
required |
distance_from_bottom
|
Numeric
|
Distance along the timber's length from the bottom end |
required |
distance_from_centerline
|
Numeric
|
Distance from the timber's centerline along the face |
required |
peg_size
|
Numeric
|
Size/diameter of the peg (for square pegs, this is the side length) |
required |
peg_shape
|
PegShape
|
Shape of the peg (SQUARE or ROUND) |
required |
forward_length
|
Numeric
|
How far the peg reaches in the forward direction |
required |
stickout_length
|
Numeric
|
How far the peg sticks out in the back direction |
required |
Returns:
| Type | Description |
|---|---|
Peg
|
Peg object positioned and oriented appropriately in timber's local space |
Source code in kumiki/timber_shavings.py
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 | |
create_wedge_in_timber_end
¶
create_wedge_in_timber_end(timber: Timber, end: TimberEnd, position: V3, shape: WedgeShape) -> Wedge
Create a wedge at the end of a timber.
The wedge is created in the local space of the timber. In identity orientation, the point of the wedge goes in the length direction (Z-axis in local space).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
timber
|
Timber
|
The timber to insert the wedge into |
required |
end
|
TimberEnd
|
Which end of the timber (TOP or BOTTOM) |
required |
position
|
V3
|
Position in the timber's cross-section (X, Y in local space, Z ignored) |
required |
shape
|
WedgeShape
|
Specification of wedge dimensions |
required |
Returns:
| Type | Description |
|---|---|
Wedge
|
Wedge object positioned and oriented appropriately in timber's local space |
Source code in kumiki/timber_shavings.py
are_timbers_parallel
¶
are_timbers_parallel(timber1: PerfectTimberWithin, timber2: PerfectTimberWithin, tolerance: Optional[Numeric] = None) -> bool
Check if two timbers have parallel length directions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
timber1
|
PerfectTimberWithin
|
First timber |
required |
timber2
|
PerfectTimberWithin
|
Second timber |
required |
tolerance
|
Optional[Numeric]
|
Optional tolerance for approximate comparison. If None, attempts exact comparison and uses default epsilon if not possible. |
None
|
Returns:
| Type | Description |
|---|---|
bool
|
True if timbers have parallel length directions, False otherwise |
Source code in kumiki/timber_shavings.py
are_timbers_orthogonal
¶
are_timbers_orthogonal(timber1: PerfectTimberWithin, timber2: PerfectTimberWithin, tolerance: Optional[Numeric] = None) -> bool
Check if two timbers have orthogonal (perpendicular) length directions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
timber1
|
PerfectTimberWithin
|
First timber |
required |
timber2
|
PerfectTimberWithin
|
Second timber |
required |
tolerance
|
Optional[Numeric]
|
Optional tolerance for approximate comparison. If None, automatically uses exact comparison for rational values or fuzzy comparison for floats. |
None
|
Returns:
| Type | Description |
|---|---|
bool
|
True if timbers have orthogonal length directions, False otherwise |
Source code in kumiki/timber_shavings.py
are_timbers_face_aligned
¶
are_timbers_face_aligned(timber1: PerfectTimberWithin, timber2: PerfectTimberWithin, tolerance: Optional[Numeric] = None) -> bool
Check if two timbers are face-aligned.
Two timbers are face-aligned if any face of one timber is parallel to any face of the other timber. This occurs when their orientations are related by 90-degree rotations around any axis (i.e., they share the same coordinate grid alignment).
Mathematically, timbers are face-aligned if any of their orthogonal direction vectors (length_direction, width_direction, height_direction) are parallel to each other.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
timber1
|
PerfectTimberWithin
|
First timber |
required |
timber2
|
PerfectTimberWithin
|
Second timber |
required |
tolerance
|
Optional[Numeric]
|
Optional numerical tolerance for parallel check. If None, uses exact equality. If provided, uses approximate floating-point comparison. |
None
|
Returns:
| Type | Description |
|---|---|
bool
|
True if timbers are face-aligned, False otherwise |
Source code in kumiki/timber_shavings.py
do_xy_cross_section_on_parallel_timbers_overlap
¶
do_xy_cross_section_on_parallel_timbers_overlap(timberA: PerfectTimberWithin, timberB: PerfectTimberWithin) -> bool
Check if the cross-section of two parallel timbers overlap.
Converts timberB into timberA's local space and checks if the XY cross-sections (defined by bottom_position and size) overlap.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
timberA
|
PerfectTimberWithin
|
First timber |
required |
timberB
|
PerfectTimberWithin
|
Second timber |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if the cross-sections overlap, False otherwise |
Source code in kumiki/timber_shavings.py
locate_mortise_timber_shoulder_plane_from_centerline_towards_tenon_timber
¶
locate_mortise_timber_shoulder_plane_from_centerline_towards_tenon_timber(arrangement: ButtJointTimberArrangement, distance_from_centerline: Numeric) -> Plane
Computes the shoulder plane of the mortise timber, offset from its centerline toward the tenon.
The shoulder plane is parallel to the mortise timber's length axis and offset from the mortise centerline in the mortise cross-section toward the tenon. Its reference point is chosen using the tenon centerline relation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
arrangement
|
ButtJointTimberArrangement
|
Butt joint arrangement (receiving_timber = mortise, butt_timber = tenon). |
required |
distance_from_centerline
|
Numeric
|
Signed offset from the mortise centerline toward the tenon. 0 = plane through the mortise centerline. Positive = toward tenon. |
required |
Returns:
| Type | Description |
|---|---|
Plane
|
Plane parallel to the mortise length axis, offset by distance_from_centerline |
Plane
|
from the mortise centerline toward the tenon. |
Source code in kumiki/joints/workshop/shavings/build_a_butt.py
resolve_parallel_shoulder_face
¶
resolve_parallel_shoulder_face(arrangement: ButtJointTimberArrangement, set_mortise_shoulder_parallel_to_face: Union[TimberLongFace, bool]) -> TimberLongFace
Resolves the parallel shoulder face. If set_mortise_shoulder_parallel_to_face is True, it auto-detects the long face most perpendicular to the tenon axis. Otherwise, returns it directly.
Source code in kumiki/joints/workshop/shavings/build_a_butt.py
locate_mortise_timber_shoulder_plane_from_centerplane_towards_long_face
¶
locate_mortise_timber_shoulder_plane_from_centerplane_towards_long_face(arrangement: ButtJointTimberArrangement, distance_from_centerplane: Numeric, face: TimberLongFace) -> Plane
Computes a shoulder plane that is forced to be parallel to a specific face of the mortise timber.
Source code in kumiki/joints/workshop/shavings/build_a_butt.py
compute_butt_joint_shoulder
¶
compute_butt_joint_shoulder(arrangement: ButtJointTimberArrangement, distance_from_centerline_or_centerplane: Numeric, up_direction: Direction3D, set_mortise_shoulder_parallel_to_face: Union[TimberLongFace, bool] = False) -> ButtJointShoulderResult
Compute the shoulder plane and an oriented marking space for a butt joint.
The marking space is positioned where the tenon (butt) timber's centerline intersects the shoulder plane, oriented with: +X = shoulder_plane.normal (from mortise centerline toward tenon) +Y = up_direction (orthogonalized against +X) +Z = right-hand rule cross product
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
arrangement
|
ButtJointTimberArrangement
|
Butt joint arrangement (receiving_timber = mortise, butt_timber = tenon). |
required |
distance_from_centerline_or_centerplane
|
Numeric
|
Signed offset from the mortise centerline toward the tenon. 0 = plane through the mortise centerline. Positive = toward tenon. |
required |
up_direction
|
Direction3D
|
Direction for +Y axis of the marking space. Will be orthogonalized against the shoulder plane normal. |
required |
set_mortise_shoulder_parallel_to_face
|
Union[TimberLongFace, bool]
|
Force shoulder plane parallel to a face. |
False
|
Returns:
| Type | Description |
|---|---|
ButtJointShoulderResult
|
ButtJointShoulderResult with the shoulder plane, intersection point, and marking space. |
Source code in kumiki/joints/workshop/shavings/build_a_butt.py
build_dovetail_shoulder_geometery
¶
build_dovetail_shoulder_geometery(arrangement: ButtJointTimberArrangement, shoulder_result: ButtJointShoulderResult, dovetail_depth: Numeric) -> CutCSG
Creates the shoulder geometry for a dovetail shoulder. The height of the dovetail is determined by the dimensions of the receiving timber. The depth of the dovetail is determined by the dovetail_depth parameter.
|
__| v | \ | \ | _\ | ^ | ^ | | | dovetail_depth | dovetail_pointy_face_on_butt_timber
The resulting CutCSG object is in global space. It includes part of the butt timber itself, not just the dovetail shape. The resulting CutCSG object includes part of the butt timber itself, not just the dovetail shape. This is useful for cutting notches into the receiving timber for non perfect receiving timbers.
Source code in kumiki/joints/workshop/shavings/build_a_butt.py
dovetail_tenon_geometry
¶
dovetail_tenon_geometry(arrangement: ButtJointTimberArrangement, shoulder_result: ButtJointShoulderResult, dovetail_top_side_on_butt_timber: TimberLongFace, tenon_size: V2, tenon_depth: Numeric, dovetail_depth: Numeric, wedge_accessory_parameters: DovetailTenonWedgeAccessoryParameters, tenon_lateral_offset: Numeric = 0, receiving_timber_mortise_extra_depth: Numeric = 0, tenon_waste_label: CutCSGLabel = CutCSGLabel('tenon_waste'), mortise_label: CutCSGLabel = CutCSGLabel('mortise')) -> DovetailTenonGeometeryResult
Build the tenon geometry for a dovetail shoulder. The "top" of dovetail tenon is always flush with dovetail_top_side_on_butt_timber face of the butt timber, however x/y sizing still aligns with the usual width/height axis of the butt timber. tenon_lateral_offset is always in the perpendicular axis of the joint on the tenon timber. When 0, the tenon is laterally centered on the the butt timber.
dovetail_top_side_on_butt_timber
v
|
|
|\ | < dovetail_depth
| \| <
_| ^^^ tenondepth
Source code in kumiki/joints/workshop/shavings/build_a_butt.py
450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 | |
tusk_tenon_geometry
¶
tusk_tenon_geometry(arrangement: ButtJointTimberArrangement, opposite_shoulder_position_global: V3, tenon_length_direction: Direction3D, entry_face_designation: TimberLongFace, entry_axis_extent: Numeric, tusk_parameters: Any, rough_half_extent_past_opposite_shoulder: Numeric, tusk_hole_label: CutCSGLabel = CutCSGLabel('tusk_hole'), tusk_clearance_label: CutCSGLabel = CutCSGLabel('tusk_clearance')) -> TuskTenonGeometryResult
Build the crosswise locking key ("tusk") for a through mortise-and-tenon joint, the hole cut through the tenon for it, and (if needed) extra clearance cut into the receiving timber so the key can be slid crosswise into place.
Mirrors dovetail_tenon_geometry's wedge: the key is a tapered prism, driven crosswise (perpendicular to the tenon's own length, through one of the tenon's long faces -- entry_face_designation) through a hole in the through-tenon, positioned at the "opposite shoulder" reference. Like the wedge, it is asymmetric -- one side (Y=0) is flush with the opposite shoulder plane (the receiving timber's exit face), and the other side tapers (per tusk_angle) into the tenon's own body. The key's taper runs along the tenon's own length axis (the narrow, leading edge sits deepest, at the far side of the tenon's own cross-section; the taper continues into the back/tip stickout regions), so driving it in wedges the tenon's shoulder tight against the receiving timber's exit face. It is centered on the tenon's other cross axis (tusk_thickness).
entry_face_designation
v
| _ | _ |
|---|---|
| ___ | |
| / ___ <- tapers down to tusk_small_width at X=entry_axis_extent | |
| /_____ <- flush at Y=0 (opposite shoulder plane / receiving timber exit face) | |
| tusk_back_ tusk_tip_ | |
| stickout stickout | |
| <-- entry_axis_extent --> |
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
arrangement
|
ButtJointTimberArrangement
|
Butt joint arrangement (butt_timber = tenon, receiving_timber = mortise). |
required |
opposite_shoulder_position_global
|
V3
|
Center of the tenon's cross-section at the "opposite shoulder" reference position (where the tusk hole is centered along the tenon's length axis). |
required |
tenon_length_direction
|
Direction3D
|
Direction from the entry shoulder toward the tenon's tip. |
required |
entry_face_designation
|
TimberLongFace
|
Which of the tenon's long faces the tusk is driven in from. |
required |
entry_axis_extent
|
Numeric
|
The tenon's own cross-sectional size along the entry axis. |
required |
tusk_parameters
|
Any
|
Tusk shape parameters. |
required |
rough_half_extent_past_opposite_shoulder
|
Numeric
|
How far the receiving timber's rough stock extends past the "opposite shoulder" reference, measured along tenon_length_direction. <= 0 means no rough excess there (no clearance needed). |
required |
Returns:
| Type | Description |
|---|---|
TuskTenonGeometryResult
|
TuskTenonGeometryResult with the tenon hole, optional mortise clearance, and the tusk |
TuskTenonGeometryResult
|
accessory, all in global space. |
Source code in kumiki/joints/workshop/shavings/build_a_butt.py
815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 | |
compute_peg_positions
¶
compute_peg_positions(arrangement: ButtJointTimberArrangement, shoulder_plane: Plane, peg_parameters: SimplePegParameters, tenon_position: V2) -> List[PegPositionResult]
Compute peg positions in global space for a mortise and tenon joint.
Uses the arrangement's front_face_on_butt_timber as the peg face on the tenon. All computations are done in global space, using the measure/mark pattern where possible.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
arrangement
|
ButtJointTimberArrangement
|
Butt joint arrangement (butt_timber = tenon, receiving_timber = mortise). Must have front_face_on_butt_timber set. |
required |
shoulder_plane
|
Plane
|
The shoulder plane in global space (from _compute_plane_parallel_to_receiving_length_axis_partially_perpendicular_to_butt). |
required |
peg_parameters
|
SimplePegParameters
|
Peg configuration (shape, positions, size, depth, offset). |
required |
tenon_position
|
V2
|
Offset of tenon center from timber centerline in tenon local cross-section (X, Y). |
required |
Returns:
| Type | Description |
|---|---|
List[PegPositionResult]
|
List of PegPositionResult, one per peg_position entry. |
Source code in kumiki/joints/workshop/shavings/build_a_butt.py
1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 | |
convert_mortise_shoulder_inset_to_centerline_distance
¶
convert_mortise_shoulder_inset_to_centerline_distance(mortise_shoulder_inset: Numeric, mortise_face: TimberFace, receiving_timber: TimberLike) -> Numeric
Convert user-facing mortise shoulder inset parameter to centerline-relative distance.
Inset is measured from the mortise entry face surface toward the centerline (inward). This function converts it to the signed distance from centerline (measured toward the tenon).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mortise_shoulder_inset
|
Numeric
|
Distance from mortise entry face inward. 0 = shoulder flush with the entry face. Positive = shoulder deeper into the timber. |
required |
mortise_face
|
TimberFace
|
The face of the receiving timber where the mortise enters. |
required |
receiving_timber
|
TimberLike
|
The receiving timber. |
required |
Returns:
| Type | Description |
|---|---|
Numeric
|
Signed distance from the timber centerline to the shoulder plane, measured toward |
Numeric
|
the tenon side. 0 = shoulder at centerline. |
Source code in kumiki/joints/workshop/shavings/build_a_butt.py
scalar
¶
Create a float scalar value.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
numerator
|
The numerator (can be int, float, or str) |
required | |
denominator
|
The denominator (default=1) |
1
|
Returns:
| Type | Description |
|---|---|
float
|
float value |
Examples:
scalar(3) # 3.0 scalar(1, 2) # 0.5 scalar(2.5) # 2.5 scalar("1.5") # 1.5 from string scalar("1/32") # Parses fraction string
Source code in kumiki/rule.py
sqrt
¶
Source code in kumiki/rule.py
simplify
¶
No-op: floats need no symbolic simplification. Kept so old call sites
(mostly simplify(a - b) == 0-style exactness checks) still parse; see
safe_equality_test/safe_zero_test for the epsilon-based replacement.
Source code in kumiki/rule.py
prune
¶
giraffe_evalf
¶
giraffe_simplify
¶
giraffe_compare
¶
giraffe_compare(a, b, comparison: Comparison, collapse_mode=None, eps: Optional[float] = None) -> bool
Compare two values: evaluates a - b and applies comparison against zero.
eps overrides the default comparison tolerance for this one call.
Examples:
giraffe_compare(x, y, Comparison.GT) # x > y ? giraffe_compare(x, 0, Comparison.EQ) # x == 0 ?
Source code in kumiki/rule.py
giraffe_dot_product
¶
giraffe_transform_vector
¶
giraffe_normalize_vector
¶
create_v2
¶
create_v3
¶
as_ndarray
¶
Three floats as a numpy array, from a V3 (3x1), a numpy array or any sequence of three numbers.
Source code in kumiki/rule.py
unit_ndarray
¶
vector at length one. A zero vector gives fallback when one is given; otherwise the
caller has said it cannot be zero.
Source code in kumiki/rule.py
cross_product
¶
radians
¶
Identity function for angles already in radians. Use this to make it explicit that an angle is in radians.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
angle
|
Numeric
|
Angle value in radians |
required |
Returns:
| Type | Description |
|---|---|
Numeric
|
The same angle value (unchanged) |
Examples:
radians(pi / 2) # 90 degrees in radians radians(pi / 4) # 45 degrees in radians
Source code in kumiki/rule.py
degrees
¶
Convert an angle from degrees to radians.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
angle
|
Numeric
|
Angle value in degrees |
required |
Returns:
| Type | Description |
|---|---|
Numeric
|
Angle value in radians |
Examples:
degrees(90) # 90 degrees = pi/2 radians degrees(45) # 45 degrees = pi/4 radians degrees(180) # 180 degrees = pi radians
Source code in kumiki/rule.py
inches
¶
Create a measurement in meters from inches.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
numerator
|
The numerator (can be int, float, or str) |
required | |
denominator
|
The denominator (default=1) |
1
|
Returns:
| Type | Description |
|---|---|
|
float value in meters |
Examples:
inches(1, 32) # 1/32 inch inches(4) # 4 inches inches(3.5) # 3.5 inches inches("1.5") # 1.5 inches from string inches("1/32") # Parses fraction string
Source code in kumiki/rule.py
feet
¶
Create a measurement in meters from feet.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
numerator
|
The numerator (can be int, float, or str) |
required | |
denominator
|
The denominator (default=1) |
1
|
Returns:
| Type | Description |
|---|---|
|
float value in meters |
Examples:
feet(8) # 8 feet feet(1, 2) # 1/2 foot feet(6.5) # 6.5 feet
Source code in kumiki/rule.py
mm
¶
Create a measurement in meters from millimeters.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
numerator
|
The numerator (can be int, float, or str) |
required | |
denominator
|
The denominator (default=1) |
1
|
Returns:
| Type | Description |
|---|---|
|
float value in meters |
Examples:
mm(90) # 90 millimeters mm(1, 2) # 1/2 millimeter mm(25.4) # 25.4 millimeters
Source code in kumiki/rule.py
cm
¶
Create a measurement in meters from centimeters.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
numerator
|
The numerator (can be int, float, or str) |
required | |
denominator
|
The denominator (default=1) |
1
|
Returns:
| Type | Description |
|---|---|
|
float value in meters |
Examples:
cm(9) # 9 centimeters cm(1, 2) # 1/2 centimeter cm(2.54) # 2.54 centimeters
Source code in kumiki/rule.py
m
¶
Create a measurement in meters.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
numerator
|
The numerator (can be int, float, or str) |
required | |
denominator
|
The denominator (default=1) |
1
|
Returns:
| Type | Description |
|---|---|
|
float value in meters |
Examples:
m(1) # 1 meter m(1, 2) # 1/2 meter m(2.5) # 2.5 meters
Source code in kumiki/rule.py
shaku
¶
Create a measurement in meters from shaku (尺). Traditional Japanese carpentry unit.
1 shaku ≈ 303.03 mm (exactly 10/33 meters)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
numerator
|
The numerator (can be int, float, or str) |
required | |
denominator
|
The denominator (default=1) |
1
|
Returns:
| Type | Description |
|---|---|
|
float value in meters |
Examples:
shaku(1) # 1 shaku shaku(3, 2) # 3/2 shaku (1.5 shaku) shaku(2.5) # 2.5 shaku
Source code in kumiki/rule.py
sun
¶
Create a measurement in meters from sun (寸). Traditional Japanese carpentry unit.
1 sun = 1/10 shaku ≈ 30.303 mm
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
numerator
|
The numerator (can be int, float, or str) |
required | |
denominator
|
The denominator (default=1) |
1
|
Returns:
| Type | Description |
|---|---|
|
float value in meters |
Examples:
sun(1) # 1 sun sun(5) # 5 sun sun(1, 2) # 1/2 sun
Source code in kumiki/rule.py
bu
¶
Create a measurement in meters from bu (分). Traditional Japanese carpentry unit.
1 bu = 1/10 sun = 1/100 shaku ≈ 3.0303 mm
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
numerator
|
The numerator (can be int, float, or str) |
required | |
denominator
|
The denominator (default=1) |
1
|
Returns:
| Type | Description |
|---|---|
|
float value in meters |
Examples:
bu(1) # 1 bu bu(5) # 5 bu bu(1, 2) # 1/2 bu
Source code in kumiki/rule.py
parse_length
¶
A length, in metres, from what someone wrote.
Examples:
parse_length("450mm") # 0.45 parse_length("18in") # 0.4572 parse_length('1 1/4"') # 0.03175 parse_length("2'6"") # 0.762 parse_length("450") # 0.45, taking default_unit
Source code in kumiki/rule.py
parse_angle
¶
An angle, in radians, from what someone wrote.
Examples:
parse_angle("30deg") # pi/6 parse_angle("30°") # pi/6 parse_angle("1.5rad") # 1.5 parse_angle("30") # pi/6, taking default_unit
Source code in kumiki/rule.py
format_length
¶
A length in metres, written the way it would be on a cut list.
Imperial lengths come out as mixed fractions to the nearest 1/denominator, reduced, because that is what a tape measure reads.
Source code in kumiki/rule.py
format_angle
¶
An angle in radians, written for people.
Source code in kumiki/rule.py
safe_zero_test
¶
Test if a value is approximately zero, within eps (default EPSILON_GENERIC).
safe_equality_test
¶
Test if two values are approximately equal, within eps (default EPSILON_GENERIC).
safe_zero_test_sq
¶
Test whether a SQUARED quantity is approximately zero.
Takes a LINEAR tolerance and squares it internally, so eps means the same thing here as everywhere else in the library: a distance in model units, never a distance squared.
safe_zero_test_sq(dx * dx + dy * dy, eps) # is the distance ~0?
Use this rather than safe_zero_test wherever the value under test is a square. Passing a squared value to safe_zero_test compares it against a linear tolerance, which sounds harmless and is not: at eps=5e-4 it treats any length below 22mm as zero. That has been the shape of two real bugs here already -- polygon edges declared degenerate, and pick tolerances meaning millimetres on one primitive and centimetres on another.
Source code in kumiki/rule.py
are_vectors_parallel
¶
Check if two vectors are parallel.
For normalized vectors: dot product ≈ ±1 means parallel
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
vector1
|
Matrix
|
First direction vector |
required |
vector2
|
Matrix
|
Second direction vector |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if |abs(dot_product) - 1| is approximately zero (vectors are parallel) |
Source code in kumiki/rule.py
are_vectors_perpendicular
¶
Check if two vectors are perpendicular.
For any vectors: dot product ≈ 0 means perpendicular
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
vector1
|
Matrix
|
First direction vector |
required |
vector2
|
Matrix
|
Second direction vector |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if dot_product is approximately zero (vectors are perpendicular) |
Source code in kumiki/rule.py
strip_features
¶
Return csg with every override and extra feature removed, all the way down; only defaults remain.
For copies of geometry already in another tree (e.g. a relief built from another timber).
Source code in kumiki/csg/cutcsg.py
with_purpose
¶
with_purpose(csg: CutCSG, purpose: FeaturePurpose) -> CutCSG
Return csg with every feature, all the way down, given purpose purpose.
Source code in kumiki/csg/cutcsg.py
adopt_csg
¶
adopt_csg(orig_transform: Optional[Transform], adopting_transform: Optional[Transform], csg_in_orig_space: CutCSG) -> CutCSG
Return csg_in_orig_space re-expressed from orig_transform's local space in adopting_transform's.
Either transform may be None for global space.
Example
cut_on_b = adopt_csg(timber_a.transform, timber_b.transform, cut_csg) csg_in_global = adopt_csg(timber_a.transform, None, cut_csg)
Source code in kumiki/csg/cutcsg.py
4149 4150 4151 4152 4153 4154 4155 4156 4157 4158 4159 4160 4161 4162 4163 4164 4165 4166 4167 4168 4169 4170 4171 4172 4173 4174 4175 4176 4177 4178 4179 4180 4181 4182 4183 4184 4185 4186 4187 4188 4189 4190 4191 4192 4193 4194 4195 4196 4197 4198 4199 4200 4201 4202 4203 4204 4205 4206 4207 4208 4209 4210 4211 4212 4213 4214 4215 4216 4217 4218 4219 4220 4221 4222 4223 4224 4225 4226 4227 4228 4229 4230 4231 4232 4233 4234 4235 4236 4237 4238 4239 4240 4241 4242 4243 4244 4245 4246 4247 4248 4249 4250 4251 4252 4253 4254 4255 4256 4257 4258 | |
get_center_point_on_face_global
¶
get_center_point_on_face_global(face: SomeTimberFace, timber: PerfectTimberWithin) -> V3
Get the center point of a timber face in global coordinates.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
face
|
SomeTimberFace
|
The face to get the center of |
required |
timber
|
PerfectTimberWithin
|
The timber |
required |
Returns:
| Type | Description |
|---|---|
V3
|
Center point of the face surface in global coordinates |
Source code in kumiki/measuring.py
locate_centerline
¶
locate_centerline(timber: PerfectTimberWithin) -> Line
Measure the centerline of a timber. Thin wrapper around locate_edge.
locate_plane_from_edge_in_direction
¶
locate_plane_from_edge_in_direction(timber: PerfectTimberWithin, edge: EdgeOrCenterline, direction: Direction3D, distance: Numeric = scalar(0)) -> Plane
Return a Plane that is parallel to the given edge, has direction as its
normal, and sits distance away from the edge in that direction.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
timber
|
PerfectTimberWithin
|
The timber whose edge to measure from |
required |
edge
|
EdgeOrCenterline
|
Which edge or centerline |
required |
direction
|
Direction3D
|
Normal direction of the resulting plane |
required |
distance
|
Numeric
|
How far from the edge to place the plane (default 0 = through the edge) |
scalar(0)
|
Source code in kumiki/measuring.py
get_perfect_support_distance_from_centerline
¶
get_perfect_support_distance_from_centerline(timber: PerfectTimberWithin, direction: V2) -> Numeric
distance from cross-section centerline to support plane of the perfect timber dimensions in direction
Source code in kumiki/timber_shavings.py
are_timbers_plane_aligned
¶
are_timbers_plane_aligned(timber1: PerfectTimberWithin, timber2: PerfectTimberWithin, tolerance: Optional[Numeric] = None) -> bool
Check if two timbers are plane aligned
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
timber1
|
PerfectTimberWithin
|
First timber |
required |
timber2
|
PerfectTimberWithin
|
Second timber |
required |
tolerance
|
Optional[Numeric]
|
Optional numerical tolerance for parallel check. If None, uses exact equality. If provided, uses approximate floating-point comparison. |
None
|
Returns:
| Type | Description |
|---|---|
bool
|
True if timbers have at least one pair of parallel long faces, False otherwise |
Source code in kumiki/timber_shavings.py
warn_if_arrangement_timbers_imperfect
¶
Warn when a joint arrangement uses any timber that is not perfect.
Source code in kumiki/joints/workshop/shavings/relief.py
does_shoulder_plane_need_notching
¶
does_shoulder_plane_need_notching(arrangement: ButtJointTimberArrangement, mortise_shoulder_distance_from_centerline_or_centerplane: Numeric, check_against_rough_size: bool = True, set_mortise_shoulder_parallel_to_face: Union[TimberLongFace, bool] = False) -> bool
Determines whether a shoulder notch is needed on the mortise timber.
For plane-aligned timbers, checks whether the shoulder is inset from the mortise face surface. For non-plane-aligned timbers, always returns True.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
arrangement
|
ButtJointTimberArrangement
|
Butt joint arrangement (receiving_timber = mortise, butt_timber = tenon). |
required |
mortise_shoulder_distance_from_centerline_or_centerplane
|
Numeric
|
Distance from the mortise centerline to the shoulder plane, measured toward the tenon. |
required |
check_against_rough_size
|
bool
|
If True (default), compare against the mortise timber's
rough half-size on the entry face (using |
True
|
set_mortise_shoulder_parallel_to_face
|
Union[TimberLongFace, bool]
|
If set to a face, then force the mortise shoulder to be parallel to that face. |
False
|
Source code in kumiki/joints/workshop/shavings/relief.py
chop_shoulder_notch_aligned_with_timber
¶
chop_shoulder_notch_aligned_with_timber(notch_timber: TimberLike, butting_timber: TimberLike, butting_timber_end: TimberEnd, distance_from_centerline: Numeric, notch_wall_relief_cut_angle_radians: Numeric = scalar(0), set_mortise_shoulder_parallel_to_face: Union[TimberLongFace, bool] = False, label: CutCSGLabel = CutCSGLabel('shoulder_notch_relief')) -> Union[RectangularPrism, SolidUnion]
Create a shoulder notch on notch_timber at a given distance from its centerline, oriented by the butting_timber's approach direction.
Unlike chop_shoulder_notch_on_timber_face which is aligned to a specific face, this notch is aligned to the shoulder plane derived from the butting timber's approach direction (projected perpendicular to the notch timber's length axis if set_mortise_shoulder_parallel_to_face is not False).
The notch bottom (shoulder plane) is distance_from_centerline away from the notch_timber's centerline. The notch opens outward from the centerline. The notch width is along the notch_timber's length axis and hugs the butting timber's shoulder-plane slice exactly (its perfect cross-section; imperfect material beyond that is scribe relief's job, not the housing's). The span and depth clear the notch timber's entire rough cross-section via a worst-case corner-radius bound -- overshoot is free in both of those directions (the span channel exits the timber's sides, the depth exits its outer face) so neither needs to be exact.
notch_wall_relief_cut_angle_radians is a MINIMUM: the walls are always relieved by at least the butting timber's rake away from the shoulder-plane normal, since anything less would leave housing walls colliding with the raking butting timber above the shoulder plane.
Source code in kumiki/joints/workshop/shavings/relief.py
438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 | |
chop_shoulder_notch_on_timber_face
¶
chop_shoulder_notch_on_timber_face(timber: TimberLike, notch_face: TimberFace, distance_along_timber: Numeric, notch_width: Numeric, notch_depth: Numeric, notch_wall_relief_cut_angle: Numeric = scalar(0), label: CutCSGLabel = CutCSGLabel('shoulder_notch_relief')) -> Union[RectangularPrism, SolidUnion]
Create a rectangular shoulder notch on a timber face with optional angled walls.
Source code in kumiki/joints/workshop/shavings/relief.py
639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 | |
chop_butt_joint_shoulder_notch_relief_on_plane_aligned_timbers_2sided
¶
chop_butt_joint_shoulder_notch_relief_on_plane_aligned_timbers_2sided(arrangement: ButtJointTimberArrangement, mortise_shoulder_distance_from_centerline_or_centerplane: Numeric, notch_angle: Optional[Numeric] = None) -> ShoulderReliefCSGGeometry | None
Like chop_butt_joint_shoulder_notch_relief_4sided, but restricted to PLANE-ALIGNED
arrangements, where it produces a simpler notch: only 2 of the 4 walls flare via the
dihedral-bisector construction; the other 2 don't flare at all -- they're pushed straight
out, for the notch's ENTIRE depth, to whichever is FURTHER along the "joint normal axis"
-- the shared normal of the two timbers' aligned long faces
(arrangement.compute_normalized_timber_cross_product()) -- between the RECEIVING
timber's own ROUGH edge and the BUTT timber's own ROUGH edge. This guarantees a full
TRANSVERSE relief spanning the receiving timber's entire width on this axis (never a
pocket that stops partway across it), and is safe specifically because plane-alignment
guarantees the butt timber's faces in that axis are exactly PARALLEL to the receiving
timber's own faces there -- there's no dihedral angle to bisect, so a straight-walled
channel is the natural choice, unlike the 4-sided version's fully general per-wall flare.
Geometry, in outline (P = joint normal axis, Q = the butt timber's other cross-sectional
axis, both perpendicular to n_depth = the shoulder plane's normal):
- Along P: both the shoulder-plane cross-section (quad-1) and the far cross-section
(quad-2) span the SAME fixed extent -- per side, the FURTHER of the receiving timber's
own ROUGH half-size and the butt timber's own ROUGH half-size on that axis
(plane-alignment guarantees P is EXACTLY -- not just approximately -- one of EACH
timber's own width/height axes, the same shared axis are_timbers_plane_aligned
identifies, so there's no oblique stretching to account for). Flat, unflared walls in
this axis, for the notch's whole depth.
- Along Q: quad-1 uses the butt timber's PERFECT cross-section as it actually crosses
the shoulder plane (same as the 4-sided version's quad-1 corners) -- NOT simply the
tenon's raw PTW half-size, which understates the footprint whenever the tenon's length
axis isn't perpendicular to the shoulder plane within the (Q, n_depth) plane (e.g. any
raking brace-style joint); quad-2 flares outward via the SAME per-wall dihedral-bisector
construction as the 4-sided version, using the SIGNED dihedral angle between each of
the butt timber's two Q-normal faces and the shoulder plane independently -- negated
normals generally give supplementary (not equal) signed angles, so the two flared
walls generally reach out by different amounts, same as any two non-opposite walls in
the 4-sided version would.
- The two flared (Q-direction) walls reach a common depth exactly as in the 4-sided
case (the deeper of their two natural depths); the two flat (P-direction) walls are,
by construction, already at a fixed extent for that whole depth, so no rescaling is
needed for them.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
arrangement
|
ButtJointTimberArrangement
|
butt joint arrangement; must be plane-aligned (raises via
|
required |
mortise_shoulder_distance_from_centerline_or_centerplane
|
Numeric
|
same as the 4-sided version -- signed distance from the receiving timber's centerline to the shoulder plane, toward the butt timber. |
required |
notch_angle
|
Optional[Numeric]
|
optional MINIMUM wall-relief angle (radians) for the 2 flared (Q-axis)
walls, floored independently against each wall's own natural dihedral-bisector
angle -- same "floor, not override" convention as
|
None
|
Returns None when no notch is required -- see does_shoulder_plane_need_notching.
Source code in kumiki/joints/workshop/shavings/relief.py
790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 | |
chop_butt_joint_shoulder_notch_relief_4sided
¶
chop_butt_joint_shoulder_notch_relief_4sided(arrangement: ButtJointTimberArrangement, mortise_shoulder_distance_from_centerline_or_centerplane: Numeric) -> ShoulderReliefCSGGeometry | None
Compute the shoulder notch on the receiving timber AND the matching relief cut on the butting timber, for arrangements where the butt timber may approach the shoulder at a compound angle (not necessarily plane-aligned with the receiving timber).
The notch is a single 4-sided frustum
(a ConvexPolygonSimpleLoft) rather than a union of a straight prism plus 2 tilted
relief prisms, so all 4 walls can relieve independently based on how each of the butt
timber's 4 long faces actually meets the shoulder plane.
Geometry, in outline: - quad-1 is the butt timber's PERFECT cross-section sliced by the shoulder plane (an oblique quadrilateral in general, since the butt timber's length axis need not be perpendicular to the shoulder plane). - Each of quad-1's 4 edges lies exactly on the line where one of the butt timber's long face planes crosses the shoulder plane (by construction: both of that edge's corners sit on that face). For each edge, the relieved wall direction bisects the dihedral angle between that face and the shoulder plane -- this stays on the safe (non-colliding) side of the face for any distance travelled along it, so a wall can safely be extended further than its own bisector's "natural" depth without becoming unsafe. - Each edge's own bisector reaches a different depth for the same in-plane reach (since the 4 dihedral angles generally differ), which would make quad-2 non-planar. Instead we take the deepest of the 4 (the "maximal loft distance") as a common depth, and rescale each edge's in-plane offset to match -- still safely on that edge's own bisector, just further out -- so quad-2 stays flat and parallel to quad-1 and every wall gets at least as much depth-clearance as it individually needs.
Returns None when no notch is required (shoulder sits at or past the receiving
timber's rough entry face) -- see does_shoulder_plane_need_notching.
Source code in kumiki/joints/workshop/shavings/relief.py
1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 | |
chop_scribe_relief
¶
chop_scribe_relief(timber_to_be_scribed_cutting: Cutting, timber_to_be_cut_cutting: Cutting, scribe_relief_label: CutCSGLabel = CutCSGLabel('scribe_relief'), scribe_hollow_label: CutCSGLabel = CutCSGLabel('scribe_hollow_relief')) -> tuple[CutCSG, CutCSG]
scribes timber_to_be_scribed onto timber_to_be_cut such that the entirety of timber_to_be_scribed is cut out of timber_to_be_cut excluding the perfect timber within portion of timber_to_be_cut
Both timbers are given as their (already cut) Cutting, each of which carries
its own timber via Cutting.timber -- callers never need to pass the bare
timbers separately.
timber_to_be_scribed_cutting is the cutting already computed for timber_to_be_scribed elsewhere in the current joint (e.g. the tenon's shoulder cut, plus any end cut) -- material already removed there is excluded from what gets scribed onto timber_to_be_cut, otherwise the relief would be based on timber_to_be_scribed's full, uncut extent (its entire length) rather than just the part of it that actually survives near the joint.
returns a pair of CSG geometries, the first to be removed from timber_to_be_scribed and the second to be removed from timber_to_be_cut, both expressed in their respective local frames
Source code in kumiki/joints/workshop/shavings/relief.py
1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 | |
chop_scribe_relief_and_apply
¶
chop_scribe_relief_and_apply(timber_to_be_scribed_cutting: Cutting, timber_to_be_cut_cutting: Cutting) -> tuple[Cutting, Cutting]
Apply scribe relief cuts from chop_scribe_relief to the given cuttings, unioning the
new relief CSGs into each cutting's existing negative_csg.
Both timbers are given as their (already cut) Cutting, each of which carries
its own timber via Cutting.timber -- callers never need to pass the bare
timbers separately.
Returns (updated_cut_cutting, updated_scribed_cutting) (matching the order of
the early-return path when both timbers are perfect).
Source code in kumiki/joints/workshop/shavings/relief.py
chop_scribe_relief_and_apply_for_butt_joint_arrangement
¶
chop_scribe_relief_and_apply_for_butt_joint_arrangement(relief: ButtJointScribeReliefConfig, butt_cut: Cutting, receiving_cut: Cutting) -> tuple[Cutting, Cutting]
Helper shared by the butt-joint cutting functions: apply scribe relief
between the butt and receiving timbers, honoring
relief.timber_to_be_scribed to decide which timber is scribed onto the
other.
relief is required here -- callers are responsible for skipping this
call entirely when relief isn't configured (e.g. it's None).
Source code in kumiki/joints/workshop/shavings/relief.py
apply_scribe_relief_if_configured
¶
apply_scribe_relief_if_configured(relief: Optional[ButtJointScribeReliefConfig], butt_cut: Cutting, receiving_cut: Cutting) -> tuple[Cutting, Cutting]
Wrapper around chop_scribe_relief_and_apply_for_butt_joint_arrangement:
passes butt_cut/receiving_cut through unchanged when relief is None, otherwise
delegates to apply the configured scribe relief.