Skip to content

FEMA_SAC_SteelFrame

FEMA_SAC_SteelFrame

FEMA_SAC_SteelFrame(name_prefix: str = 'FEMA_SAC_Frame', x_bays: Optional[List[float]] = None, y_bays: Optional[List[float]] = None, story_heights: Optional[List[float]] = None, section_map: Optional[Dict[str, List[Union[str, Tuple]]]] = None, defaults: Optional[Dict[str, str]] = None, n_ele_col: int = 1, n_ele_beam: int = 1, length_unit_system: str = 'm', origin: Tuple[float, float, float] = (0.0, 0.0, 0.0), floor_masses: Optional[List[float]] = None)

Bases: Building

Represents a FEMA/SAC Steel Frame benchmark building.

Defaults to the SAC 9-Story (LA9) configuration if no geometry is provided, but can be initialized with custom bays, stories, and sections.

Attributes

x_bays instance-attribute

x_bays = [360.0 * inch] * 3

y_bays instance-attribute

y_bays = [360.0 * inch] * 3

story_heights instance-attribute

story_heights = [144.0 * inch, 216.0 * inch] + [156.0 * inch] * 8

name_prefix instance-attribute

name_prefix = name_prefix

n_ele_col instance-attribute

n_ele_col = n_ele_col

n_ele_beam instance-attribute

n_ele_beam = n_ele_beam

section_unit_system instance-attribute

section_unit_system = section_unit_system

origin instance-attribute

origin = origin

num_x_grid instance-attribute

num_x_grid = len(x_bays) + 1

num_y_grid instance-attribute

num_y_grid = len(y_bays) + 1

num_stories instance-attribute

num_stories = len(story_heights)

floor_masses instance-attribute

floor_masses = [0.0] * num_stories

defaults instance-attribute

defaults = {'grav_column': 'W14X159', 'mom_column': 'W24X229', 'mom_girder': 'W27X114'}

_user_provided_defaults instance-attribute

_user_provided_defaults = True

col_sections instance-attribute

col_sections: Dict[Tuple[int, int, int], str] = {}

beam_x_sections instance-attribute

beam_x_sections: Dict[Tuple[int, int, int], str] = {}

beam_y_sections instance-attribute

beam_y_sections: Dict[Tuple[int, int, int], str] = {}

building_region instance-attribute

building_region: Optional[RegionBase] = None

section_map instance-attribute

section_map = section_map

Methods:

_is_perimeter

_is_perimeter(i, j)

_is_valid_column

_is_valid_column(story, i, j)

_is_valid_beam

_is_valid_beam(story, direction, i, j)

Validates if a beam exists at this location based on FEMA/SAC pinwheel logic.

set_column

set_column(story, x_grid, y_grid, section)

Set a specific column section.

set_beam

set_beam(story, direction, i, j, section)

Set a specific beam section.

get_coordinates

get_coordinates() -> Tuple[np.ndarray, np.ndarray, np.ndarray]

Returns the X, Y, and Z coordinates of the building grid intersections.

_initialize_sections

_initialize_sections()

Populate the section grids based on defaults and section_map.

view_story

view_story(story: int, mode: str = 'text')

Visualize the layout and sections for a specific story.

Parameters:

Name Type Description Default
story int

1-based story index.

required
mode str

'text' for ASCII output, 'plot' for matplotlib window.

'text'

_load_sac_9_story_defaults

_load_sac_9_story_defaults()

Internal helper to load the specific LA9 benchmark sections.

build

build(model, material: Material, material_density: float = 0.0) -> CompositeMesh

Generate the steel frame geometry and return as a CompositeMesh.

create_rigid_diaphragms

create_rigid_diaphragms(model, verbose: bool = True)

Creates rigid diaphragm constraints for each floor.

This method identifies the Center-of-Mass (CoM) node at each floor and connects it to all column endpoint nodes on that floor. It ensures that intermediate beam nodes (from subdivided beams) are excluded.

Must be called AFTER model.assembler.assemble().

create_gravity_pattern

create_gravity_pattern(model, g: float) -> Pattern

Build a plain gravity load pattern for the assembled frame.

For each elevated floor (z levels from the building grid), the translational mass stored in floor_masses for that story is multiplied by g to obtain a total vertical force, which is applied in equal parts at every structural grid node on that level (num_x_grid * num_y_grid nodes). Ghost center-of-mass nodes are not loaded.

Prerequisites: :meth:build and mesh assembly on model must be completed so nodal coordinates and tags are available.

Parameters:

Name Type Description Default
model

Femora model instance (Model) exposing assembler, timeSeries, pattern, and _start_nodetag.

required
g float

Acceleration magnitude; must combine with floor_masses units so mass * g is a force in the model's force unit.

required

Returns:

Type Description
Pattern

A Pattern instance (plain pattern with a constant time series and

Pattern

six-DOF nodal loads in global Z) ready to attach to a process or export.

get_modes

get_modes(num_modes: int, *, material: Optional[Union[Dict[str, object], Callable]] = None, material_density: Optional[float] = None, opensees_exe: Optional[str] = None, print_results: bool = False, plot: bool = False, plot_scale: Optional[float] = None) -> Dict[str, np.ndarray]

Return modal frequencies, periods, and eigenvectors.

The method builds a copy of this frame in a separate Model instance, applies the same gravity and diaphragm setup used by the example modal script, runs OpenSees in a temporary directory, and parses frequencies, periods, and nodal eigenvectors from OpenSees stdout.

Parameters:

Name Type Description Default
num_modes int

Number of eigen modes to request from OpenSees.

required
material Optional[Union[Dict[str, object], Callable]]

Material definition for the isolated modal model. Use None to create the default A992 elastic isotropic steel material. Pass a dict to create an elastic isotropic material from values such as E, nu, and rho. Pass a callable when the material must be created manually inside the temporary modal model.

None
material_density Optional[float]

Density used by build() to compute member self-mass. This is separate from the material object because member mass is a building-generation calculation, while rho on the material is an OpenSees material parameter. If omitted, Femora uses material["rho"] for dict materials, attempts to infer density from callable-created materials, and otherwise falls back to the default A992 density.

None
opensees_exe Optional[str]

Optional OpenSees executable path. If omitted, the FEMORA_OPENSEES environment variable is used.

None
print_results bool

If True, print a formatted table of modal frequencies and periods.

False
plot bool

If True, show PyVista mode-shape plots for the requested modes using the isolated building mesh.

False
plot_scale Optional[float]

Optional deformation scale for plotting. If omitted, a scale is estimated from the mesh size and eigenvector magnitudes.

None

Returns:

Type Description
Dict[str, ndarray]

Dictionary with frequencies, periods, node_tags, and

Dict[str, ndarray]

eigenvectors. The eigenvector array has shape

Dict[str, ndarray]

(num_modes, num_nodes, ndof).

Raises:

Type Description
ValueError

If num_modes is invalid.

RuntimeError

If OpenSees is not configured, fails to run, or does not return the expected modal data.

Material ownership

get_modes() builds a separate temporary Model() so modal analysis cannot accidentally mutate the user's active model. For that reason, do not pass a Material object from another model. Use a material dictionary or a callable factory so the material is created inside the temporary modal model with the correct managers and tags.

Examples:

Default A992 steel:

result = frame.get_modes(
    num_modes=6,
    opensees_exe=opensees_path,
    print_results=True,
)

Custom elastic isotropic material from a dictionary:

result = frame.get_modes(
    num_modes=6,
    material={
        "name": "CustomSteel",
        "E": 2.0e8,
        "nu": 0.3,
        "rho": 7.85,
    },
    opensees_exe=opensees_path,
)

Advanced material factory:

result = frame.get_modes(
    num_modes=6,
    material=lambda model: model.material.nd.elastic_isotropic(
        "CustomSteel",
        E=2.0e8,
        nu=0.3,
        rho=7.85,
    ),
    material_density=7.85,
)

get_recorders

get_recorders(model, *, file_name: Optional[str] = None, delta_t: Optional[float] = None, element_responses: List[str] = ['force'], node_responses: List[str] = ['displacement', 'acceleration'])

Returns an MPCO recorder for the building's elements.

This method must be called after build() has been called to ensure the building region is created.

Parameters:

Name Type Description Default
model

Active Femora model (Model instance).

required
file_name Optional[str]

Output file name with .mpco extension. Defaults to {name_prefix}.mpco.

None
delta_t Optional[float]

Optional recorder time interval (-T dt).

None
element_responses List[str]

List of element responses to record (-E). Defaults to ["force"].

['force']
node_responses List[str]

List of element responses to record (-N). Defaults to ["displacement", "acceleration"].

['displacement', 'acceleration']

Returns:

Type Description

List containing the MPCORecorder instance.