Skip to content

Event Guide

The Femora event system is an advanced runtime API for lifecycle-sensitive work. It exists so model-owned subsystems can react to assembly and export stages without hard-coding every extension directly into the core pipeline.

Most modeling workflows do not need this package directly. If you are defining ordinary materials, elements, loads, sections, or analyses, you can usually ignore it. This guide is for the cases where timing matters: when the right thing to do depends on whether the mesh has already been assembled, whether partition ownership has been resolved, or whether an export pipeline has started.

The Idea In One Sentence

Femora emits lifecycle events, and advanced components subscribe to those events when they need to react at exactly the right runtime stage.

What Happens Under The Hood

At a high level, the runtime flow is:

  1. a subsystem such as the assembler reaches a lifecycle boundary
  2. it emits a FemoraEvent
  3. the model-owned event bus dispatches payload data to subscribers
  4. subscribers inspect that payload and react
  5. the runtime continues to the next stage

In practice this means the event system is less about creating objects and more about coordinating when an advanced object is allowed to inspect or modify state.

The Most Important Lifecycle Stages

The most important practical events are:

  • PRE_ASSEMBLE Called before the final assembled mesh is built. Use this when something must be prepared before the assembly pipeline commits the final mesh state.

  • POST_ASSEMBLE Called after the assembled mesh exists. This is the most important hook for advanced interfaces because the full model mesh can now be inspected.

  • RESOLVE_CORE_CONFLICTS Called after assembly when partition/core ownership updates may require follow-up work.

  • PRE_EXPORT / POST_EXPORT Called around export workflows. These are mainly useful for specialized export augmentation and compatibility layers.

When You Should Use Events

Use the event system when the right behavior depends on lifecycle timing, for example:

  • generating extra interface mesh after the final assembled mesh exists
  • pairing constrained and retained nodes after assembly
  • creating constraints only after geometry relationships have been discovered
  • reacting to partition-aware updates
  • injecting custom export-time behavior

When You Should Not Use Events

Do not use the event system as a replacement for ordinary model construction. If a component can be created directly through a manager or configured entirely at construction time, that is usually the better design.

Minimal Subscription Example

from femora.core.model import Model
from femora.components.event.event_bus import FemoraEvent

model = Model()

def on_post_assemble(**payload):
    assembled_mesh = payload.get("assembled_mesh")
    if assembled_mesh is not None:
        print(f"assembled cells: {assembled_mesh.n_cells}")

model.events.subscribe(FemoraEvent.POST_ASSEMBLE, on_post_assemble)

This kind of callback is appropriate for diagnostics, inspection, or small advanced extensions.

A More Realistic Femora Workflow

The more important use case is a model-owned advanced component.

For example, an interface component may:

  1. subscribe to POST_ASSEMBLE
  2. wait until the assembled mesh exists
  3. inspect nearby cells or nodes
  4. generate extra interface geometry or constraints
  5. register those additions with the owning model

That is why the event package also documents lifecycle mixins such as:

Those mixins are not end-user features by themselves. They describe the kind of lifecycle work an advanced component performs.

  • Read event bus for the actual event enum and subscription API.
  • Read the lifecycle mixin pages when you are implementing or studying an advanced interface-like component.