george2cool36's picture
Upload app.py with huggingface_hub
8560cc0 verified
# =========================
# Shear & Moment Diagram Generator (Simply Supported Beam)
# + Add (max 2) & Delete controls for loads
# =========================
import math
import numpy as np
import matplotlib.pyplot as plt
import gradio as gr
import pandas as pd
plt.rcParams.update({"figure.dpi": 120})
# ---------- Core statics ----------
def reactions_simply_supported(L, point_loads, udls):
Wp = sum(P for (_x, P) in point_loads)
Wu = sum(w * (x2 - x1) for (x1, x2, w) in udls)
Wtot = Wp + Wu
Mp = sum(P * x for (x, P) in point_loads)
Mu = sum((w * (x2 - x1)) * ((x1 + x2) / 2.0) for (x1, x2, w) in udls)
RB = (Mp + Mu) / L
RA = Wtot - RB
return RA, RB
def vm_diagrams(L, point_loads, udls, n=1001):
RA, RB = reactions_simply_supported(L, point_loads, udls)
x = np.linspace(0.0, L, n)
V = np.zeros_like(x, dtype=float)
M = np.zeros_like(x, dtype=float)
def udl_active_length(xi, x1, x2):
if xi <= x1:
return 0.0
elif xi >= x2:
return (x2 - x1)
else:
return (xi - x1)
for i, xi in enumerate(x):
v = RA
for (xp, P) in point_loads:
if xp <= xi + 1e-12:
v -= P
for (x1, x2, w) in udls:
a = udl_active_length(xi, x1, x2)
v -= w * max(0.0, a)
V[i] = v
m = RA * xi
for (xp, P) in point_loads:
a = max(0.0, xi - xp)
m -= P * a
for (x1, x2, w) in udls:
a = udl_active_length(xi, x1, x2)
if a > 0:
centroid = x1 + a / 2.0
m -= w * a * (xi - centroid)
M[i] = m
return x, V, M, (RA, RB)
# ---------- Plot helpers ----------
def draw_beam_sketch(L, point_loads, udls, RA, RB):
fig, ax = plt.subplots(figsize=(7, 1.8))
ax.set_xlim(-0.02*L, 1.02*L)
ax.set_ylim(-1.2, 1.2)
ax.plot([0, L], [0, 0], lw=4, color="black")
triA = plt.Polygon([[0,0],[ -0.05*L, -0.5],[ 0.05*L, -0.5]], color="gray")
triB = plt.Polygon([[L,0],[ L-0.05*L, -0.5],[ L+0.05*L, -0.5]], color="gray")
ax.add_patch(triA); ax.add_patch(triB)
ax.annotate("", xy=(0,0.6), xytext=(0,0.05),
arrowprops=dict(arrowstyle="->", lw=2))
ax.text(0, 0.7, f"R_A={RA:.1f} N", ha="center", va="bottom", fontsize=9)
ax.annotate("", xy=(L,0.6), xytext=(L,0.05),
arrowprops=dict(arrowstyle="->", lw=2))
ax.text(L, 0.7, f"R_B={RB:.1f} N", ha="center", va="bottom", fontsize=9)
for (xp, P) in point_loads:
ax.annotate("", xy=(xp,-0.7), xytext=(xp,0.05),
arrowprops=dict(arrowstyle="->", lw=2))
ax.text(xp, -0.8, f"P={P:.0f} N", ha="center", va="top", fontsize=9)
for (x1, x2, w) in udls:
ax.plot([x1, x2], [0.25, 0.25], lw=6, color="tab:blue")
n_ar = max(2, int((x2-x1)/(L/10)) )
xs = np.linspace(x1, x2, n_ar)
for xk in xs:
ax.annotate("", xy=(xk,0.05), xytext=(xk,0.4),
arrowprops=dict(arrowstyle="->", lw=1.5))
ax.text((x1+x2)/2, 0.45, f"w={w:.0f} N/m", ha="center", va="bottom", fontsize=9, color="tab:blue")
ax.axis("off")
ax.set_title("Beam & Loads (simply supported)")
fig.tight_layout()
return fig
def plot_line(x, y, title, ylbl):
fig, ax = plt.subplots(figsize=(7, 2.5))
ax.axhline(0, color="k", lw=1)
ax.plot(x, y, lw=2)
ax.set_xlabel("x [m]")
ax.set_ylabel(ylbl)
ax.set_title(title)
ax.grid(True, alpha=0.3)
fig.tight_layout()
return fig
# ---------- Defaults & Help ----------
EX_POINT = pd.DataFrame(
[[2.0, 8000],
[5.0, 6000]],
columns=["x (m)", "P (N, down +)"]
)
EX_UDL = pd.DataFrame(
[[1.0, 3.0, 1500],
[6.0, 8.5, 1000]],
columns=["x1 (m)", "x2 (m)", "w (N/m, down +)"]
)
HELP_MD = """
**How to use**
1) Set **span L** and enter loads:
- **Point loads:** `(x, P)`; `P>0` is downward.
- **UDLs:** `(x1, x2, w)`; `w>0` is downward, active on `[x1,x2]`.
2) Use **Add** (limited to 2 rows per type), **Delete rows**, **Clear**, or **Reset examples**.
3) Click **Compute** to see reactions, the beam sketch, **V(x)** and **M(x)**, and equilibrium checks.
**Sign convention**
- Upward reactions positive. Downward loads positive inputs.
- Shear: positive upward on left face; Moment: sagging positive.
"""
# ---------- Small helpers for add/delete ----------
def _parse_row_list(s, n_rows):
if not s:
return []
idxs = []
for tok in str(s).replace(" ", "").split(","):
if not tok:
continue
try:
k = int(tok)
if 1 <= k <= n_rows:
idxs.append(k-1)
except ValueError:
pass
return sorted(set(idxs), reverse=True)
def delete_point_rows(df_points, rows_to_delete, confirm):
if not confirm:
return df_points, "Deletion NOT performed (check 'Confirm delete')."
if df_points is None or len(df_points) == 0:
return pd.DataFrame(columns=["x (m)","P (N, down +)"]), "No point rows to delete."
idxs = _parse_row_list(rows_to_delete, len(df_points))
if not idxs:
return df_points, "No valid point row indices provided."
df = df_points.copy()
for i in idxs:
df = df.drop(df.index[i])
df = df.reset_index(drop=True)
return df, f"Deleted point rows: {', '.join(str(i+1) for i in idxs)}."
def delete_udl_rows(df_udl, rows_to_delete, confirm):
if not confirm:
return df_udl, "Deletion NOT performed (check 'Confirm delete')."
if df_udl is None or len(df_udl) == 0:
return pd.DataFrame(columns=["x1 (m)","x2 (m)","w (N/m, down +)"]), "No UDL rows to delete."
idxs = _parse_row_list(rows_to_delete, len(df_udl))
if not idxs:
return df_udl, "No valid UDL row indices provided."
df = df_udl.copy()
for i in idxs:
df = df.drop(df.index[i])
df = df.reset_index(drop=True)
return df, f"Deleted UDL rows: {', '.join(str(i+1) for i in idxs)}."
def clear_points():
return pd.DataFrame(columns=["x (m)","P (N, down +)"]), "Cleared all point loads."
def clear_udls():
return pd.DataFrame(columns=["x1 (m)","x2 (m)","w (N/m, down +)"]), "Cleared all UDLs."
def reset_examples_points():
return EX_POINT.copy(), "Restored example point loads."
def reset_examples_udls():
return EX_UDL.copy(), "Restored example UDLs."
# --- ADD with limit = 2 ---
MAX_POINTS = 2
MAX_UDLS = 2
def add_point_row(df_points, x, P):
df = df_points.copy() if df_points is not None else pd.DataFrame(columns=["x (m)","P (N, down +)"])
if len(df) >= MAX_POINTS:
return df, f"Limit reached: max {MAX_POINTS} point loads."
try:
x = float(x); P = float(P)
except Exception:
return df, "Provide numeric x and P."
if P < 0:
return df, "Use P ≥ 0 (downward +)."
df = pd.concat([df, pd.DataFrame([[x, P]], columns=df.columns)], ignore_index=True)
return df, f"Added point load #{len(df)}."
def add_udl_row(df_udl, x1, x2, w):
df = df_udl.copy() if df_udl is not None else pd.DataFrame(columns=["x1 (m)","x2 (m)","w (N/m, down +)"])
if len(df) >= MAX_UDLS:
return df, f"Limit reached: max {MAX_UDLS} UDLs."
try:
x1 = float(x1); x2 = float(x2); w = float(w)
except Exception:
return df, "Provide numeric x1, x2, w."
if not (x1 < x2):
return df, "Require x1 < x2."
if w < 0:
return df, "Use w ≥ 0 (downward +)."
df = pd.concat([df, pd.DataFrame([[x1, x2, w]], columns=df.columns)], ignore_index=True)
return df, f"Added UDL #{len(df)}."
# ---------- Core compute ----------
def run_once(L, df_points, df_udl, npts):
try:
L = float(L)
n = int(npts)
if L <= 0:
raise ValueError("Beam length L must be > 0.")
if n < 201:
n = 201
point_loads = []
if df_points is not None and len(df_points) > 0:
for _, row in df_points.iterrows():
x = float(row[0]); P = float(row[1])
if 0 <= x <= L and P >= 0:
point_loads.append((x, P))
udls = []
if df_udl is not None and len(df_udl) > 0:
for _, row in df_udl.iterrows():
x1 = float(row[0]); x2 = float(row[1]); w = float(row[2])
if 0 <= x1 < x2 <= L and w >= 0:
udls.append((x1, x2, w))
x, V, M, (RA, RB) = vm_diagrams(L, point_loads, udls, n=n)
fig_beam = draw_beam_sketch(L, point_loads, udls, RA, RB)
fig_V = plot_line(x, V, "Shear Force Diagram V(x)", "V [N]")
fig_M = plot_line(x, M, "Bending Moment Diagram M(x)", "M [N·m]")
total_down = sum(P for _, P in point_loads) + sum(w*(x2-x1) for (x1,x2,w) in udls)
eq_sumF = RA + RB - total_down
Mp = sum(P * x for (x, P) in point_loads)
Mu = sum(w*(x2-x1)*( (x1+x2)/2 ) for (x1,x2,w) in udls)
eq_MA = RB*L - (Mp + Mu)
df = pd.DataFrame([{
"RA [N]": round(RA, 3),
"RB [N]": round(RB, 3),
"ΣF (should ≈ 0) [N]": round(eq_sumF, 6),
"ΣM_A (should ≈ 0) [N·m]": round(eq_MA, 6),
"max|V| [N]": round(float(np.max(np.abs(V))), 3),
"max|M| [N·m]": round(float(np.max(np.abs(M))), 3),
}])
return df, fig_beam, fig_V, fig_M, ""
except Exception as e:
return pd.DataFrame(), None, None, None, f"Input error:\n{e}"
# ---------- UI ----------
with gr.Blocks(title="Shear & Moment Diagrams — Simply Supported Beam") as demo:
gr.Markdown("# Shear & Bending Moment Diagram Generator")
gr.Markdown(HELP_MD)
with gr.Row():
with gr.Column():
L = gr.Number(value=10.0, label="Span L [m]")
npts = gr.Slider(minimum=201, maximum=5001, step=100, value=1201, label="Resolution (points)")
with gr.Column():
gr.Markdown("### Point loads (downward +) — max 2")
df_points = gr.Dataframe(
value=EX_POINT, headers=["x (m)","P (N, down +)"],
datatype=["number","number"], row_count=(1, "dynamic")
)
with gr.Row():
x_new = gr.Number(value=1.0, label="x (m)")
P_new = gr.Number(value=5000.0, label="P (N, down +)")
btn_add_pt = gr.Button("Add point load", variant="primary")
with gr.Row():
del_pts_rows = gr.Textbox(label="Delete point rows (e.g., 1,2)")
confirm_pts = gr.Checkbox(value=False, label="Confirm delete")
with gr.Row():
btn_del_pts = gr.Button("Delete selected point rows", variant="secondary")
btn_clr_pts = gr.Button("Clear ALL point loads", variant="stop")
btn_rst_pts = gr.Button("Reset example points", variant="primary")
with gr.Column():
gr.Markdown("### Uniform distributed loads (downward +) — max 2")
df_udl = gr.Dataframe(
value=EX_UDL, headers=["x1 (m)","x2 (m)","w (N/m, down +)"],
datatype=["number","number","number"], row_count=(1, "dynamic")
)
with gr.Row():
x1_new = gr.Number(value=2.0, label="x1 (m)")
x2_new = gr.Number(value=4.0, label="x2 (m)")
w_new = gr.Number(value=1000.0, label="w (N/m)")
btn_add_udl = gr.Button("Add UDL", variant="primary")
with gr.Row():
del_udl_rows = gr.Textbox(label="Delete UDL rows (e.g., 1)")
confirm_udl = gr.Checkbox(value=False, label="Confirm delete")
with gr.Row():
btn_del_udl = gr.Button("Delete selected UDL rows", variant="secondary")
btn_clr_udl = gr.Button("Clear ALL UDLs", variant="stop")
btn_rst_udl = gr.Button("Reset example UDLs", variant="primary")
go_btn = gr.Button("Compute", variant="primary")
gr.Markdown("### Reactions & Checks")
out_tbl = gr.Dataframe(interactive=False)
gr.Markdown("### Beam sketch")
out_beam = gr.Plot()
with gr.Row():
out_V = gr.Plot()
out_M = gr.Plot()
status = gr.Textbox(label="Status / Errors", interactive=False)
# --- Wire add actions (limit enforced) ---
def _add_point(df, x, P):
new_df, msg = add_point_row(df, x, P)
return new_df, msg
btn_add_pt.click(_add_point, inputs=[df_points, x_new, P_new], outputs=[df_points, status])
def _add_udl(df, x1, x2, w):
new_df, msg = add_udl_row(df, x1, x2, w)
return new_df, msg
btn_add_udl.click(_add_udl, inputs=[df_udl, x1_new, x2_new, w_new], outputs=[df_udl, status])
# --- Wire delete/clear/reset actions ---
def _delete_pts(df, rows_txt, confirm):
new_df, msg = delete_point_rows(df, rows_txt, confirm)
return new_df, msg
btn_del_pts.click(_delete_pts, inputs=[df_points, del_pts_rows, confirm_pts],
outputs=[df_points, status])
def _clear_pts():
return clear_points()
btn_clr_pts.click(_clear_pts, inputs=None, outputs=[df_points, status])
def _reset_pts():
return reset_examples_points()
btn_rst_pts.click(_reset_pts, inputs=None, outputs=[df_points, status])
def _delete_udl(df, rows_txt, confirm):
new_df, msg = delete_udl_rows(df, rows_txt, confirm)
return new_df, msg
btn_del_udl.click(_delete_udl, inputs=[df_udl, del_udl_rows, confirm_udl],
outputs=[df_udl, status])
def _clear_udl():
return clear_udls()
btn_clr_udl.click(_clear_udl, inputs=None, outputs=[df_udl, status])
def _reset_udl():
return reset_examples_udls()
btn_rst_udl.click(_reset_udl, inputs=None, outputs=[df_udl, status])
# --- Compute ---
go_btn.click(
fn=run_once,
inputs=[L, df_points, df_udl, npts],
outputs=[out_tbl, out_beam, out_V, out_M, status]
)
if __name__ == "__main__":
demo.launch(debug=False)