Skip to main content

Overview

SystemModel is the primary class for building 2D structural analysis models in milcapy. It manages all model components including materials, sections, nodes, elements, loads, and analysis results.

Constructor

from milcapy import SystemModel

model = SystemModel()
Creates an empty structural model.

Materials

add_material()

Adds a material to the model.
model.add_material(
    name="concrete",
    modulus_elasticity=2.1e6,
    poisson_ratio=0.2,
    specific_weight=2400
)
name
str
required
Unique name for the material
modulus_elasticity
float
required
Modulus of elasticity (E) in force per area units
poisson_ratio
float
required
Poisson’s ratio (ν), must be in range (-1, 0.5)
specific_weight
float
default:"0.0"
Specific weight or density (kg/m³ or N/m³)
Material
Material
Returns the created Material object

Sections

add_rectangular_section()

Adds a rectangular cross-section to the model.
model.add_rectangular_section(
    name="beam_section",
    material_name="concrete",
    base=0.3,
    height=0.5
)
name
str
required
Unique name for the section
material_name
str
required
Name of previously defined material
base
float
required
Width of the rectangular section
height
float
required
Height of the rectangular section
Section
RectangularSection
Returns the created Section object

add_circular_section()

Adds a circular cross-section to the model.
model.add_circular_section(
    name="column_section",
    material_name="steel",
    diameter=0.3
)
name
str
required
Unique name for the section
material_name
str
required
Name of previously defined material
diameter
float
required
Diameter of the circular section (must be positive)
Section
CircularSection
Returns the created Section object

add_generic_section()

Adds a generic section with custom geometric properties.
model.add_generic_section(
    name="custom_section",
    material_name="steel",
    area=0.05,
    inertia=0.0001,
    k_factor=0.85
)
name
str
required
Unique name for the section
material_name
str
required
Name of previously defined material
area
float
required
Cross-sectional area
inertia
float
required
Moment of inertia
k_factor
float
required
Shear coefficient factor (Ac = A * k_factor)
Section
GenericSection
Returns the created Section object

add_shell_section()

Adds a shell section for membrane/plate elements.
model.add_shell_section(
    name="wall_section",
    material_name="concrete",
    thickness=0.2
)
name
str
required
Unique name for the section
material_name
str
required
Name of previously defined material
thickness
float
required
Thickness of the shell element
ShellSection
ShellSection
Returns the created ShellSection object

Nodes

add_node()

Adds a node to the model.
model.add_node(id=1, x=0.0, y=0.0)
model.add_node(id=2, x=5.0, y=0.0)
id
int
required
Unique node identifier
x
float
required
X-coordinate of the node
y
float
required
Y-coordinate of the node
Node
Node
Returns the created Node object

Members

add_member()

Adds a frame member (beam/column) to the model.
from milcapy import BeamTheoriesType

model.add_member(
    id=1,
    node_i_id=1,
    node_j_id=2,
    section_name="beam_section",
    beam_theory=BeamTheoriesType.TIMOSHENKO
)
id
int
required
Unique member identifier
node_i_id
int
required
ID of the start node
node_j_id
int
required
ID of the end node
section_name
str
required
Name of previously defined section
beam_theory
BeamTheoriesType | str
default:"BeamTheoriesType.TIMOSHENKO"
Beam theory to use: TIMOSHENKO or EULER_BERNOULLI
Member
Member
Returns the created Member object

add_elastic_timoshenko_beam()

Adds a Timoshenko beam with axial stiffness.
model.add_elastic_timoshenko_beam(
    id=1,
    node_i_id=1,
    node_j_id=2,
    section_name="beam_section"
)
id
int
required
Unique member identifier
node_i_id
int
required
ID of the start node
node_j_id
int
required
ID of the end node
section_name
str
required
Name of previously defined section

add_elastic_euler_bernoulli_beam()

Adds an Euler-Bernoulli beam with axial stiffness.
model.add_elastic_euler_bernoulli_beam(
    id=1,
    node_i_id=1,
    node_j_id=2,
    section_name="beam_section"
)
id
int
required
Unique member identifier
node_i_id
int
required
ID of the start node
node_j_id
int
required
ID of the end node
section_name
str
required
Name of previously defined section

add_truss()

Adds a truss element (axial force only).
model.add_truss(
    id=1,
    node_i_id=1,
    node_j_id=2,
    section_name="truss_section"
)
id
int
required
Unique truss identifier
node_i_id
int
required
ID of the start node
node_j_id
int
required
ID of the end node
section_name
str
required
Name of previously defined section

Boundary Conditions

add_restraint()

Adds restraints (boundary conditions) to a node.
model.add_restraint(
    node_id=1,
    ux=True,   # Restrain X translation
    uy=True,   # Restrain Y translation
    rz=True    # Restrain Z rotation
)
node_id
int
required
ID of the node to restrain
ux
bool
required
Restrain translation in X direction
uy
bool
required
Restrain translation in Y direction
rz
bool
required
Restrain rotation about Z axis

Load Patterns

add_load_pattern()

Creates a load pattern for organizing loads.
from milcapy import StateType

model.add_load_pattern(
    name="Live Load",
    self_weight_multiplier=0.0,
    state=StateType.ACTIVE
)
name
str
required
Unique name for the load pattern
self_weight_multiplier
float
default:"0.0"
Multiplier for self-weight (1.0 = full self-weight)
state
StateType | str
default:"StateType.ACTIVE"
Load pattern state: ACTIVE or INACTIVE

add_point_load()

Adds a point load to a node.
from milcapy import CoordinateSystemType

model.add_point_load(
    node_id=2,
    load_pattern_name="Live Load",
    fx=100.0,
    fy=-200.0,
    mz=50.0,
    CSys=CoordinateSystemType.GLOBAL
)
node_id
int
required
ID of the node to apply load
load_pattern_name
str
required
Name of the load pattern
fx
float
default:"0.0"
Force in X direction
fy
float
default:"0.0"
Force in Y direction
mz
float
default:"0.0"
Moment about Z axis
CSys
CoordinateSystemType | str
default:"GLOBAL"
Coordinate system: GLOBAL or LOCAL
replace
bool
default:"False"
Replace existing load if True, otherwise add to existing

add_distributed_load()

Adds a distributed load to a member.
from milcapy import DirectionType, LoadType

model.add_distributed_load(
    member_id=1,
    load_pattern_name="Dead Load",
    load_start=-10.0,
    load_end=-10.0,
    CSys="LOCAL",
    direction=DirectionType.LOCAL_2,
    load_type=LoadType.FORCE
)
member_id
int
required
ID of the member to apply load
load_pattern_name
str
required
Name of the load pattern
load_start
float
default:"0.0"
Load magnitude at start of member
load_end
float
default:"0.0"
Load magnitude at end of member
CSys
CoordinateSystemType | str
default:"LOCAL"
Coordinate system: GLOBAL or LOCAL
direction
DirectionType | str
default:"LOCAL_2"
Load direction: LOCAL_1, LOCAL_2, X, Y, GRAVITY, etc.
load_type
LoadType | str
default:"FORCE"
Type of load: FORCE or MOMENT
replace
bool
default:"False"
Replace existing load if True

Analysis

solve()

Solves the structural model using the direct stiffness method.
results = model.solve()

# Or solve specific load patterns
results = model.solve(load_pattern_name=["Dead Load", "Live Load"])
load_pattern_name
list[str] | None
default:"None"
List of load pattern names to solve. If None, solves all active patterns.
results
Dict[str, Results]
Dictionary mapping load pattern names to Results objects

Results

get_results()

Retrieves analysis results for a specific load pattern.
results = model.get_results("Live Load")

# Access node displacements
displacements = results.get_node_displacements(2)
print(f"ux={displacements[0]}, uy={displacements[1]}, rz={displacements[2]}")
load_pattern_name
str
required
Name of the load pattern
Results
Results
Results object containing displacements, reactions, and internal forces

get_global_stiffness_matrix()

Returns the assembled global stiffness matrix.
K = model.get_global_stiffness_matrix()
K
NDArray
Global stiffness matrix

get_global_load_vector()

Returns the global load vector for a load pattern.
F = model.get_global_load_vector("Live Load")
load_pattern_name
str
required
Name of the load pattern
F
NDArray
Global load vector

Visualization

show()

Launches the interactive model viewer window.
model.solve()
model.show()
The model must be solved before calling show(). The viewer displays the model geometry, loads, deformed shape, and internal force diagrams.

plot_model()

Creates a static plot of the model.
model.plot_model(
    load_pattern="Live Load",
    node_labels=True,
    member_labels=True
)
load_pattern
str | None
default:"None"
Load pattern to display loads for
node_labels
bool
default:"True"
Show node ID labels
member_labels
bool
default:"True"
Show member ID labels

Advanced Features

add_elastic_support()

Adds an elastic support (spring) to a node.
model.add_elastic_support(
    node_id=2,
    kx=1000.0,  # Spring stiffness in X
    ky=2000.0,  # Spring stiffness in Y
    krz=500.0,  # Rotational spring stiffness
    CSys="GLOBAL"
)
node_id
int
required
ID of the node
kx
float | None
default:"None"
Spring stiffness in X direction
ky
float | None
default:"None"
Spring stiffness in Y direction
krz
float | None
default:"None"
Rotational spring stiffness about Z
CSys
CoordinateSystemType | str
default:"GLOBAL"
Coordinate system for the spring

add_end_length_offset()

Adds rigid end offsets to a member (rigid arms).
model.add_end_length_offset(
    member_id=1,
    la=0.2,  # Offset at start node
    lb=0.3,  # Offset at end node
    qla=True,  # Apply loads on rigid arm at start
    qlb=True,  # Apply loads on rigid arm at end
    fla=1.0,   # Rigidity factor at start
    flb=1.0    # Rigidity factor at end
)
member_id
int
required
ID of the member
la
float
default:"0"
Length offset at start node
lb
float
default:"0"
Length offset at end node
qla
bool
default:"True"
Apply loads on rigid arm at start
qlb
bool
default:"True"
Apply loads on rigid arm at end
fla
float
default:"1"
Rigidity factor for start offset (1.0 = fully rigid)
flb
float
default:"1"
Rigidity factor for end offset (1.0 = fully rigid)

add_releases()

Adds member end releases.
model.add_releases(
    member_id=1,
    pi=False,  # Axial release at start
    vi=False,  # Shear release at start
    mi=True,   # Moment release at start (pinned)
    pj=False,  # Axial release at end
    vj=False,  # Shear release at end
    mj=False   # Moment release at end
)
member_id
int
required
ID of the member
pi
bool
default:"False"
Release axial force at start node
vi
bool
default:"False"
Release shear force at start node
mi
bool
default:"False"
Release moment at start node
pj
bool
default:"False"
Release axial force at end node
vj
bool
default:"False"
Release shear force at end node
mj
bool
default:"False"
Release moment at end node
Cannot release both ends for the same force type (e.g., pi=True and pj=True is invalid)

Example

from milcapy import SystemModel, BeamTheoriesType

# Create model
model = SystemModel()

# Define material
model.add_material(
    name="concrete",
    modulus_elasticity=2.1e6,
    poisson_ratio=0.2,
    specific_weight=2400
)

# Define section
model.add_rectangular_section(
    name="beam_30x50",
    material_name="concrete",
    base=0.3,
    height=0.5
)

# Create nodes
model.add_node(1, 0, 0)
model.add_node(2, 5, 0)
model.add_node(3, 10, 0)

# Create members
model.add_member(1, 1, 2, "beam_30x50", BeamTheoriesType.TIMOSHENKO)
model.add_member(2, 2, 3, "beam_30x50", BeamTheoriesType.TIMOSHENKO)

# Apply supports
model.add_restraint(1, True, True, True)  # Fixed
model.add_restraint(3, False, True, False)  # Roller

# Add loads
model.add_load_pattern("Dead Load", self_weight_multiplier=1.0)
model.add_distributed_load(1, "Dead Load", load_start=-10, load_end=-10)
model.add_distributed_load(2, "Dead Load", load_start=-10, load_end=-10)

model.add_load_pattern("Live Load")
model.add_point_load(2, "Live Load", fy=-50.0)

# Solve
results = model.solve()

# Get results
node_2_disp = model.get_results("Live Load").get_node_displacements(2)
print(f"Node 2 vertical displacement: {node_2_disp[1]:.6f}")

# Visualize
model.show()

Build docs developers (and LLMs) love