Sompote commited on
Commit
0be7e4d
·
verified ·
1 Parent(s): 787ad0b

Upload 4 files

Browse files
Files changed (4) hide show
  1. app.py +234 -0
  2. beam_design_app.py +202 -0
  3. continuous_beam.py +1036 -0
  4. requirements.txt +4 -0
app.py ADDED
@@ -0,0 +1,234 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import pandas as pd
3
+ from continuous_beam import ContinuousBeam
4
+ import matplotlib.pyplot as plt
5
+ import numpy as np
6
+ import io
7
+ import base64
8
+
9
+ class GradioBeamApp:
10
+ def __init__(self):
11
+ self.beam = ContinuousBeam()
12
+ self.spans_data = []
13
+
14
+ def parse_point_loads(self, point_loads_text):
15
+ """Parse point loads from text input"""
16
+ if not point_loads_text or point_loads_text.strip() == "":
17
+ return []
18
+
19
+ point_loads = []
20
+ try:
21
+ # Expected format: "position1,load1; position2,load2; ..."
22
+ # Example: "2.0,50; 4.0,30"
23
+ load_pairs = point_loads_text.split(';')
24
+ for pair in load_pairs:
25
+ if pair.strip():
26
+ pos_str, load_str = pair.split(',')
27
+ position = float(pos_str.strip())
28
+ load = float(load_str.strip())
29
+ point_loads.append((position, load))
30
+ except:
31
+ raise ValueError("Invalid point load format. Use: position1,load1; position2,load2")
32
+
33
+ return point_loads
34
+
35
+ def add_span(self, length, distributed_load, point_loads_text, spans_display):
36
+ """Add a span to the beam"""
37
+ try:
38
+ length = float(length)
39
+ distributed_load = float(distributed_load)
40
+
41
+ if length <= 0 or distributed_load < 0:
42
+ return spans_display, "Error: Please enter valid values (length > 0, distributed_load >= 0)"
43
+
44
+ # Parse point loads
45
+ point_loads = self.parse_point_loads(point_loads_text)
46
+
47
+ # Validate point load positions
48
+ for pos, load in point_loads:
49
+ if pos < 0 or pos > length:
50
+ return spans_display, f"Error: Point load at {pos}m is outside span length {length}m"
51
+ if load < 0:
52
+ return spans_display, f"Error: Point load {load}kN must be positive"
53
+
54
+ self.beam.add_span(length, distributed_load, point_loads)
55
+
56
+ # Create span description
57
+ span_info = f"Span {len(self.beam.spans)}: L={length}m, w={distributed_load}kN/m"
58
+ if point_loads:
59
+ point_load_str = ", ".join([f"P={load}kN@{pos}m" for pos, load in point_loads])
60
+ span_info += f", {point_load_str}"
61
+
62
+ self.spans_data.append(span_info)
63
+
64
+ # Update display
65
+ updated_display = "\n".join(self.spans_data)
66
+ return updated_display, f"Added: {span_info}"
67
+
68
+ except ValueError as e:
69
+ return spans_display, f"Error: {str(e)}"
70
+
71
+ def clear_spans(self):
72
+ """Clear all spans"""
73
+ self.beam = ContinuousBeam()
74
+ self.spans_data = []
75
+ return "", "All spans cleared"
76
+
77
+ def design_beam(self, width, depth, fc, fy, cover, spans_display):
78
+ """Design the beam and return results"""
79
+ if not self.beam.spans:
80
+ return "No spans added. Please add at least one span.", None, None, None, None, None
81
+
82
+ try:
83
+ # Update beam properties
84
+ self.beam.beam_width = float(width)
85
+ self.beam.beam_depth = float(depth)
86
+ self.beam.fc = float(fc)
87
+ self.beam.fy = float(fy)
88
+ self.beam.cover = float(cover)
89
+ self.beam.d = self.beam.beam_depth - self.beam.cover
90
+
91
+ # Perform design
92
+ design_results = self.beam.design_beam()
93
+
94
+ # Generate report
95
+ report = self.beam.generate_report(design_results)
96
+
97
+ # Create summary table
98
+ summary_data = []
99
+ for span_data in design_results:
100
+ span_num = span_data['span']
101
+ for i, (reinf, stirrup) in enumerate(zip(span_data['reinforcement'], span_data['stirrups'])):
102
+ if reinf['moment'] != 0 or stirrup['shear'] != 0:
103
+ summary_data.append([
104
+ span_num if i == 0 else '',
105
+ reinf['location'],
106
+ f"{reinf['moment']:.2f}" if reinf['moment'] != 0 else '-',
107
+ reinf['bars'] if reinf['moment'] != 0 else '-',
108
+ f"{stirrup['shear']:.2f}" if stirrup['shear'] != 0 else '-',
109
+ stirrup['stirrup_spacing'] if stirrup['shear'] != 0 else '-'
110
+ ])
111
+
112
+ # Create DataFrame for table display
113
+ df = pd.DataFrame(summary_data, columns=[
114
+ 'Span', 'Location', 'Moment (kN-m)', 'Reinforcement', 'Shear (kN)', 'Stirrups'
115
+ ])
116
+
117
+ # Generate plots
118
+ bmd_sfd_plot = self.beam.plot_bmd_sfd(design_results)
119
+ reinforcement_plot = self.beam.plot_reinforcement_layout(design_results)
120
+ stirrup_plot = self.beam.plot_stirrup_layout(design_results)
121
+
122
+ return report, df, bmd_sfd_plot, reinforcement_plot, stirrup_plot, "Design completed successfully!"
123
+
124
+ except Exception as e:
125
+ return f"Design calculation failed: {str(e)}", None, None, None, None, f"Error: {str(e)}"
126
+
127
+ def create_interface():
128
+ app = GradioBeamApp()
129
+
130
+ with gr.Blocks(title="Continuous Beam RC Design - ACI Code", theme=gr.themes.Default()) as interface:
131
+
132
+ gr.Markdown("# Continuous Beam RC Design - ACI Code")
133
+ gr.Markdown("*Using Finite Element Analysis for accurate structural behavior*")
134
+
135
+ with gr.Row():
136
+ with gr.Column(scale=1):
137
+ gr.Markdown("## Beam Properties")
138
+
139
+ with gr.Row():
140
+ width_input = gr.Number(label="Beam Width (mm)", value=300)
141
+ depth_input = gr.Number(label="Beam Depth (mm)", value=500)
142
+
143
+ with gr.Row():
144
+ fc_input = gr.Number(label="f'c (MPa)", value=28)
145
+ fy_input = gr.Number(label="fy (MPa)", value=420)
146
+
147
+ cover_input = gr.Number(label="Cover (mm)", value=40)
148
+
149
+ gr.Markdown("## Spans Configuration")
150
+
151
+ with gr.Row():
152
+ span_length_input = gr.Number(label="Span Length (m)", value=6.0)
153
+ distributed_load_input = gr.Number(label="Distributed Load (kN/m)", value=25.0)
154
+
155
+ point_loads_input = gr.Textbox(
156
+ label="Point Loads (position,load; position,load; ...)",
157
+ placeholder="Example: 2.0,50; 4.0,30 (means 50kN at 2m and 30kN at 4m)",
158
+ value=""
159
+ )
160
+
161
+ with gr.Row():
162
+ add_span_btn = gr.Button("Add Span", variant="primary")
163
+ clear_spans_btn = gr.Button("Clear All", variant="secondary")
164
+
165
+ spans_display = gr.Textbox(
166
+ label="Added Spans",
167
+ lines=5,
168
+ interactive=False,
169
+ placeholder="No spans added yet..."
170
+ )
171
+
172
+ design_btn = gr.Button("Design Beam", variant="primary", size="lg")
173
+
174
+ status_output = gr.Textbox(label="Status", interactive=False)
175
+
176
+ with gr.Column(scale=2):
177
+ gr.Markdown("## Design Results")
178
+
179
+ with gr.Tabs():
180
+ with gr.TabItem("Summary"):
181
+ summary_table = gr.Dataframe(
182
+ label="Design Summary",
183
+ headers=["Span", "Location", "Moment (kN-m)", "Reinforcement", "Shear (kN)", "Stirrups"],
184
+ interactive=False
185
+ )
186
+
187
+ with gr.TabItem("BMD & SFD"):
188
+ bmd_sfd_plot = gr.Plot(label="Bending Moment and Shear Force Diagrams")
189
+
190
+ with gr.TabItem("Reinforcement Layout"):
191
+ reinforcement_plot = gr.Plot(label="Reinforcement Bar Layout")
192
+
193
+ with gr.TabItem("Stirrup Layout"):
194
+ stirrup_plot = gr.Plot(label="Shear Stirrup Layout")
195
+
196
+ with gr.TabItem("Detailed Report"):
197
+ results_output = gr.Textbox(
198
+ label="Detailed Design Report",
199
+ lines=20,
200
+ interactive=False,
201
+ show_copy_button=True
202
+ )
203
+
204
+ # Event handlers
205
+ add_span_btn.click(
206
+ fn=lambda length, dist_load, point_loads, spans: app.add_span(length, dist_load, point_loads, spans),
207
+ inputs=[span_length_input, distributed_load_input, point_loads_input, spans_display],
208
+ outputs=[spans_display, status_output]
209
+ )
210
+
211
+ clear_spans_btn.click(
212
+ fn=lambda: app.clear_spans(),
213
+ outputs=[spans_display, status_output]
214
+ )
215
+
216
+ design_btn.click(
217
+ fn=lambda w, d, fc, fy, c, spans: app.design_beam(w, d, fc, fy, c, spans),
218
+ inputs=[width_input, depth_input, fc_input, fy_input, cover_input, spans_display],
219
+ outputs=[results_output, summary_table, bmd_sfd_plot, reinforcement_plot, stirrup_plot, status_output]
220
+ )
221
+
222
+ return interface
223
+
224
+ def main():
225
+ interface = create_interface()
226
+ interface.launch(
227
+ server_name="0.0.0.0", # Allow external access
228
+ server_port=8000, # Default Gradio port
229
+ share=False, # Set to True if you want a public link
230
+ debug=True
231
+ )
232
+
233
+ if __name__ == "__main__":
234
+ main()
beam_design_app.py ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import tkinter as tk
2
+ from tkinter import ttk, messagebox, scrolledtext
3
+ from continuous_beam import ContinuousBeam
4
+ import matplotlib.pyplot as plt
5
+ from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
6
+ import numpy as np
7
+
8
+ class BeamDesignApp:
9
+ def __init__(self, root):
10
+ self.root = root
11
+ self.root.title("Continuous Beam RC Design - ACI Code")
12
+ self.root.geometry("1200x800")
13
+
14
+ self.beam = ContinuousBeam()
15
+ self.setup_ui()
16
+
17
+ def setup_ui(self):
18
+ # Create main frame
19
+ main_frame = ttk.Frame(self.root, padding="10")
20
+ main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
21
+
22
+ # Configure grid weights
23
+ self.root.columnconfigure(0, weight=1)
24
+ self.root.rowconfigure(0, weight=1)
25
+ main_frame.columnconfigure(1, weight=1)
26
+ main_frame.rowconfigure(3, weight=1)
27
+
28
+ # Title
29
+ title_label = ttk.Label(main_frame, text="Continuous Beam RC Design",
30
+ font=('Arial', 16, 'bold'))
31
+ title_label.grid(row=0, column=0, columnspan=2, pady=(0, 20))
32
+
33
+ # Input frame
34
+ input_frame = ttk.LabelFrame(main_frame, text="Beam Properties", padding="10")
35
+ input_frame.grid(row=1, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(0, 10))
36
+
37
+ # Beam properties
38
+ ttk.Label(input_frame, text="Beam Width (mm):").grid(row=0, column=0, sticky=tk.W)
39
+ self.width_var = tk.StringVar(value="300")
40
+ ttk.Entry(input_frame, textvariable=self.width_var, width=10).grid(row=0, column=1, sticky=tk.W, padx=(5, 20))
41
+
42
+ ttk.Label(input_frame, text="Beam Depth (mm):").grid(row=0, column=2, sticky=tk.W)
43
+ self.depth_var = tk.StringVar(value="500")
44
+ ttk.Entry(input_frame, textvariable=self.depth_var, width=10).grid(row=0, column=3, sticky=tk.W, padx=(5, 20))
45
+
46
+ ttk.Label(input_frame, text="f'c (MPa):").grid(row=1, column=0, sticky=tk.W)
47
+ self.fc_var = tk.StringVar(value="28")
48
+ ttk.Entry(input_frame, textvariable=self.fc_var, width=10).grid(row=1, column=1, sticky=tk.W, padx=(5, 20))
49
+
50
+ ttk.Label(input_frame, text="fy (MPa):").grid(row=1, column=2, sticky=tk.W)
51
+ self.fy_var = tk.StringVar(value="420")
52
+ ttk.Entry(input_frame, textvariable=self.fy_var, width=10).grid(row=1, column=3, sticky=tk.W, padx=(5, 20))
53
+
54
+ ttk.Label(input_frame, text="Cover (mm):").grid(row=2, column=0, sticky=tk.W)
55
+ self.cover_var = tk.StringVar(value="40")
56
+ ttk.Entry(input_frame, textvariable=self.cover_var, width=10).grid(row=2, column=1, sticky=tk.W, padx=(5, 20))
57
+
58
+ # Spans frame
59
+ spans_frame = ttk.LabelFrame(main_frame, text="Spans Configuration", padding="10")
60
+ spans_frame.grid(row=2, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(0, 10))
61
+
62
+ # Span input
63
+ ttk.Label(spans_frame, text="Span Length (m):").grid(row=0, column=0, sticky=tk.W)
64
+ self.span_length_var = tk.StringVar(value="6.0")
65
+ ttk.Entry(spans_frame, textvariable=self.span_length_var, width=10).grid(row=0, column=1, sticky=tk.W, padx=(5, 20))
66
+
67
+ ttk.Label(spans_frame, text="Distributed Load (kN/m):").grid(row=0, column=2, sticky=tk.W)
68
+ self.load_var = tk.StringVar(value="25.0")
69
+ ttk.Entry(spans_frame, textvariable=self.load_var, width=10).grid(row=0, column=3, sticky=tk.W, padx=(5, 20))
70
+
71
+ # Buttons
72
+ button_frame = ttk.Frame(spans_frame)
73
+ button_frame.grid(row=1, column=0, columnspan=4, pady=10)
74
+
75
+ ttk.Button(button_frame, text="Add Span", command=self.add_span).pack(side=tk.LEFT, padx=(0, 10))
76
+ ttk.Button(button_frame, text="Clear All", command=self.clear_spans).pack(side=tk.LEFT, padx=(0, 10))
77
+ ttk.Button(button_frame, text="Design Beam", command=self.design_beam).pack(side=tk.LEFT, padx=(0, 10))
78
+
79
+ # Spans list
80
+ self.spans_listbox = tk.Listbox(spans_frame, height=4)
81
+ self.spans_listbox.grid(row=2, column=0, columnspan=4, sticky=(tk.W, tk.E), pady=(10, 0))
82
+ spans_frame.columnconfigure(0, weight=1)
83
+ spans_frame.columnconfigure(1, weight=1)
84
+ spans_frame.columnconfigure(2, weight=1)
85
+ spans_frame.columnconfigure(3, weight=1)
86
+
87
+ # Results frame
88
+ results_frame = ttk.LabelFrame(main_frame, text="Design Results", padding="10")
89
+ results_frame.grid(row=3, column=0, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S))
90
+ results_frame.columnconfigure(0, weight=1)
91
+ results_frame.rowconfigure(0, weight=1)
92
+
93
+ # Results text area
94
+ self.results_text = scrolledtext.ScrolledText(results_frame, width=80, height=20, font=('Courier', 9))
95
+ self.results_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
96
+
97
+ def add_span(self):
98
+ try:
99
+ length = float(self.span_length_var.get())
100
+ load = float(self.load_var.get())
101
+
102
+ if length <= 0 or load < 0:
103
+ messagebox.showerror("Error", "Please enter valid values (length > 0, load >= 0)")
104
+ return
105
+
106
+ self.beam.add_span(length, load)
107
+
108
+ # Update spans list
109
+ span_text = f"Span {len(self.beam.spans)}: L={length}m, w={load}kN/m"
110
+ self.spans_listbox.insert(tk.END, span_text)
111
+
112
+ # Clear input fields
113
+ self.span_length_var.set("")
114
+ self.load_var.set("")
115
+
116
+ except ValueError:
117
+ messagebox.showerror("Error", "Please enter valid numeric values")
118
+
119
+ def clear_spans(self):
120
+ self.beam = ContinuousBeam()
121
+ self.spans_listbox.delete(0, tk.END)
122
+ self.results_text.delete('1.0', tk.END)
123
+
124
+ def design_beam(self):
125
+ if not self.beam.spans:
126
+ messagebox.showwarning("Warning", "Please add at least one span")
127
+ return
128
+
129
+ try:
130
+ # Update beam properties
131
+ self.beam.beam_width = float(self.width_var.get())
132
+ self.beam.beam_depth = float(self.depth_var.get())
133
+ self.beam.fc = float(self.fc_var.get())
134
+ self.beam.fy = float(self.fy_var.get())
135
+ self.beam.cover = float(self.cover_var.get())
136
+ self.beam.d = self.beam.beam_depth - self.beam.cover
137
+
138
+ # Perform design
139
+ design_results = self.beam.design_beam()
140
+
141
+ # Generate and display report
142
+ report = self.beam.generate_report(design_results)
143
+
144
+ self.results_text.delete('1.0', tk.END)
145
+ self.results_text.insert('1.0', report)
146
+
147
+ # Show summary dialog
148
+ self.show_summary(design_results)
149
+
150
+ except Exception as e:
151
+ messagebox.showerror("Error", f"Design calculation failed: {str(e)}")
152
+
153
+ def show_summary(self, design_results):
154
+ """Show design summary in a popup window"""
155
+ summary_window = tk.Toplevel(self.root)
156
+ summary_window.title("Design Summary")
157
+ summary_window.geometry("600x400")
158
+
159
+ # Create treeview for summary
160
+ tree = ttk.Treeview(summary_window, columns=('Span', 'Location', 'Moment', 'Reinforcement', 'Shear', 'Stirrups'))
161
+ tree.heading('#0', text='', anchor='w')
162
+ tree.heading('Span', text='Span')
163
+ tree.heading('Location', text='Location')
164
+ tree.heading('Moment', text='Moment (kN-m)')
165
+ tree.heading('Reinforcement', text='Reinforcement')
166
+ tree.heading('Shear', text='Shear (kN)')
167
+ tree.heading('Stirrups', text='Stirrups')
168
+
169
+ tree.column('#0', width=0, stretch=False)
170
+ tree.column('Span', width=60)
171
+ tree.column('Location', width=100)
172
+ tree.column('Moment', width=100)
173
+ tree.column('Reinforcement', width=120)
174
+ tree.column('Shear', width=80)
175
+ tree.column('Stirrups', width=120)
176
+
177
+ for span_data in design_results:
178
+ span_num = span_data['span']
179
+ for i, (reinf, stirrup) in enumerate(zip(span_data['reinforcement'], span_data['stirrups'])):
180
+ if reinf['moment'] != 0 or stirrup['shear'] != 0:
181
+ tree.insert('', 'end', values=(
182
+ span_num if i == 0 else '',
183
+ reinf['location'],
184
+ f"{reinf['moment']:.2f}" if reinf['moment'] != 0 else '-',
185
+ reinf['bars'] if reinf['moment'] != 0 else '-',
186
+ f"{stirrup['shear']:.2f}" if stirrup['shear'] != 0 else '-',
187
+ stirrup['stirrup_spacing'] if stirrup['shear'] != 0 else '-'
188
+ ))
189
+
190
+ tree.pack(expand=True, fill='both', padx=10, pady=10)
191
+
192
+ # Close button
193
+ ttk.Button(summary_window, text="Close",
194
+ command=summary_window.destroy).pack(pady=10)
195
+
196
+ def main():
197
+ root = tk.Tk()
198
+ app = BeamDesignApp(root)
199
+ root.mainloop()
200
+
201
+ if __name__ == "__main__":
202
+ main()
continuous_beam.py ADDED
@@ -0,0 +1,1036 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import matplotlib.pyplot as plt
3
+ from typing import List, Dict, Tuple
4
+ import math
5
+
6
+ class ContinuousBeam:
7
+ """
8
+ Continuous beam analysis and RC design according to ACI code
9
+ """
10
+
11
+ def __init__(self):
12
+ self.spans = []
13
+ self.loads = []
14
+ self.supports = []
15
+ self.moments = []
16
+ self.shears = []
17
+ self.fc = 28 # Concrete compressive strength (MPa)
18
+ self.fy = 420 # Steel yield strength (MPa)
19
+ self.beam_width = 300 # mm
20
+ self.beam_depth = 500 # mm
21
+ self.cover = 40 # mm
22
+ self.d = self.beam_depth - self.cover # Effective depth
23
+
24
+ def add_span(self, length: float, distributed_load: float = 0, point_loads: List[Tuple[float, float]] = None):
25
+ """
26
+ Add a span to the continuous beam
27
+ length: span length in meters
28
+ distributed_load: uniformly distributed load in kN/m
29
+ point_loads: list of (position, load) tuples in (m, kN)
30
+ """
31
+ span = {
32
+ 'length': length,
33
+ 'distributed_load': distributed_load,
34
+ 'point_loads': point_loads or []
35
+ }
36
+ self.spans.append(span)
37
+
38
+ def create_beam_element_stiffness(self, length, E, I):
39
+ """
40
+ Create local stiffness matrix for a beam element
41
+ [k] = EI/L^3 * [[12, 6L, -12, 6L],
42
+ [6L, 4L^2, -6L, 2L^2],
43
+ [-12, -6L, 12, -6L],
44
+ [6L, 2L^2, -6L, 4L^2]]
45
+ DOFs: [v1, θ1, v2, θ2] where v=deflection, θ=rotation
46
+ """
47
+ L = length
48
+ EI_L3 = E * I / (L**3)
49
+
50
+ k = EI_L3 * np.array([
51
+ [12, 6*L, -12, 6*L],
52
+ [6*L, 4*L**2, -6*L, 2*L**2],
53
+ [-12, -6*L, 12, -6*L],
54
+ [6*L, 2*L**2, -6*L, 4*L**2]
55
+ ])
56
+
57
+ return k
58
+
59
+ def create_distributed_load_vector(self, length, w):
60
+ """
61
+ Create equivalent nodal force vector for distributed load
62
+ For uniformly distributed load w:
63
+ [F] = wL/12 * [6, L, 6, -L]
64
+ """
65
+ L = length
66
+ wL_12 = w * L / 12
67
+
68
+ f = wL_12 * np.array([6, L, 6, -L])
69
+ return f
70
+
71
+ def create_point_load_vector(self, length, point_loads):
72
+ """
73
+ Create equivalent nodal force vector for point loads
74
+ """
75
+ L = length
76
+ f = np.zeros(4)
77
+
78
+ for pos, load in point_loads:
79
+ a = pos # distance from left node
80
+ b = L - pos # distance from right node
81
+
82
+ # Shape functions at load position
83
+ xi = pos / L # normalized position
84
+
85
+ # Equivalent nodal forces using shape functions
86
+ N1 = 1 - 3*xi**2 + 2*xi**3
87
+ N2 = L * (xi - 2*xi**2 + xi**3)
88
+ N3 = 3*xi**2 - 2*xi**3
89
+ N4 = L * (-xi**2 + xi**3)
90
+
91
+ f += load * np.array([N1, N2, N3, N4])
92
+
93
+ return f
94
+
95
+ def finite_element_analysis(self):
96
+ """
97
+ Perform finite element analysis of continuous beam
98
+ """
99
+ if len(self.spans) == 0:
100
+ raise ValueError("No spans defined")
101
+
102
+ # Material properties (assumed values for analysis)
103
+ E = 30000 # MPa (typical for concrete)
104
+ # Calculate moment of inertia from beam dimensions
105
+ b = self.beam_width / 1000 # Convert mm to m
106
+ h = self.beam_depth / 1000 # Convert mm to m
107
+ I = b * h**3 / 12 # m^4
108
+
109
+ # Create mesh - each span divided into elements
110
+ elements_per_span = 10 # Number of elements per span
111
+ total_elements = len(self.spans) * elements_per_span
112
+ total_nodes = total_elements + 1
113
+
114
+ # Node coordinates
115
+ node_coords = []
116
+ current_x = 0
117
+
118
+ for span in self.spans:
119
+ span_length = span['length']
120
+ element_length = span_length / elements_per_span
121
+
122
+ for i in range(elements_per_span):
123
+ node_coords.append(current_x + i * element_length)
124
+
125
+ current_x += span_length
126
+
127
+ # Add final node
128
+ node_coords.append(current_x)
129
+ node_coords = np.array(node_coords)
130
+
131
+ # Global stiffness matrix (2 DOFs per node: deflection and rotation)
132
+ n_dofs = 2 * total_nodes
133
+ K_global = np.zeros((n_dofs, n_dofs))
134
+ F_global = np.zeros(n_dofs)
135
+
136
+ # Assembly process
137
+ for elem_idx in range(total_elements):
138
+ # Element properties
139
+ span_idx = elem_idx // elements_per_span
140
+ local_elem_idx = elem_idx % elements_per_span
141
+
142
+ span = self.spans[span_idx]
143
+ element_length = span['length'] / elements_per_span
144
+
145
+ # Local stiffness matrix
146
+ k_local = self.create_beam_element_stiffness(element_length, E, I)
147
+
148
+ # Global DOF indices for this element
149
+ node1 = elem_idx
150
+ node2 = elem_idx + 1
151
+
152
+ dofs = [2*node1, 2*node1+1, 2*node2, 2*node2+1] # [v1, θ1, v2, θ2]
153
+
154
+ # Assemble into global matrix
155
+ for i in range(4):
156
+ for j in range(4):
157
+ K_global[dofs[i], dofs[j]] += k_local[i, j]
158
+
159
+ # Create load vector for this element
160
+ # Distributed load
161
+ w = span['distributed_load'] * 1000 # Convert kN/m to N/m
162
+ f_dist = self.create_distributed_load_vector(element_length, w)
163
+
164
+ # Point loads (only if they fall within this element)
165
+ point_loads = span.get('point_loads', [])
166
+ f_point = np.zeros(4)
167
+
168
+ span_start = sum(self.spans[j]['length'] for j in range(span_idx))
169
+ elem_start = span_start + local_elem_idx * element_length
170
+ elem_end = elem_start + element_length
171
+
172
+ for pos, load in point_loads:
173
+ global_pos = span_start + pos
174
+ if elem_start <= global_pos <= elem_end:
175
+ local_pos = global_pos - elem_start
176
+ f_point += self.create_point_load_vector(element_length, [(local_pos, load * 1000)])
177
+
178
+ f_total = f_dist + f_point
179
+
180
+ # Assemble into global force vector
181
+ for i in range(4):
182
+ F_global[dofs[i]] += f_total[i]
183
+
184
+ # Apply boundary conditions (pin supports at all support locations)
185
+ # Support locations are at the ends of each span
186
+ support_nodes = [0] # First support
187
+ current_node = 0
188
+ for span in self.spans:
189
+ current_node += elements_per_span
190
+ support_nodes.append(current_node)
191
+
192
+ # Create arrays for free DOFs (removing constrained deflections)
193
+ constrained_dofs = [2 * node for node in support_nodes] # Vertical deflections at supports
194
+ free_dofs = [i for i in range(n_dofs) if i not in constrained_dofs]
195
+
196
+ # Extract free DOF matrices
197
+ K_free = K_global[np.ix_(free_dofs, free_dofs)]
198
+ F_free = F_global[free_dofs]
199
+
200
+ # Solve for displacements
201
+ try:
202
+ U_free = np.linalg.solve(K_free, F_free)
203
+ except np.linalg.LinAlgError:
204
+ # Fallback to least squares if matrix is singular
205
+ U_free = np.linalg.lstsq(K_free, F_free, rcond=None)[0]
206
+
207
+ # Reconstruct full displacement vector
208
+ U_global = np.zeros(n_dofs)
209
+ U_global[free_dofs] = U_free
210
+
211
+ # Store results for post-processing
212
+ self.node_coords = node_coords
213
+ self.displacements = U_global
214
+ self.elements_per_span = elements_per_span
215
+ self.element_properties = {'E': E, 'I': I}
216
+ self.K_global = K_global # Store for reaction calculation
217
+
218
+ return node_coords, U_global
219
+
220
+ def calculate_element_forces(self):
221
+ """
222
+ Calculate internal forces (moment and shear) for each element
223
+ """
224
+ if not hasattr(self, 'displacements'):
225
+ self.finite_element_analysis()
226
+
227
+ E = self.element_properties['E']
228
+ I = self.element_properties['I']
229
+
230
+ moments = []
231
+ shears = []
232
+ x_coords = []
233
+
234
+ # Calculate reactions first for proper shear calculation
235
+ reactions = self.calculate_reactions()
236
+
237
+ elem_idx = 0
238
+ for span_idx, span in enumerate(self.spans):
239
+ span_length = span['length']
240
+ element_length = span_length / self.elements_per_span
241
+
242
+ span_start_x = sum(self.spans[j]['length'] for j in range(span_idx))
243
+
244
+ for local_elem in range(self.elements_per_span):
245
+ # Element nodes
246
+ node1 = elem_idx
247
+ node2 = elem_idx + 1
248
+
249
+ # Element displacements
250
+ u1 = self.displacements[2*node1] # deflection at node 1
251
+ theta1 = self.displacements[2*node1+1] # rotation at node 1
252
+ u2 = self.displacements[2*node2] # deflection at node 2
253
+ theta2 = self.displacements[2*node2+1] # rotation at node 2
254
+
255
+ # Calculate forces at multiple points within element
256
+ n_points = 10
257
+ for i in range(n_points):
258
+ xi = i / (n_points - 1) # 0 to 1
259
+ x_local = xi * element_length
260
+ x_global = span_start_x + local_elem * element_length + x_local
261
+
262
+ # Shape function derivatives for moment calculation
263
+ # M = -EI * d²v/dx²
264
+ d2N1_dx2 = (-6 + 12*xi) / element_length**2
265
+ d2N2_dx2 = (-4 + 6*xi) / element_length
266
+ d2N3_dx2 = (6 - 12*xi) / element_length**2
267
+ d2N4_dx2 = (-2 + 6*xi) / element_length
268
+
269
+ curvature = (d2N1_dx2 * u1 + d2N2_dx2 * theta1 +
270
+ d2N3_dx2 * u2 + d2N4_dx2 * theta2)
271
+ moment = -E * I * curvature / 1000 # Convert to kN-m
272
+
273
+ # Calculate shear using equilibrium method (more reliable)
274
+ shear = self.calculate_shear_at_position(x_global, reactions)
275
+
276
+ x_coords.append(x_global)
277
+ moments.append(moment)
278
+ shears.append(shear)
279
+
280
+ elem_idx += 1
281
+
282
+ return np.array(x_coords), np.array(moments), np.array(shears)
283
+
284
+ def calculate_reactions(self):
285
+ """
286
+ Calculate support reactions from finite element solution
287
+ """
288
+ if not hasattr(self, 'K_global') or not hasattr(self, 'displacements'):
289
+ self.finite_element_analysis()
290
+
291
+ # Get support node indices
292
+ support_nodes = [0] # First support
293
+ current_node = 0
294
+ for span in self.spans:
295
+ current_node += self.elements_per_span
296
+ support_nodes.append(current_node)
297
+
298
+ # Calculate reactions using R = K*U - F for constrained DOFs
299
+ reactions = []
300
+
301
+ # Build complete force vector including applied loads
302
+ n_dofs = len(self.displacements)
303
+ F_complete = np.zeros(n_dofs)
304
+
305
+ # Assemble applied load vector (same as in FE analysis)
306
+ elem_idx = 0
307
+ for span_idx, span in enumerate(self.spans):
308
+ element_length = span['length'] / self.elements_per_span
309
+
310
+ for local_elem_idx in range(self.elements_per_span):
311
+ # Global DOF indices for this element
312
+ node1 = elem_idx
313
+ node2 = elem_idx + 1
314
+ dofs = [2*node1, 2*node1+1, 2*node2, 2*node2+1]
315
+
316
+ # Create load vector for this element
317
+ w = span['distributed_load'] * 1000 # Convert to N/m
318
+ f_dist = self.create_distributed_load_vector(element_length, w)
319
+
320
+ # Point loads (only if they fall within this element)
321
+ point_loads = span.get('point_loads', [])
322
+ f_point = np.zeros(4)
323
+
324
+ span_start = sum(self.spans[j]['length'] for j in range(span_idx))
325
+ elem_start = span_start + local_elem_idx * element_length
326
+ elem_end = elem_start + element_length
327
+
328
+ for pos, load in point_loads:
329
+ global_pos = span_start + pos
330
+ if elem_start <= global_pos <= elem_end:
331
+ local_pos = global_pos - elem_start
332
+ f_point += self.create_point_load_vector(element_length, [(local_pos, load * 1000)])
333
+
334
+ f_total = f_dist + f_point
335
+
336
+ # Assemble into global force vector
337
+ for i in range(4):
338
+ F_complete[dofs[i]] += f_total[i]
339
+
340
+ elem_idx += 1
341
+
342
+ # Calculate reactions at each support
343
+ for support_node in support_nodes:
344
+ # Vertical DOF for this support
345
+ dof = 2 * support_node
346
+
347
+ # Reaction = K*U - F at constrained DOF
348
+ # Since displacement is zero at support, reaction = -F_applied + K*U_other
349
+ reaction_force = 0
350
+
351
+ # Sum contributions from all DOFs
352
+ for j in range(n_dofs):
353
+ reaction_force += self.K_global[dof, j] * self.displacements[j]
354
+
355
+ # Subtract applied force (if any) at this DOF
356
+ reaction_force -= F_complete[dof]
357
+
358
+ # Convert to kN and store (positive = upward reaction)
359
+ # Note: FE convention may give negative values for upward reactions
360
+ reactions.append(-reaction_force / 1000)
361
+
362
+ # Store for debugging
363
+ self.reactions = reactions
364
+ return reactions
365
+
366
+ def calculate_shear_at_position(self, x_global, reactions):
367
+ """
368
+ Calculate shear force at any position using equilibrium
369
+ """
370
+ shear = 0
371
+ current_pos = 0
372
+
373
+ # Add reaction at first support
374
+ if len(reactions) > 0:
375
+ shear += reactions[0]
376
+
377
+ # Subtract loads to the left of current position
378
+ support_idx = 1
379
+ for span_idx, span in enumerate(self.spans):
380
+ span_start = current_pos
381
+ span_end = current_pos + span['length']
382
+
383
+ if x_global <= span_start:
384
+ break
385
+
386
+ # Check if we passed a support
387
+ if x_global > span_end and support_idx < len(reactions):
388
+ shear += reactions[support_idx]
389
+ support_idx += 1
390
+
391
+ # Calculate how much of this span is to the left of current position
392
+ span_length_to_left = min(x_global - span_start, span['length'])
393
+
394
+ if span_length_to_left > 0:
395
+ # Distributed load effect
396
+ w = span['distributed_load']
397
+ shear -= w * span_length_to_left
398
+
399
+ # Point load effects
400
+ point_loads = span.get('point_loads', [])
401
+ for pos, load in point_loads:
402
+ if pos <= span_length_to_left:
403
+ shear -= load
404
+
405
+ current_pos += span['length']
406
+
407
+ return shear
408
+
409
+ def analyze_moments(self):
410
+ """
411
+ Analyze continuous beam using finite element method
412
+ """
413
+ num_spans = len(self.spans)
414
+ if num_spans == 0:
415
+ raise ValueError("No spans defined")
416
+
417
+ # Perform finite element analysis
418
+ self.finite_element_analysis()
419
+
420
+ # Calculate detailed forces along the beam
421
+ x_coords, moments_detailed, shears_detailed = self.calculate_element_forces()
422
+
423
+ # Extract critical moments and shears for each span (for compatibility with existing code)
424
+ self.moments = []
425
+ self.shears = []
426
+
427
+ current_pos = 0
428
+ for i, span in enumerate(self.spans):
429
+ span_length = span['length']
430
+ span_start = current_pos
431
+ span_mid = current_pos + span_length / 2
432
+ span_end = current_pos + span_length
433
+
434
+ # Find indices closest to critical points
435
+ start_idx = np.argmin(np.abs(x_coords - span_start))
436
+ mid_idx = np.argmin(np.abs(x_coords - span_mid))
437
+ end_idx = np.argmin(np.abs(x_coords - span_end))
438
+
439
+ # Extract moments and shears at critical points
440
+ M_start = moments_detailed[start_idx]
441
+ M_mid = moments_detailed[mid_idx]
442
+ M_end = moments_detailed[end_idx]
443
+
444
+ V_start = shears_detailed[start_idx]
445
+ V_mid = shears_detailed[mid_idx]
446
+ V_end = shears_detailed[end_idx]
447
+
448
+ # Store for span (maintaining compatibility with existing design methods)
449
+ self.moments.append([M_start, M_mid, M_end])
450
+ self.shears.append([V_start, V_mid, V_end])
451
+
452
+ current_pos += span_length
453
+
454
+ # Store detailed results for plotting
455
+ self.detailed_x = x_coords
456
+ self.detailed_moments = moments_detailed
457
+ self.detailed_shears = shears_detailed
458
+
459
+
460
+ def calculate_required_reinforcement(self, moment: float, beam_type: str = "rectangular"):
461
+ """
462
+ Calculate required area of reinforcement according to ACI code
463
+ moment: Design moment in kN-m
464
+ beam_type: Type of beam section
465
+ """
466
+ if moment == 0:
467
+ return 0
468
+
469
+ # Convert moment to N-mm
470
+ Mu = abs(moment) * 1e6
471
+
472
+ # Material properties
473
+ fc = self.fc # MPa
474
+ fy = self.fy # MPa
475
+ b = self.beam_width # mm
476
+ d = self.d # mm
477
+
478
+ # Strength reduction factor
479
+ phi = 0.9
480
+
481
+ # Calculate required reinforcement
482
+ # Using simplified rectangular stress block
483
+ beta1 = 0.85 if fc <= 28 else max(0.65, 0.85 - 0.05 * (fc - 28) / 7)
484
+
485
+ # Calculate Rn
486
+ Rn = Mu / (phi * b * d**2)
487
+
488
+ # Calculate reinforcement ratio
489
+ rho = (0.85 * fc / fy) * (1 - math.sqrt(1 - 2 * Rn / (0.85 * fc)))
490
+
491
+ # Minimum reinforcement ratio
492
+ rho_min = max(1.4 / fy, 0.25 * math.sqrt(fc) / fy)
493
+
494
+ # Maximum reinforcement ratio (75% of balanced ratio)
495
+ rho_b = (0.85 * fc * beta1 * 600) / (fy * (600 + fy))
496
+ rho_max = 0.75 * rho_b
497
+
498
+ # Check limits
499
+ rho = max(rho, rho_min)
500
+ if rho > rho_max:
501
+ raise ValueError(f"Required reinforcement ratio {rho:.4f} exceeds maximum {rho_max:.4f}")
502
+
503
+ # Calculate required area
504
+ As_required = rho * b * d
505
+
506
+ return As_required
507
+
508
+ def calculate_shear_reinforcement(self, shear: float):
509
+ """
510
+ Calculate shear reinforcement (stirrups) according to ACI code
511
+ shear: Design shear force in kN
512
+ """
513
+ if shear == 0:
514
+ return {"stirrup_spacing": "No stirrups required", "Av": 0}
515
+
516
+ # Convert shear to N
517
+ Vu = abs(shear) * 1000
518
+
519
+ # Material properties
520
+ fc = self.fc # MPa
521
+ fy = self.fy # MPa (for stirrups)
522
+ b = self.beam_width # mm
523
+ d = self.d # mm
524
+
525
+ # Strength reduction factor for shear
526
+ phi_v = 0.75
527
+
528
+ # Concrete shear capacity
529
+ Vc = 0.17 * math.sqrt(fc) * b * d # N
530
+
531
+ # Check if shear reinforcement is required
532
+ if Vu <= phi_v * Vc / 2:
533
+ return {"stirrup_spacing": "No stirrups required", "Av": 0}
534
+
535
+ # Calculate required shear reinforcement
536
+ Vs = Vu / phi_v - Vc # Required steel shear capacity
537
+
538
+ # Maximum shear that can be carried by steel
539
+ Vs_max = 0.66 * math.sqrt(fc) * b * d
540
+
541
+ if Vs > Vs_max:
542
+ raise ValueError("Shear exceeds maximum capacity - increase beam size")
543
+
544
+ # Calculate required stirrup area
545
+ # Assuming #10 stirrups (2 legs, Area = 2 × 71 = 142 mm²)
546
+ Av = 142 # mm² (2-leg #10 stirrups)
547
+
548
+ # Calculate required spacing
549
+ s_required = Av * fy * d / Vs # mm
550
+
551
+ # Maximum spacing limits
552
+ s_max = min(d / 2, 600) # mm
553
+
554
+ # Minimum stirrup requirements
555
+ if Vu > phi_v * Vc:
556
+ Av_min = 0.35 * b * s_required / fy
557
+ s_max_min = min(d / 4, 300) # More restrictive for high shear
558
+ s_required = min(s_required, s_max_min)
559
+
560
+ s_required = min(s_required, s_max)
561
+ s_required = max(s_required, 50) # Minimum practical spacing
562
+
563
+ return {
564
+ "stirrup_spacing": f"{s_required:.0f} mm c/c",
565
+ "Av": Av,
566
+ "Vs": Vs / 1000, # Convert back to kN
567
+ "Vc": Vc / 1000 # Convert back to kN
568
+ }
569
+
570
+ def design_beam(self):
571
+ """
572
+ Complete beam design including flexural and shear design
573
+ """
574
+ if not self.moments:
575
+ self.analyze_moments()
576
+
577
+ design_results = []
578
+
579
+ for i, (moments, shears) in enumerate(zip(self.moments, self.shears)):
580
+ span_design = {
581
+ 'span': i + 1,
582
+ 'length': self.spans[i]['length'],
583
+ 'moments': moments,
584
+ 'shears': shears,
585
+ 'reinforcement': [],
586
+ 'stirrups': []
587
+ }
588
+
589
+ # Design for each critical section
590
+ moment_locations = ['Left Support', 'Mid-span', 'Right Support']
591
+
592
+ for j, (moment, shear) in enumerate(zip(moments, shears)):
593
+ # Flexural design
594
+ if moment != 0:
595
+ As_required = self.calculate_required_reinforcement(moment)
596
+
597
+ # Select reinforcement bars
598
+ bar_areas = {10: 79, 12: 113, 16: 201, 20: 314, 25: 491} # mm²
599
+
600
+ # Try different bar sizes
601
+ for bar_size, bar_area in bar_areas.items():
602
+ num_bars = math.ceil(As_required / bar_area)
603
+ if num_bars <= 8: # Practical limit
604
+ As_provided = num_bars * bar_area
605
+ break
606
+ else:
607
+ # Use largest bars if can't fit
608
+ bar_size = 25
609
+ bar_area = 491
610
+ num_bars = math.ceil(As_required / bar_area)
611
+ As_provided = num_bars * bar_area
612
+
613
+ reinforcement = {
614
+ 'location': moment_locations[j],
615
+ 'moment': moment,
616
+ 'As_required': As_required,
617
+ 'As_provided': As_provided,
618
+ 'bars': f"{num_bars}T{bar_size}",
619
+ 'ratio': As_provided / (self.beam_width * self.d) * 100
620
+ }
621
+ else:
622
+ reinforcement = {
623
+ 'location': moment_locations[j],
624
+ 'moment': 0,
625
+ 'As_required': 0,
626
+ 'As_provided': 0,
627
+ 'bars': "No reinforcement",
628
+ 'ratio': 0
629
+ }
630
+
631
+ span_design['reinforcement'].append(reinforcement)
632
+
633
+ # Shear design
634
+ stirrup_design = self.calculate_shear_reinforcement(shear)
635
+ stirrup_design['location'] = moment_locations[j]
636
+ stirrup_design['shear'] = shear
637
+ span_design['stirrups'].append(stirrup_design)
638
+
639
+ design_results.append(span_design)
640
+
641
+ return design_results
642
+
643
+ def generate_report(self, design_results):
644
+ """
645
+ Generate design report
646
+ """
647
+ report = []
648
+ report.append("="*60)
649
+ report.append("CONTINUOUS BEAM RC DESIGN REPORT")
650
+ report.append("According to ACI Code")
651
+ report.append("="*60)
652
+ report.append(f"Beam dimensions: {self.beam_width}mm × {self.beam_depth}mm")
653
+ report.append(f"Concrete strength (f'c): {self.fc} MPa")
654
+ report.append(f"Steel strength (fy): {self.fy} MPa")
655
+ report.append(f"Effective depth (d): {self.d} mm")
656
+ report.append("")
657
+
658
+ for span_data in design_results:
659
+ report.append(f"SPAN {span_data['span']} - Length: {span_data['length']} m")
660
+ report.append("-" * 40)
661
+
662
+ # Moments and reinforcement
663
+ report.append("FLEXURAL DESIGN:")
664
+ for reinf in span_data['reinforcement']:
665
+ if reinf['moment'] != 0:
666
+ report.append(f" {reinf['location']}:")
667
+ report.append(f" Moment: {reinf['moment']:.2f} kN-m")
668
+ report.append(f" As required: {reinf['As_required']:.0f} mm²")
669
+ report.append(f" As provided: {reinf['As_provided']:.0f} mm²")
670
+ report.append(f" Reinforcement: {reinf['bars']}")
671
+ report.append(f" Reinforcement ratio: {reinf['ratio']:.2f}%")
672
+ report.append("")
673
+
674
+ # Shear and stirrups
675
+ report.append("SHEAR DESIGN:")
676
+ for stirrup in span_data['stirrups']:
677
+ if stirrup['shear'] != 0:
678
+ report.append(f" {stirrup['location']}:")
679
+ report.append(f" Shear: {stirrup['shear']:.2f} kN")
680
+ if 'Vs' in stirrup:
681
+ report.append(f" Vc: {stirrup['Vc']:.2f} kN")
682
+ report.append(f" Vs: {stirrup['Vs']:.2f} kN")
683
+ report.append(f" Stirrup spacing: {stirrup['stirrup_spacing']}")
684
+ report.append("")
685
+
686
+ report.append("")
687
+
688
+ return "\n".join(report)
689
+
690
+ def plot_bmd_sfd(self, design_results=None):
691
+ """
692
+ Generate BMD and SFD plots
693
+ """
694
+ if design_results is None:
695
+ design_results = self.design_beam()
696
+
697
+ fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 8))
698
+
699
+ # Calculate total beam length and positions
700
+ total_length = 0
701
+ span_positions = [0]
702
+
703
+ for span in self.spans:
704
+ total_length += span['length']
705
+ span_positions.append(total_length)
706
+
707
+ # Use detailed FE results if available, otherwise fall back to approximate method
708
+ if hasattr(self, 'detailed_x') and hasattr(self, 'detailed_moments'):
709
+ # Use finite element results
710
+ x_coords = self.detailed_x
711
+ moments_detailed = self.detailed_moments
712
+ shears_detailed = self.detailed_shears
713
+ else:
714
+ # Fallback to approximate method for backwards compatibility
715
+ x_coords = []
716
+ moments_detailed = []
717
+ shears_detailed = []
718
+
719
+ for i, span_data in enumerate(design_results):
720
+ span_length = span_data['length']
721
+ start_pos = span_positions[i]
722
+
723
+ # Create x coordinates for this span
724
+ x_span = np.linspace(start_pos, start_pos + span_length, 100)
725
+
726
+ # Get moments and shears for this span
727
+ moments = span_data['moments'] # [left, mid, right]
728
+ shears = span_data['shears']
729
+
730
+ # Simple interpolation between critical points
731
+ moment_curve = np.interp(x_span,
732
+ [start_pos, start_pos + span_length/2, start_pos + span_length],
733
+ moments)
734
+ shear_curve = np.interp(x_span,
735
+ [start_pos, start_pos + span_length/2, start_pos + span_length],
736
+ shears)
737
+
738
+ x_coords.extend(x_span)
739
+ moments_detailed.extend(moment_curve)
740
+ shears_detailed.extend(shear_curve)
741
+
742
+ # Plot BMD
743
+ ax1.plot(x_coords, moments_detailed, 'b-', linewidth=2, label='Bending Moment')
744
+ ax1.fill_between(x_coords, moments_detailed, alpha=0.3, color='blue')
745
+ ax1.axhline(y=0, color='k', linestyle='-', alpha=0.3)
746
+ ax1.set_ylabel('Bending Moment (kN-m)', fontsize=12)
747
+ ax1.set_title('Bending Moment Diagram (BMD)', fontsize=14, fontweight='bold')
748
+ ax1.grid(True, alpha=0.3)
749
+ ax1.legend()
750
+
751
+ # Add support symbols
752
+ for pos in span_positions:
753
+ ax1.axvline(x=pos, color='red', linestyle='--', alpha=0.7)
754
+ ax1.plot(pos, 0, 'rs', markersize=8, label='Support' if pos == span_positions[0] else "")
755
+
756
+ # Plot SFD
757
+ ax2.plot(x_coords, shears_detailed, 'r-', linewidth=2, label='Shear Force')
758
+ ax2.fill_between(x_coords, shears_detailed, alpha=0.3, color='red')
759
+ ax2.axhline(y=0, color='k', linestyle='-', alpha=0.3)
760
+ ax2.set_ylabel('Shear Force (kN)', fontsize=12)
761
+ ax2.set_xlabel('Distance along beam (m)', fontsize=12)
762
+ ax2.set_title('Shear Force Diagram (SFD)', fontsize=14, fontweight='bold')
763
+ ax2.grid(True, alpha=0.3)
764
+ ax2.legend()
765
+
766
+ # Add support symbols
767
+ for pos in span_positions:
768
+ ax2.axvline(x=pos, color='red', linestyle='--', alpha=0.7)
769
+ ax2.plot(pos, 0, 'rs', markersize=8, label='Support' if pos == span_positions[0] else "")
770
+
771
+ plt.tight_layout()
772
+ return fig
773
+
774
+ def plot_reinforcement_layout(self, design_results=None):
775
+ """
776
+ Generate reinforcement layout diagram
777
+ """
778
+ if design_results is None:
779
+ design_results = self.design_beam()
780
+
781
+ fig, ax = plt.subplots(1, 1, figsize=(14, 8))
782
+
783
+ # Calculate positions
784
+ total_length = sum(span['length'] for span in self.spans)
785
+ span_positions = [0]
786
+ current_pos = 0
787
+
788
+ for span in self.spans:
789
+ current_pos += span['length']
790
+ span_positions.append(current_pos)
791
+
792
+ # Draw beam outline
793
+ beam_height = 0.5 # Normalized height for drawing
794
+ ax.add_patch(plt.Rectangle((0, -beam_height/2), total_length, beam_height,
795
+ fill=False, edgecolor='black', linewidth=2))
796
+
797
+ # Add beam dimensions text
798
+ ax.text(total_length/2, beam_height/2 + 0.1,
799
+ f'{self.beam_width}mm × {self.beam_depth}mm',
800
+ ha='center', va='bottom', fontsize=10, fontweight='bold')
801
+
802
+ # Draw reinforcement for each span
803
+ colors = ['blue', 'green', 'orange', 'purple', 'brown']
804
+
805
+ for i, span_data in enumerate(design_results):
806
+ span_start = span_positions[i]
807
+ span_end = span_positions[i + 1]
808
+ span_center = (span_start + span_end) / 2
809
+ color = colors[i % len(colors)]
810
+
811
+ # Process reinforcement
812
+ for j, reinf in enumerate(span_data['reinforcement']):
813
+ if reinf['As_provided'] > 0:
814
+ location = reinf['location']
815
+ bars = reinf['bars']
816
+
817
+ if location == 'Left Support':
818
+ x_pos = span_start
819
+ y_pos = beam_height/3 # Top reinforcement
820
+ marker = '^'
821
+ label_pos = 'top'
822
+ elif location == 'Mid-span':
823
+ x_pos = span_center
824
+ y_pos = -beam_height/3 # Bottom reinforcement
825
+ marker = 'v'
826
+ label_pos = 'bottom'
827
+ else: # Right Support
828
+ x_pos = span_end
829
+ y_pos = beam_height/3 # Top reinforcement
830
+ marker = '^'
831
+ label_pos = 'top'
832
+
833
+ # Draw reinforcement symbol
834
+ ax.scatter(x_pos, y_pos, s=100, c=color, marker=marker,
835
+ edgecolor='black', linewidth=1, zorder=5)
836
+
837
+ # Add reinforcement label
838
+ if label_pos == 'top':
839
+ ax.text(x_pos, y_pos + 0.15, bars, ha='center', va='bottom',
840
+ fontsize=9, fontweight='bold', rotation=0)
841
+ else:
842
+ ax.text(x_pos, y_pos - 0.15, bars, ha='center', va='top',
843
+ fontsize=9, fontweight='bold', rotation=0)
844
+
845
+ # Draw supports
846
+ for i, pos in enumerate(span_positions):
847
+ # Support triangle
848
+ triangle_height = 0.2
849
+ triangle_width = 0.1
850
+
851
+ triangle = plt.Polygon([
852
+ [pos - triangle_width/2, -beam_height/2],
853
+ [pos + triangle_width/2, -beam_height/2],
854
+ [pos, -beam_height/2 - triangle_height]
855
+ ], fill=True, facecolor='red', edgecolor='black')
856
+
857
+ ax.add_patch(triangle)
858
+
859
+ # Support label
860
+ ax.text(pos, -beam_height/2 - triangle_height - 0.1,
861
+ f'Support {i+1}', ha='center', va='top', fontsize=8)
862
+
863
+ # Add span labels and loads
864
+ for i, span in enumerate(self.spans):
865
+ span_start = span_positions[i]
866
+ span_end = span_positions[i + 1]
867
+ span_center = (span_start + span_end) / 2
868
+
869
+ # Create span label text
870
+ label_text = f'Span {i+1}\nL = {span["length"]}m\nw = {span["distributed_load"]}kN/m'
871
+
872
+ # Add point loads to label if any
873
+ if span.get('point_loads'):
874
+ point_load_text = '\nPoint Loads:'
875
+ for pos, load in span['point_loads']:
876
+ point_load_text += f'\n{load}kN @ {pos}m'
877
+ label_text += point_load_text
878
+
879
+ # Span label
880
+ ax.text(span_center, -beam_height/2 - 0.4, label_text,
881
+ ha='center', va='top', fontsize=9,
882
+ bbox=dict(boxstyle="round,pad=0.3", facecolor="lightyellow", alpha=0.7))
883
+
884
+ # Distributed load arrows
885
+ if span["distributed_load"] > 0:
886
+ num_arrows = 5
887
+ for j in range(num_arrows):
888
+ x_arrow = span_start + (span_end - span_start) * j / (num_arrows - 1)
889
+ ax.arrow(x_arrow, beam_height/2 + 0.3, 0, -0.2,
890
+ head_width=0.05, head_length=0.05, fc='red', ec='red')
891
+
892
+ # Point load arrows
893
+ if span.get('point_loads'):
894
+ for pos, load in span['point_loads']:
895
+ x_point = span_start + pos
896
+ # Larger arrow for point loads
897
+ ax.arrow(x_point, beam_height/2 + 0.5, 0, -0.4,
898
+ head_width=0.08, head_length=0.08, fc='blue', ec='blue', linewidth=2)
899
+ # Point load label
900
+ ax.text(x_point, beam_height/2 + 0.6, f'{load}kN',
901
+ ha='center', va='bottom', fontsize=8, fontweight='bold', color='blue')
902
+
903
+ # Formatting
904
+ ax.set_xlim(-0.5, total_length + 0.5)
905
+ ax.set_ylim(-1.2, 1.0)
906
+ ax.set_xlabel('Distance along beam (m)', fontsize=12)
907
+ ax.set_title('Reinforcement Layout', fontsize=14, fontweight='bold')
908
+ ax.grid(True, alpha=0.3)
909
+ ax.set_aspect('equal')
910
+
911
+ # Legend
912
+ legend_elements = [
913
+ plt.scatter([], [], s=100, c='blue', marker='^', edgecolor='black',
914
+ label='Top Reinforcement (Negative Moment)'),
915
+ plt.scatter([], [], s=100, c='blue', marker='v', edgecolor='black',
916
+ label='Bottom Reinforcement (Positive Moment)')
917
+ ]
918
+ ax.legend(handles=legend_elements, loc='upper right')
919
+
920
+ plt.tight_layout()
921
+ return fig
922
+
923
+ def plot_stirrup_layout(self, design_results=None):
924
+ """
925
+ Generate shear stirrup layout diagram
926
+ """
927
+ if design_results is None:
928
+ design_results = self.design_beam()
929
+
930
+ fig, ax = plt.subplots(1, 1, figsize=(14, 6))
931
+
932
+ # Calculate positions
933
+ total_length = sum(span['length'] for span in self.spans)
934
+ span_positions = [0]
935
+ current_pos = 0
936
+
937
+ for span in self.spans:
938
+ current_pos += span['length']
939
+ span_positions.append(current_pos)
940
+
941
+ # Draw beam outline (side view)
942
+ beam_height = 0.5
943
+ ax.add_patch(plt.Rectangle((0, 0), total_length, beam_height,
944
+ fill=False, edgecolor='black', linewidth=2))
945
+
946
+ # Draw stirrups for each span
947
+ for i, span_data in enumerate(design_results):
948
+ span_start = span_positions[i]
949
+ span_end = span_positions[i + 1]
950
+ span_length = span_end - span_start
951
+
952
+ # Get stirrup information
953
+ stirrup_info = []
954
+ for stirrup in span_data['stirrups']:
955
+ if 'No stirrups' not in stirrup['stirrup_spacing']:
956
+ spacing_str = stirrup['stirrup_spacing'].replace(' mm c/c', '')
957
+ try:
958
+ spacing = float(spacing_str) / 1000 # Convert mm to m
959
+ stirrup_info.append({
960
+ 'location': stirrup['location'],
961
+ 'spacing': spacing,
962
+ 'shear': stirrup['shear']
963
+ })
964
+ except:
965
+ pass
966
+
967
+ if stirrup_info:
968
+ # Determine stirrup spacing pattern
969
+ # For simplicity, use average spacing
970
+ avg_spacing = np.mean([s['spacing'] for s in stirrup_info])
971
+
972
+ # Draw stirrups
973
+ num_stirrups = int(span_length / avg_spacing) + 1
974
+ for j in range(num_stirrups):
975
+ x_pos = span_start + j * avg_spacing
976
+ if x_pos <= span_end:
977
+ # Draw stirrup as vertical line
978
+ ax.plot([x_pos, x_pos], [0, beam_height], 'g-', linewidth=2, alpha=0.7)
979
+
980
+ # Add stirrup symbol (small rectangle)
981
+ stirrup_width = avg_spacing * 0.1
982
+ ax.add_patch(plt.Rectangle((x_pos - stirrup_width/2, beam_height*0.1),
983
+ stirrup_width, beam_height*0.8,
984
+ fill=False, edgecolor='green', linewidth=1))
985
+
986
+ # Add spacing annotation
987
+ mid_span = (span_start + span_end) / 2
988
+ ax.annotate(f'Stirrups @ {avg_spacing*1000:.0f}mm c/c',
989
+ xy=(mid_span, beam_height + 0.1),
990
+ ha='center', va='bottom', fontsize=9,
991
+ bbox=dict(boxstyle="round,pad=0.2", facecolor="lightgreen", alpha=0.7))
992
+
993
+ # Draw supports
994
+ for pos in span_positions:
995
+ # Support line
996
+ ax.plot([pos, pos], [-0.1, beam_height + 0.05], 'r--', linewidth=2, alpha=0.7)
997
+
998
+ # Support symbol
999
+ triangle = plt.Polygon([
1000
+ [pos - 0.05, -0.1],
1001
+ [pos + 0.05, -0.1],
1002
+ [pos, -0.2]
1003
+ ], fill=True, facecolor='red', edgecolor='black')
1004
+ ax.add_patch(triangle)
1005
+
1006
+ # Add dimension lines and labels
1007
+ for i, span in enumerate(self.spans):
1008
+ span_start = span_positions[i]
1009
+ span_end = span_positions[i + 1]
1010
+ span_center = (span_start + span_end) / 2
1011
+
1012
+ # Dimension line
1013
+ ax.annotate('', xy=(span_start, -0.3), xytext=(span_end, -0.3),
1014
+ arrowprops=dict(arrowstyle='<->', color='black', lw=1))
1015
+
1016
+ # Span length label
1017
+ ax.text(span_center, -0.35, f'{span["length"]}m',
1018
+ ha='center', va='top', fontsize=10)
1019
+
1020
+ # Formatting
1021
+ ax.set_xlim(-0.2, total_length + 0.2)
1022
+ ax.set_ylim(-0.5, beam_height + 0.4)
1023
+ ax.set_xlabel('Distance along beam (m)', fontsize=12)
1024
+ ax.set_ylabel('Beam Height', fontsize=12)
1025
+ ax.set_title('Shear Stirrup Layout', fontsize=14, fontweight='bold')
1026
+ ax.grid(True, alpha=0.3)
1027
+
1028
+ # Add legend
1029
+ legend_elements = [
1030
+ plt.Line2D([0], [0], color='green', linewidth=2, alpha=0.7, label='Stirrups'),
1031
+ plt.Line2D([0], [0], color='red', linestyle='--', linewidth=2, alpha=0.7, label='Supports')
1032
+ ]
1033
+ ax.legend(handles=legend_elements, loc='upper right')
1034
+
1035
+ plt.tight_layout()
1036
+ return fig
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ numpy>=1.21.0
2
+ matplotlib>=3.5.0
3
+ gradio>=4.0.0
4
+ pandas>=1.3.0