Skip to content

kumiki.joints.workshop.splice

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.splice -- you do not need to import from the submodule path shown in the heading above.

kumiki.joints.workshop.splice

Kumiki - Splice joint construction functions

cut_腰掛鎌継ぎ_joint_on_aligned_timbers module-attribute

cut_腰掛鎌継ぎ_joint_on_aligned_timbers = cut_lapped_gooseneck_joint_on_aligned_timbers

cut_koshikake_kama_tsugi_joint_on_aligned_timbers module-attribute

cut_koshikake_kama_tsugi_joint_on_aligned_timbers = cut_lapped_gooseneck_joint_on_aligned_timbers

cut_kanawa_tsugi_joint_on_aligned_timbers module-attribute

cut_kanawa_tsugi_joint_on_aligned_timbers = cut_rebated_oblique_and_dadoed_scarf_joint_on_aligned_timbers

cut_kanawa_tsugi module-attribute

cut_金輪継ぎ_joint_on_aligned_timbers module-attribute

cut_金輪継ぎ_joint_on_aligned_timbers = cut_rebated_oblique_and_dadoed_scarf_joint_on_aligned_timbers

cut_金輪継ぎ module-attribute

cut_かなわつぎ_joint_on_aligned_timbers module-attribute

cut_かなわつぎ_joint_on_aligned_timbers = cut_rebated_oblique_and_dadoed_scarf_joint_on_aligned_timbers

cut_かなわつぎ module-attribute

cut_plain_butt_splice_joint_on_aligned_timbers

cut_plain_butt_splice_joint_on_aligned_timbers(arrangement: SpliceJointTimberArrangement, splice_point: Optional[V3] = None) -> Joint

Creates a plain butt splice joint between two parallel timbers cut at a shared plane.

Both timbers are cut at the splice plane, creating a flush end-to-end connection.

Parameters:

Name Type Description Default
arrangement SpliceJointTimberArrangement

Splice joint arrangement with timber1, timber2, timber1_end, timber2_end. Timbers must have parallel length axes.

required
splice_point Optional[V3]

Point where the splice occurs. If not provided, the midpoint between the two timber ends is used. If provided but off the centerline, it is projected onto timber1's centerline.

None

Returns:

Type Description
Joint

Joint object containing the two CutTimbers.

Raises:

Type Description
ValueError

If the timbers do not have parallel length axes.

Source code in kumiki/joints/workshop/splice/plain_butt_splice_joint.py
def cut_plain_butt_splice_joint_on_aligned_timbers(
    arrangement: SpliceJointTimberArrangement,
    splice_point: Optional[V3] = None,
) -> Joint:
    """
    Creates a plain butt splice joint between two parallel timbers cut at a shared plane.

    Both timbers are cut at the splice plane, creating a flush end-to-end connection.

    Args:
        arrangement: Splice joint arrangement with timber1, timber2, timber1_end, timber2_end.
                     Timbers must have parallel length axes.
        splice_point: Point where the splice occurs. If not provided, the midpoint between
            the two timber ends is used. If provided but off the centerline, it is projected
            onto timber1's centerline.

    Returns:
        Joint object containing the two CutTimbers.

    Raises:
        ValueError: If the timbers do not have parallel length axes.
    """
    from kumiki.construction import _are_directions_parallel

    timberA = arrangement.timber1
    timberA_end = arrangement.timber1_end
    timberB = arrangement.timber2
    timberB_end = arrangement.timber2_end

    if not _are_directions_parallel(timberA.get_length_direction_global(), timberB.get_length_direction_global()):
        raise ValueError("Timbers must have parallel length axes for a splice joint")

    if timberA_end == TimberEnd.TOP:
        endA_position = locate_top_center_position(timberA).position
    else:
        endA_position = locate_bottom_center_position(timberA).position

    if timberB_end == TimberEnd.TOP:
        endB_position = locate_top_center_position(timberB).position
    else:
        endB_position = locate_bottom_center_position(timberB).position

    length_dir_norm = safe_normalize_vector(timberA.get_length_direction_global())

    if splice_point is None:
        splice_point = (endA_position + endB_position) / 2
    else:
        to_splice = splice_point - timberA.get_bottom_position_global()
        distance_along_centerline = safe_dot_product(to_splice, length_dir_norm)
        projected_point = timberA.get_bottom_position_global() + length_dir_norm * distance_along_centerline

        distance_from_centerline = safe_magnitude(splice_point - projected_point)
        if not safe_zero_test(distance_from_centerline):
            warnings.warn(
                f"Splice point was not on timberA's centerline (distance: {float(distance_from_centerline)}). "
                f"Projecting onto centerline."
            )
            splice_point = projected_point

    centerline_distance = safe_magnitude(
        (splice_point - timberA.get_bottom_position_global()) -
        length_dir_norm * safe_dot_product(splice_point - timberA.get_bottom_position_global(), length_dir_norm) -
        ((splice_point - timberB.get_bottom_position_global()) -
         length_dir_norm * safe_dot_product(splice_point - timberB.get_bottom_position_global(), length_dir_norm))
    )

    max_dimension = max(timberA.size[0], timberA.size[1], timberB.size[0], timberB.size[1])
    if centerline_distance > max_dimension / scalar(2):
        warnings.warn(
            f"Timber cross sections may not overlap (centerline distance: {float(centerline_distance)}). "
            f"Check joint geometry."
        )

    distance_A_from_bottom = safe_dot_product(splice_point - timberA.get_bottom_position_global(), timberA.get_length_direction_global())
    distance_B_from_bottom = safe_dot_product(splice_point - timberB.get_bottom_position_global(), timberB.get_length_direction_global())

    endA_direction = timberA.get_face_direction_global(timberA_end)
    endB_direction = timberB.get_face_direction_global(timberB_end)

    cutA = Cutting(
        timber=timberA,
        maybe_top_end_cut_distance_from_bottom=distance_A_from_bottom if timberA_end == TimberEnd.TOP else None,
        maybe_bottom_end_cut_distance_from_bottom=distance_A_from_bottom if timberA_end == TimberEnd.BOTTOM else None,
        negative_csg=None,
        assembly_freedom=AssemblyFreedom.translation(-endA_direction, freed_after=scalar(0)),
        label=CutCSGLabel("splice_cross_cut"),
    )

    cutB = Cutting(
        timber=timberB,
        maybe_top_end_cut_distance_from_bottom=distance_B_from_bottom if timberB_end == TimberEnd.TOP else None,
        maybe_bottom_end_cut_distance_from_bottom=distance_B_from_bottom if timberB_end == TimberEnd.BOTTOM else None,
        negative_csg=None,
        assembly_freedom=AssemblyFreedom.translation(-endB_direction, freed_after=scalar(0)),
        label=CutCSGLabel("splice_cross_cut"),
    )

    return Joint(
        cuttings={"timberA": cutA, "timberB": cutB},
        ticket=JointTicket(joint_type="plain_butt_splice"),
        jointAccessories={},
    )

cut_plain_splice_lap_joint_on_aligned_timbers

cut_plain_splice_lap_joint_on_aligned_timbers(arrangement: SpliceJointTimberArrangement, lap_length: Numeric, top_lap_shoulder_position_from_top_lap_shoulder_timber_end: Numeric, lap_depth: Optional[Numeric] = None) -> Joint

Creates a splice lap joint between two parallel timber ends with interlocking relief cuts.

One timber has material removed from the specified face; the other has material removed from the opposite face. Timbers must be parallel and face-aligned.

arrangement.front_face_on_timber1
v           |--------| lap_length

╔════════════════════════╗╔══════╗ - ║timber1 ║║ ║ | lap_depth ║ ╔════════╝║ ║ - ║ ║╔════════╝ ║ ║ ║║ timber2 ║ ╚═══════════════╝╚═══════════════╝ ^ top_lap_shoulder_position_from_top_lap_shoulder_timber_end

Parameters:

Name Type Description Default
arrangement SpliceJointTimberArrangement

Splice joint arrangement with timber1, timber2, timber1_end, timber2_end, and optionally front_face_on_timber1 (the lap cut face on timber1). If front_face_on_timber1 is None, defaults to FRONT face. Timbers must be parallel and face-aligned.

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 timber1 end to the shoulder, measured inward along the timber.

required
lap_depth Optional[Numeric]

Depth of material to remove perpendicular to the face. If None, defaults to half the timber thickness in the face normal axis.

None

Returns:

Type Description
Joint

Joint object containing the two CutTimbers with lap cuts.

Source code in kumiki/joints/workshop/splice/plain_splice_lap_joint.py
def cut_plain_splice_lap_joint_on_aligned_timbers(
    arrangement: SpliceJointTimberArrangement,
    lap_length: Numeric,
    top_lap_shoulder_position_from_top_lap_shoulder_timber_end: Numeric,
    lap_depth: Optional[Numeric] = None,
) -> Joint:
    """
    Creates a splice lap joint between two parallel timber ends with interlocking relief cuts.

    One timber has material removed from the specified face; the other has material
    removed from the opposite face. Timbers must be parallel and face-aligned.

        arrangement.front_face_on_timber1
        v           |--------| lap_length
    ╔════════════════════════╗╔══════╗  -
    ║timber1                 ║║      ║  | lap_depth
    ║               ╔════════╝║      ║  -
    ║               ║╔════════╝      ║
    ║               ║║ timber2       ║
    ╚═══════════════╝╚═══════════════╝
                    ^ top_lap_shoulder_position_from_top_lap_shoulder_timber_end

    Args:
        arrangement: Splice joint arrangement with timber1, timber2, timber1_end, timber2_end,
                     and optionally front_face_on_timber1 (the lap cut face on timber1).
                     If front_face_on_timber1 is None, defaults to FRONT face.
                     Timbers must be parallel and face-aligned.
        lap_length: Length of the lap region along the timber length.
        top_lap_shoulder_position_from_top_lap_shoulder_timber_end: Distance from the
            timber1 end to the shoulder, measured inward along the timber.
        lap_depth: Depth of material to remove perpendicular to the face. If None,
            defaults to half the timber thickness in the face normal axis.

    Returns:
        Joint object containing the two CutTimbers with lap cuts.
    """
    top_lap_timber = arrangement.timber1
    top_lap_timber_end = arrangement.timber1_end
    bottom_lap_timber = arrangement.timber2
    bottom_lap_timber_end = arrangement.timber2_end
    top_lap_timber_face = arrangement.front_face_on_timber1 if arrangement.front_face_on_timber1 is not None else TimberLongFace.FRONT

    if lap_depth is None:
        if top_lap_timber_face == TimberLongFace.LEFT or top_lap_timber_face == TimberLongFace.RIGHT:
            lap_depth = top_lap_timber.size[1] / scalar(2)
        else:
            lap_depth = min(top_lap_timber.size[0], top_lap_timber.size[1]) / scalar(2)

    (top_lap_prism, top_end_cut), (bottom_lap_prism, bottom_end_cut) = chop_lap_on_timber_ends(
        top_lap_timber=top_lap_timber,
        top_lap_timber_end=top_lap_timber_end,
        bottom_lap_timber=bottom_lap_timber,
        bottom_lap_timber_end=bottom_lap_timber_end,
        top_lap_timber_face=top_lap_timber_face,
        lap_length=lap_length,
        lap_depth=lap_depth,
        top_lap_shoulder_position_from_top_lap_shoulder_timber_end=top_lap_shoulder_position_from_top_lap_shoulder_timber_end,
    )

    top_end_cut_distance_from_bottom = (
        top_end_cut.offset
        if top_lap_timber_end == TimberEnd.TOP
        else -top_end_cut.offset
    )
    bottom_end_cut_distance_from_bottom = (
        bottom_end_cut.offset
        if bottom_lap_timber_end == TimberEnd.TOP
        else -bottom_end_cut.offset
    )

    lap_face_normal_global = top_lap_timber.get_face_direction_global(top_lap_timber_face.to.face())
    lap_thickness = top_lap_timber.get_size_in_face_normal_axis(top_lap_timber_face)

    cut_top = Cutting(
        timber=top_lap_timber,
        maybe_top_end_cut_distance_from_bottom=top_end_cut_distance_from_bottom if top_lap_timber_end == TimberEnd.TOP else None,
        maybe_bottom_end_cut_distance_from_bottom=top_end_cut_distance_from_bottom if top_lap_timber_end == TimberEnd.BOTTOM else None,
        negative_csg=top_lap_prism,
        assembly_freedom=AssemblyFreedom.translation(lap_face_normal_global, freed_after=lap_thickness),
        label=CutCSGLabel("splice_lap_cut"),
    )

    cut_bottom = Cutting(
        timber=bottom_lap_timber,
        maybe_top_end_cut_distance_from_bottom=bottom_end_cut_distance_from_bottom if bottom_lap_timber_end == TimberEnd.TOP else None,
        maybe_bottom_end_cut_distance_from_bottom=bottom_end_cut_distance_from_bottom if bottom_lap_timber_end == TimberEnd.BOTTOM else None,
        negative_csg=bottom_lap_prism,
        assembly_freedom=AssemblyFreedom.translation(-lap_face_normal_global, freed_after=lap_thickness),
        label=CutCSGLabel("splice_lap_cut"),
    )

    return Joint(
        cuttings={"top_lap_timber": cut_top, "bottom_lap_timber": cut_bottom},
        ticket=JointTicket(joint_type="plain_splice_lap"),
        jointAccessories={},
    )

cut_lapped_gooseneck_joint_on_aligned_timbers

cut_lapped_gooseneck_joint_on_aligned_timbers(arrangement: SpliceJointTimberArrangement, gooseneck_length: Numeric, gooseneck_small_width: Numeric, gooseneck_large_width: Numeric, gooseneck_head_length: Numeric, lap_length: Numeric = scalar(0), gooseneck_lateral_offset: Numeric = scalar(0), gooseneck_depth: Optional[Numeric] = None) -> Joint

Creates a lapped gooseneck joint (腰掛鎌継ぎ / Koshikake Kama Tsugi) between two timbers.

This is a traditional Japanese timber joint that combines a lap joint with a gooseneck-shaped profile. The gooseneck profile provides mechanical interlock while the lap provides additional bearing surface.

Parameters:

Name Type Description Default
arrangement SpliceJointTimberArrangement

Splice arrangement where timber1 is the gooseneck timber, timber2 is the receiving timber, timber1_end/timber2_end are the joined ends, and front_face_on_timber1 is the face on timber1 where the gooseneck profile is visible.

required
gooseneck_length Numeric

Length of the gooseneck shape (does not include lap length)

required
gooseneck_small_width Numeric

Width of the narrow end of the gooseneck taper

required
gooseneck_large_width Numeric

Width of the wide end of the gooseneck taper

required
gooseneck_head_length Numeric

Length of the head portion of the gooseneck

required
lap_length Numeric

Length of the lap portion of the joint

scalar(0)
gooseneck_lateral_offset Numeric

Lateral offset of gooseneck profile

scalar(0)
gooseneck_depth Optional[Numeric]

Optional depth of the gooseneck cut. If None, defaults to half the timber dimension perpendicular to arrangement.front_face_on_timber1

None

Returns:

Type Description
Joint

Joint object containing the two CutTimbers with the gooseneck cuts applied

Raises:

Type Description
ValueError

If the parameters are invalid or the timbers are not properly positioned

Source code in kumiki/joints/workshop/splice/lapped_gooseneck_joint.py
def cut_lapped_gooseneck_joint_on_aligned_timbers(
    arrangement: SpliceJointTimberArrangement,
    gooseneck_length: Numeric,
    gooseneck_small_width: Numeric,
    gooseneck_large_width: Numeric,
    gooseneck_head_length: Numeric,
    lap_length: Numeric = scalar(0),
    gooseneck_lateral_offset: Numeric = scalar(0),
    gooseneck_depth: Optional[Numeric] = None,
) -> Joint:
    """
    Creates a lapped gooseneck joint (腰掛鎌継ぎ / Koshikake Kama Tsugi) between two timbers.

    This is a traditional Japanese timber joint that combines a lap joint with a gooseneck-shaped
    profile. The gooseneck profile provides mechanical interlock while the lap provides additional
    bearing surface.

    Args:
        arrangement: Splice arrangement where timber1 is the gooseneck timber,
            timber2 is the receiving timber, timber1_end/timber2_end are the joined
            ends, and front_face_on_timber1 is the face on timber1 where the
            gooseneck profile is visible.
        gooseneck_length: Length of the gooseneck shape (does not include lap length)
        gooseneck_small_width: Width of the narrow end of the gooseneck taper
        gooseneck_large_width: Width of the wide end of the gooseneck taper
        gooseneck_head_length: Length of the head portion of the gooseneck
        lap_length: Length of the lap portion of the joint
        gooseneck_lateral_offset: Lateral offset of gooseneck profile
        gooseneck_depth: Optional depth of the gooseneck cut. If None, defaults to half the timber dimension
                        perpendicular to arrangement.front_face_on_timber1

    Returns:
        Joint object containing the two CutTimbers with the gooseneck cuts applied

    Raises:
        ValueError: If the parameters are invalid or the timbers are not properly positioned
    """
    require_check(arrangement.check_face_aligned_and_parallel_axis())
    warn_if_arrangement_timbers_imperfect(arrangement)
    assert arrangement.front_face_on_timber1 is not None, (
        "arrangement.front_face_on_timber1 must be set to determine the gooseneck face"
    )
    gooseneck_timber = arrangement.timber1
    receiving_timber = arrangement.timber2
    gooseneck_timber_end = arrangement.timber1_end
    receiving_timber_end = arrangement.timber2_end
    gooseneck_timber_face = arrangement.front_face_on_timber1

    if gooseneck_length <= 0:
        raise ValueError(f"gooseneck_length must be positive, got {gooseneck_length}")
    if gooseneck_small_width <= 0:
        raise ValueError(f"gooseneck_small_width must be positive, got {gooseneck_small_width}")
    if gooseneck_large_width <= 0:
        raise ValueError(f"gooseneck_large_width must be positive, got {gooseneck_large_width}")
    if gooseneck_head_length <= 0:
        raise ValueError(f"gooseneck_head_length must be positive, got {gooseneck_head_length}")

    if gooseneck_large_width <= gooseneck_small_width:
        raise ValueError(
            f"gooseneck_large_width ({gooseneck_large_width}) must be greater than "
            f"gooseneck_small_width ({gooseneck_small_width})"
        )

    if gooseneck_depth is not None and gooseneck_depth <= 0:
        raise ValueError(f"gooseneck_depth must be positive if provided, got {gooseneck_depth}")

    overlap_error = check_timber_overlap_for_splice_joint_is_sensible(
        gooseneck_timber, receiving_timber, gooseneck_timber_end, receiving_timber_end
    )
    if overlap_error:
        warnings.warn(f"Gooseneck joint configuration may not be sensible: {overlap_error}")

    gooseneck_direction_global = -receiving_timber.get_face_direction_global(receiving_timber_end)
    gooseneck_lateral_offset_direction_global = receiving_timber.get_face_direction_global(gooseneck_timber_face.rotate_right())

    if receiving_timber_end == TimberEnd.TOP:
        receiving_timber_end_position_global = locate_top_center_position(receiving_timber).position
    else:
        receiving_timber_end_position_global = receiving_timber.get_bottom_position_global()

    gooseneck_starting_position_on_receiving_timber_centerline_with_lateral_offset_global = (
        receiving_timber_end_position_global
        + gooseneck_direction_global * lap_length
        + gooseneck_lateral_offset_direction_global * gooseneck_lateral_offset
    )

    gooseneck_shape = draw_gooseneck_polygon(gooseneck_length, gooseneck_small_width, gooseneck_large_width, gooseneck_head_length)

    if gooseneck_depth is None:
        gooseneck_depth = gooseneck_timber.get_size_in_face_normal_axis(
            gooseneck_timber_face.to.face()
        ) / scalar(2)

    gooseneck_starting_position_on_receiving_timber = (
        (gooseneck_starting_position_on_receiving_timber_centerline_with_lateral_offset_global - receiving_timber.get_bottom_position_global()).T
        * receiving_timber.get_length_direction_global()
    )[0, 0]

    gooseneck_cutting_plane = locate_into_face(gooseneck_depth, gooseneck_timber_face, gooseneck_timber)
    gooseneck_face_direction = gooseneck_timber.get_face_direction_global(gooseneck_timber_face)
    receiving_face_direction = -gooseneck_face_direction
    receiving_face = receiving_timber.get_closest_oriented_face_from_global_direction(receiving_face_direction)
    marking = mark_distance_from_face_in_normal_direction(gooseneck_cutting_plane, receiving_timber, receiving_face)
    receiving_timber_lap_depth = Abs(marking.distance)

    if receiving_timber_end == TimberEnd.TOP:
        receiving_timber_shoulder_from_end = receiving_timber.length - gooseneck_starting_position_on_receiving_timber
    else:
        receiving_timber_shoulder_from_end = gooseneck_starting_position_on_receiving_timber

    receiving_timber_lap_face_direction = -gooseneck_timber.get_face_direction_global(gooseneck_timber_face)
    receiving_timber_lap_face = receiving_timber.get_closest_oriented_face_from_global_direction(receiving_timber_lap_face_direction)

    if lap_length > 0:
        receiving_timber_lap_prism, receiving_timber_end_cut = chop_lap_on_timber_end(
            lap_timber=receiving_timber,
            lap_timber_end=receiving_timber_end,
            lap_timber_face=receiving_timber_lap_face,
            lap_length=lap_length,
            lap_shoulder_position_from_lap_timber_end=receiving_timber_shoulder_from_end,
            lap_depth=receiving_timber_lap_depth,
            label=CutCSGLabel("gooseneck_lap"),
        )
    else:
        receiving_timber_end_cut = None

    gooseneck_lap_start_global = receiving_timber_end_position_global
    gooseneck_lap_start_on_gooseneck_timber = (
        (gooseneck_lap_start_global - gooseneck_timber.get_bottom_position_global()).T
        * gooseneck_timber.get_length_direction_global()
    )[0, 0]

    if gooseneck_timber_end == TimberEnd.TOP:
        gooseneck_timber_lap_shoulder_from_end = gooseneck_timber.length - gooseneck_lap_start_on_gooseneck_timber
    else:
        gooseneck_timber_lap_shoulder_from_end = gooseneck_lap_start_on_gooseneck_timber

    gooseneck_timber_lap_prism, _ = chop_lap_on_timber_end(
        lap_timber=gooseneck_timber,
        lap_timber_end=gooseneck_timber_end,
        lap_timber_face=TimberFace(gooseneck_timber_face.value),
        lap_length=lap_length + gooseneck_length,
        lap_shoulder_position_from_lap_timber_end=gooseneck_timber_lap_shoulder_from_end,
        lap_depth=gooseneck_depth,
        label=CutCSGLabel("gooseneck_lap"),
    )

    gooseneck_profile_y_position = gooseneck_timber_lap_shoulder_from_end + lap_length

    gooseneck_profile_csg = chop_profile_on_timber_face(
        timber=gooseneck_timber,
        end=gooseneck_timber_end,
        face=gooseneck_timber_face.to.face(),
        profile=gooseneck_shape,
        depth=gooseneck_depth,
        profile_y_offset_from_end=-gooseneck_profile_y_position,
        label=CutCSGLabel("gooseneck"),
    )

    gooseneck_profile_prism = chop_timber_end_with_prism(
        timber=gooseneck_timber,
        end=gooseneck_timber_end,
        distance_from_end_to_cut=-(gooseneck_profile_y_position),
        label=CutCSGLabel("gooseneck_shoulder"),
        is_shoulder=True,
    )

    gooseneck_profile_difference_csg = Difference(
        gooseneck_profile_prism, [gooseneck_profile_csg],
        label=CutCSGLabel("gooseneck_waste"),
    )

    gooseneck_timber_combined_csg = SolidUnion([gooseneck_timber_lap_prism, gooseneck_profile_difference_csg])

    gooseneck_extension_from_receiving_end = lap_length + gooseneck_length
    gooseneck_end_position_from_timber_end = gooseneck_timber_lap_shoulder_from_end - gooseneck_extension_from_receiving_end

    if gooseneck_timber_end == TimberEnd.TOP:
        gooseneck_end_cut_local_z = gooseneck_timber.length - gooseneck_end_position_from_timber_end
    else:
        gooseneck_end_cut_local_z = gooseneck_end_position_from_timber_end

    receiving_end_cut_local_z = None
    if receiving_timber_end_cut is not None:
        receiving_end_cut_local_z = (
            receiving_timber_end_cut.offset
            if receiving_timber_end == TimberEnd.TOP
            else -receiving_timber_end_cut.offset
        )

    gooseneck_csg_on_receiving_timber = adopt_csg(gooseneck_timber.transform, receiving_timber.transform, gooseneck_profile_csg)

    if lap_length > 0:
        receiving_timber_negative_csg: CutCSG = SolidUnion([receiving_timber_lap_prism, gooseneck_csg_on_receiving_timber])
    else:
        receiving_timber_negative_csg = gooseneck_csg_on_receiving_timber

    gooseneck_timber_freedom = AssemblyFreedom.translation(gooseneck_face_direction, freed_after=gooseneck_depth)
    receiving_timber_freedom = AssemblyFreedom.translation(-gooseneck_face_direction, freed_after=gooseneck_depth)

    receiving_timber_cut_obj = Cutting(
        timber=receiving_timber,
        maybe_top_end_cut_distance_from_bottom=receiving_end_cut_local_z if receiving_timber_end == TimberEnd.TOP else None,
        maybe_bottom_end_cut_distance_from_bottom=receiving_end_cut_local_z if receiving_timber_end == TimberEnd.BOTTOM else None,
        negative_csg=receiving_timber_negative_csg,
        label=CutCSGLabel("gooseneck_socket_cut"),
        assembly_freedom=receiving_timber_freedom,
    )
    gooseneck_timber_cut_obj = Cutting(
        timber=gooseneck_timber,
        maybe_top_end_cut_distance_from_bottom=gooseneck_end_cut_local_z if gooseneck_timber_end == TimberEnd.TOP else None,
        maybe_bottom_end_cut_distance_from_bottom=gooseneck_end_cut_local_z if gooseneck_timber_end == TimberEnd.BOTTOM else None,
        negative_csg=gooseneck_timber_combined_csg,
        label=CutCSGLabel("gooseneck_cut"),
        assembly_freedom=gooseneck_timber_freedom,
    )

    return Joint(
        cuttings={
            receiving_timber.ticket.path: receiving_timber_cut_obj,
            gooseneck_timber.ticket.path: gooseneck_timber_cut_obj,
        },
        ticket=JointTicket(joint_type="lapped_gooseneck"),
        jointAccessories={},
    )

cut_rebated_oblique_and_dadoed_scarf_joint_on_aligned_timbers

cut_rebated_oblique_and_dadoed_scarf_joint_on_aligned_timbers(arrangement: SpliceJointTimberArrangement, rebated_shoulder_depth: Numeric, scarf_length: Numeric, dado_depth: Numeric, dado_height: Numeric, stub_tenon_width: Numeric, rebated_shoulder_length: Optional[Numeric] = None, joint_center_relative_to_timber1_end: Numeric = scalar(0), lateral_offset_from_midline: Numeric = scalar(0)) -> Joint

Creates a rebated oblique and dadoed scarf joint (金輪継ぎ / Kanawa Tsugi).

arrangement.front_face_on_timber1 determines the face which scarf cut profile is visible on.

Parameters:

Name Type Description Default
arrangement SpliceJointTimberArrangement

Splice arrangement with timber1, timber2, timber1_end, timber2_end, and front_face_on_timber1.

required
rebated_shoulder_depth Numeric

(Tsuki-tsuke (突付)) determines the depth of the rebated shoulder cut in the scarf joint, the 2 rebated shoulders form the pin hole for the square peg forming the Kusabi-ana (楔穴)

required
scarf_length Numeric

determines the length of the scarf cut on each timber, it is measured from the corner that lies on the midline of the scarf joint of the opposite dadoes of the joint when fully assembled

required
dado_depth Numeric

the "depth" of both dadoes (measured in the length axis of the timbers)

required
dado_height Numeric

the "height" of both dadoes (measured in the long face axis of the long face adjacent front_face_on_timber1), the dado width is always the entire size of the timber in the front_face_on_timber1 axis

required
stub_tenon_width Numeric

the "width" of the stub tenon (measured in the long face axis of the long face adjacent front_face_on_timber1), the stub tenon depth is always the distance from the surface to the dado wall.

required
rebated_shoulder_length Optional[Numeric]

determines the length of the rebated shoulder cut in the scarf joint, if None, defaults to rebated_shoulder_depth (forming a rectangular peg hole). Note this measures the width of the rectangular hole that gets created by the rebated shoulder, rather than the width from corner to corner of the rectangular hole which is how you might draw this joint by hand.

None
joint_center_relative_to_timber1_end Numeric

determines the "center" of the joint (right in the middle of the rectangular peg hole) measured inward from the joint end of timber1. (positive means the joint center is further into timber1)

scalar(0)
lateral_offset_from_midline Numeric

determines the lateral offset of the joint profiles centerline from the midline of front_face_on_timber1

scalar(0)

Returns:

Type Description
Joint

Joint object containing both cut timbers and the peg accessory.

Notes

the oblique scarf face angle (Sogi-michi (斜面)) is determined by the rebated_shoulder_depth and the scarf_half_length all measurements are done relative to timber1, timber2 is expected to share the same axis and be the same size as timber1.

Source code in kumiki/joints/workshop/splice/half_blind_tenoned_dadoed_rabbeted_scarf_joint.py
def cut_rebated_oblique_and_dadoed_scarf_joint_on_aligned_timbers(
    arrangement: SpliceJointTimberArrangement,
    rebated_shoulder_depth: Numeric,
    scarf_length: Numeric,
    dado_depth: Numeric,
    dado_height: Numeric,
    stub_tenon_width: Numeric,
    rebated_shoulder_length: Optional[Numeric] = None,
    joint_center_relative_to_timber1_end: Numeric = scalar(0),
    lateral_offset_from_midline: Numeric = scalar(0),
) -> Joint:
    """
    Creates a rebated oblique and dadoed scarf joint (金輪継ぎ / Kanawa Tsugi).

    arrangement.front_face_on_timber1 determines the face which scarf cut profile is visible on.

    Args:
        arrangement: Splice arrangement with timber1, timber2, timber1_end, timber2_end,
            and front_face_on_timber1.
        rebated_shoulder_depth: (Tsuki-tsuke (突付)) determines the depth of the rebated shoulder cut in the scarf joint, the 2 rebated shoulders form the pin hole for the square peg forming the Kusabi-ana (楔穴)
        scarf_length: determines the length of the scarf cut on each timber, it is measured from the corner that lies on the midline of the scarf joint of the opposite dadoes of the joint when fully assembled
        dado_depth: the "depth" of both dadoes (measured in the length axis of the timbers)
        dado_height: the "height" of both dadoes (measured in the long face axis of the long face adjacent front_face_on_timber1), the dado width is always the entire size of the timber in the front_face_on_timber1 axis
        stub_tenon_width: the "width" of the stub tenon (measured in the long face axis of the long face adjacent front_face_on_timber1), the stub tenon depth is always the distance from the surface to the dado wall.
        rebated_shoulder_length: determines the length of the rebated shoulder cut in the scarf joint, if None, defaults to rebated_shoulder_depth (forming a rectangular peg hole). Note this measures the width of the rectangular hole that gets created by the rebated shoulder, rather than the width from corner to corner of the rectangular hole which is how you might draw this joint by hand.
        joint_center_relative_to_timber1_end: determines the "center" of the joint (right in the middle of the rectangular peg hole) measured inward from the joint end of timber1. (positive means the joint center is further into timber1)
        lateral_offset_from_midline: determines the lateral offset of the joint profiles centerline from the midline of front_face_on_timber1

    Returns:
        Joint object containing both cut timbers and the peg accessory.

    Notes:
        the oblique scarf face angle (Sogi-michi (斜面)) is determined by the rebated_shoulder_depth and the scarf_half_length
        all measurements are done relative to timber1, timber2 is expected to share the same axis and be the same size as timber1.
    """
    require_check(arrangement.check_face_aligned_and_parallel_axis())
    warn_if_arrangement_timbers_imperfect(arrangement)
    assert arrangement.front_face_on_timber1 is not None, (
        "arrangement.front_face_on_timber1 must be set to determine the joint orientation"
    )

    timber1 = arrangement.timber1
    timber2 = arrangement.timber2
    timber1_end = arrangement.timber1_end
    timber2_end = arrangement.timber2_end
    front_face = arrangement.front_face_on_timber1
    v_face = front_face.rotate_right()

    if rebated_shoulder_length is None:
        rebated_shoulder_length = rebated_shoulder_depth

    SL = scarf_length
    DD = dado_depth
    DH = dado_height
    SSD = rebated_shoulder_depth
    SSL = rebated_shoulder_length
    STW = stub_tenon_width

    require_check(
        None if SSL == SSD
        else "rebated_shoulder_length must equal rebated_shoulder_depth: the Kusabi peg accessory "
             "(kumiki.timber.Peg) only supports a square or round cross-section, so a non-square "
             "peg hole can't yet be represented by an accessory"
    )
    require_check(None if SL >= SSD else "rebated_shoulder_depth must be less than scarf_length")
    require_check(None if DD > 0 else "dado_depth must be positive")
    require_check(None if DH > 0 else "dado_height must be positive")
    require_check(None if SSD > 0 else "rebated_shoulder_depth must be positive")
    require_check(None if SL > 0 else "scarf_length must be positive")
    require_check(None if STW > 0 else "stub_tenon_width must be positive")

    some_var_x = sqrt((SL / scalar(2)) ** 2 - (SSD / scalar(2)) ** 2)
    scarf_hypotneuse = some_var_x + SSL / scalar(2)
    scarf_angle = atan2(SSD / scalar(2), some_var_x)
    sin_scarf_angle = sin(scarf_angle)
    cos_scarf_angle = cos(scarf_angle)

    H = timber1.get_size_in_face_normal_axis(v_face)
    depth_size = timber1.get_size_in_face_normal_axis(front_face)

    require_check(None if STW < depth_size else "stub_tenon_width must be less than the timber's dimension in the front_face_on_timber1 normal axis")
    require_check(None if DH < H / scalar(2) else "dado_height must be less than half the timber's dimension in the front_face_on_timber1-adjacent axis")
    require_check(None if DD < (SL - SSD) / scalar(2) else "dado_depth must be less than (scarf_length - rebated_shoulder_depth) / 2")
    require_check(None if DH >= SSD else "dado_height must be at least rebated_shoulder_depth")

    u_dir = -timber1.get_face_direction_global(timber1_end)
    v_dir = timber1.get_face_direction_global(v_face)
    lateral_dir = arrangement.timber1.get_face_direction_global(front_face)

    if timber1_end == TimberEnd.TOP:
        timber1_end_position_global = locate_top_center_position(timber1).position
    else:
        timber1_end_position_global = timber1.get_bottom_position_global()

    scarf_joint_center_global = timber1_end_position_global + u_dir * joint_center_relative_to_timber1_end + lateral_dir * lateral_offset_from_midline

    corner = (SL / scalar(2), scalar(0))
    p1 = (corner[0], DH)
    p2 = (p1[0] - DD, DH)
    p3 = (p2[0], H / scalar(2))

    p4 = (corner[0] - cos_scarf_angle * scarf_hypotneuse, corner[1] - (sin_scarf_angle * scarf_hypotneuse))
    p5 = (p4[0] - sin_scarf_angle * SSD, p4[1] + cos_scarf_angle * SSD)

    p6 = (-SL / scalar(2), 0)
    p7 = (p6[0], p6[1] - DH)
    p8 = (p7[0] + DD, p7[1])
    p9 = (p8[0], -H / scalar(2))

    left_boundary_u = -SL / scalar(2)
    p10 = (left_boundary_u, p3[1])
    p11 = (left_boundary_u, p9[1])
    profile_points = [p3, p2, p1, corner, p4, p5, p6, p7, p8, p9, p11, p10]

    depth_dir = timber1.get_face_direction_global(front_face)
    profile_orientation = Orientation(Matrix([
        [u_dir[0], v_dir[0], depth_dir[0]],
        [u_dir[1], v_dir[1], depth_dir[1]],
        [u_dir[2], v_dir[2], depth_dir[2]],
    ]))
    profile_transform = Transform(position=scarf_joint_center_global, orientation=profile_orientation)

    convex_pieces = [
        ConvexPolygonExtrusion(
            points=quad,
            transform=profile_transform,
            start_distance=-depth_size,
            end_distance=depth_size,
        )
        for quad in decompose_simple_polygon_into_convex_pieces(
            [create_v2(u, v) for (u, v) in profile_points]
        )
    ]

    timber1_profile_csg_global = SolidUnion(convex_pieces, label=CutCSGLabel("scarf_profile"))

    def reflect(p: Tuple[Numeric, Numeric]) -> Tuple[Numeric, Numeric]:
        return (-p[0], -p[1])

    corner2 = reflect(corner)
    p1_2 = reflect(p1)
    p2_2 = reflect(p2)
    p3_2 = reflect(p3)
    p4_2 = reflect(p4)
    p5_2 = reflect(p5)
    p6_2 = reflect(p6)
    p7_2 = reflect(p7)
    p8_2 = reflect(p8)
    p9_2 = reflect(p9)

    right_boundary_u = -left_boundary_u
    p10_2 = (right_boundary_u, p9_2[1])
    p11_2 = (right_boundary_u, p3_2[1])
    profile_points_2 = [p3_2, p2_2, p1_2, corner2, p4_2, p5_2, p6_2, p7_2, p8_2, p9_2, p10_2, p11_2]

    convex_pieces_2 = [
        ConvexPolygonExtrusion(
            points=quad,
            transform=profile_transform,
            start_distance=-depth_size,
            end_distance=depth_size,
        )
        for quad in decompose_simple_polygon_into_convex_pieces(
            [create_v2(u, v) for (u, v) in profile_points_2]
        )
    ]

    timber2_profile_csg_global = SolidUnion(convex_pieces_2, label=CutCSGLabel("scarf_profile"))

    v_face_size = timber1.get_size_in_face_normal_axis(v_face)
    timber1_stub_tenon_middle_surface_outer_edge_point = scarf_joint_center_global - scarf_length / scalar(2) * u_dir - v_dir * v_face_size / scalar(2)
    timber1_stub_tenon_middle_surface_inner_edge_point = timber1_stub_tenon_middle_surface_outer_edge_point + u_dir * dado_depth
    timber1_stub_tenon_start = (timber1_stub_tenon_middle_surface_outer_edge_point + timber1_stub_tenon_middle_surface_inner_edge_point) / scalar(2)
    timber1_stub_tenon_prism = RectangularPrism(
        transform=Transform(position=timber1_stub_tenon_start, orientation=Orientation.from_z_and_y(v_dir, u_dir)),
        start_distance=0,
        end_distance=v_face_size / scalar(2) - dado_depth,
        size=create_v2(stub_tenon_width, dado_depth),
        label=CutCSGLabel("stub_tenon"),
    )

    def reflect_about_joint_center(point: V3):
        relative = point - scarf_joint_center_global
        reflected = -relative
        return reflected + scarf_joint_center_global

    timber2_stub_tenon_prism = RectangularPrism(
        transform=Transform(position=reflect_about_joint_center(timber1_stub_tenon_start), orientation=Orientation.from_z_and_y(-v_dir, -u_dir)),
        start_distance=0,
        end_distance=v_face_size / scalar(2) - dado_depth,
        size=create_v2(stub_tenon_width, dado_depth),
        label=CutCSGLabel("stub_tenon"),
    )

    # A stub tenon is the material the scarf cut leaves standing, so these faces are the
    # stub's own: its two cheeks and the face it turns to the end of its timber. Its
    # front face looks back into the standing timber, and its start cap lies in the
    # timber's own long face, so neither is named -- a name there is one nothing can
    # pick, or one the body's own face already answers for.
    #
    # Each stub is also the socket the other timber is cut with, so only the copy that
    # stays is named: the plain prisms these are copied from still cut the sockets, and a
    # socket wall is the receiving timber's surface, not a face of the stub.
    stub_tenon_overrides = [
        FeatureOverride(prism_face_key(PrismFace.RIGHT), "stub_tenon_right", measured_against(_STUB_TENON_FACE, "stub_tenon_left")),
        FeatureOverride(prism_face_key(PrismFace.LEFT), "stub_tenon_left", measured_against(_STUB_TENON_FACE, "stub_tenon_right")),
        FeatureOverride(prism_face_key(PrismFace.BACK), "stub_tenon_back", _STUB_TENON_FACE),
    ]
    timber1_stub_tenon_standing = replace(
        timber1_stub_tenon_prism, feature_overrides=stub_tenon_overrides,
    )
    timber2_stub_tenon_standing = replace(
        timber2_stub_tenon_prism, feature_overrides=stub_tenon_overrides,
    )

    timber1_with_stubs_global = SolidUnion([
        Difference(
            timber1_profile_csg_global, [timber1_stub_tenon_standing],
            label=CutCSGLabel("scarf_waste"),
        ),
        timber2_stub_tenon_prism,
    ])
    timber1_negative_csg_local = adopt_csg(None, timber1.transform, timber1_with_stubs_global)

    timber2_with_stubs_global = SolidUnion([
        Difference(
            timber2_profile_csg_global, [timber2_stub_tenon_standing],
            label=CutCSGLabel("scarf_waste"),
        ),
        timber1_stub_tenon_prism,
    ])
    timber2_negative_csg_local = adopt_csg(None, timber2.transform, timber2_with_stubs_global)

    left_boundary_global = scarf_joint_center_global + u_dir * left_boundary_u
    left_boundary_distance_from_bottom = safe_dot_product(
        left_boundary_global - timber1.get_bottom_position_global(),
        timber1.get_length_direction_global(),
    )

    right_boundary_global = scarf_joint_center_global + u_dir * right_boundary_u
    right_boundary_distance_from_bottom = safe_dot_product(
        right_boundary_global - timber2.get_bottom_position_global(),
        timber2.get_length_direction_global(),
    )

    disassembly_direction = u_dir * (p4[0] - corner[0]) + v_dir * (p4[1] - corner[1])
    timber1_freedom = AssemblyFreedom.translation(-disassembly_direction, freed_after=DD)
    timber2_freedom = AssemblyFreedom.translation(disassembly_direction, freed_after=DD)

    timber1_cut = Cutting(
        timber=timber1,
        maybe_top_end_cut_distance_from_bottom=left_boundary_distance_from_bottom if timber1_end == TimberEnd.TOP else None,
        maybe_bottom_end_cut_distance_from_bottom=left_boundary_distance_from_bottom if timber1_end == TimberEnd.BOTTOM else None,
        negative_csg=timber1_negative_csg_local,
        label=CutCSGLabel("scarf_cut"),
        assembly_freedom=timber1_freedom,
    )

    timber2_cut = Cutting(
        timber=timber2,
        maybe_top_end_cut_distance_from_bottom=right_boundary_distance_from_bottom if timber2_end == TimberEnd.TOP else None,
        maybe_bottom_end_cut_distance_from_bottom=right_boundary_distance_from_bottom if timber2_end == TimberEnd.BOTTOM else None,
        negative_csg=timber2_negative_csg_local,
        label=CutCSGLabel("scarf_cut"),
        assembly_freedom=timber2_freedom,
    )
    front_face_dir = arrangement.timber1.get_face_direction_global(arrangement.front_face_on_timber1)

    peg_orientation = Orientation.from_z_and_y(front_face_dir, v_dir)
    peg_orientation = Orientation.from_angle_axis(scarf_angle, front_face_dir) * peg_orientation
    peg = Peg(
        transform=Transform(position=scarf_joint_center_global, orientation=peg_orientation),
        size=rebated_shoulder_depth,
        shape=PegShape.SQUARE,
        forward_length=depth_size * scalar(3 / 5),
        stickout_length=depth_size * scalar(3 / 5),
    )

    return Joint(
        cuttings={
            timber1.ticket.path: timber1_cut,
            timber2.ticket.path: timber2_cut,
        },
        ticket=JointTicket(joint_type="half_blind_tenoned_dadoed_rabbeted_scarf"),
        jointAccessories={"peg": peg},
    )

cut_half_blind_tenoned_dadoed_rabbeted_scarf_joint_on_aligned_timbers

cut_half_blind_tenoned_dadoed_rabbeted_scarf_joint_on_aligned_timbers(arrangement: SpliceJointTimberArrangement, stepped_shoulder_depth: Optional[Numeric] = None, scarf_length: Optional[Numeric] = None, dado_depth: Optional[Numeric] = None, dado_height: Optional[Numeric] = None, stub_tenon_width: Optional[Numeric] = None, stepped_shoulder_length: Optional[Numeric] = None, joint_center_relative_to_timber1_end: Numeric = scalar(0), lateral_offset_from_midline: Numeric = scalar(0), *, rebated_shoulder_depth: Optional[Numeric] = None, rebated_shoulder_length: Optional[Numeric] = None) -> Joint

Compatibility alias for cut_rebated_oblique_and_dadoed_scarf_joint_on_aligned_timbers.

Source code in kumiki/joints/workshop/splice/half_blind_tenoned_dadoed_rabbeted_scarf_joint.py
def cut_half_blind_tenoned_dadoed_rabbeted_scarf_joint_on_aligned_timbers(
    arrangement: SpliceJointTimberArrangement,
    stepped_shoulder_depth: Optional[Numeric] = None,
    scarf_length: Optional[Numeric] = None,
    dado_depth: Optional[Numeric] = None,
    dado_height: Optional[Numeric] = None,
    stub_tenon_width: Optional[Numeric] = None,
    stepped_shoulder_length: Optional[Numeric] = None,
    joint_center_relative_to_timber1_end: Numeric = scalar(0),
    lateral_offset_from_midline: Numeric = scalar(0),
    *,
    rebated_shoulder_depth: Optional[Numeric] = None,
    rebated_shoulder_length: Optional[Numeric] = None,
) -> Joint:
    """Compatibility alias for cut_rebated_oblique_and_dadoed_scarf_joint_on_aligned_timbers."""
    actual_depth = rebated_shoulder_depth if rebated_shoulder_depth is not None else stepped_shoulder_depth
    actual_length = rebated_shoulder_length if rebated_shoulder_length is not None else stepped_shoulder_length
    assert actual_depth is not None, "rebated_shoulder_depth (or stepped_shoulder_depth) must be provided"
    assert scarf_length is not None, "scarf_length must be provided"
    assert dado_depth is not None, "dado_depth must be provided"
    assert dado_height is not None, "dado_height must be provided"
    assert stub_tenon_width is not None, "stub_tenon_width must be provided"
    return cut_rebated_oblique_and_dadoed_scarf_joint_on_aligned_timbers(
        arrangement=arrangement,
        rebated_shoulder_depth=actual_depth,
        scarf_length=scarf_length,
        dado_depth=dado_depth,
        dado_height=dado_height,
        stub_tenon_width=stub_tenon_width,
        rebated_shoulder_length=actual_length,
        joint_center_relative_to_timber1_end=joint_center_relative_to_timber1_end,
        lateral_offset_from_midline=lateral_offset_from_midline,
    )