import gradio as gr import pandas as pd from continuous_beam import ContinuousBeam import matplotlib.pyplot as plt import numpy as np import io import base64 import gc # Garbage collection for memory management import warnings warnings.filterwarnings('ignore') # Suppress warnings for cleaner output # Configure matplotlib for cloud deployment plt.style.use('default') # Use simple style plt.rcParams['figure.max_open_warning'] = 0 # Disable figure warnings class GradioBeamApp: def __init__(self): self.beam = ContinuousBeam() self.spans_data = [] def parse_point_loads(self, point_loads_text): """Parse point loads from text input""" if not point_loads_text or point_loads_text.strip() == "": return [] point_loads = [] try: # Expected format: "position1,load1; position2,load2; ..." # Example: "2.0,50; 4.0,30" load_pairs = point_loads_text.split(';') for pair in load_pairs: if pair.strip(): pos_str, load_str = pair.split(',') position = float(pos_str.strip()) load = float(load_str.strip()) point_loads.append((position, load)) except: raise ValueError("Invalid point load format. Use: position1,load1; position2,load2") return point_loads def add_span(self, length, distributed_load, point_loads_text, spans_display): """Add a span to the beam""" try: length = float(length) distributed_load = float(distributed_load) if length <= 0 or distributed_load < 0: return spans_display, "Error: Please enter valid values (length > 0, distributed_load >= 0)" # Parse point loads point_loads = self.parse_point_loads(point_loads_text) # Validate point load positions for pos, load in point_loads: if pos < 0 or pos > length: return spans_display, f"Error: Point load at {pos}m is outside span length {length}m" if load < 0: return spans_display, f"Error: Point load {load}kN must be positive" self.beam.add_span(length, distributed_load, point_loads) # Create span description span_info = f"Span {len(self.beam.spans)}: L={length}m, w={distributed_load}kN/m" if point_loads: point_load_str = ", ".join([f"P={load}kN@{pos}m" for pos, load in point_loads]) span_info += f", {point_load_str}" self.spans_data.append(span_info) # Update display updated_display = "\n".join(self.spans_data) return updated_display, f"Added: {span_info}" except ValueError as e: return spans_display, f"Error: {str(e)}" def clear_spans(self): """Clear all spans""" self.beam = ContinuousBeam() self.spans_data = [] return "", "All spans cleared" def design_beam(self, width, depth, fc, fy, cover, spans_display): """Design the beam and return results""" if not self.beam.spans: return "No spans added. Please add at least one span.", None, None, None, None, None try: # Limit spans for cloud deployment if len(self.beam.spans) > 10: return "Error: Maximum 10 spans allowed for cloud deployment.", None, None, None, None, "Error: Too many spans" # Update beam properties self.beam.beam_width = float(width) self.beam.beam_depth = float(depth) self.beam.fc = float(fc) self.beam.fy = float(fy) self.beam.cover = float(cover) self.beam.d = self.beam.beam_depth - self.beam.cover # Perform design with timeout protection design_results = self.beam.design_beam() # Generate report report = self.beam.generate_report(design_results) # Create summary table summary_data = [] for span_data in design_results: span_num = span_data['span'] for i, (reinf, stirrup) in enumerate(zip(span_data['reinforcement'], span_data['stirrups'])): if reinf['moment'] != 0 or stirrup['shear'] != 0: summary_data.append([ span_num if i == 0 else '', reinf['location'], f"{reinf['moment']:.2f}" if reinf['moment'] != 0 else '-', reinf['bars'] if reinf['moment'] != 0 else '-', f"{stirrup['shear']:.2f}" if stirrup['shear'] != 0 else '-', stirrup['stirrup_spacing'] if stirrup['shear'] != 0 else '-' ]) # Create DataFrame for table display df = pd.DataFrame(summary_data, columns=[ 'Span', 'Location', 'Moment (kN-m)', 'Reinforcement', 'Shear (kN)', 'Stirrups' ]) # Generate plots with error handling try: bmd_sfd_plot = self.beam.plot_bmd_sfd(design_results) reinforcement_plot = self.beam.plot_reinforcement_layout(design_results) stirrup_plot = self.beam.plot_stirrup_layout(design_results) except Exception as plot_error: print(f"Plotting error: {plot_error}") # Return simplified results if plotting fails return report, df, None, None, None, "Design completed (plots unavailable)" # Force garbage collection to free memory gc.collect() return report, df, bmd_sfd_plot, reinforcement_plot, stirrup_plot, "Design completed successfully!" except Exception as e: gc.collect() # Clean up memory on error error_msg = str(e) if "memory" in error_msg.lower() or "allocation" in error_msg.lower(): return "Error: Insufficient memory. Try reducing the number of spans or load complexity.", None, None, None, None, "Memory Error" return f"Design calculation failed: {error_msg}", None, None, None, None, f"Error: {error_msg}" def create_interface(): app = GradioBeamApp() with gr.Blocks(title="Continuous Beam RC Design - Thai Standards", theme=gr.themes.Default()) as interface: gr.Markdown("# Continuous Beam RC Design - Thai Standards") gr.Markdown("*Using Finite Element Analysis with Thai reinforcement standards (DB bars, RB stirrups)*") with gr.Row(): with gr.Column(scale=1): gr.Markdown("## Beam Properties") with gr.Row(): width_input = gr.Number(label="Beam Width (mm)", value=300) depth_input = gr.Number(label="Beam Depth (mm)", value=500) with gr.Row(): fc_input = gr.Number(label="f'c (ksc)", value=280) fy_input = gr.Number(label="fy (ksc)", value=4000) cover_input = gr.Number(label="Cover (mm)", value=40) gr.Markdown("## Spans Configuration") with gr.Row(): span_length_input = gr.Number(label="Span Length (m)", value=6.0) distributed_load_input = gr.Number(label="Distributed Load (kN/m)", value=25.0) point_loads_input = gr.Textbox( label="Point Loads (position,load; position,load; ...)", placeholder="Example: 2.0,50; 4.0,30 (means 50kN at 2m and 30kN at 4m)", value="" ) with gr.Row(): add_span_btn = gr.Button("Add Span", variant="primary") clear_spans_btn = gr.Button("Clear All", variant="secondary") spans_display = gr.Textbox( label="Added Spans", lines=5, interactive=False, placeholder="No spans added yet..." ) design_btn = gr.Button("Design Beam", variant="primary", size="lg") status_output = gr.Textbox(label="Status", interactive=False) with gr.Column(scale=2): gr.Markdown("## Design Results") with gr.Tabs(): with gr.TabItem("Summary"): summary_table = gr.Dataframe( label="Design Summary", headers=["Span", "Location", "Moment (kN-m)", "Reinforcement", "Shear (kN)", "Stirrups"], interactive=False ) with gr.TabItem("BMD & SFD"): bmd_sfd_plot = gr.Plot(label="Bending Moment and Shear Force Diagrams") with gr.TabItem("Reinforcement Layout"): reinforcement_plot = gr.Plot(label="Reinforcement Bar Layout") with gr.TabItem("Stirrup Layout"): stirrup_plot = gr.Plot(label="Shear Stirrup Layout") with gr.TabItem("Detailed Report"): results_output = gr.Textbox( label="Detailed Design Report", lines=20, interactive=False, show_copy_button=True ) # Event handlers add_span_btn.click( fn=lambda length, dist_load, point_loads, spans: app.add_span(length, dist_load, point_loads, spans), inputs=[span_length_input, distributed_load_input, point_loads_input, spans_display], outputs=[spans_display, status_output] ) clear_spans_btn.click( fn=lambda: app.clear_spans(), outputs=[spans_display, status_output] ) design_btn.click( fn=lambda w, d, fc, fy, c, spans: app.design_beam(w, d, fc, fy, c, spans), inputs=[width_input, depth_input, fc_input, fy_input, cover_input, spans_display], outputs=[results_output, summary_table, bmd_sfd_plot, reinforcement_plot, stirrup_plot, status_output] ) return interface def main(): interface = create_interface() # Optimized for Hugging Face Spaces interface.launch( share=False, debug=False, # Disable debug for production show_error=True, quiet=True, # Reduce console output ssr_mode=False, # Disable SSR for compatibility auth=None, # No authentication required max_threads=10 # Limit concurrent threads ) if __name__ == "__main__": main()