Skip to content

kumiki.frame_decorators

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

kumiki.frame_decorators

Marking the frames and tools in a file for kigumi to find.

A file may declare any number of frames and tools. Each frame declares its own parameters, resolves the values it is handed onto them, and returns them on the Frame::

STOOL = kiwari(legs=kiwari.count(4, minimum=3))

@frame
def stool(k: Optional[Kiwari] = None) -> Frame:
    k = STOOL.resolve(k)
    ...
    return Frame.from_joints(joints, kiwari=k)

@frame
def bench(k: Optional[Kiwari] = None) -> Frame:
    k = STOOL.resolve(k)    # the same kiwari object: one set of parameters for both
    ...

@tool
def leg_report(frame: Frame) -> str:
    return f"{frame.kiwari.count('legs')} legs"

Kigumi builds every @frame and shows them together. Frames whose kiwari were resolved from the same kiwari object share one set of parameters (see Kiwari.origin); each different one gets its own section in the parameters panel. A frame is handed back the values for its own kiwari, or None on the first build. A @tool runs on demand against the shown frame and its text is displayed. The decorators only mark a function; it is still an ordinary function to call from a script or a test.

A marked function is admitted only if its annotations say exactly that: (k: Kiwari) -> Frame for a frame, (frame: Frame) -> str for a tool. Optional[Kiwari] is accepted for the parameters.

A pattern book marks each of its patterns instead, with a path and, if it takes parameters, its own kiwari::

@pattern("basic_joints/basic_mortise_and_tenon", tags=["main"], kiwari=MORTISE_AND_TENON_OPTIONS)
def basic_mortise_and_tenon(k: Optional[Kiwari] = None) -> Joint:
    ...

A pattern takes (k: Kiwari) if it declares a kiwari and nothing otherwise, and returns a Frame, a Joint or a CutCSG. Further parameters are allowed if they have defaults.

FRAME module-attribute

FRAME = 'frame'

TOOL module-attribute

TOOL = 'tool'

PATTERN module-attribute

PATTERN = 'pattern'

F module-attribute

F = TypeVar('F', bound=Callable[..., Any])

PatternDeclaration dataclass

PatternDeclaration(path: str, tags: Tuple[str, ...], kiwari: Optional[Kiwari])

path instance-attribute

path: str

tags instance-attribute

tags: Tuple[str, ...]

kiwari instance-attribute

kiwari: Optional[Kiwari]

Entry dataclass

Entry(name: str, function: Callable[..., Any])

name instance-attribute

name: str

function instance-attribute

function: Callable[..., Any]

Rejected dataclass

Rejected(name: str, kind: str, reason: str)

name instance-attribute

name: str

kind instance-attribute

kind: str

reason instance-attribute

reason: str

ModuleEntries dataclass

ModuleEntries(frames: Tuple[Entry, ...], tools: Tuple[Entry, ...], rejected: Tuple[Rejected, ...])

What a module marked: its admitted frames and tools, and those rejected.

frames instance-attribute

frames: Tuple[Entry, ...]

tools instance-attribute

tools: Tuple[Entry, ...]

rejected instance-attribute

rejected: Tuple[Rejected, ...]

frame

frame(function: F) -> F

Marks a function (k: Kiwari) -> Frame as one of the file's frames.

It returns its parameters on the Frame: Frame(..., kiwari=k).

Source code in kumiki/frame_decorators.py
def frame(function: F) -> F:
    """Marks a function `(k: Kiwari) -> Frame` as one of the file's frames.

    It returns its parameters on the Frame: `Frame(..., kiwari=k)`.
    """
    setattr(function, _MARK, FRAME)
    return function

tool

tool(function: F) -> F

Marks a function (frame: Frame) -> str as a tool to run on the file's frame.

Source code in kumiki/frame_decorators.py
def tool(function: F) -> F:
    """Marks a function `(frame: Frame) -> str` as a tool to run on the file's frame."""
    setattr(function, _MARK, TOOL)
    return function

pattern

pattern(path: str, *, tags: Sequence[str] = (), kiwari: Optional[Kiwari] = None) -> Callable[[F], F]

Marks a function as one of a pattern book's patterns, at path, with its own kiwari if it takes one.

path is hierarchical ("corner_joints/plain_miter_joint"); each segment is also a tag. Of the tags, 'main' shows the pattern when the file is opened and 'poop' hides it from the sidebar.

Source code in kumiki/frame_decorators.py
def pattern(path: str, *, tags: Sequence[str] = (), kiwari: Optional[Kiwari] = None) -> Callable[[F], F]:
    """Marks a function as one of a pattern book's patterns, at `path`, with its own kiwari if it takes one.

    `path` is hierarchical ("corner_joints/plain_miter_joint"); each segment is also a tag. Of the
    tags, 'main' shows the pattern when the file is opened and 'poop' hides it from the sidebar.
    """
    if not isinstance(path, str) or not path:
        raise TypeError("@pattern needs a path, e.g. @pattern(\"corner_joints/plain_miter_joint\")")

    def mark(function: F) -> F:
        setattr(function, _MARK, PATTERN)
        setattr(function, _PATTERN_DECLARATION, PatternDeclaration(path, tuple(tags), kiwari))
        return function

    return mark

module_entries

module_entries(module: Any) -> ModuleEntries

The frames and tools module marked, in source order, each admitted or rejected.

Source code in kumiki/frame_decorators.py
def module_entries(module: Any) -> ModuleEntries:
    """The frames and tools `module` marked, in source order, each admitted or rejected."""
    marked = _marked(module, (FRAME, TOOL))
    frames: List[Entry] = []
    tools: List[Entry] = []
    rejected: List[Rejected] = []
    for name, function in marked:
        kind = getattr(function, _MARK)
        problem = _signature_problem(function, kind)
        if problem is not None:
            rejected.append(Rejected(name, kind, problem))
        else:
            (frames if kind == FRAME else tools).append(Entry(name, function))
    return ModuleEntries(tuple(frames), tuple(tools), tuple(rejected))

module_patterns

module_patterns(module: Any) -> Tuple[List[Any], Tuple[Rejected, ...]]

The patterns module marked with @pattern, in source order, and those rejected for their signature.

Source code in kumiki/frame_decorators.py
def module_patterns(module: Any) -> Tuple[List[Any], Tuple[Rejected, ...]]:
    """The patterns `module` marked with @pattern, in source order, and those rejected for their signature."""
    from .csg.cutcsg import CutCSG
    from .patternbook import Pattern, make_pattern_from_csg, make_pattern_from_frame, make_pattern_from_joint
    from .timber import Joint

    patterns: List[Any] = []
    rejected: List[Rejected] = []
    for name, function in _marked(module, (PATTERN,)):
        problem = _signature_problem(function, PATTERN)
        if problem is not None:
            rejected.append(Rejected(name, PATTERN, problem))
            continue
        declaration: PatternDeclaration = getattr(function, _PATTERN_DECLARATION)
        returns = typing.get_type_hints(function)["return"]
        if _is_subclass(returns, CutCSG):
            lambda_, pattern_type = make_pattern_from_csg(function), "csg"
        elif _is_class(returns, Joint):
            lambda_, pattern_type = make_pattern_from_joint(function), "frame"
        else:
            lambda_, pattern_type = make_pattern_from_frame(function), "frame"
        patterns.append(Pattern(path=declaration.path, lambda_=lambda_, tags=list(declaration.tags),
                                pattern_type=pattern_type, kiwari=declaration.kiwari))
    return patterns, tuple(rejected)

overlay_frames

overlay_frames(frames: List[Any], name: Optional[str]) -> Any

Several frames shown together, each where it was built.

The overlay carries a kiwari only when every frame was built from the same one; otherwise each frame's parameters are its own, and stay on it.

Source code in kumiki/frame_decorators.py
def overlay_frames(frames: List[Any], name: Optional[str]) -> Any:
    """Several frames shown together, each where it was built.

    The overlay carries a kiwari only when every frame was built from the same one; otherwise
    each frame's parameters are its own, and stay on it.
    """
    from .timber import Frame

    if len(frames) == 1:
        return frames[0]
    kiwaris = [getattr(built, "kiwari", None) for built in frames]
    origins = {id(k.origin) for k in kiwaris if k is not None}
    shared = kiwaris[0] if None not in kiwaris and len(origins) == 1 else None
    joints = [joint for built in frames for joint in (built.source_joints or [])]
    return Frame(
        cut_timbers=[timber for built in frames for timber in built.cut_timbers],
        accessories=[accessory for built in frames for accessory in built.accessories],
        footprints=[footprint for built in frames for footprint in built.footprints],
        drawings=[drawing for built in frames for drawing in built.drawings],
        source_joints=joints or None,
        name=name,
        kiwari=shared,
    )