jackvinati commited on
Commit
f37e0dd
·
verified ·
1 Parent(s): f3ced6d

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +640 -0
app.py ADDED
@@ -0,0 +1,640 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from pint import UnitRegistry, set_application_registry
3
+ import matplotlib.pyplot as plt
4
+ import io
5
+ import base64
6
+ from sympy import symbols, Symbol, Eq, solve, Rational
7
+ from reportlab.lib.pagesizes import letter
8
+ from reportlab.pdfgen import canvas
9
+ from PIL import Image
10
+ import base64
11
+ import io
12
+ from reportlab.lib.utils import ImageReader
13
+ import os
14
+ import subprocess
15
+
16
+
17
+
18
+ # Initialize unit registry
19
+ u = UnitRegistry()
20
+ set_application_registry(u)
21
+ Q_ = u.Quantity
22
+
23
+ def generate_beam_diagram(n_spans, lengths, loads, moments, R_sx, R_dx,
24
+ cantilever_left_length=0 * u.meter, cantilever_left_load=0 * u.newton / u.meter,
25
+ cantilever_right_length=0 * u.meter, cantilever_right_load=0 * u.newton / u.meter,
26
+ unit_system='SI'):
27
+ # Define units and their abbreviations based on the unit system
28
+ if unit_system == 'SI':
29
+ length_unit = u.meter
30
+ load_unit = u.newton / u.meter
31
+ moment_unit = u.newton * u.meter
32
+ reaction_unit = u.newton
33
+ length_unit_str = 'm'
34
+ load_unit_str = 'N/m'
35
+ moment_unit_str = 'N·m'
36
+ reaction_unit_str = 'N'
37
+ length_display_unit = u.meter # For consistent plotting
38
+ else:
39
+ length_unit = u.foot
40
+ load_unit = u.pound_force / u.foot
41
+ moment_unit = u.pound_force * u.foot
42
+ reaction_unit = u.pound_force
43
+ length_unit_str = 'ft'
44
+ load_unit_str = 'lb/ft'
45
+ moment_unit_str = 'lb·ft'
46
+ reaction_unit_str = 'lb'
47
+ length_display_unit = u.foot # For consistent plotting
48
+
49
+ # Adjust lengths to include cantilevers
50
+ lengths_display = []
51
+ x_positions = [0]
52
+ # Left cantilever
53
+ if cantilever_left_length.magnitude > 0:
54
+ l_cant_left_display = cantilever_left_length.to(length_display_unit)
55
+ lengths_display.append(l_cant_left_display)
56
+ x_positions.append(x_positions[-1] + l_cant_left_display.magnitude)
57
+ # Main spans
58
+ for l in lengths:
59
+ l_display = l.to(length_display_unit)
60
+ lengths_display.append(l_display)
61
+ x_positions.append(x_positions[-1] + l_display.magnitude)
62
+ # Right cantilever
63
+ if cantilever_right_length.magnitude > 0:
64
+ l_cant_right_display = cantilever_right_length.to(length_display_unit)
65
+ lengths_display.append(l_cant_right_display)
66
+ x_positions.append(x_positions[-1] + l_cant_right_display.magnitude)
67
+
68
+ total_length = x_positions[-1]
69
+
70
+ fig, ax = plt.subplots(figsize=(10, 2))
71
+
72
+ # Draw the beam as a line
73
+ ax.hlines(0, 0, total_length, colors='black', linewidth=2)
74
+
75
+ # Draw supports
76
+ support_width = total_length / 50 # Adjust support width relative to total length
77
+ n_supports = n_spans + 1
78
+ support_positions = []
79
+ idx = 0
80
+ # Left support
81
+ if cantilever_left_length.magnitude > 0:
82
+ # Support at end of cantilever
83
+ x = x_positions[1]
84
+ support_positions.append(x)
85
+ ax.plot([x, x], [0, -0.1], color='black')
86
+ support = plt.Polygon([[x - support_width, -0.1],
87
+ [x + support_width, -0.1],
88
+ [x, -0.2]], color='black')
89
+ ax.add_patch(support)
90
+ idx = 2
91
+ else:
92
+ # Support at start
93
+ x = x_positions[0]
94
+ support_positions.append(x)
95
+ ax.plot([x, x], [0, -0.1], color='black')
96
+ support = plt.Polygon([[x - support_width, -0.1],
97
+ [x + support_width, -0.1],
98
+ [x, -0.2]], color='black')
99
+ ax.add_patch(support)
100
+ idx = 1
101
+
102
+ # Intermediate supports
103
+ for i in range(1, n_supports - 1):
104
+ x = x_positions[idx]
105
+ support_positions.append(x)
106
+ ax.plot([x, x], [0, -0.1], color='black')
107
+ support = plt.Polygon([[x - support_width, -0.1],
108
+ [x + support_width, -0.1],
109
+ [x, -0.2]], color='black')
110
+ ax.add_patch(support)
111
+ idx += 1
112
+
113
+ # Right support
114
+ if cantilever_right_length.magnitude > 0:
115
+ # Support before cantilever
116
+ x = x_positions[-2]
117
+ support_positions.append(x)
118
+ ax.plot([x, x], [0, -0.1], color='black')
119
+ support = plt.Polygon([[x - support_width, -0.1],
120
+ [x + support_width, -0.1],
121
+ [x, -0.2]], color='black')
122
+ ax.add_patch(support)
123
+ else:
124
+ # Support at end
125
+ x = x_positions[-1]
126
+ support_positions.append(x)
127
+ ax.plot([x, x], [0, -0.1], color='black')
128
+ support = plt.Polygon([[x - support_width, -0.1],
129
+ [x + support_width, -0.1],
130
+ [x, -0.2]], color='black')
131
+ ax.add_patch(support)
132
+
133
+ # Annotate reactions
134
+ for i, x in enumerate(support_positions):
135
+ # Left-side reaction
136
+ R_sx_value = R_sx[i]
137
+ R_sx_unit = R_sx_value.to(reaction_unit)
138
+ R_sx_num = round(R_sx_unit.magnitude, 6)
139
+ # Right-side reaction
140
+ R_dx_value = R_dx[i]
141
+ R_dx_unit = R_dx_value.to(reaction_unit)
142
+ R_dx_num = round(R_dx_unit.magnitude, 6)
143
+ # Display reactions with proper offsets to avoid overlap
144
+ ax.text(x, 0.15, f'$R_{{{i+1},\, \\mathrm{{sx}}}} = {R_sx_num}$ {reaction_unit_str}',
145
+ ha='center', va='bottom', color='green')
146
+ ax.text(x, 0.25, f'$R_{{{i+1},\, \\mathrm{{dx}}}} = {R_dx_num}$ {reaction_unit_str}',
147
+ ha='center', va='bottom', color='green')
148
+
149
+ # Annotate internal moments
150
+ for i, x in enumerate(support_positions):
151
+ M_value = moments[i]
152
+ M_unit = M_value.to(moment_unit)
153
+ M_num = round(M_unit.magnitude, 6)
154
+ ax.text(x, -0.25, f'$M_{{{i+1}}} = {M_num}$ {moment_unit_str}',
155
+ ha='center', va='top', color='blue')
156
+
157
+ # Remove axes
158
+ ax.axis('off')
159
+ plt.xlim(-0.05 * total_length, total_length * 1.05)
160
+ plt.ylim(-0.4, 0.4)
161
+
162
+ # Save plot to a buffer
163
+ buf = io.BytesIO()
164
+ plt.savefig(buf, format='png', bbox_inches='tight', dpi=150)
165
+ plt.close(fig)
166
+ buf.seek(0)
167
+ img_base64 = base64.b64encode(buf.getvalue()).decode('utf-8')
168
+ return f'<img src="data:image/png;base64,{img_base64}" alt="Beam Diagram"/>'
169
+
170
+ def calculate_reactions(n_spans, lengths, distributed_loads, moments,
171
+ cantilever_left_length=Q_(0.0, u.meter), cantilever_left_load=Q_(0.0, u.newton / u.meter),
172
+ cantilever_right_length=Q_(0.0, u.meter), cantilever_right_load=Q_(0.0, u.newton / u.meter)):
173
+ """
174
+ Calculate reactions using Excel formulas and print torques:
175
+ r1_sx = (sx cantilever * weight)
176
+ r1_dx = (ln-1 * weight)/2 + (torque1 - torque2)/ln-1
177
+ r2_sx = (ln-1 * weight) - r(m-1)_dx
178
+ r2_dx = (ln-1 * weight)/2 + (torque_m - torque_m+1)/ln-1
179
+ And so on...
180
+ """
181
+ n_supports = n_spans + 1
182
+
183
+ # Print all torques (moments)
184
+ print("\nTorques at each support:")
185
+ for i, moment in enumerate(moments):
186
+ print(f"Torque {i+1}: {moment}")
187
+ print("\n")
188
+
189
+ # Initialize reaction lists
190
+ R_sx = [Q_(0.0, 'newton') for _ in range(n_supports)]
191
+ R_dx = [Q_(0.0, 'newton') for _ in range(n_supports)]
192
+
193
+ # ---------------------------
194
+ # First support (m = 0)
195
+ # ---------------------------
196
+ print(f"Calculating R1:")
197
+ # r1_sx = (sx cantilever * weight)
198
+ if cantilever_left_length.magnitude > 0:
199
+ R_sx[0] = cantilever_left_load * cantilever_left_length
200
+ print(f"R1_sx = cantilever_load * cantilever_length = {R_sx[0]}")
201
+
202
+ # r1_dx = (l0 * w0)/2 + (torque1 - torque2)/l0
203
+ R_dx[0] = (distributed_loads[0] * lengths[0]) / 2 + \
204
+ (moments[0] - moments[1]) / lengths[0]
205
+ print(f"R1_dx = ({distributed_loads[0]} * {lengths[0]})/2 + ({moments[0]} - {moments[1]})/{lengths[0]} = {R_dx[0]}")
206
+
207
+ # ---------------------------
208
+ # Middle supports (1..n-1)
209
+ # ---------------------------
210
+ for m in range(1, n_supports - 1):
211
+ print(f"\nCalculating R{m+1}:")
212
+ # r(m)_sx = (l(m-1)*w(m-1)) - r(m-1)_dx
213
+ R_sx[m] = distributed_loads[m-1] * lengths[m-1] - R_dx[m-1]
214
+ print(f"R{m+1}_sx = {distributed_loads[m-1]} * {lengths[m-1]} - {R_dx[m-1]} = {R_sx[m]}")
215
+
216
+ # r(m)_dx = (l(m)*w(m))/2 + (torque_m - torque_(m+1))/l(m)
217
+ R_dx[m] = (distributed_loads[m] * lengths[m]) / 2 + \
218
+ (moments[m] - moments[m+1]) / lengths[m]
219
+ print(f"R{m+1}_dx = ({distributed_loads[m]} * {lengths[m]})/2 + ({moments[m]} - {moments[m+1]})/{lengths[m]} = {R_dx[m]}")
220
+
221
+ # ---------------------------
222
+ # Last support (m = n_supports-1)
223
+ # ---------------------------
224
+ m = n_supports - 1
225
+ print(f"\nCalculating R{m+1}:")
226
+
227
+ # Last left reaction
228
+ R_sx[m] = distributed_loads[m-1] * lengths[m-1] - R_dx[m-1]
229
+ print(f"R{m+1}_sx = {distributed_loads[m-1]} * {lengths[m-1]} - {R_dx[m-1]} = {R_sx[m]}")
230
+
231
+ # --- Key Fix: Force R_dx at the last support to always be 0 ---
232
+ R_dx[m] = Q_(0.0, 'newton')
233
+ print(f"R{m+1}_dx is forced to 0: {R_dx[m]}")
234
+ # --------------------------------------------------------------
235
+
236
+ # Print final summary of all reactions
237
+ print("\nFinal Reactions Summary:")
238
+ for i in range(n_supports):
239
+ print(f"R{i+1}_sx = {R_sx[i]}")
240
+ print(f"R{i+1}_dx = {R_dx[i]}")
241
+ print(f"R{i+1}_total = {R_sx[i] + R_dx[i]}\n")
242
+
243
+ return R_sx, R_dx
244
+
245
+
246
+
247
+
248
+ def continuous_beam_solver(unit_system, n_spans, lengths_str, loads_str,
249
+ cantilever_left_length_str='0', cantilever_left_load_str='0',
250
+ cantilever_right_length_str='0', cantilever_right_load_str='0'):
251
+ # Parse the input strings into lists
252
+ try:
253
+ n_spans = int(n_spans)
254
+ l_values = [float(val.strip()) for val in lengths_str.split(',')]
255
+ p_values = [float(val.strip()) for val in loads_str.split(',')]
256
+ cantilever_left_length = float(cantilever_left_length_str.strip())
257
+ cantilever_left_load = float(cantilever_left_load_str.strip())
258
+ cantilever_right_length = float(cantilever_right_length_str.strip())
259
+ cantilever_right_load = float(cantilever_right_load_str.strip())
260
+ except ValueError:
261
+ return "Invalid input. Please ensure all inputs are numbers.", "", "", "", "", ""
262
+
263
+ if len(l_values) != n_spans or len(p_values) != n_spans:
264
+ return "The number of lengths and loads must match the number of spans.", "", "", "", "", ""
265
+
266
+ n_supports = n_spans + 1 # Total number of supports
267
+
268
+ # Define units based on the selected unit system
269
+ if unit_system == 'SI':
270
+ length_unit = u.meter
271
+ load_unit = u.newton / u.meter
272
+ moment_unit = u.newton * u.meter
273
+ reaction_unit = u.newton
274
+ else:
275
+ length_unit = u.foot
276
+ load_unit = u.pound_force / u.foot
277
+ moment_unit = u.pound_force * u.foot
278
+ reaction_unit = u.pound_force
279
+
280
+ # Convert lengths and loads to quantities with units
281
+ l = [Q_(length, length_unit) for length in l_values]
282
+ p = [Q_(load, load_unit) for load in p_values]
283
+
284
+ # Convert lengths and loads to SI units for calculation
285
+ l_SI = [length.to(u.meter) for length in l]
286
+ p_SI = [load.to(u.newton / u.meter) for load in p]
287
+
288
+ # Process cantilever inputs
289
+ cantilever_left_length = Q_(cantilever_left_length, length_unit)
290
+ cantilever_left_load = Q_(cantilever_left_load, load_unit)
291
+ cantilever_right_length = Q_(cantilever_right_length, length_unit)
292
+ cantilever_right_load = Q_(cantilever_right_load, load_unit)
293
+
294
+ # Convert to SI units for calculations
295
+ cantilever_left_length_SI = cantilever_left_length.to(u.meter)
296
+ cantilever_left_load_SI = cantilever_left_load.to(u.newton / u.meter)
297
+ cantilever_right_length_SI = cantilever_right_length.to(u.meter)
298
+ cantilever_right_load_SI = cantilever_right_load.to(u.newton / u.meter)
299
+
300
+ # Initialize moments at supports
301
+ M_values_SI = []
302
+ M_values_Imperial = []
303
+
304
+ # Handle single-span beam separately
305
+ if n_spans == 1:
306
+ # Moments at supports A and B for a single span
307
+ M_cantilever_left = 0.0
308
+ if cantilever_left_length_SI.magnitude > 0 and cantilever_left_load_SI.magnitude != 0:
309
+ M_cantilever_left = (cantilever_left_load_SI * cantilever_left_length_SI**2 / 2).to(u.newton * u.meter).magnitude
310
+
311
+ M_cantilever_right = 0.0
312
+ if cantilever_right_length_SI.magnitude > 0 and cantilever_right_load_SI.magnitude != 0:
313
+ M_cantilever_right = (cantilever_right_load_SI * cantilever_right_length_SI**2 / 2).to(u.newton * u.meter).magnitude
314
+
315
+ # Assign moments for supports A and B
316
+ M_A = M_cantilever_left
317
+ M_B = M_cantilever_right
318
+
319
+ # Store the moments in lists
320
+ M_values_SI = [Q_(M_A, u.newton * u.meter), Q_(M_B, u.newton * u.meter)]
321
+ M_values_Imperial = [M.to(u.pound_force * u.foot) for M in M_values_SI]
322
+
323
+ # Calculate reactions
324
+ R_sx_SI, R_dx_SI = calculate_reactions(n_spans, l_SI, p_SI, M_values_SI)
325
+
326
+ # Convert reactions to Imperial units
327
+ R_sx_Imperial = [R.to(u.pound_force) for R in R_sx_SI]
328
+ R_dx_Imperial = [R.to(u.pound_force) for R in R_dx_SI]
329
+
330
+ # Prepare results
331
+ results_SI = ""
332
+ results_Imperial = ""
333
+ for i in range(n_supports):
334
+ M_value_SI = M_values_SI[i]
335
+ M_value_Imperial = M_values_Imperial[i]
336
+ results_SI += f"M_{i+1} = {M_value_SI.magnitude:.6f} N·m\n"
337
+ results_Imperial += f"M_{i+1} = {M_value_Imperial.magnitude:.6f} lb·ft\n"
338
+
339
+ # Generate beam diagrams for SI and Imperial units
340
+ beam_diagram_SI = generate_beam_diagram(
341
+ n_spans, l, p, M_values_SI, R_sx_SI, R_dx_SI,
342
+ cantilever_left_length=cantilever_left_length,
343
+ cantilever_left_load=cantilever_left_load,
344
+ cantilever_right_length=cantilever_right_length,
345
+ cantilever_right_load=cantilever_right_load,
346
+ unit_system='SI'
347
+ )
348
+ beam_diagram_Imperial = generate_beam_diagram(
349
+ n_spans, l, p, M_values_Imperial, R_sx_Imperial, R_dx_Imperial,
350
+ cantilever_left_length=cantilever_left_length,
351
+ cantilever_left_load=cantilever_left_load,
352
+ cantilever_right_length=cantilever_right_length,
353
+ cantilever_right_load=cantilever_right_load,
354
+ unit_system='Imperial'
355
+ )
356
+
357
+ # There are no complex equations for the single-span beam, so leave the equations blank.
358
+ equations_md = ""
359
+ return results_SI, results_Imperial, beam_diagram_SI, beam_diagram_Imperial, equations_md, ""
360
+
361
+ # For multiple spans
362
+ else:
363
+ # Compute fixed-end moments due to cantilever loads
364
+ M_cantilever_left = 0.0
365
+ if cantilever_left_length_SI.magnitude > 0 and cantilever_left_load_SI.magnitude != 0:
366
+ M_cantilever_left = (cantilever_left_load_SI * cantilever_left_length_SI**2 / 2).to(u.newton * u.meter).magnitude
367
+
368
+ M_cantilever_right = 0.0
369
+ if cantilever_right_length_SI.magnitude > 0 and cantilever_right_load_SI.magnitude != 0:
370
+ M_cantilever_right = (cantilever_right_load_SI * cantilever_right_length_SI**2 / 2).to(u.newton * u.meter).magnitude
371
+
372
+ # Initialize moments M_i (M_1 to M_{n_supports})
373
+ M_symbols = []
374
+ M_symbols.append(M_cantilever_left) # M_1
375
+
376
+ for i in range(1, n_supports - 1):
377
+ M_symbols.append(symbols(f'M_{i+1}')) # M_2 to M_{n_supports-1}
378
+
379
+ M_symbols.append(M_cantilever_right) # M_n
380
+
381
+ # Set up the system of equations
382
+ equations = []
383
+ equations_latex = []
384
+ for k in range(1, n_supports - 1):
385
+ # Indices for spans and supports
386
+ l_prev = l_SI[k - 1].magnitude # l_{k} in meters
387
+ l_curr = l_SI[k].magnitude # l_{k+1} in meters
388
+ p_prev = p_SI[k - 1].magnitude # p_{k} in N/m
389
+ p_curr = p_SI[k].magnitude # p_{k+1} in N/m
390
+ M_prev = M_symbols[k - 1] # M_{k}
391
+ M_curr = M_symbols[k] # M_{k+1}
392
+ M_next = M_symbols[k + 1] # M_{k+2}
393
+
394
+ # Left-hand side of the equation (N·m²)
395
+ lhs = (1/24) * (l_prev**3 * p_prev + l_curr**3 * p_curr)
396
+
397
+ # Right-hand side of the equation (N·m²)
398
+ rhs = (1/6) * (l_prev * M_prev + l_curr * M_next) + (1/3) * (l_prev + l_curr) * M_curr
399
+
400
+ # Form the equation
401
+ equation = Eq(lhs, rhs)
402
+ equations.append(equation)
403
+
404
+ # Convert equation to LaTeX for display
405
+ equation_latex = f"\\frac{{1}}{{24}}(l_{{{k}}}^3 p_{{{k}}} + l_{{{k+1}}}^3 p_{{{k+1}}}) = \\frac{{1}}{{6}}(l_{{{k}}} M_{{{k}}} + l_{{{k+1}}} M_{{{k+2}}}) + \\frac{{1}}{{3}}(l_{{{k}}} + l_{{{k+1}}}) M_{{{k+1}}}"
406
+ equations_latex.append(equation_latex)
407
+
408
+ # Solve the system of equations
409
+ unknown_M_symbols = [M_symbols[i] for i in range(
410
+ 1, n_supports - 1) if isinstance(M_symbols[i], Symbol)]
411
+
412
+ solution = solve(equations, unknown_M_symbols, dict=True)
413
+
414
+ # Prepare results and collect moments for diagrams
415
+ if solution:
416
+ solution = solution[0] # Extract the solution dictionary
417
+ results_SI = ""
418
+ results_Imperial = ""
419
+ M_values_SI = []
420
+ M_values_Imperial = []
421
+ for i in range(n_supports):
422
+ M_i_value = M_symbols[i]
423
+ if isinstance(M_i_value, Symbol):
424
+ M_i_value = float(solution.get(M_i_value, 0))
425
+ else:
426
+ M_i_value = float(M_i_value)
427
+
428
+ # SI Units
429
+ M_quantity_SI = Q_(M_i_value, u.newton * u.meter)
430
+ M_values_SI.append(M_quantity_SI)
431
+ results_SI += f"M_{i+1} = {M_quantity_SI.magnitude:.6f} N·m\n"
432
+ # Imperial Units
433
+ M_quantity_Imperial = M_quantity_SI.to(u.pound_force * u.foot)
434
+ M_values_Imperial.append(M_quantity_Imperial)
435
+ results_Imperial += f"M_{i+1} = {M_quantity_Imperial.magnitude:.6f} lb·ft\n"
436
+ else:
437
+ return "No solution found.", "", "", "", "", ""
438
+
439
+ # Calculate reactions
440
+ R_sx_SI, R_dx_SI = calculate_reactions(n_spans, l_SI, p_SI, M_values_SI)
441
+
442
+ # Convert reactions to Imperial units
443
+ R_sx_Imperial = [R.to(u.pound_force) for R in R_sx_SI]
444
+ R_dx_Imperial = [R.to(u.pound_force) for R in R_dx_SI]
445
+
446
+ # Generate beam diagrams
447
+ beam_diagram_SI = generate_beam_diagram(
448
+ n_spans, l, p, M_values_SI, R_sx_SI, R_dx_SI,
449
+ cantilever_left_length=cantilever_left_length,
450
+ cantilever_left_load=cantilever_left_load,
451
+ cantilever_right_length=cantilever_right_length,
452
+ cantilever_right_load=cantilever_right_load,
453
+ unit_system='SI')
454
+ beam_diagram_Imperial = generate_beam_diagram(
455
+ n_spans, l, p, M_values_Imperial, R_sx_Imperial, R_dx_Imperial,
456
+ cantilever_left_length=cantilever_left_length,
457
+ cantilever_left_load=cantilever_left_load,
458
+ cantilever_right_length=cantilever_right_length,
459
+ cantilever_right_load=cantilever_right_load,
460
+ unit_system='Imperial')
461
+
462
+ # Prepare equations in LaTeX
463
+ equations_md = "\n\n".join(
464
+ [f"**Equation {i+1}:**\n\n$$ {eq} $$" for i, eq in enumerate(equations_latex)])
465
+
466
+ return results_SI, results_Imperial, beam_diagram_SI, beam_diagram_Imperial, equations_md, ""
467
+
468
+
469
+
470
+ # def add_image_to_pdf(canvas, image_data, x, y, max_width, max_height):
471
+ # """Helper function to add an image to the PDF canvas while preserving aspect ratio."""
472
+ # # Decode the base64 image data
473
+ # # image_data is like "data:image/png;base64,iVBOR..." so split off the header
474
+ # base64_data = image_data.split(",")[1]
475
+ # img_bytes = base64.b64decode(base64_data)
476
+ # img = Image.open(io.BytesIO(img_bytes))
477
+
478
+ # # Get the original size
479
+ # orig_width, orig_height = img.size
480
+
481
+ # # Compute a scale factor so that the image fits within max_width x max_height
482
+ # # while preserving aspect ratio
483
+ # scale = min(max_width / orig_width, max_height / orig_height)
484
+
485
+ # # Scaled dimensions
486
+ # display_width = orig_width * scale
487
+ # display_height = orig_height * scale
488
+
489
+ # # Convert the (scaled) PIL image to something reportlab can draw
490
+ # buffer = io.BytesIO()
491
+ # img.save(buffer, format="PNG")
492
+ # buffer.seek(0)
493
+ # img_reader = ImageReader(buffer)
494
+
495
+ # # Draw the image onto the canvas at (x, y), with the scaled width/height
496
+ # canvas.drawImage(img_reader, x, y, width=display_width, height=display_height)
497
+
498
+
499
+ # def create_pdf_report(filename, results_SI, results_Imperial, beam_diagram_SI, beam_diagram_Imperial, equations_md):
500
+ # """
501
+ # Creates a multi-page PDF:
502
+ # - Page 1: Title, summary, internal moments (SI & Imperial)
503
+ # - Page 2: SI beam diagram
504
+ # - Page 3: Imperial beam diagram
505
+ # - Page 4: Equations in LaTeX syntax (un-rendered, but still readable).
506
+ # """
507
+ # c = canvas.Canvas(filename, pagesize=letter)
508
+ # page_width, page_height = letter
509
+
510
+ # # -------------
511
+ # # PAGE 1: Title & Moments
512
+ # # -------------
513
+ # c.setFont("Helvetica-Bold", 16)
514
+ # c.drawString(30, page_height - 40, "Continuous Beam Analysis Report")
515
+
516
+ # # Description
517
+ # c.setFont("Helvetica", 12)
518
+ # c.drawString(30, page_height - 70, "This report contains a detailed analysis of a continuous beam, including:")
519
+ # c.drawString(50, page_height - 90, "- Internal moments at supports")
520
+ # c.drawString(50, page_height - 110, "- Beam diagrams (SI and Imperial)")
521
+ # c.drawString(50, page_height - 130, "- Equations used (in LaTeX)")
522
+
523
+ # # Internal Moments (SI)
524
+ # y_offset = page_height - 170
525
+ # c.setFont("Helvetica-Bold", 14)
526
+ # c.drawString(30, y_offset, "Internal Moments at Supports (SI Units):")
527
+ # y_offset -= 20
528
+ # c.setFont("Helvetica", 12)
529
+ # text_obj = c.beginText(30, y_offset)
530
+ # text_obj.textLines(results_SI)
531
+ # c.drawText(text_obj)
532
+
533
+ # # Internal Moments (Imperial)
534
+ # y_offset -= 20 + 14 * (results_SI.count("\n") + 1) # move down after SI text
535
+ # c.setFont("Helvetica-Bold", 14)
536
+ # c.drawString(30, y_offset, "Internal Moments at Supports (Imperial Units):")
537
+ # y_offset -= 20
538
+ # c.setFont("Helvetica", 12)
539
+ # text_obj = c.beginText(30, y_offset)
540
+ # text_obj.textLines(results_Imperial)
541
+ # c.drawText(text_obj)
542
+
543
+ # # Finish Page 1
544
+ # c.showPage()
545
+
546
+ # # -------------
547
+ # # PAGE 2: SI Diagram
548
+ # # -------------
549
+ # c.setFont("Helvetica-Bold", 14)
550
+ # c.drawString(30, page_height - 40, "Beam Diagram (SI Units)")
551
+ # c.setFont("Helvetica", 12)
552
+ # # You could add more descriptive text here if desired.
553
+
554
+ # # Place the SI diagram, preserving aspect ratio
555
+ # if beam_diagram_SI:
556
+ # max_w = 500
557
+ # max_h = 400
558
+ # # 30 points from left, 100 points from bottom
559
+ # add_image_to_pdf(c, beam_diagram_SI, 30, page_height - 100 - max_h, max_w, max_h)
560
+
561
+ # c.showPage()
562
+
563
+ # # -------------
564
+ # # PAGE 3: Imperial Diagram
565
+ # # -------------
566
+ # c.setFont("Helvetica-Bold", 14)
567
+ # c.drawString(30, page_height - 40, "Beam Diagram (Imperial Units)")
568
+ # c.setFont("Helvetica", 12)
569
+
570
+ # # Place the Imperial diagram, preserving aspect ratio
571
+ # if beam_diagram_Imperial:
572
+ # max_w = 500
573
+ # max_h = 400
574
+ # add_image_to_pdf(c, beam_diagram_Imperial, 30, page_height - 100 - max_h, max_w, max_h)
575
+
576
+ # c.showPage()
577
+
578
+ # # -------------
579
+ # # PAGE 4: Equations in LaTeX
580
+ # # -------------
581
+ # c.setFont("Helvetica-Bold", 16)
582
+ # c.drawString(30, page_height - 40, "Equations Used in Analysis (LaTeX):")
583
+ # c.setFont("Helvetica", 12)
584
+
585
+ # y_offset = page_height - 70
586
+ # text_obj = c.beginText(30, y_offset)
587
+
588
+ # # Here, we do NOT strip out the $$ or **, so the raw LaTeX is visible
589
+ # lines = equations_md.split("\n")
590
+ # text_obj.textLines(lines)
591
+ # c.drawText(text_obj)
592
+
593
+ # # Finish the PDF
594
+ # c.save()
595
+
596
+ def gradio_interface(unit_system, n_spans, lengths_str, loads_str,
597
+ cantilever_left_length, cantilever_left_load,
598
+ cantilever_right_length, cantilever_right_load):
599
+ # Call continuous beam solver to get results
600
+ results_SI, results_Imperial, beam_diagram_SI, beam_diagram_Imperial, equations_md, pdf_filename = continuous_beam_solver(
601
+ unit_system, n_spans, lengths_str, loads_str,
602
+ cantilever_left_length, cantilever_left_load,
603
+ cantilever_right_length, cantilever_right_load)
604
+
605
+ # # Create the PDF report
606
+ # pdf_filename = "continuous_beam_analysis_report.pdf"
607
+ # create_pdf_report(pdf_filename, results_SI, results_Imperial, beam_diagram_SI, beam_diagram_Imperial, equations_md)
608
+
609
+ return results_SI, results_Imperial, beam_diagram_SI, beam_diagram_Imperial, equations_md # ,pdf_filename
610
+
611
+ iface = gr.Interface(
612
+ fn=gradio_interface,
613
+ inputs=[
614
+ gr.Radio(['SI', 'Imperial'], label="Unit System", value='SI'),
615
+ gr.Number(label="Number of Spans (n)", value=3, precision=0),
616
+ gr.Textbox(label="Lengths l_i (comma-separated)", placeholder="e.g., 5, 5, 5", value='7.91667,7.91667,7.91667'),
617
+ gr.Textbox(label="Loads p_i (comma-separated)", placeholder="e.g., 10000, 10000, 10000", value='200,200,200'),
618
+ gr.Textbox(label="Cantilever Left Length", placeholder="e.g., 2.0", value='6.66667'),
619
+ gr.Textbox(label="Cantilever Left Load", placeholder="e.g., 5000", value='200'),
620
+ gr.Textbox(label="Cantilever Right Length", placeholder="e.g., 0", value='6.66667'),
621
+ gr.Textbox(label="Cantilever Right Load", placeholder="e.g., 0", value='200'),
622
+ ],
623
+ outputs=[
624
+ gr.Textbox(label="Internal Moments at Supports (SI Units)"),
625
+ gr.Textbox(label="Internal Moments at Supports (Imperial Units)"),
626
+ gr.HTML(label="Beam Diagram (SI Units)"),
627
+ gr.HTML(label="Beam Diagram (Imperial Units)"),
628
+ gr.Markdown(label="Equations Used"),
629
+ # gr.File(label="Download Report")
630
+ ],c
631
+ title="Continuous Beam Solver with Cantilevers",
632
+ description="Solve for internal moments at supports of a continuous beam with multiple spans, including cantilevers.\n"
633
+ "Select the unit system for input. The results will be displayed in both SI and Imperial units.\n\n"
634
+ "The beam diagrams and equations used will be displayed below.",
635
+ allow_flagging="never",
636
+ )
637
+
638
+
639
+ if __name__ == "__main__":
640
+ iface.launch()