Skip to content

Common

Dependency-free primitives every stage shares: config areas, tags, vectors, protocols, logging, progress and console formatting.

femforge.common

Dependency-free primitives shared by every stage: config areas, tags, vectors, protocols, logging, progress UI, and console formatting.

Modules:

  • cli –

    The failure every femforge subcommand raises, shared so a command module needs no import back into the entry point.

  • config –

    The shared base of the config area models.

  • files –

    The run-bundle filename vocabulary every writer and the retention sweep share, plus the file primitives they use.

  • frame –

    The solve frame: how an authored model maps onto the frame Elmer solves in.

  • introspect –

    Source-level reflection: the attribute docstrings an IDE shows on hover, read back at runtime.

  • log –

    The only logging config site; every Rich output path (RichSink, StepTimer, tree prints) must share the one stderr Console from get_console().

  • names –

    Memorable bundle names derived deterministically from the content hash: <adjective>-<noun>-<hash8>.

  • progress –

    Progress UI: live per-step elapsed-time rows rendered on the shared console.

  • protocols –

    Structural typing contracts shared across pipeline stages.

  • pydantic –

    Field-level Annotated markers read back by reflection: Map() ties a field to an external key name and HashExclude() keeps it out of a content hash.

  • rich_sink –

    Rich-powered Loguru sink that renders each record as a fixed-column row via a Rich Console.

  • tags –

    Tags: the shared key vocabulary, tagged objects and the grouping of tagged items into hierarchies.

  • utils –

    Console formatting helpers: Rich color styles, Title Case labels and markup handling.

  • vector –

    Small geometric value types: 2D and 3D vectors, unit directions, boxes, quaternions and coordinate frames.

Type Aliases:

  • Levels –

    The tag keys to group by at each level, outermost first.

  • Nested –

    A leaf X or a TagGroup of nested nodes.

  • TagDict –

    Tag key=value pairs; a key is a TagKey or a user-defined name.

Classes:

  • ConfigArea –

    Shared base for the config area models: arbitrary_types_allowed plus use_attribute_docstrings.

  • StepTimer –

    Context manager showing a live elapsed-time line for one pipeline step.

  • TimerRenderable –

    Live-updating timer row; Rich calls __rich__ on every refresh tick.

  • ElmerEmitter –

    Something that emits its own Elmer sif keywords; materials, conditions, and boundaries all satisfy it.

  • MeshGroupSource –

    Something that resolves to one or more gmsh physical groups.

  • Map –

    Annotated metadata marker tying a model field to an external key name.

  • RichSink –

    Loguru sink callable; add with format="{message}" and colorize=False so Rich owns all styling.

  • CurrentDir –

    Values of TagKey.CURRENT_DIR: whether an electrode part is a current entry or exit.

  • HasTags –

    A mixin that holds an object's tag key=value pairs.

  • TagGroup –

    A list of tagged items or nested groups that carries its own tags.

  • TagGrouper –

    Groups tagged items into a TagGroup hierarchy, one level per list of tag keys.

  • TagKey –

    The shared tag-key vocabulary: geometry authors these on parts, meshing/FEA select by them.

  • COLORS –

    For use in rich console output. Not for physical materials.

  • Box –

    An axis-aligned 3D box spanned by its min and max corners.

  • CoordinateFrame –

    A position and orientation that maps local coordinates to global ones.

  • Direction –

    A unit-length orientation, normalized and rounded on construction; arithmetic on it returns a plain Vec3.

  • Quaternion –

    A rotation quaternion w + xi + yj + zk.

  • Rect –

    An axis-aligned 2D rectangle spanned by its min and max corners.

  • Vec2 –

    A mutable 2D vector.

  • Vec3 –

    An immutable 3D vector; == and hash compare its exact components.

Functions:

  • get_console –

    Return the process-wide stderr Console shared by logging and all progress/tree rendering.

  • run_log_context –

    Bind run_id into the log context and write every record at DEBUG or above that carries it to run_dir/run.log.

  • setup_logging –

    Install the terminal RichSink plus stdlib/warnings interception; repeat calls only adjust the sink level, library gates and telemetry root.

  • fmt_time –

    Format a duration in seconds as m:ss.s or s.s, right-justified to width.

  • get_map_key –

    The external key from a field's Map() metadata, or None if the field carries no Map().

  • mapped_items –

    Yield (external_key, value) for each field carrying a Map() whose current value is not None.

  • map_group_leaves –

    Collapse each deepest bucket of sibling leaves (a list[X]) into one Y,

  • map_leaves –

    Transform every leaf X -> Y, keeping the hierarchy identical.

  • parse_tags –

    The key=value pairs found in a label / STEP-name path; a repeated key keeps its last (deepest) value.

  • contains_rich_markup –

    Whether text looks like it carries Rich markup.

  • preserve_and_format –

    Preserves existing string formatting and applies a new style safely.

  • title –

    A label or identifier in Title Case: underscores and camelCase humps become spaces, acronyms are capitalized, joining words stay lower case.

Attributes:

DirectionField module-attribute

DirectionField = Direction

A Direction pydantic field, normalized on load.

Vec3Field module-attribute

Vec3Field = Vec3

A Vec3 pydantic field, loaded from a Vec3, [x, y, z] or {x, y, z}.

Levels

Levels = list[list[str]]

The tag keys to group by at each level, outermost first.

Nested

Nested = X | TagGroup[Nested[X]]

A leaf X or a TagGroup of nested nodes.

TagDict

TagDict = dict[TagKey | str, str]

Tag key=value pairs; a key is a TagKey or a user-defined name.

ConfigArea pydantic-model

Bases: BaseModel

Shared base for the config area models: arbitrary_types_allowed plus use_attribute_docstrings.

StepTimer

StepTimer(title: str, msg: str = '', console: Console | None = None)

Bases: Live

Context manager showing a live elapsed-time line for one pipeline step.

Parameters:

  • title (str) –

    The step name.

  • msg (str, default: '' ) –

    A status message shown after the step name.

  • console (Console | None, default: None ) –

    The console to render on; None uses the shared one.

Source code in src/femforge/common/progress.py
def __init__(self, title: str, msg: str = "", console: Console | None = None) -> None:
    """Prepare the live line for one step.

    Args:
        title: The step name.
        msg: A status message shown after the step name.
        console: The console to render on; None uses the shared one.
    """
    self._timer_renderable = TimerRenderable(title, msg)
    super().__init__(
        self._timer_renderable,
        console=console if console is not None else get_console(),
        refresh_per_second=4,
        auto_refresh=True,
    )

TimerRenderable

TimerRenderable(title: str, msg: str)

Bases: RichCast

Live-updating timer row; Rich calls __rich__ on every refresh tick.

Methods:

Source code in src/femforge/common/progress.py
def __init__(self, title: str, msg: str) -> None:
    """Start timing the step ``title``, shown with the status ``msg``."""
    self.title = title
    self.message = msg
    self.start_time = time.time()
    self.completed = False

elapsed

elapsed() -> float

Seconds since the timer started.

Source code in src/femforge/common/progress.py
def elapsed(self) -> float:
    """Seconds since the timer started."""
    return time.time() - self.start_time

elapsed_str

elapsed_str() -> str

The elapsed time as fmt_time formats it.

Source code in src/femforge/common/progress.py
def elapsed_str(self) -> str:
    """The elapsed time as [`fmt_time`][femforge.common.progress.fmt_time] formats it."""
    return fmt_time(self.elapsed())

ElmerEmitter

Bases: Protocol

Something that emits its own Elmer sif keywords; materials, conditions, and boundaries all satisfy it.

Methods:

  • to_elmer –

    This object's Elmer sif keywords.

to_elmer

to_elmer(**ctx: object) -> dict[str, object]

This object's Elmer sif keywords.

Source code in src/femforge/common/protocols.py
def to_elmer(self, **ctx: object) -> dict[str, object]:
    """This object's Elmer sif keywords."""
    ...

MeshGroupSource

Bases: Protocol

Something that resolves to one or more gmsh physical groups.

Methods:

physical_group_name

physical_group_name() -> str

The stable group name the Elmer side references the region by.

Source code in src/femforge/common/protocols.py
def physical_group_name(self) -> str:
    """The stable group name the Elmer side references the region by."""
    ...

Map

Map(map: str)

Annotated metadata marker tying a model field to an external key name.

Example: Annotated[bool, Map("Geometry.OCCImportLabels"), Field(...)] -- the gmsh option name lives on the field.

Source code in src/femforge/common/pydantic.py
def __init__(self, map: str) -> None:
    """Tie the field to the external key ``map``."""
    self.map = map

RichSink

RichSink(console: Console | None = None, *, time_format: str = '%H:%M:%S.%f', show_path: bool = True, path_width: int = 0, rich_tracebacks: bool = True, show_locals: bool = False, markup: bool = False, hide_keys: frozenset[str] = frozenset())

Loguru sink callable; add with format="{message}" and colorize=False so Rich owns all styling.

Parameters:

  • console (Console | None, default: None ) –

    The console to print to; None prints to a new stderr console.

  • time_format (str, default: '%H:%M:%S.%f' ) –

    The strftime format of the time column.

  • show_path (bool, default: True ) –

    Show the module:function column.

  • path_width (int, default: 0 ) –

    Truncate the location column to this width, keeping its tail; 0 sizes it to its content.

  • rich_tracebacks (bool, default: True ) –

    Render a logged exception as a Rich traceback.

  • show_locals (bool, default: False ) –

    Include local variables in Rich tracebacks.

  • markup (bool, default: False ) –

    Parse Rich markup in messages.

  • hide_keys (frozenset[str], default: frozenset() ) –

    The bound extra keys to leave out of the context column.

Source code in src/femforge/common/rich_sink.py
def __init__(
    self,
    console: Console | None = None,
    *,
    time_format: str = "%H:%M:%S.%f",
    show_path: bool = True,
    path_width: int = 0,
    rich_tracebacks: bool = True,
    show_locals: bool = False,
    markup: bool = False,
    hide_keys: frozenset[str] = frozenset(),
) -> None:
    """Configure the sink's columns and styling.

    Args:
        console: The console to print to; None prints to a new stderr console.
        time_format: The ``strftime`` format of the time column.
        show_path: Show the ``module:function`` column.
        path_width: Truncate the location column to this width, keeping its tail; 0 sizes it to its content.
        rich_tracebacks: Render a logged exception as a Rich traceback.
        show_locals: Include local variables in Rich tracebacks.
        markup: Parse Rich markup in messages.
        hide_keys: The bound ``extra`` keys to leave out of the context column.
    """
    self.console = console if console is not None else Console(stderr=True)
    self.time_format = time_format
    self.show_path = show_path
    self.path_width = path_width
    self.rich_tracebacks = rich_tracebacks
    self.show_locals = show_locals
    self.markup = markup
    self.hide_keys = hide_keys

CurrentDir

Bases: StrEnum

Values of TagKey.CURRENT_DIR: whether an electrode part is a current entry or exit.

Attributes:

  • ENTRY –

    The electrode where current enters.

  • EXIT –

    The electrode where current leaves.

ENTRY class-attribute instance-attribute

ENTRY = 'Entry'

The electrode where current enters.

EXIT class-attribute instance-attribute

EXIT = 'Exit'

The electrode where current leaves.

HasTags

HasTags(tags: TagDict | None = None)

A mixin that holds an object's tag key=value pairs.

Methods:

  • set_tag –

    Set key to the cleaned value.

  • add_tag –

    Set key to the cleaned value, like set_tag.

  • get_tag –

    The value of key, or default when it is unset.

  • get_tags –

    A copy of the tags, or of only keys with an unset key as an empty string.

  • get_key_string –

    Return a string representation of the tags for the given keys.

  • remove_tag –

    Drop key when it is set.

  • clear_tags –

    Drop every tag.

  • set_tags –

    Replace every tag with tags, each value cleaned.

  • tags_str –

    The tags as comma-separated key=value pairs.

Attributes:

Source code in src/femforge/common/tags.py
def __init__(self, tags: TagDict | None = None):
    """Start with ``tags``, each value cleaned, or with none."""
    self.tags: TagDict = {key: clean_tag(key, value) for key, value in tags.items()} if tags is not None else {}

current_tags property

current_tags: TagDict

A copy of the tags.

set_tag

set_tag(key: str, value: str)

Set key to the cleaned value.

Source code in src/femforge/common/tags.py
def set_tag(self, key: str, value: str):
    """Set ``key`` to the cleaned ``value``."""
    self.tags[key] = clean_tag(key, value)

add_tag

add_tag(key: str, value: str)

Set key to the cleaned value, like set_tag.

Source code in src/femforge/common/tags.py
def add_tag(self, key: str, value: str):
    """Set ``key`` to the cleaned ``value``, like ``set_tag``."""
    self.set_tag(key, value)

get_tag

get_tag(key: str, default: str | None = None) -> str | None

The value of key, or default when it is unset.

Source code in src/femforge/common/tags.py
def get_tag(self, key: str, default: str | None = None) -> str | None:
    """The value of ``key``, or ``default`` when it is unset."""
    return self.tags.get(key, default)

get_tags

get_tags(keys: list[str] | None = None) -> TagDict

A copy of the tags, or of only keys with an unset key as an empty string.

Source code in src/femforge/common/tags.py
def get_tags(self, keys: list[str] | None = None) -> TagDict:
    """A copy of the tags, or of only ``keys`` with an unset key as an empty string."""
    if keys is None:
        return self.tags.copy()
    return {key: self.tags.get(key, "") for key in keys}

get_key_string

get_key_string(keys: list[str], default: str = 'default') -> str

Return a string representation of the tags for the given keys.

Source code in src/femforge/common/tags.py
def get_key_string(self, keys: list[str], default: str = "default") -> str:
    """Return a string representation of the tags for the given keys."""
    return "_".join([f"{key} {value}" for key, value in self.get_tags(keys).items()]) or default

remove_tag

remove_tag(key: str)

Drop key when it is set.

Source code in src/femforge/common/tags.py
def remove_tag(self, key: str):
    """Drop ``key`` when it is set."""
    if key in self.tags:
        del self.tags[key]

clear_tags

clear_tags()

Drop every tag.

Source code in src/femforge/common/tags.py
def clear_tags(self):
    """Drop every tag."""
    self.tags.clear()

set_tags

set_tags(tags: TagDict)

Replace every tag with tags, each value cleaned.

Source code in src/femforge/common/tags.py
def set_tags(self, tags: TagDict):
    """Replace every tag with ``tags``, each value cleaned."""
    self.tags = {key: clean_tag(key, value) for key, value in tags.items()}

tags_str

tags_str() -> str

The tags as comma-separated key=value pairs.

Source code in src/femforge/common/tags.py
def tags_str(self) -> str:
    """The tags as comma-separated ``key=value`` pairs."""
    return ", ".join([f"{key}={value}" for key, value in self.tags.items()])

TagGroup

TagGroup(tags: TagDict, items: Iterable[T] = ())

Bases: HasTags, list[T]

A list of tagged items or nested groups that carries its own tags.

Methods:

  • flatten –

    Roll the hierarchy up into a flat list of leaves, merging each path's tags onto the item (its own tags win only for keys the hierarchy doesn't set). Mutates .tags in place.

  • tree –

    Return a tree representation of the tag group.

Source code in src/femforge/common/tags.py
def __init__(self, tags: TagDict, items: Iterable[T] = ()) -> None:
    """Hold ``items`` under ``tags``."""
    list.__init__(self, items)
    HasTags.__init__(self, tags)

flatten

flatten() -> list[X]

Roll the hierarchy up into a flat list of leaves, merging each path's tags onto the item (its own tags win only for keys the hierarchy doesn't set). Mutates .tags in place.

Source code in src/femforge/common/tags.py
def flatten[X: HasTags](self: "TagGroup[X | TagGroup[Any]]") -> list[X]:
    """Roll the hierarchy up into a flat list of leaves, merging each path's tags onto the item (its own tags win only for keys the hierarchy doesn't set). Mutates `.tags` in place."""
    out: list[X] = []

    def visit(node: object, inherited: TagDict) -> None:
        if isinstance(node, TagGroup):
            merged = {**inherited, **node.tags}
            for child in node:
                visit(child, merged)
        else:
            item = cast(X, node)

            item.tags = {**item.tags, **inherited}

            if isinstance(getattr(item, "label", None), str):
                setattr(item, "label", item.tags_str())

            out.append(item)

    visit(self, {})
    return out

tree

tree(parent: Tree | None = None) -> Tree

Return a tree representation of the tag group.

Source code in src/femforge/common/tags.py
def tree(self, parent: Tree | None = None) -> Tree:
    """Return a tree representation of the tag group."""
    # Tree.add(label) creates-and-appends a child node from a label; passing a Tree object would nest it as a label, not a child.
    label = self.tags_str()
    node = parent.add(label) if parent is not None else Tree(label=label)

    for item in self:
        if isinstance(item, TagGroup):
            item.tree(parent=node)
        else:
            node.add(str(item))

    return node

TagGrouper

TagGrouper(keys: list[str] | str | None = None, *, _levels: Levels | None = None)

Bases: HasTags

Groups tagged items into a TagGroup hierarchy, one level per list of tag keys.

Methods:

  • then_by –

    A grouper with keys added as the next level.

  • apply –

    Group items into a single root TagGroup (root has empty tags).

  • map –

    Group items and transform each leaf with fn.

  • map_groups –

    Group items and collapse each deepest bucket of leaves with fn.

Source code in src/femforge/common/tags.py
def __init__(self, keys: list[str] | str | None = None, *, _levels: Levels | None = None) -> None:
    """Group by ``keys`` at the first level."""
    super().__init__()
    self._levels: Levels = _levels if _levels is not None else [_normalize(keys)]

then_by

then_by(keys: list[str] | str) -> TagGrouper

A grouper with keys added as the next level.

Source code in src/femforge/common/tags.py
def then_by(self, keys: list[str] | str) -> "TagGrouper":
    """A grouper with ``keys`` added as the next level."""
    return TagGrouper(_levels=[*self._levels, _normalize(keys)])

apply

apply(items: list[X]) -> TagGroup[Nested[X]]

Group items into a single root TagGroup (root has empty tags).

Source code in src/femforge/common/tags.py
def apply[X: HasTags](self, items: list[X]) -> TagGroup[Nested[X]]:
    """Group items into a single root TagGroup (root has empty tags)."""
    return TagGroup({}, _group_levels(items, self._levels))

map

map(items: list[X], fn: Callable[[X], Y]) -> TagGroup[Nested[Y]]

Group items and transform each leaf with fn.

Source code in src/femforge/common/tags.py
def map[X: HasTags, Y](self, items: list[X], fn: Callable[[X], Y]) -> TagGroup[Nested[Y]]:
    """Group ``items`` and transform each leaf with ``fn``."""
    return map_leaves(self.apply(items), fn)

map_groups

map_groups(items: list[X], fn: Callable[[list[X]], Y]) -> TagGroup[Nested[Y]]

Group items and collapse each deepest bucket of leaves with fn.

Source code in src/femforge/common/tags.py
def map_groups[X: HasTags, Y](self, items: list[X], fn: Callable[[list[X]], Y]) -> TagGroup[Nested[Y]]:
    """Group ``items`` and collapse each deepest bucket of leaves with ``fn``."""
    return map_group_leaves(self.apply(items), fn)

TagKey

Bases: StrEnum

The shared tag-key vocabulary: geometry authors these on parts, meshing/FEA select by them.

Attributes:

  • MATERIAL –

    The registry tag of the body's material.

  • PHASE –

    The winding phase a conductor belongs to.

  • CURRENT_DIR –

    Whether an electrode is a current entry or exit, a CurrentDir value.

  • SEGMENT –

    The winding segment a conductor belongs to.

  • LAYER –

    The layer a conductor lies on, such as a PCB copper layer.

  • TYPE –

    The kind of part, such as Trace or Via.

  • MAGNETIZATION –

    A magnet's magnetization label, such as N or S.

  • SLOT –

    The pole or slot position a part occupies.

  • NAME –

    The authored part name; when set it is the body's name everywhere (mesh group, Elmer body, entities.json, viewer).

MATERIAL class-attribute instance-attribute

MATERIAL = 'Material'

The registry tag of the body's material.

PHASE class-attribute instance-attribute

PHASE = 'Phase'

The winding phase a conductor belongs to.

CURRENT_DIR class-attribute instance-attribute

CURRENT_DIR = 'CurrentDir'

Whether an electrode is a current entry or exit, a CurrentDir value.

SEGMENT class-attribute instance-attribute

SEGMENT = 'Segment'

The winding segment a conductor belongs to.

LAYER class-attribute instance-attribute

LAYER = 'Layer'

The layer a conductor lies on, such as a PCB copper layer.

TYPE class-attribute instance-attribute

TYPE = 'Type'

The kind of part, such as Trace or Via.

MAGNETIZATION class-attribute instance-attribute

MAGNETIZATION = 'Magnetization'

A magnet's magnetization label, such as N or S.

SLOT class-attribute instance-attribute

SLOT = 'Slot'

The pole or slot position a part occupies.

NAME class-attribute instance-attribute

NAME = 'Name'

The authored part name; when set it is the body's name everywhere (mesh group, Elmer body, entities.json, viewer).

COLORS

For use in rich console output. Not for physical materials.

Methods:

  • gradient –

    Generate steps hex colors interpolating from start_color to end_color.

  • Prop –

    Format a property name/value pair with colors for rich console output.

  • PropName –

    A property name in the property-name style.

  • PropValue –

    A property value in the property-value style.

  • H1 –

    A top-level heading.

  • H2 –

    A second-level heading.

  • H3 –

    A third-level heading.

  • Units –

    A unit label in the units style.

gradient staticmethod

gradient(start_color: str, end_color: str, steps: int) -> list[str]

Generate steps hex colors interpolating from start_color to end_color.

Parameters:

  • start_color (str) –

    The first color, as #RRGGBB.

  • end_color (str) –

    The last color, as #RRGGBB.

  • steps (int) –

    How many colors to generate.

Source code in src/femforge/common/utils.py
@staticmethod
def gradient(start_color: str, end_color: str, steps: int) -> list[str]:
    """Generate ``steps`` hex colors interpolating from ``start_color`` to ``end_color``.

    Args:
        start_color: The first color, as ``#RRGGBB``.
        end_color: The last color, as ``#RRGGBB``.
        steps: How many colors to generate.
    """
    start_rgb = tuple(int(start_color[i : i + 2], 16) for i in (1, 3, 5))
    end_rgb = tuple(int(end_color[i : i + 2], 16) for i in (1, 3, 5))

    gradient_colors: list[str] = []
    for step in range(steps):
        ratio = step / max(steps - 1, 1)
        intermediate_rgb = tuple(int(start + (end - start) * ratio) for start, end in zip(start_rgb, end_rgb))
        gradient_colors.append("#{:02X}{:02X}{:02X}".format(*intermediate_rgb))

    return gradient_colors

Prop staticmethod

Prop(name: str, value: str) -> Text

Format a property name/value pair with colors for rich console output.

Source code in src/femforge/common/utils.py
@staticmethod
def Prop(name: str, value: str) -> Text:
    """Format a property name/value pair with colors for rich console output."""
    if contains_rich_markup(value):
        return COLORS.PropName(name).append(Text(": ", style="bold")).append(preserve_and_format(value))

    return COLORS.PropName(name) + Text(": ", style="bold") + COLORS.PropValue(value)

PropName staticmethod

PropName(name: str) -> Text

A property name in the property-name style.

Source code in src/femforge/common/utils.py
@staticmethod
def PropName(name: str) -> Text:
    """A property name in the property-name style."""
    return Text(name, style=COLORS.PROPERTY_NAME)

PropValue staticmethod

PropValue(value: str) -> Text

A property value in the property-value style.

Source code in src/femforge/common/utils.py
@staticmethod
def PropValue(value: str) -> Text:
    """A property value in the property-value style."""
    return Text(value, style=COLORS.PROPERTY_VALUE)

H1 staticmethod

H1(text: str) -> Text

A top-level heading.

Source code in src/femforge/common/utils.py
@staticmethod
def H1(text: str) -> Text:
    """A top-level heading."""
    return Text(text, style=COLORS.HEADER_1 + " bold")

H2 staticmethod

H2(text: str) -> Text

A second-level heading.

Source code in src/femforge/common/utils.py
@staticmethod
def H2(text: str) -> Text:
    """A second-level heading."""
    return Text(text, style=COLORS.HEADER_2 + " bold")

H3 staticmethod

H3(text: str) -> Text

A third-level heading.

Source code in src/femforge/common/utils.py
@staticmethod
def H3(text: str) -> Text:
    """A third-level heading."""
    return Text(text, style=COLORS.HEADER_3 + " bold")

Units staticmethod

Units(text: str) -> Text

A unit label in the units style.

Source code in src/femforge/common/utils.py
@staticmethod
def Units(text: str) -> Text:
    """A unit label in the units style."""
    return Text(text, style=COLORS.UNITS)

Box

Box(min: Vec3, max: Vec3)

An axis-aligned 3D box spanned by its min and max corners.

Attributes:

  • center (Vec3) –

    The midpoint; setting it moves the box.

  • size (Vec3) –

    The edge lengths; setting them resizes the box about its center.

Source code in src/femforge/common/vector.py
def __init__(self, min: Vec3, max: Vec3):
    """Span ``min`` to ``max``."""
    self.min = min
    self.max = max

center property writable

center: Vec3

The midpoint; setting it moves the box.

size property writable

size: Vec3

The edge lengths; setting them resizes the box about its center.

CoordinateFrame

CoordinateFrame(origin: Vec3, orientation: Quaternion)

A position and orientation that maps local coordinates to global ones.

Methods:

  • transform_position –

    Transform a position from local to global coordinates

  • inverse_transform_position –

    Transform a position from global to local coordinates

  • transform_direction –

    Transform a direction vector (ignoring translation) from local to global coordinates

  • inverse_transform_direction –

    Transform a direction vector (ignoring translation) from global to local coordinates

  • __mul__ –

    Combine two coordinate frames by applying this transformation followed by the other

  • __rmul__ –

    Allow left multiplication to combine coordinate frames

  • __imul__ –

    In-place combination of coordinate frames

Source code in src/femforge/common/vector.py
def __init__(self, origin: Vec3, orientation: Quaternion):
    """Place the frame at ``origin``, rotated by ``orientation``."""
    self.origin = origin
    self.orientation = orientation

transform_position

transform_position(position: Vec3) -> Vec3

Transform a position from local to global coordinates

Source code in src/femforge/common/vector.py
def transform_position(self, position: Vec3) -> Vec3:
    """Transform a position from local to global coordinates"""
    rotated_position = self.orientation.rotate_vector(position)
    return self.origin + rotated_position

inverse_transform_position

inverse_transform_position(position: Vec3) -> Vec3

Transform a position from global to local coordinates

Source code in src/femforge/common/vector.py
def inverse_transform_position(self, position: Vec3) -> Vec3:
    """Transform a position from global to local coordinates"""
    translated_position = position - self.origin
    inverse_orientation = self.orientation.conjugate()
    return inverse_orientation.rotate_vector(translated_position)

transform_direction

transform_direction(direction: Vec3) -> Vec3

Transform a direction vector (ignoring translation) from local to global coordinates

Source code in src/femforge/common/vector.py
def transform_direction(self, direction: Vec3) -> Vec3:
    """Transform a direction vector (ignoring translation) from local to global coordinates"""
    return self.orientation.rotate_vector(direction)

inverse_transform_direction

inverse_transform_direction(direction: Vec3) -> Vec3

Transform a direction vector (ignoring translation) from global to local coordinates

Source code in src/femforge/common/vector.py
def inverse_transform_direction(self, direction: Vec3) -> Vec3:
    """Transform a direction vector (ignoring translation) from global to local coordinates"""
    inverse_orientation = self.orientation.conjugate()
    return inverse_orientation.rotate_vector(direction)

__mul__

__mul__(other: CoordinateFrame) -> CoordinateFrame

Combine two coordinate frames by applying this transformation followed by the other

Source code in src/femforge/common/vector.py
def __mul__(self, other: "CoordinateFrame") -> "CoordinateFrame":
    """Combine two coordinate frames by applying this transformation followed by the other"""
    new_origin = self.transform_position(other.origin)
    new_orientation = self.orientation * other.orientation
    return CoordinateFrame(new_origin, new_orientation)

__rmul__

__rmul__(other: CoordinateFrame) -> CoordinateFrame

Allow left multiplication to combine coordinate frames

Source code in src/femforge/common/vector.py
def __rmul__(self, other: "CoordinateFrame") -> "CoordinateFrame":
    """Allow left multiplication to combine coordinate frames"""
    return self.__mul__(other)

__imul__

__imul__(other: CoordinateFrame) -> CoordinateFrame

In-place combination of coordinate frames

Source code in src/femforge/common/vector.py
def __imul__(self, other: "CoordinateFrame") -> "CoordinateFrame":
    """In-place combination of coordinate frames"""
    combined = self * other
    self.origin = combined.origin
    self.orientation = combined.orientation
    return self

Direction

Direction(x: float, y: float, z: float)

Bases: Vec3

A unit-length orientation, normalized and rounded on construction; arithmetic on it returns a plain Vec3.

Parameters:

  • x (float) –

    The x component of any non-zero vector along the direction.

  • y (float) –

    The y component.

  • z (float) –

    The z component.

Source code in src/femforge/common/vector.py
def __init__(self, x: float, y: float, z: float):
    """Normalize ``(x, y, z)`` to unit length and round it to ``DIRECTION_DIGITS`` places.

    Args:
        x: The x component of any non-zero vector along the direction.
        y: The y component.
        z: The z component.
    """
    magnitude: float = math.sqrt(x**2 + y**2 + z**2)

    if magnitude == 0:
        raise ValueError("A direction needs a non-zero vector.")

    # An already-rounded unit vector is kept as given, so a dump and reload cannot shift its last digit.
    scale: float = 1.0 if abs(magnitude - 1.0) <= DIRECTION_SLACK else magnitude
    unit: list[float] = [round(component / scale, DIRECTION_DIGITS) + 0.0 for component in (x, y, z)]
    super().__init__(*unit)

Quaternion

Quaternion(w: float, x: float, y: float, z: float)

A rotation quaternion w + xi + yj + zk.

Parameters:

  • w (float) –

    The scalar part.

  • x (float) –

    The i component.

  • y (float) –

    The j component.

  • z (float) –

    The k component.

Methods:

  • __mul__ –

    Hamilton product of two quaternions

  • conjugate –

    Conjugate of the quaternion

  • magnitude –

    The Euclidean norm of the quaternion.

  • normalized –

    The unit quaternion with the same orientation; raises ValueError for a zero quaternion.

  • rotate_vector –

    Rotate a vector by this quaternion

  • to_rotation_matrix –

    Convert the quaternion to a 3x3 rotation matrix

  • to_euler_angles –

    Convert the quaternion to Euler angles (yaw, pitch, roll) in degrees

  • from_euler_angles –

    Create a quaternion from Euler angles in degrees.

  • from_axis_angle –

    Create a quaternion from an axis and angle in degrees

  • slerp –

    Spherical linear interpolation between two quaternions.

Source code in src/femforge/common/vector.py
def __init__(self, w: float, x: float, y: float, z: float):
    """Set the components.

    Args:
        w: The scalar part.
        x: The i component.
        y: The j component.
        z: The k component.
    """
    self.w = w
    self.x = x
    self.y = y
    self.z = z

__mul__

__mul__(other: Quaternion) -> Quaternion

Hamilton product of two quaternions

Source code in src/femforge/common/vector.py
def __mul__(self, other: "Quaternion") -> "Quaternion":
    """Hamilton product of two quaternions"""
    w1, x1, y1, z1 = self.w, self.x, self.y, self.z
    w2, x2, y2, z2 = other.w, other.x, other.y, other.z

    w = w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2
    x = w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2
    y = w1 * y2 - x1 * z2 + y1 * w2 + z1 * x2
    z = w1 * z2 + x1 * y2 - y1 * x2 + z1 * w2

    return Quaternion(w, x, y, z)

conjugate

conjugate() -> Quaternion

Conjugate of the quaternion

Source code in src/femforge/common/vector.py
def conjugate(self) -> "Quaternion":
    """Conjugate of the quaternion"""
    return Quaternion(self.w, -self.x, -self.y, -self.z)

magnitude

magnitude() -> float

The Euclidean norm of the quaternion.

Source code in src/femforge/common/vector.py
def magnitude(self) -> float:
    """The Euclidean norm of the quaternion."""
    import math

    return math.sqrt(self.w**2 + self.x**2 + self.y**2 + self.z**2)

normalized

normalized() -> Quaternion

The unit quaternion with the same orientation; raises ValueError for a zero quaternion.

Source code in src/femforge/common/vector.py
def normalized(self) -> "Quaternion":
    """The unit quaternion with the same orientation; raises ``ValueError`` for a zero quaternion."""
    mag = self.magnitude()
    if mag == 0:
        raise ValueError("Cannot normalize a zero-length quaternion")
    return Quaternion(self.w / mag, self.x / mag, self.y / mag, self.z / mag)

rotate_vector

rotate_vector(vec: Vec3) -> Vec3

Rotate a vector by this quaternion

Source code in src/femforge/common/vector.py
def rotate_vector(self, vec: Vec3) -> Vec3:
    """Rotate a vector by this quaternion"""
    q_vec = Quaternion(0, vec.x, vec.y, vec.z)
    rotated_q = self * q_vec * self.conjugate()
    return Vec3(rotated_q.x, rotated_q.y, rotated_q.z)

to_rotation_matrix

to_rotation_matrix() -> list[list[float]]

Convert the quaternion to a 3x3 rotation matrix

Source code in src/femforge/common/vector.py
def to_rotation_matrix(self) -> list[list[float]]:
    """Convert the quaternion to a 3x3 rotation matrix"""
    w, x, y, z = self.w, self.x, self.y, self.z

    return [
        [1 - 2 * (y**2 + z**2), 2 * (x * y - z * w), 2 * (x * z + y * w)],
        [2 * (x * y + z * w), 1 - 2 * (x**2 + z**2), 2 * (y * z - x * w)],
        [2 * (x * z - y * w), 2 * (y * z + x * w), 1 - 2 * (x**2 + y**2)],
    ]

to_euler_angles

to_euler_angles() -> tuple[float, float, float]

Convert the quaternion to Euler angles (yaw, pitch, roll) in degrees

Source code in src/femforge/common/vector.py
def to_euler_angles(self) -> tuple[float, float, float]:
    """Convert the quaternion to Euler angles (yaw, pitch, roll) in degrees"""
    w, x, y, z = self.w, self.x, self.y, self.z

    # Yaw (Z-axis rotation)
    siny_cosp = 2 * (w * z + x * y)
    cosy_cosp = 1 - 2 * (y**2 + z**2)
    yaw = math.atan2(siny_cosp, cosy_cosp)

    # Pitch (Y-axis rotation)
    sinp = 2 * (w * y - z * x)
    if abs(sinp) >= 1:
        pitch = math.copysign(math.pi / 2, sinp)  # use 90 degrees if out of range
    else:
        pitch = math.asin(sinp)

    # Roll (X-axis rotation)
    sinr_cosp = 2 * (w * x + y * z)
    cosr_cosp = 1 - 2 * (x**2 + y**2)
    roll = math.atan2(sinr_cosp, cosr_cosp)

    return (math.degrees(yaw), math.degrees(pitch), math.degrees(roll))

from_euler_angles staticmethod

from_euler_angles(yaw: float, pitch: float, roll: float)

Create a quaternion from Euler angles in degrees.

Parameters:

  • yaw (float) –

    The rotation about Z, in degrees.

  • pitch (float) –

    The rotation about Y, in degrees.

  • roll (float) –

    The rotation about X, in degrees.

Source code in src/femforge/common/vector.py
@staticmethod
def from_euler_angles(yaw: float, pitch: float, roll: float):
    """Create a quaternion from Euler angles in degrees.

    Args:
        yaw: The rotation about Z, in degrees.
        pitch: The rotation about Y, in degrees.
        roll: The rotation about X, in degrees.
    """
    yaw_rad = math.radians(yaw)
    pitch_rad = math.radians(pitch)
    roll_rad = math.radians(roll)

    cy = math.cos(yaw_rad * 0.5)
    sy = math.sin(yaw_rad * 0.5)
    cp = math.cos(pitch_rad * 0.5)
    sp = math.sin(pitch_rad * 0.5)
    cr = math.cos(roll_rad * 0.5)
    sr = math.sin(roll_rad * 0.5)

    return Quaternion(
        w=cy * cp * cr + sy * sp * sr,
        x=cy * cp * sr - sy * sp * cr,
        y=cy * sp * cr + sy * cp * sr,
        z=sy * cp * cr - cy * sp * sr,
    ).normalized()

from_axis_angle staticmethod

from_axis_angle(axis: Vec3, angle_degrees: float) -> Quaternion

Create a quaternion from an axis and angle in degrees

Source code in src/femforge/common/vector.py
@staticmethod
def from_axis_angle(axis: Vec3, angle_degrees: float) -> "Quaternion":
    """Create a quaternion from an axis and angle in degrees"""
    angle_rad = math.radians(angle_degrees)
    half_angle = angle_rad / 2
    sin_half_angle = math.sin(half_angle)

    return Quaternion(
        w=math.cos(half_angle),
        x=axis.x * sin_half_angle,
        y=axis.y * sin_half_angle,
        z=axis.z * sin_half_angle,
    ).normalized()

slerp staticmethod

slerp(q1: Quaternion, q2: Quaternion, t: float) -> Quaternion

Spherical linear interpolation between two quaternions.

Parameters:

  • q1 (Quaternion) –

    The orientation at t = 0.

  • q2 (Quaternion) –

    The orientation at t = 1.

  • t (float) –

    The interpolation fraction.

Source code in src/femforge/common/vector.py
@staticmethod
def slerp(q1: "Quaternion", q2: "Quaternion", t: float) -> "Quaternion":
    """Spherical linear interpolation between two quaternions.

    Args:
        q1: The orientation at ``t = 0``.
        q2: The orientation at ``t = 1``.
        t: The interpolation fraction.
    """
    q1 = q1.normalized()
    q2 = q2.normalized()

    dot = q1.w * q2.w + q1.x * q2.x + q1.y * q2.y + q1.z * q2.z

    if dot < 0.0:
        q2 = Quaternion(-q2.w, -q2.x, -q2.y, -q2.z)
        dot = -dot

    if dot > 0.9995:
        # If the quaternions are very close, use linear interpolation
        result = Quaternion(
            w=q1.w + t * (q2.w - q1.w),
            x=q1.x + t * (q2.x - q1.x),
            y=q1.y + t * (q2.y - q1.y),
            z=q1.z + t * (q2.z - q1.z),
        ).normalized()
        return result

    theta_0 = math.acos(dot)
    theta = theta_0 * t

    sin_theta_0 = math.sin(theta_0)
    sin_theta = math.sin(theta)

    s0 = math.cos(theta) - dot * sin_theta / sin_theta_0
    s1 = sin_theta / sin_theta_0

    return Quaternion(
        w=(s0 * q1.w) + (s1 * q2.w),
        x=(s0 * q1.x) + (s1 * q2.x),
        y=(s0 * q1.y) + (s1 * q2.y),
        z=(s0 * q1.z) + (s1 * q2.z),
    ).normalized()

Rect

Rect(min: Vec2, max: Vec2)

An axis-aligned 2D rectangle spanned by its min and max corners.

Attributes:

  • center (Vec2) –

    The midpoint; setting it moves the rectangle.

  • size (Vec2) –

    The width and height; setting them resizes the rectangle about its center.

Source code in src/femforge/common/vector.py
def __init__(self, min: Vec2, max: Vec2):
    """Span ``min`` to ``max``."""
    self.min = min
    self.max = max

center property writable

center: Vec2

The midpoint; setting it moves the rectangle.

size property writable

size: Vec2

The width and height; setting them resizes the rectangle about its center.

Vec2

Vec2(x: float, y: float)

A mutable 2D vector.

Methods:

  • rotate –

    Rotate the vector by a given angle in degrees around a center point

  • magnitude –

    Euclidean length of the vector.

  • to_list –

    The components as [x, y].

  • to_tuple –

    The components as (x, y).

Source code in src/femforge/common/vector.py
def __init__(self, x: float, y: float):
    """Set the components."""
    self.x = x
    self.y = y

rotate

rotate(angle: float, center: Vec2 | None = None) -> Vec2

Rotate the vector by a given angle in degrees around a center point

Source code in src/femforge/common/vector.py
def rotate(self, angle: float, center: "Vec2 | None" = None) -> "Vec2":
    """Rotate the vector by a given angle in degrees around a center point"""
    import math

    if center is None:
        center = Vec2(0, 0)

    radians = math.radians(angle)
    cos_angle = math.cos(radians)
    sin_angle = math.sin(radians)

    # Translate the point to the origin
    translated_x = self.x - center.x
    translated_y = self.y - center.y

    # Rotate the point
    rotated_x = translated_x * cos_angle - translated_y * sin_angle
    rotated_y = translated_x * sin_angle + translated_y * cos_angle

    # Translate back to the original position
    final_x = rotated_x + center.x
    final_y = rotated_y + center.y

    return Vec2(final_x, final_y)

magnitude

magnitude() -> float

Euclidean length of the vector.

Source code in src/femforge/common/vector.py
def magnitude(self) -> float:
    """Euclidean length of the vector."""
    import math

    return math.sqrt(self.x**2 + self.y**2)

to_list

to_list() -> list[float]

The components as [x, y].

Source code in src/femforge/common/vector.py
def to_list(self) -> list[float]:
    """The components as ``[x, y]``."""
    return [self.x, self.y]

to_tuple

to_tuple() -> tuple[float, float]

The components as (x, y).

Source code in src/femforge/common/vector.py
def to_tuple(self) -> tuple[float, float]:
    """The components as ``(x, y)``."""
    return (self.x, self.y)

Vec3 dataclass

Vec3(x: float, y: float, z: float)

An immutable 3D vector; == and hash compare its exact components.

Methods:

  • isclose –

    Whether every component matches other within tolerance, relative or absolute.

  • magnitude –

    Euclidean length |v| of the vector.

  • dot –

    The scalar product with other.

  • normalized –

    A unit vector in the same direction; raises ValueError for a zero-length vector.

  • to_list –

    The components as [x, y, z].

  • to_tuple –

    The components as (x, y, z).

Attributes:

  • x (float) –

    x [?]: The x component.

  • y (float) –

    y [?]: The y component.

  • z (float) –

    z [?]: The z component.

x instance-attribute

x: float

x [?]: The x component.

y instance-attribute

y: float

y [?]: The y component.

z instance-attribute

z: float

z [?]: The z component.

isclose

isclose(other: Vec3, tolerance: float = 1e-09) -> bool

Whether every component matches other within tolerance, relative or absolute.

Source code in src/femforge/common/vector.py
def isclose(self, other: "Vec3", tolerance: float = 1e-9) -> bool:
    """Whether every component matches ``other`` within ``tolerance``, relative or absolute."""
    pairs = zip(self.to_tuple(), other.to_tuple())
    return all(math.isclose(mine, theirs, rel_tol=tolerance, abs_tol=tolerance) for mine, theirs in pairs)

magnitude

magnitude() -> float

Euclidean length |v| of the vector.

Source code in src/femforge/common/vector.py
def magnitude(self) -> float:
    """Euclidean length |v| of the vector."""
    return math.sqrt(self.x**2 + self.y**2 + self.z**2)

dot

dot(other: Vec3) -> float

The scalar product with other.

Source code in src/femforge/common/vector.py
def dot(self, other: "Vec3") -> float:
    """The scalar product with ``other``."""
    return self.x * other.x + self.y * other.y + self.z * other.z

normalized

normalized() -> Vec3

A unit vector in the same direction; raises ValueError for a zero-length vector.

Source code in src/femforge/common/vector.py
def normalized(self) -> "Vec3":
    """A unit vector in the same direction; raises ``ValueError`` for a zero-length vector."""
    mag: float = self.magnitude()
    if mag == 0:
        raise ValueError("cannot normalize a zero-length Vec3")
    return self / mag

to_list

to_list() -> list[float]

The components as [x, y, z].

Source code in src/femforge/common/vector.py
def to_list(self) -> list[float]:
    """The components as ``[x, y, z]``."""
    return [self.x, self.y, self.z]

to_tuple

to_tuple() -> tuple[float, float, float]

The components as (x, y, z).

Source code in src/femforge/common/vector.py
def to_tuple(self) -> tuple[float, float, float]:
    """The components as ``(x, y, z)``."""
    return (self.x, self.y, self.z)

get_console

get_console() -> Console

Return the process-wide stderr Console shared by logging and all progress/tree rendering.

Source code in src/femforge/common/log.py
def get_console() -> Console:
    """Return the process-wide stderr Console shared by logging and all progress/tree rendering."""
    global _console
    if _console is None:
        _console = Console(stderr=True)
    return _console

run_log_context

run_log_context(run_id: str, run_dir: Path) -> Iterator[None]

Bind run_id into the log context and write every record at DEBUG or above that carries it to run_dir/run.log.

Source code in src/femforge/common/log.py
@contextmanager
def run_log_context(run_id: str, run_dir: Path) -> Iterator[None]:
    """Bind ``run_id`` into the log context and write every record at DEBUG or above that carries it to ``run_dir/run.log``."""
    # Setup must precede the file sink: a first-time setup_logging() calls _logger.remove(), which
    # would silently drop a file sink added before it.
    _ensure_setup()

    def _same_run(record: Record) -> bool:
        bound: object = record["extra"].get("run_id")
        return bound == run_id

    # Explicit format: loguru's default omits {extra}, and the run_id (plus any future worker/batch ids) must
    # survive in the file for post-hoc aggregation across runs. The telemetry route is a local path, and bundles are published.
    def line(record: Record) -> str:
        bound: dict[str, object] = cast("dict[str, object]", record["extra"])
        shown: dict[str, object] = {key: value for key, value in bound.items() if key != TELEMETRY_ROUTE}
        literal: str = repr(shown).replace("{", "{{").replace("}", "}}")
        return "{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {name}:{function}:{line} - {message} | " + literal + "\n{exception}"

    sink_id = _logger.add(
        run_dir / BundleFile.RUN_LOG,
        level=LogLevel.DEBUG,
        encoding="utf-8",
        filter=_same_run,
        format=line,
    )
    try:
        with _logger.contextualize(run_id=run_id):
            yield
    finally:
        _logger.remove(sink_id)

setup_logging

setup_logging(*, level: LogLevel | str | None = None, console: Console | None = None, library_levels: Mapping[str, LogLevel | str] | None = None, telemetry: Path | None = None, process: str | None = None) -> None

Install the terminal RichSink plus stdlib/warnings interception; repeat calls only adjust the sink level, library gates and telemetry root.

Parameters:

  • level (LogLevel | str | None, default: None ) –

    Terminal sink threshold; None falls back to the FEMFORGE_LOG_LEVEL env var, then INFO.

  • console (Console | None, default: None ) –

    Console the sink renders into; None uses the shared stderr console from get_console().

  • library_levels (Mapping[str, LogLevel | str] | None, default: None ) –

    Stdlib logger name to minimum level, gating chatty libraries at the source; None applies DEFAULT_LIBRARY_LEVELS.

  • telemetry (Path | None, default: None ) –

    Telemetry root the parquet sink writes to; None falls back to FEMFORGE_TELEMETRY, and telemetry stays off when that is unset too.

  • process (str | None, default: None ) –

    Name the telemetry rows are partitioned under; None uses the sanitized sys.argv[0] stem.

Source code in src/femforge/common/log.py
def setup_logging(
    *,
    level: LogLevel | str | None = None,
    console: Console | None = None,
    library_levels: Mapping[str, LogLevel | str] | None = None,
    telemetry: Path | None = None,
    process: str | None = None,
) -> None:
    """Install the terminal RichSink plus stdlib/warnings interception; repeat calls only adjust the sink level, library gates and telemetry root.

    Args:
        level: Terminal sink threshold; ``None`` falls back to the ``FEMFORGE_LOG_LEVEL`` env var, then ``INFO``.
        console: Console the sink renders into; ``None`` uses the shared stderr console from ``get_console()``.
        library_levels: Stdlib logger name to minimum level, gating chatty libraries at the source; ``None`` applies ``DEFAULT_LIBRARY_LEVELS``.
        telemetry: Telemetry root the parquet sink writes to; ``None`` falls back to ``FEMFORGE_TELEMETRY``, and telemetry stays off when that is unset too.
        process: Name the telemetry rows are partitioned under; ``None`` uses the sanitized ``sys.argv[0]`` stem.
    """
    global _terminal, _terminal_level
    resolved = (level or os.environ.get(LOG_LEVEL_ENV) or "INFO").upper()

    if _terminal is not None:
        if resolved != _terminal_level:
            sink, sink_id = _terminal
            _logger.remove(sink_id)
            _terminal = (sink, _add_terminal_sink(sink, resolved))
            _terminal_level = resolved
        _apply_library_levels(library_levels if library_levels is not None else DEFAULT_LIBRARY_LEVELS)
        _install_telemetry(telemetry, process)

        return

    sink = RichSink(
        console if console is not None else get_console(),
        path_width=_PATH_WIDTH,
        hide_keys=frozenset({"run_id", TELEMETRY_ROUTE}),
    )
    _logger.remove()
    _terminal = (sink, _add_terminal_sink(sink, resolved))
    _terminal_level = resolved

    logging.basicConfig(handlers=[_InterceptHandler()], level=0, force=True)
    logging.captureWarnings(True)
    _apply_library_levels(library_levels if library_levels is not None else DEFAULT_LIBRARY_LEVELS)

    _logger.level(LogLevel.TRACE, icon="🔍")
    _logger.level(LogLevel.DEBUG, icon="🐛")
    _logger.level(LogLevel.INFO, icon="â„šī¸")
    _logger.level(LogLevel.SUCCESS, icon="✅")
    _logger.level(LogLevel.WARNING, icon="âš ī¸")
    _logger.level(LogLevel.ERROR, icon="❌")
    _logger.level(LogLevel.CRITICAL, icon="🚨")

    # After _logger.remove(), which would otherwise drop the sink again.
    _install_telemetry(telemetry, process)

fmt_time

fmt_time(seconds: float, width: int = 6) -> str

Format a duration in seconds as m:ss.s or s.s, right-justified to width.

Source code in src/femforge/common/progress.py
def fmt_time(seconds: float, width: int = 6) -> str:
    """Format a duration in seconds as ``m:ss.s`` or ``s.s``, right-justified to ``width``."""
    minutes = int(seconds // 60)
    seconds = seconds % 60
    if minutes > 0:
        return f"{minutes}:{seconds:04.1f}".rjust(width)
    return f"{seconds:.1f}".rjust(width)

get_map_key

get_map_key(field: FieldInfo) -> str | None

The external key from a field's Map() metadata, or None if the field carries no Map().

Source code in src/femforge/common/pydantic.py
def get_map_key(field: FieldInfo) -> str | None:
    """The external key from a field's ``Map()`` metadata, or None if the field carries no ``Map()``."""
    for meta in field.metadata:  # pyright: ignore[reportAny]  # FieldInfo.metadata is list[Any]
        if isinstance(meta, Map):
            return meta.map

    return None

mapped_items

mapped_items(model: BaseModel) -> Iterator[tuple[str, object]]

Yield (external_key, value) for each field carrying a Map() whose current value is not None.

Source code in src/femforge/common/pydantic.py
def mapped_items(model: BaseModel) -> Iterator[tuple[str, object]]:
    """Yield ``(external_key, value)`` for each field carrying a ``Map()`` whose current value is not None."""
    for name, field in type(model).model_fields.items():
        key: str | None = get_map_key(field)
        if key is None:
            continue

        value: object = getattr(model, name)  # pyright: ignore[reportAny]  # model field values are dynamic
        if value is None:
            continue

        yield key, value

map_group_leaves

map_group_leaves(root: TagGroup[Nested[X]], fn: Callable[[list[X]], Y]) -> TagGroup[Nested[Y]]

Collapse each deepest bucket of sibling leaves (a list[X]) into one Y, keeping its enclosing group and tags.

Source code in src/femforge/common/tags.py
def map_group_leaves[X, Y](root: TagGroup[Nested[X]], fn: Callable[[list[X]], Y]) -> TagGroup[Nested[Y]]:
    """Collapse each deepest bucket of sibling leaves (a list[X]) into one Y,
    keeping its enclosing group and tags."""

    def map_group(group: TagGroup[Nested[X]]) -> TagGroup[Nested[Y]]:
        # a leaf-group has only non-group children
        if len(group) and all(not isinstance(child, TagGroup) for child in group):
            return TagGroup(group.tags, [fn(cast("list[X]", group))])
        return TagGroup(
            group.tags,
            [map_group(cast("TagGroup[Nested[X]]", child)) for child in group],
        )

    return map_group(root)

map_leaves

map_leaves(root: TagGroup[Nested[X]], fn: Callable[[X], Y]) -> TagGroup[Nested[Y]]

Transform every leaf X -> Y, keeping the hierarchy identical.

Source code in src/femforge/common/tags.py
def map_leaves[X, Y](root: TagGroup[Nested[X]], fn: Callable[[X], Y]) -> TagGroup[Nested[Y]]:
    """Transform every leaf X -> Y, keeping the hierarchy identical."""
    return cast("TagGroup[Nested[Y]]", _map_node(root, fn))

parse_tags

parse_tags(text: str) -> TagDict

The key=value pairs found in a label / STEP-name path; a repeated key keeps its last (deepest) value.

Source code in src/femforge/common/tags.py
def parse_tags(text: str) -> TagDict:
    """The ``key=value`` pairs found in a label / STEP-name path; a repeated key keeps its last (deepest) value."""
    return dict(TAG_PAIR_PATTERN.findall(text))

contains_rich_markup

contains_rich_markup(text: str) -> bool

Whether text looks like it carries Rich markup.

Source code in src/femforge/common/utils.py
def contains_rich_markup(text: str) -> bool:
    """Whether ``text`` looks like it carries Rich markup."""
    if not isinstance(text, str):
        return False
    # Checks for unbalanced/basic Rich markup sequences
    has_brackets = "[" in text and "]" in text
    style_markers = ["bold", "italic", "underline", "on ", "color", "dim", "[#", "[/]"]
    return has_brackets and any(style in text for style in style_markers)

preserve_and_format

preserve_and_format(content: object, new_style: str = 'bold') -> Text

Preserves existing string formatting and applies a new style safely.

Source code in src/femforge/common/utils.py
def preserve_and_format(content: object, new_style: str = "bold") -> Text:
    """Preserves existing string formatting and applies a new style safely."""
    # Initialize a clean parent container holding the new global style
    container = Text(style=new_style)

    # 1. Handle already instantiated Rich Text objects
    if isinstance(content, Text):
        container.append(content)
        return container

    if not isinstance(content, str):
        container.append(str(content))
        return container

    # 2. Handle ANSI Escape Codes (e.g., \x1b[32m)
    ansi_regex = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")
    if ansi_regex.search(content):
        container.append(Text.from_ansi(content))
        return container

    # 3. Handle Rich Console Markup (e.g., [red]text[/red])
    if "[" in content and "]" in content:
        container.append(Text.from_markup(content))
        return container

    # 4. Handle plain text
    container.append(content)
    return container

title

title(value: str) -> str

A label or identifier in Title Case: underscores and camelCase humps become spaces, acronyms are capitalized, joining words stay lower case.

Source code in src/femforge/common/utils.py
def title(value: str) -> str:
    """A label or identifier in Title Case: underscores and camelCase humps become spaces, acronyms are capitalized, joining words stay lower case."""

    def cased(word: str, *, first: bool) -> str:
        if word.upper() in TITLE_ACRONYMS:
            return word.upper()

        if any(char.isupper() for char in word):
            return word

        if not first and word in TITLE_MINOR:
            return word

        return "-".join(part[:1].upper() + part[1:] for part in word.split("-"))

    words = CAMEL_HUMP.sub(" ", value.replace("_", " ")).split(" ")

    return " ".join(cased(word, first=index == 0) for index, word in enumerate(words))