Skip to main content

Overview

Nodes define geometric points in 2D space, while elements are structural members that connect nodes. Together, they form the finite element mesh that represents your structure.

Nodes

Creating Nodes

Nodes are defined by a unique ID and x, y coordinates:
node1 = model.add_node(id=1, x=0.0, y=0.0)
node2 = model.add_node(id=2, x=4.0, y=0.0)
node3 = model.add_node(id=3, x=4.0, y=3.0)

Node Properties

Each node has several properties:
node = model.nodes[1]

node.id          # Node identifier (int)
node.vertex      # Coordinates (Vertex object with x, y)
node.dofs        # Degrees of freedom indices [ux_dof, uy_dof, rz_dof]
node.restraints  # Boundary conditions (ux, uy, rz)

Degrees of Freedom

Each node in 2D has 3 degrees of freedom (DOF):
  • ux: Translation in X direction
  • uy: Translation in Y direction
  • rz: Rotation about Z axis
node = model.nodes[1]
print(node.dofs)  # Array of global DOF indices, e.g., [1, 2, 3]
DOF indices are automatically calculated as: [node_id × 3 - 2, node_id × 3 - 1, node_id × 3]

Elements

milcapy supports multiple element types for different structural behaviors.

Frame/Beam Elements

Frame elements (members) model beams and columns with axial, shear, and bending behavior:
member = model.add_member(
    id=1,
    node_i_id=1,           # Start node
    node_j_id=2,           # End node
    section_name="BEAM-30x50",
    beam_theory="TIMOSHENKO"  # or "EULER_BERNOULLI"
)

Beam Theories

Neglects shear deformation, suitable for:
  • Slender beams (L/h > 10)
  • Long-span members
  • When shear deformation is negligible
model.add_elastic_euler_bernoulli_beam(
    id=2,
    node_i_id=2,
    node_j_id=3,
    section_name="BEAM-20x40"
)

Convenience Methods

milcapy provides specialized methods for common element types:
# Timoshenko beam (with axial stiffness)
beam = model.add_elastic_timoshenko_beam(
    id=1,
    node_i_id=1,
    node_j_id=2,
    section_name="BEAM-30x50"
)

# Euler-Bernoulli beam (with axial stiffness)
beam = model.add_elastic_euler_bernoulli_beam(
    id=2,
    node_i_id=2,
    node_j_id=3,
    section_name="BEAM-30x50"
)

Truss Elements

Truss elements only resist axial forces (no bending):
truss = model.add_truss(
    id=10,
    node_i_id=1,
    node_j_id=3,
    section_name="TRUSS-SECTION"
)
Truss elements have only 2 DOF per node (ux, uy), with no rotational stiffness.

Membrane Elements

For 2D plane stress/strain analysis:

Constant Strain Triangle (CST)

cst = model.add_cst(
    id=20,
    node_i_id=1,
    node_j_id=2,
    node_k_id=3,
    section_name="SHELL-200",
    state="PLANE_STRESS"  # or "PLANE_STRAIN"
)
Nodes for membrane elements must be ordered counter-clockwise. The model will raise a ValueError if nodes are ordered clockwise.

Quadrilateral Membrane Elements

milcapy offers several quadrilateral element formulations:
Quadrilateral with drilling DOF (rotation about z-axis):
q6 = model.add_membrane_q6(
    id=21,
    node_i_id=1,
    node_j_id=2,
    node_k_id=3,
    node_l_id=4,
    section_name="SHELL-200",
    state="PLANE_STRESS"
)
Enhanced element with incompatible modes for better accuracy:
q6i = model.add_membrane_q6i(
    id=22,
    node_i_id=1,
    node_j_id=2,
    node_k_id=3,
    node_l_id=4,
    section_name="SHELL-200",
    state="PLANE_STRESS"
)
Basic 4-node quadrilateral:
q4 = model.add_membrane_q4(
    id=23,
    node_i_id=1,
    node_j_id=2,
    node_k_id=3,
    node_l_id=4,
    section_name="SHELL-200",
    state="PLANE_STRESS"
)
Q4 elements have poor convergence. Use Q8 elements instead for better accuracy.
Higher-order element with better convergence:
q8 = model.add_membrane_q8(
    id=24,
    node_i_id=1,
    node_j_id=2,
    node_k_id=3,
    node_l_id=4,
    section_name="SHELL-200",
    state="PLANE_STRESS",
    integration="COMPLETE"  # or "REDUCED"
)

Element Properties

All elements share common properties:
member = model.members[1]

member.id          # Element ID
member.node_i      # Start node object
member.node_j      # End node object
member.section     # Section object
member.dofs        # Array of DOF indices
member.length()    # Element length
member.angle_x()   # Angle with respect to global X-axis

Advanced Element Features

End Length Offsets (Rigid Arms)

Model rigid end zones in frame members:
model.add_end_length_offset(
    member_id=1,
    la=0.25,    # Rigid arm length at start (m)
    lb=0.25,    # Rigid arm length at end (m)
    qla=True,   # Apply loads to start rigid arm
    qlb=True,   # Apply loads to end rigid arm
    fla=1.0,    # Rigid zone factor at start (1.0 = fully rigid)
    flb=1.0     # Rigid zone factor at end
)
Use end offsets to model beam-column joints where the member extends into the joint region.

Member Releases

Create internal hinges or partial releases:
model.add_releases(
    member_id=1,
    pi=False,  # Release axial force at start
    vi=False,  # Release shear force at start
    mi=True,   # Release moment at start (hinge)
    pj=False,  # Release axial force at end
    vj=False,  # Release shear force at end
    mj=False   # Release moment at end
)
Simple beam (both ends pinned):
model.add_releases(member_id=1, mi=True, mj=True)
Cantilever (free end):
model.add_releases(member_id=1, pj=True, vj=True, mj=True)
Axial release (truss-like):
model.add_releases(member_id=1, mi=True, mj=True)

Complete Example: Simple Frame

from milcapy import SystemMilcaModel

# Create model
model = SystemMilcaModel()

# Add material and section
model.add_material(name="Concrete", modulus_elasticity=30e9, 
                   poisson_ratio=0.2, specific_weight=24000)
model.add_rectangular_section(name="COL-40x40", material_name="Concrete",
                              base=0.40, height=0.40)
model.add_rectangular_section(name="BEAM-30x50", material_name="Concrete",
                              base=0.30, height=0.50)

# Create nodes
model.add_node(id=1, x=0.0, y=0.0)   # Left support
model.add_node(id=2, x=0.0, y=4.0)   # Left top
model.add_node(id=3, x=6.0, y=4.0)   # Right top
model.add_node(id=4, x=6.0, y=0.0)   # Right support

# Create columns
model.add_member(id=1, node_i_id=1, node_j_id=2, 
                section_name="COL-40x40", beam_theory="TIMOSHENKO")
model.add_member(id=2, node_i_id=4, node_j_id=3,
                section_name="COL-40x40", beam_theory="TIMOSHENKO")

# Create beam
model.add_member(id=3, node_i_id=2, node_j_id=3,
                section_name="BEAM-30x50", beam_theory="TIMOSHENKO")

# Add boundary conditions
model.add_restraint(node_id=1, ux=True, uy=True, rz=True)  # Fixed
model.add_restraint(node_id=4, ux=True, uy=True, rz=True)  # Fixed

Best Practices

Unique IDs: Each node and element must have a unique ID. Attempting to add duplicates will raise a ValueError.
Node Ordering: For membrane elements, always order nodes counter-clockwise when viewing from the positive z-axis.
Element Connectivity: Ensure nodes exist before creating elements that reference them.
Element Selection:
  • Use Timoshenko beams for most frame analysis
  • Use trusses for axial-only members
  • Use Q8 or Q6I membrane elements for best accuracy
  • Avoid Q4 elements due to poor convergence

Materials & Sections

Define section properties for elements

Boundary Conditions

Apply supports and restraints to nodes

Load Patterns

Apply loads to nodes and elements

Build docs developers (and LLMs) love