|
@@ -0,0 +1,899 @@
|
|
|
|
|
+import tkinter as tk
|
|
|
|
|
+from tkinter import ttk, messagebox, simpledialog
|
|
|
|
|
+import json
|
|
|
|
|
+import numpy as np
|
|
|
|
|
+import math
|
|
|
|
|
+import matplotlib.pyplot as plt
|
|
|
|
|
+from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
|
|
|
|
|
+import os
|
|
|
|
|
+
|
|
|
|
|
+# ==================== CONSTANTES DE MATERIAL ====================
|
|
|
|
|
+DENS = 7850 # kg/m3
|
|
|
|
|
+FY = 355 # MPa
|
|
|
|
|
+GAMMAS = 1.05 # Factor seguridad acero
|
|
|
|
|
+EYOUNG = 210000 # MPa
|
|
|
|
|
+NU = 0.3 # Coef Poisson
|
|
|
|
|
+
|
|
|
|
|
+FYD = FY * 10**6 / GAMMAS # Pa
|
|
|
|
|
+EYOUNG_PA = EYOUNG * 10**6 # Pa
|
|
|
|
|
+G = EYOUNG_PA / (2 * (1 + NU)) # Pa
|
|
|
|
|
+
|
|
|
|
|
+# ==================== FUNCIONES DE CÁLCULO ====================
|
|
|
|
|
+
|
|
|
|
|
+def generate_ipe_points(H, b, tf, tw):
|
|
|
|
|
+ """Genera los puntos de una sección IPE a partir de parámetros normalizados"""
|
|
|
|
|
+ # H: altura total, b: ancho flange, tf: espesor flange, tw: espesor alma
|
|
|
|
|
+ h_alma = H - 2*tf
|
|
|
|
|
+ x_alma_ini = (b - tw) / 2
|
|
|
|
|
+ x_alma_fin = (b + tw) / 2
|
|
|
|
|
+
|
|
|
|
|
+ # Puntos de la sección (sentido horario)
|
|
|
|
|
+ points = np.array([
|
|
|
|
|
+ [0, 0], # Esquina inferior izquierda flange
|
|
|
|
|
+ [b, 0], # Esquina inferior derecha flange
|
|
|
|
|
+ [b, tf], # Unión alma-flange derecha
|
|
|
|
|
+ [x_alma_fin, tf], # Esquina superior alma derecha
|
|
|
|
|
+ [x_alma_fin, H - tf], # Esquina inferior alma derecha (arriba)
|
|
|
|
|
+ [b, H - tf], # Unión alma-flange arriba
|
|
|
|
|
+ [b, H], # Esquina superior derecha flange
|
|
|
|
|
+ [0, H], # Esquina superior izquierda flange
|
|
|
|
|
+ [0, H - tf], # Unión alma-flange arriba izquierda
|
|
|
|
|
+ [x_alma_ini, H - tf], # Esquina inferior alma izquierda (arriba)
|
|
|
|
|
+ [x_alma_ini, tf], # Esquina superior alma izquierda
|
|
|
|
|
+ [0, tf], # Unión alma-flange izquierda
|
|
|
|
|
+ [0, 0], # Cerrar la sección
|
|
|
|
|
+ ])
|
|
|
|
|
+
|
|
|
|
|
+ return points
|
|
|
|
|
+
|
|
|
|
|
+def generate_puente_nuevo_points(h, b, tf, tw, ha, ta, tr, theta):
|
|
|
|
|
+ """Genera los puntos de la sección tipo puente nuevo a partir de parámetros normalizados"""
|
|
|
|
|
+ # h: altura exterior, b: ancho total, tf: espesor ala, tw: espesor alma,
|
|
|
|
|
+
|
|
|
|
|
+ hr = (b-tw) / 2 * math.tan(math.radians(theta))
|
|
|
|
|
+ hl = tr / math.cos(math.radians(theta))
|
|
|
|
|
+ hr_ = (b/2 - ta - tw/2)* math.tan(math.radians(theta))
|
|
|
|
|
+
|
|
|
|
|
+ # Puntos de la sección (sentido horario)
|
|
|
|
|
+ points = np.array([
|
|
|
|
|
+ [0, 0],
|
|
|
|
|
+ [b, 0],
|
|
|
|
|
+ [b, tf + ha],
|
|
|
|
|
+ [(b + tw)/2, tf + ha + hr],
|
|
|
|
|
+ [(b + tw)/2, tf + ha + hr - hl],
|
|
|
|
|
+ [b - ta, tf + ha + hr - hl - hr_],
|
|
|
|
|
+ [b - ta, tf],
|
|
|
|
|
+ [(b + tw)/2, tf],
|
|
|
|
|
+ [(b + tw)/2, h - tf],
|
|
|
|
|
+ [b - ta, h - tf],
|
|
|
|
|
+ [b - ta, h - tf - ha - hr + hl + hr_],
|
|
|
|
|
+ [(b + tw)/2, h - tf - ha - hr + hl],
|
|
|
|
|
+ [(b + tw)/2, h - tf - ha - hr],
|
|
|
|
|
+ [b, h - tf - ha],
|
|
|
|
|
+ [b, h],
|
|
|
|
|
+ [0, h],
|
|
|
|
|
+ [0, h - tf - ha],
|
|
|
|
|
+ [(b - tw)/2, h - tf - ha - hr],
|
|
|
|
|
+ [(b - tw)/2, h - tf - ha - hr + hl],
|
|
|
|
|
+ [ta, h - tf - ha - hr + hl + hr_],
|
|
|
|
|
+ [ta, h - tf],
|
|
|
|
|
+ [(b - tw)/2, h - tf],
|
|
|
|
|
+ [(b - tw)/2, tf],
|
|
|
|
|
+ [ta, tf],
|
|
|
|
|
+ [ta, tf + ha + hr - hl - hr_],
|
|
|
|
|
+ [(b - tw)/2, tf + ha + hr - hl],
|
|
|
|
|
+ [(b - tw)/2, tf + ha + hr],
|
|
|
|
|
+ [0, tf + ha],
|
|
|
|
|
+ [0, 0],
|
|
|
|
|
+ ])
|
|
|
|
|
+
|
|
|
|
|
+ return points
|
|
|
|
|
+
|
|
|
|
|
+def calculate_section_properties(points):
|
|
|
|
|
+ """Calcula todas las propiedades de la sección - CÓDIGO ORIGINAL SIN MODIFICAR"""
|
|
|
|
|
+ npuntos = len(points)
|
|
|
|
|
+ puntos = points
|
|
|
|
|
+
|
|
|
|
|
+ px = puntos[:, 0]
|
|
|
|
|
+ py = puntos[:, 1]
|
|
|
|
|
+
|
|
|
|
|
+ # ANCHO Y CANTO MÁXIMO
|
|
|
|
|
+ bmax = np.amax(px) - np.amin(px)
|
|
|
|
|
+ hmax = np.amax(py) - np.amin(py)
|
|
|
|
|
+
|
|
|
|
|
+ # PERÍMETRO
|
|
|
|
|
+ long_i = np.zeros(npuntos - 1)
|
|
|
|
|
+ for i in range(npuntos - 1):
|
|
|
|
|
+ long_i[i] = ((puntos[i+1, 0] - puntos[i, 0])**2 + (puntos[i+1, 1] - puntos[i, 1])**2)**(1/2)
|
|
|
|
|
+ perimetro = abs(sum(long_i))
|
|
|
|
|
+
|
|
|
|
|
+ # ÁREA
|
|
|
|
|
+ area_i = np.zeros(npuntos - 1)
|
|
|
|
|
+ for i in range(npuntos - 1):
|
|
|
|
|
+ area_i[i] = (puntos[i+1, 0] - puntos[i, 0]) * (puntos[i+1, 1] + puntos[i, 1]) / 2
|
|
|
|
|
+ area = abs(sum(area_i))
|
|
|
|
|
+
|
|
|
|
|
+ # PESO POR METRO
|
|
|
|
|
+ peso = area * DENS
|
|
|
|
|
+
|
|
|
|
|
+ # CENTRO DE GRAVEDAD
|
|
|
|
|
+ cdg_i = np.zeros([npuntos, 2])
|
|
|
|
|
+ for i in range(npuntos - 1):
|
|
|
|
|
+ h1 = puntos[i, 1]
|
|
|
|
|
+ h2 = puntos[i+1, 1]
|
|
|
|
|
+ b = puntos[i+1, 0] - puntos[i, 0]
|
|
|
|
|
+ d = puntos[i, 0]
|
|
|
|
|
+
|
|
|
|
|
+ if h1 + h2 == 0:
|
|
|
|
|
+ cdg_i[i, 1] = 0
|
|
|
|
|
+ else:
|
|
|
|
|
+ cdg_i[i, 1] = 1/3 * (h1*h1 + h1*h2 + h2*h2) / (h1 + h2)
|
|
|
|
|
+
|
|
|
|
|
+ if h1 + h2 == 0:
|
|
|
|
|
+ cdg_i[i, 0] = d + b/2
|
|
|
|
|
+ else:
|
|
|
|
|
+ cdg_i[i, 0] = d + b/3 * (h1 + 2*h2) / (h1 + h2)
|
|
|
|
|
+
|
|
|
|
|
+ # MOMENTO ESTÁTICO
|
|
|
|
|
+ statico_i = np.zeros([npuntos, 2])
|
|
|
|
|
+ for i in range(npuntos - 1):
|
|
|
|
|
+ statico_i[i, 1] = area_i[i] * cdg_i[i, 1]
|
|
|
|
|
+ statico_i[i, 0] = area_i[i] * cdg_i[i, 0]
|
|
|
|
|
+
|
|
|
|
|
+ cdg = sum(statico_i) / sum(area_i)
|
|
|
|
|
+ xg = cdg[0]
|
|
|
|
|
+ yg = cdg[1]
|
|
|
|
|
+
|
|
|
|
|
+ # FIBRAS MÁS ALEJADAS
|
|
|
|
|
+ v1y = np.amax(py) - yg
|
|
|
|
|
+ v2y = np.amin(py) - yg
|
|
|
|
|
+ v1x = np.amax(px) - xg
|
|
|
|
|
+ v2x = np.amin(px) - xg
|
|
|
|
|
+
|
|
|
|
|
+ # MOMENTOS DE INERCIA
|
|
|
|
|
+ inercia_i = np.zeros([npuntos, 3])
|
|
|
|
|
+ for i in range(npuntos - 1):
|
|
|
|
|
+ h1 = puntos[i, 1]
|
|
|
|
|
+ h2 = puntos[i+1, 1]
|
|
|
|
|
+ b = puntos[i+1, 0] - puntos[i, 0]
|
|
|
|
|
+ d = puntos[i, 0]
|
|
|
|
|
+ xgi = cdg_i[i, 0]
|
|
|
|
|
+ ygi = cdg_i[i, 1]
|
|
|
|
|
+ ai = area_i[i]
|
|
|
|
|
+
|
|
|
|
|
+ # Ixg
|
|
|
|
|
+ if h2 >= h1:
|
|
|
|
|
+ ixcuad_G_local = 1/12 * b * (h1**3) + b * h1 * (h1/2 - ygi)**2
|
|
|
|
|
+ ixtriang_G_loc = 1/36 * b * (h2-h1)**3 + 1/2 * b * (h2-h1) * ((2*h1+h2)/3 - ygi)**2
|
|
|
|
|
+ else:
|
|
|
|
|
+ ixcuad_G_local = 1/12 * b * (h2**3) + b * h2 * (h2/2 - ygi)**2
|
|
|
|
|
+ ixtriang_G_loc = 1/36 * b * (h1-h2)**3 + 1/2 * b * (h1-h2) * ((2*h2+h1)/3 - ygi)**2
|
|
|
|
|
+ inercia_i[i, 0] = ixcuad_G_local + ixtriang_G_loc + ai * (yg - ygi)**2
|
|
|
|
|
+
|
|
|
|
|
+ # Iyg
|
|
|
|
|
+ if h2 >= h1:
|
|
|
|
|
+ iycuad = 1/12 * h1 * b**3 + h1 * b * (b/2 + d - xgi)**2
|
|
|
|
|
+ iytrian = 1/36 * (h2-h1) * b**3 + 1/2 * b * (h2-h1) * (2/3*b + d - xgi)**2
|
|
|
|
|
+ else:
|
|
|
|
|
+ iycuad = 1/12 * h2 * b**3 + h2 * b * (b/2 + d - xgi)**2
|
|
|
|
|
+ iytrian = 1/36 * (h1-h2) * b**3 + 1/2 * b * (h1-h2) * (1/3*b + d - xgi)**2
|
|
|
|
|
+ inercia_i[i, 1] = iycuad + iytrian + ai * (xg - xgi)**2
|
|
|
|
|
+
|
|
|
|
|
+ # Pxyg
|
|
|
|
|
+ if h2 >= h1:
|
|
|
|
|
+ pxygcuadrado = b * h1 * (-h1/2 + ygi) * (-d - b/2 + xgi)
|
|
|
|
|
+ pxytriangulo = b*b * (h2-h1)**2 / 72 + b * (h2-h1) / 2 * (-(h2-h1)/3 - h1 + ygi) * (-d - 2/3*b + xgi)
|
|
|
|
|
+ else:
|
|
|
|
|
+ pxygcuadrado = b * h2 * (-h2/2 + ygi) * (-d - b/2 + xgi)
|
|
|
|
|
+ pxytriangulo = -b*b * (h1-h2)**2 / 72 + b * (h1-h2) / 2 * (-(h1-h2)/3 - h2 + ygi) * (-d - 1/3*b + xgi)
|
|
|
|
|
+ inercia_i[i, 2] = pxygcuadrado + pxytriangulo + ai * (-xg + xgi) * (-yg + ygi)
|
|
|
|
|
+
|
|
|
|
|
+ ig = sum(inercia_i)
|
|
|
|
|
+ ixg = abs(ig[0])
|
|
|
|
|
+ iyg = abs(ig[1])
|
|
|
|
|
+ pxyg = ig[2]
|
|
|
|
|
+ if sum(area_i) >= 0:
|
|
|
|
|
+ pxyg = pxyg
|
|
|
|
|
+ else:
|
|
|
|
|
+ pxyg = -pxyg
|
|
|
|
|
+
|
|
|
|
|
+ # RADIOS DE GIRO
|
|
|
|
|
+ rx = (ixg / area)**0.5
|
|
|
|
|
+ ry = (iyg / area)**0.5
|
|
|
|
|
+
|
|
|
|
|
+ # EJES PRINCIPALES DE INERCIA
|
|
|
|
|
+ ic = (ixg + iyg) / 2
|
|
|
|
|
+ ir = (((ixg - iyg)/2)**2 + pxyg**2)**0.5
|
|
|
|
|
+
|
|
|
|
|
+ imax = ic + ir
|
|
|
|
|
+ imin = ic - ir
|
|
|
|
|
+ rmax = (imax / area)**0.5
|
|
|
|
|
+ rmin = (imin / area)**0.5
|
|
|
|
|
+
|
|
|
|
|
+ # ORIENTACIÓN DE EJES PRINCIPALES
|
|
|
|
|
+ rest = ixg - iyg
|
|
|
|
|
+ if abs(rest) < 10**-12:
|
|
|
|
|
+ tetha = 45
|
|
|
|
|
+ else:
|
|
|
|
|
+ tetha = 0.5 * math.atan((pxyg*2) / (ixg - iyg))
|
|
|
|
|
+ tetha = abs(tetha) * 180 / math.pi
|
|
|
|
|
+
|
|
|
|
|
+ if pxyg > 0:
|
|
|
|
|
+ if ixg > iyg:
|
|
|
|
|
+ tetha = -tetha
|
|
|
|
|
+ else:
|
|
|
|
|
+ tetha = tetha
|
|
|
|
|
+ else:
|
|
|
|
|
+ if ixg > iyg:
|
|
|
|
|
+ tetha = tetha
|
|
|
|
|
+ else:
|
|
|
|
|
+ tetha = -tetha
|
|
|
|
|
+
|
|
|
|
|
+ # MÓDULO RESISTENTE ELÁSTICO
|
|
|
|
|
+ wel1x = abs(ixg / v1y)
|
|
|
|
|
+ wel2x = abs(ixg / v2y)
|
|
|
|
|
+ wel1y = abs(iyg / v1x)
|
|
|
|
|
+ wel2y = abs(iyg / v2x)
|
|
|
|
|
+
|
|
|
|
|
+ # AXIL Y MOMENTO ELÁSTICO
|
|
|
|
|
+ nel = area * FYD
|
|
|
|
|
+ melx = min(wel1x, wel2x) * FYD
|
|
|
|
|
+ mely = min(wel1y, wel2y) * FYD
|
|
|
|
|
+
|
|
|
|
|
+ # CONVERSIÓN A UNIDADES PRÁCTICAS (cm, cm2, cm3, cm4, kN)
|
|
|
|
|
+ pot = 2
|
|
|
|
|
+ bmax_cm = bmax * 10**pot
|
|
|
|
|
+ hmax_cm = hmax * 10**pot
|
|
|
|
|
+ perimetro_cm = perimetro * 10**pot
|
|
|
|
|
+ xg_cm = xg * 10**pot
|
|
|
|
|
+ yg_cm = yg * 10**pot
|
|
|
|
|
+ v1y_cm = v1y * 10**pot
|
|
|
|
|
+ v2y_cm = v2y * 10**pot
|
|
|
|
|
+ v1x_cm = v1x * 10**pot
|
|
|
|
|
+ v2x_cm = v2x * 10**pot
|
|
|
|
|
+ rx_cm = rx * 10**pot
|
|
|
|
|
+ ry_cm = ry * 10**pot
|
|
|
|
|
+ rmax_cm = rmax * 10**pot
|
|
|
|
|
+ rmin_cm = rmin * 10**pot
|
|
|
|
|
+
|
|
|
|
|
+ area_cm2 = area * 10**(pot*2)
|
|
|
|
|
+
|
|
|
|
|
+ wel1x_cm3 = wel1x * 10**(pot*3)
|
|
|
|
|
+ wel2x_cm3 = wel2x * 10**(pot*3)
|
|
|
|
|
+ wel1y_cm3 = wel1y * 10**(pot*3)
|
|
|
|
|
+ wel2y_cm3 = wel2y * 10**(pot*3)
|
|
|
|
|
+
|
|
|
|
|
+ ixg_cm4 = ixg * 10**(pot*4)
|
|
|
|
|
+ iyg_cm4 = iyg * 10**(pot*4)
|
|
|
|
|
+ pxyg_cm4 = pxyg * 10**(pot*4)
|
|
|
|
|
+ imax_cm4 = imax * 10**(pot*4)
|
|
|
|
|
+ imin_cm4 = imin * 10**(pot*4)
|
|
|
|
|
+
|
|
|
|
|
+ nel_kn = nel / 1000
|
|
|
|
|
+ melx_kn = melx / 1000
|
|
|
|
|
+ mely_kn = mely / 1000
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ 'area': area_cm2,
|
|
|
|
|
+ 'perimetro': perimetro_cm,
|
|
|
|
|
+ 'xg': xg_cm,
|
|
|
|
|
+ 'yg': yg_cm,
|
|
|
|
|
+ 'v1y': v1y_cm,
|
|
|
|
|
+ 'v2y': v2y_cm,
|
|
|
|
|
+ 'v1x': v1x_cm,
|
|
|
|
|
+ 'v2x': v2x_cm,
|
|
|
|
|
+ 'ixg': ixg_cm4,
|
|
|
|
|
+ 'iyg': iyg_cm4,
|
|
|
|
|
+ 'pxyg': pxyg_cm4,
|
|
|
|
|
+ 'imax': imax_cm4,
|
|
|
|
|
+ 'imin': imin_cm4,
|
|
|
|
|
+ 'rx': rx_cm,
|
|
|
|
|
+ 'ry': ry_cm,
|
|
|
|
|
+ 'rmax': rmax_cm,
|
|
|
|
|
+ 'rmin': rmin_cm,
|
|
|
|
|
+ 'wel1x': wel1x_cm3,
|
|
|
|
|
+ 'wel2x': wel2x_cm3,
|
|
|
|
|
+ 'wel1y': wel1y_cm3,
|
|
|
|
|
+ 'wel2y': wel2y_cm3,
|
|
|
|
|
+ 'tetha': tetha,
|
|
|
|
|
+ 'peso': peso,
|
|
|
|
|
+ 'nel': nel_kn,
|
|
|
|
|
+ 'melx': melx_kn,
|
|
|
|
|
+ 'mely': mely_kn,
|
|
|
|
|
+ 'xg_orig': xg,
|
|
|
|
|
+ 'yg_orig': yg,
|
|
|
|
|
+ 'bmax': bmax,
|
|
|
|
|
+ 'hmax': hmax,
|
|
|
|
|
+ 'points': points
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+# ==================== APLICACIÓN TKINTER ====================
|
|
|
|
|
+
|
|
|
|
|
+class SectionDesignerApp(tk.Tk):
|
|
|
|
|
+ def __init__(self):
|
|
|
|
|
+ super().__init__()
|
|
|
|
|
+ self.title("Diseñador de Secciones de Acero")
|
|
|
|
|
+ self.geometry("1200x800")
|
|
|
|
|
+
|
|
|
|
|
+ self.json_path = r"c:\Users\Daniel.p\Documents\Automatizaciones\Propiedades seccion\secciones_config.json"
|
|
|
|
|
+ self.load_json()
|
|
|
|
|
+ self.current_section_name = None
|
|
|
|
|
+ self.current_points = None
|
|
|
|
|
+ self.current_properties = None
|
|
|
|
|
+
|
|
|
|
|
+ self.create_widgets()
|
|
|
|
|
+
|
|
|
|
|
+ def load_json(self):
|
|
|
|
|
+ """Carga el archivo JSON de configuración"""
|
|
|
|
|
+ if os.path.exists(self.json_path):
|
|
|
|
|
+ with open(self.json_path, 'r') as f:
|
|
|
|
|
+ self.secciones_data = json.load(f)
|
|
|
|
|
+ else:
|
|
|
|
|
+ self.secciones_data = {"secciones": []}
|
|
|
|
|
+
|
|
|
|
|
+ def save_json(self):
|
|
|
|
|
+ """Guarda los cambios al JSON"""
|
|
|
|
|
+ with open(self.json_path, 'w') as f:
|
|
|
|
|
+ json.dump(self.secciones_data, f, indent=2)
|
|
|
|
|
+
|
|
|
|
|
+ def create_widgets(self):
|
|
|
|
|
+ """Crea la interfaz gráfica"""
|
|
|
|
|
+
|
|
|
|
|
+ # ========== PANEL IZQUIERDO (CONTROLES) ==========
|
|
|
|
|
+ left_panel = ttk.Frame(self, width=250)
|
|
|
|
|
+ left_panel.pack(side=tk.LEFT, fill=tk.BOTH, padx=10, pady=10)
|
|
|
|
|
+
|
|
|
|
|
+ # Selector de sección
|
|
|
|
|
+ ttk.Label(left_panel, text="Sección:", font=("Arial", 10, "bold")).pack(anchor=tk.W, pady=(10, 5))
|
|
|
|
|
+ self.combo_seccion = ttk.Combobox(
|
|
|
|
|
+ left_panel,
|
|
|
|
|
+ values=[s['nombre'] for s in self.secciones_data['secciones']],
|
|
|
|
|
+ state='readonly',
|
|
|
|
|
+ width=20
|
|
|
|
|
+ )
|
|
|
|
|
+ self.combo_seccion.pack(anchor=tk.W)
|
|
|
|
|
+ self.combo_seccion.bind('<<ComboboxSelected>>', self.on_section_changed)
|
|
|
|
|
+
|
|
|
|
|
+ # Separador
|
|
|
|
|
+ ttk.Separator(left_panel, orient=tk.HORIZONTAL).pack(fill=tk.X, pady=10)
|
|
|
|
|
+
|
|
|
|
|
+ # Selector de tipo de sección
|
|
|
|
|
+ ttk.Label(left_panel, text="Tipo de sección:", font=("Arial", 10, "bold")).pack(anchor=tk.W, pady=(10, 5))
|
|
|
|
|
+ self.combo_tipo = ttk.Combobox(
|
|
|
|
|
+ left_panel,
|
|
|
|
|
+ values=["IPE", "Personalizada", "Puente nuevo"],
|
|
|
|
|
+ state='readonly',
|
|
|
|
|
+ width=20
|
|
|
|
|
+ )
|
|
|
|
|
+ self.combo_tipo.pack(anchor=tk.W, pady=(0, 10))
|
|
|
|
|
+ self.combo_tipo.set("IPE")
|
|
|
|
|
+ self.combo_tipo.bind('<<ComboboxSelected>>', self.on_section_type_changed)
|
|
|
|
|
+
|
|
|
|
|
+ # Frame para parámetros IPE
|
|
|
|
|
+ self.frame_ipe = ttk.Frame(left_panel)
|
|
|
|
|
+ self.frame_ipe.pack(fill=tk.BOTH, expand=False, pady=(0, 10))
|
|
|
|
|
+
|
|
|
|
|
+ ttk.Label(self.frame_ipe, text="Parámetros IPE:", font=("Arial", 10, "bold")).pack(anchor=tk.W, pady=(10, 5))
|
|
|
|
|
+
|
|
|
|
|
+ # H (Altura)
|
|
|
|
|
+ ttk.Label(self.frame_ipe, text="H (altura, m):").pack(anchor=tk.W)
|
|
|
|
|
+ self.entry_H = ttk.Spinbox(self.frame_ipe, from_=0.0, to=10000.0, increment=0.01, width=15, command=self.on_parameter_change_ipe)
|
|
|
|
|
+ self.entry_H.pack(anchor=tk.W, fill=tk.X)
|
|
|
|
|
+ self.entry_H.set("0.300")
|
|
|
|
|
+ self.entry_H.bind('<FocusOut>', self.on_parameter_change_ipe)
|
|
|
|
|
+ self.entry_H.bind('<Return>', self.on_parameter_change_ipe)
|
|
|
|
|
+
|
|
|
|
|
+ # b (Ancho)
|
|
|
|
|
+ ttk.Label(self.frame_ipe, text="b (ancho flange, m):").pack(anchor=tk.W, pady=(10, 0))
|
|
|
|
|
+ self.entry_b = ttk.Spinbox(self.frame_ipe, from_=0.0, to=10000.0, increment=0.005, width=15, command=self.on_parameter_change_ipe)
|
|
|
|
|
+ self.entry_b.pack(anchor=tk.W, fill=tk.X)
|
|
|
|
|
+ self.entry_b.set("0.150")
|
|
|
|
|
+ self.entry_b.bind('<FocusOut>', self.on_parameter_change_ipe)
|
|
|
|
|
+ self.entry_b.bind('<Return>', self.on_parameter_change_ipe)
|
|
|
|
|
+
|
|
|
|
|
+ # tf (Espesor flange)
|
|
|
|
|
+ ttk.Label(self.frame_ipe, text="tf (espesor flange, m):").pack(anchor=tk.W, pady=(10, 0))
|
|
|
|
|
+ self.entry_tf = ttk.Spinbox(self.frame_ipe, from_=0.0, to=10000.0, increment=0.0005, width=15, command=self.on_parameter_change_ipe)
|
|
|
|
|
+ self.entry_tf.pack(anchor=tk.W, fill=tk.X)
|
|
|
|
|
+ self.entry_tf.set("0.0107")
|
|
|
|
|
+ self.entry_tf.bind('<FocusOut>', self.on_parameter_change_ipe)
|
|
|
|
|
+ self.entry_tf.bind('<Return>', self.on_parameter_change_ipe)
|
|
|
|
|
+
|
|
|
|
|
+ # tw (Espesor alma)
|
|
|
|
|
+ ttk.Label(self.frame_ipe, text="tw (espesor alma, m):").pack(anchor=tk.W, pady=(10, 0))
|
|
|
|
|
+ self.entry_tw = ttk.Spinbox(self.frame_ipe, from_=0.0, to=10000.0, increment=0.0005, width=15, command=self.on_parameter_change_ipe)
|
|
|
|
|
+ self.entry_tw.pack(anchor=tk.W, fill=tk.X)
|
|
|
|
|
+ self.entry_tw.set("0.0063")
|
|
|
|
|
+ self.entry_tw.bind('<FocusOut>', self.on_parameter_change_ipe)
|
|
|
|
|
+ self.entry_tw.bind('<Return>', self.on_parameter_change_ipe)
|
|
|
|
|
+
|
|
|
|
|
+ # Frame para sección personalizada
|
|
|
|
|
+ self.frame_personalizada = ttk.Frame(left_panel)
|
|
|
|
|
+
|
|
|
|
|
+ ttk.Label(self.frame_personalizada, text="Puntos (JSON):", font=("Arial", 10, "bold")).pack(anchor=tk.W, pady=(10, 5))
|
|
|
|
|
+ ttk.Label(self.frame_personalizada, text="Formato: [[x1,y1], [x2,y2], ...]", font=("Arial", 8)).pack(anchor=tk.W)
|
|
|
|
|
+
|
|
|
|
|
+ self.text_puntos = tk.Text(self.frame_personalizada, height=8, width=30, font=("Courier", 8))
|
|
|
|
|
+ self.text_puntos.pack(fill=tk.BOTH, expand=True, pady=(0, 5))
|
|
|
|
|
+
|
|
|
|
|
+ ttk.Button(self.frame_personalizada, text="Cargar puntos", command=self.load_custom_points).pack(fill=tk.X)
|
|
|
|
|
+
|
|
|
|
|
+ # Frame para puente nuevo
|
|
|
|
|
+ self.frame_puente_nuevo = ttk.Frame(left_panel)
|
|
|
|
|
+ self.frame_puente_nuevo.pack(fill=tk.BOTH, expand=True, pady=(0, 10))
|
|
|
|
|
+
|
|
|
|
|
+ ttk.Label(self.frame_puente_nuevo, text="Parámetros puente nuevo:", font=("Arial", 10, "bold")).pack(anchor=tk.W, pady=(10, 5))
|
|
|
|
|
+
|
|
|
|
|
+ # H (Altura exterior)
|
|
|
|
|
+ ttk.Label(self.frame_puente_nuevo, text = "h (altura exterior, m):").pack(anchor=tk.W, pady=(10, 0))
|
|
|
|
|
+ self.entry_h_puente = ttk.Spinbox(self.frame_puente_nuevo, from_=0.0, to=10000.0, increment=0.01, width=15, command=self.on_parameter_change_puente_nuevo)
|
|
|
|
|
+ self.entry_h_puente.pack(anchor=tk.W, fill=tk.X)
|
|
|
|
|
+ self.entry_h_puente.set("0.300")
|
|
|
|
|
+ self.entry_h_puente.bind('<FocusOut>', self.on_parameter_change_puente_nuevo)
|
|
|
|
|
+ self.entry_h_puente.bind('<Return>', self.on_parameter_change_puente_nuevo)
|
|
|
|
|
+
|
|
|
|
|
+ # b (Ancho)
|
|
|
|
|
+ ttk.Label(self.frame_puente_nuevo, text="b (ancho, m):").pack(anchor=tk.W, pady=(10, 0))
|
|
|
|
|
+ self.entry_b_puente = ttk.Spinbox(self.frame_puente_nuevo, from_=0.0, to=10000.0, increment=0.005, width=15, command=self.on_parameter_change_puente_nuevo)
|
|
|
|
|
+ self.entry_b_puente.pack(anchor=tk.W, fill=tk.X)
|
|
|
|
|
+ self.entry_b_puente.set("0.150")
|
|
|
|
|
+ self.entry_b_puente.bind('<FocusOut>', self.on_parameter_change_puente_nuevo)
|
|
|
|
|
+ self.entry_b_puente.bind('<Return>', self.on_parameter_change_puente_nuevo)
|
|
|
|
|
+
|
|
|
|
|
+ # tf (espesor ala)
|
|
|
|
|
+ ttk.Label(self.frame_puente_nuevo, text="tf (espesor ala, m):").pack(anchor=tk.W, pady=(10, 0))
|
|
|
|
|
+ self.entry_tf_puente = ttk.Spinbox(self.frame_puente_nuevo, from_=0.0, to=10000.0, increment=0.0005, width=15, command=self.on_parameter_change_puente_nuevo)
|
|
|
|
|
+ self.entry_tf_puente.pack(anchor=tk.W, fill=tk.X)
|
|
|
|
|
+ self.entry_tf_puente.set("0.0107")
|
|
|
|
|
+ self.entry_tf_puente.bind('<FocusOut>', self.on_parameter_change_puente_nuevo)
|
|
|
|
|
+ self.entry_tf_puente.bind('<Return>', self.on_parameter_change_puente_nuevo)
|
|
|
|
|
+
|
|
|
|
|
+ # tw (espesor alma)
|
|
|
|
|
+ ttk.Label(self.frame_puente_nuevo, text="tw (espesor alma, m):").pack(anchor=tk.W, pady=(10, 0))
|
|
|
|
|
+ self.entry_tw_puente = ttk.Spinbox(self.frame_puente_nuevo, from_=0.0, to=10000.0, increment=0.0005, width=15, command=self.on_parameter_change_puente_nuevo)
|
|
|
|
|
+ self.entry_tw_puente.pack(anchor=tk.W, fill=tk.X)
|
|
|
|
|
+ self.entry_tw_puente.set("0.0063")
|
|
|
|
|
+ self.entry_tw_puente.bind('<FocusOut>', self.on_parameter_change_puente_nuevo)
|
|
|
|
|
+ self.entry_tw_puente.bind('<Return>', self.on_parameter_change_puente_nuevo)
|
|
|
|
|
+
|
|
|
|
|
+ # ha (altura refuerzo ala)
|
|
|
|
|
+ ttk.Label(self.frame_puente_nuevo, text="ha (altura refuerzo ala, m):").pack(anchor=tk.W, pady=(10, 0))
|
|
|
|
|
+ self.entry_ha_puente = ttk.Spinbox(self.frame_puente_nuevo, from_=0.0, to=10000.0, increment=0.0005, width=15, command=self.on_parameter_change_puente_nuevo)
|
|
|
|
|
+ self.entry_ha_puente.pack(anchor=tk.W, fill=tk.X)
|
|
|
|
|
+ self.entry_ha_puente.set("0.02")
|
|
|
|
|
+ self.entry_ha_puente.bind('<FocusOut>', self.on_parameter_change_puente_nuevo)
|
|
|
|
|
+ self.entry_ha_puente.bind('<Return>', self.on_parameter_change_puente_nuevo)
|
|
|
|
|
+
|
|
|
|
|
+ # ta (espesor refuerzo ala)
|
|
|
|
|
+ ttk.Label(self.frame_puente_nuevo, text="ta (espesor refuerzo ala, m):").pack(anchor=tk.W, pady=(10, 0))
|
|
|
|
|
+ self.entry_ta_puente = ttk.Spinbox(self.frame_puente_nuevo, from_=0.0, to=10000.0, increment=0.0005, width=15, command=self.on_parameter_change_puente_nuevo)
|
|
|
|
|
+ self.entry_ta_puente.pack(anchor=tk.W, fill=tk.X)
|
|
|
|
|
+ self.entry_ta_puente.set("0.01")
|
|
|
|
|
+ self.entry_ta_puente.bind('<FocusOut>', self.on_parameter_change_puente_nuevo)
|
|
|
|
|
+ self.entry_ta_puente.bind('<Return>', self.on_parameter_change_puente_nuevo)
|
|
|
|
|
+
|
|
|
|
|
+ # tr (espesor refuerzo alma)
|
|
|
|
|
+ ttk.Label(self.frame_puente_nuevo, text="tr (espesor refuerzo alma, m):").pack(anchor=tk.W, pady=(10, 0))
|
|
|
|
|
+ self.entry_tr_puente = ttk.Spinbox(self.frame_puente_nuevo, from_=0.0, to=10000.0, increment=0.0005, width=15, command=self.on_parameter_change_puente_nuevo)
|
|
|
|
|
+ self.entry_tr_puente.pack(anchor=tk.W, fill=tk.X)
|
|
|
|
|
+ self.entry_tr_puente.set("0.01")
|
|
|
|
|
+ self.entry_tr_puente.bind('<FocusOut>', self.on_parameter_change_puente_nuevo)
|
|
|
|
|
+ self.entry_tr_puente.bind('<Return>', self.on_parameter_change_puente_nuevo)
|
|
|
|
|
+
|
|
|
|
|
+ # theta (ángulo refuerzo)
|
|
|
|
|
+ ttk.Label(self.frame_puente_nuevo, text="theta (ángulo refuerzo, grados):").pack(anchor=tk.W, pady=(10, 0))
|
|
|
|
|
+ self.entry_theta_puente = ttk.Spinbox(self.frame_puente_nuevo, from_=0.0, to=360.0, increment=1.0, width=15, command=self.on_parameter_change_puente_nuevo)
|
|
|
|
|
+ self.entry_theta_puente.pack(anchor=tk.W, fill=tk.X)
|
|
|
|
|
+ self.entry_theta_puente.set("45")
|
|
|
|
|
+ self.entry_theta_puente.bind('<FocusOut>', self.on_parameter_change_puente_nuevo)
|
|
|
|
|
+ self.entry_theta_puente.bind('<Return>', self.on_parameter_change_puente_nuevo)
|
|
|
|
|
+
|
|
|
|
|
+ # Separador
|
|
|
|
|
+ ttk.Separator(left_panel, orient=tk.HORIZONTAL).pack(fill=tk.X, pady=10)
|
|
|
|
|
+
|
|
|
|
|
+ # Panel de resultados
|
|
|
|
|
+ ttk.Label(left_panel, text="Propiedades:", font=("Arial", 10, "bold")).pack(anchor=tk.W, pady=(10, 5))
|
|
|
|
|
+
|
|
|
|
|
+ self.text_results = tk.Text(left_panel, height=20, width=30, state=tk.DISABLED, font=("Courier", 8))
|
|
|
|
|
+ self.text_results.pack(fill=tk.BOTH, expand=True, pady=(0, 10))
|
|
|
|
|
+
|
|
|
|
|
+ # Botón guardar
|
|
|
|
|
+ ttk.Button(left_panel, text="Guardar nueva sección", command=self.save_new_section).pack(fill=tk.X)
|
|
|
|
|
+
|
|
|
|
|
+ # ========== PANEL DERECHO (GRÁFICO) ==========
|
|
|
|
|
+ right_panel = ttk.Frame(self)
|
|
|
|
|
+ right_panel.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=10, pady=10)
|
|
|
|
|
+
|
|
|
|
|
+ self.figure = plt.Figure(figsize=(6, 8), dpi=100)
|
|
|
|
|
+ self.ax = self.figure.add_subplot(111)
|
|
|
|
|
+
|
|
|
|
|
+ self.canvas = FigureCanvasTkAgg(self.figure, master=right_panel)
|
|
|
|
|
+ self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
|
|
|
|
|
+
|
|
|
|
|
+ # Cargar la primera sección después de que todos los widgets estén creados
|
|
|
|
|
+ if self.secciones_data['secciones']:
|
|
|
|
|
+ self.combo_seccion.current(0)
|
|
|
|
|
+ self.on_section_changed(None)
|
|
|
|
|
+
|
|
|
|
|
+ self.frame_puente_nuevo.pack_forget()
|
|
|
|
|
+
|
|
|
|
|
+ def on_section_type_changed(self, event):
|
|
|
|
|
+ """Cambia entre IPE y Personalizada"""
|
|
|
|
|
+ section_type = self.combo_tipo.get()
|
|
|
|
|
+
|
|
|
|
|
+ if section_type == "IPE":
|
|
|
|
|
+ self.frame_personalizada.pack_forget()
|
|
|
|
|
+ self.frame_puente_nuevo.pack_forget()
|
|
|
|
|
+ self.frame_ipe.pack(fill=tk.BOTH, expand=False, pady=(0, 10))
|
|
|
|
|
+ self.on_parameter_change_ipe(None)
|
|
|
|
|
+ elif section_type == "Puente nuevo":
|
|
|
|
|
+ self.frame_ipe.pack_forget()
|
|
|
|
|
+ self.frame_personalizada.pack_forget()
|
|
|
|
|
+ self.frame_puente_nuevo.pack(fill=tk.BOTH, expand=True, pady=(0, 10))
|
|
|
|
|
+ self.on_parameter_change_puente_nuevo(None)
|
|
|
|
|
+ # Aquí podrías cargar valores por defecto o actuales para el puente nuevo
|
|
|
|
|
+ else: # Personalizada
|
|
|
|
|
+ self.frame_ipe.pack_forget()
|
|
|
|
|
+ self.frame_puente_nuevo.pack_forget()
|
|
|
|
|
+ self.frame_personalizada.pack(fill=tk.BOTH, expand=True)
|
|
|
|
|
+ # Mostrar los puntos actuales en el texto
|
|
|
|
|
+ if self.current_points is not None:
|
|
|
|
|
+ import json
|
|
|
|
|
+ puntos_json = json.dumps(self.current_points.tolist(), indent=2)
|
|
|
|
|
+ self.text_puntos.delete(1.0, tk.END)
|
|
|
|
|
+ self.text_puntos.insert(1.0, puntos_json)
|
|
|
|
|
+
|
|
|
|
|
+ def load_custom_points(self):
|
|
|
|
|
+ """Carga puntos personalizados desde el texto"""
|
|
|
|
|
+ try:
|
|
|
|
|
+ import json
|
|
|
|
|
+ text = self.text_puntos.get(1.0, tk.END).strip()
|
|
|
|
|
+ puntos = json.loads(text)
|
|
|
|
|
+ self.current_points = np.array(puntos)
|
|
|
|
|
+ self.current_properties = calculate_section_properties(self.current_points)
|
|
|
|
|
+ self.update_plot()
|
|
|
|
|
+ self.update_results()
|
|
|
|
|
+ messagebox.showinfo("Éxito", "Puntos cargados correctamente")
|
|
|
|
|
+ except json.JSONDecodeError:
|
|
|
|
|
+ messagebox.showerror("Error", "Formato JSON inválido")
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ messagebox.showerror("Error", f"Error al cargar puntos: {str(e)}")
|
|
|
|
|
+
|
|
|
|
|
+ def on_section_changed(self, event):
|
|
|
|
|
+ """Se ejecuta cuando cambia la sección seleccionada"""
|
|
|
|
|
+ idx = self.combo_seccion.current()
|
|
|
|
|
+ if idx >= 0:
|
|
|
|
|
+ seccion = self.secciones_data['secciones'][idx]
|
|
|
|
|
+ self.current_section_name = seccion['nombre']
|
|
|
|
|
+ section_type = seccion.get('tipo', 'otro')
|
|
|
|
|
+
|
|
|
|
|
+ # Actualizar el combo de tipo
|
|
|
|
|
+ if section_type == 'ipe':
|
|
|
|
|
+ self.combo_tipo.set("IPE")
|
|
|
|
|
+ elif section_type == 'puente_nuevo':
|
|
|
|
|
+ self.combo_tipo.set("Puente nuevo")
|
|
|
|
|
+ else:
|
|
|
|
|
+ self.combo_tipo.set("Personalizada")
|
|
|
|
|
+
|
|
|
|
|
+ # Si tiene puntos directos, cargarlos
|
|
|
|
|
+ if 'puntos' in seccion:
|
|
|
|
|
+ self.current_points = np.array(seccion['puntos'])
|
|
|
|
|
+ if section_type == 'ipe':
|
|
|
|
|
+ self.current_properties = calculate_section_properties(self.current_points)
|
|
|
|
|
+ if section_type == 'puente_nuevo':
|
|
|
|
|
+ self.current_properties = calculate_section_properties(self.current_points)
|
|
|
|
|
+ self.update_plot()
|
|
|
|
|
+ self.update_results()
|
|
|
|
|
+
|
|
|
|
|
+ # Si además es IPE, establecer los valores
|
|
|
|
|
+ if section_type == 'ipe' and 'parametros' in seccion:
|
|
|
|
|
+ params = seccion['parametros']
|
|
|
|
|
+ self.entry_H.delete(0, tk.END)
|
|
|
|
|
+ self.entry_H.insert(0, f"{params['H']:.6f}")
|
|
|
|
|
+ self.entry_b.delete(0, tk.END)
|
|
|
|
|
+ self.entry_b.insert(0, f"{params['b']:.6f}")
|
|
|
|
|
+ self.entry_tf.delete(0, tk.END)
|
|
|
|
|
+ self.entry_tf.insert(0, f"{params['tf']:.6f}")
|
|
|
|
|
+ self.entry_tw.delete(0, tk.END)
|
|
|
|
|
+ self.entry_tw.insert(0, f"{params['tw']:.6f}")
|
|
|
|
|
+ # Mostrar frame IPE
|
|
|
|
|
+ self.frame_personalizada.pack_forget()
|
|
|
|
|
+ self.frame_ipe.pack(fill=tk.BOTH, expand=False, pady=(0, 10))
|
|
|
|
|
+ elif section_type == 'puente_nuevo' and 'parametros' in seccion:
|
|
|
|
|
+ params = seccion['parametros']
|
|
|
|
|
+ self.entry_h_puente.delete(0, tk.END)
|
|
|
|
|
+ self.entry_h_puente.insert(0, f"{params['h']:.6f}")
|
|
|
|
|
+ self.entry_b_puente.delete(0, tk.END)
|
|
|
|
|
+ self.entry_b_puente.insert(0, f"{params['b']:.6f}")
|
|
|
|
|
+ self.entry_tf_puente.delete(0, tk.END)
|
|
|
|
|
+ self.entry_tf_puente.insert(0, f"{params['tf']:.6f}")
|
|
|
|
|
+ self.entry_tw_puente.delete(0, tk.END)
|
|
|
|
|
+ self.entry_tw_puente.insert(0, f"{params['tw']:.6f}")
|
|
|
|
|
+ self.entry_ha_puente.delete(0, tk.END)
|
|
|
|
|
+ self.entry_ha_puente.insert(0, f"{params['ha']:.6f}")
|
|
|
|
|
+ self.entry_ta_puente.delete(0, tk.END)
|
|
|
|
|
+ self.entry_ta_puente.insert(0, f"{params['ta']:.6f}")
|
|
|
|
|
+ self.entry_tr_puente.delete(0, tk.END)
|
|
|
|
|
+ self.entry_tr_puente.insert(0, f"{params['tr']:.6f}")
|
|
|
|
|
+ self.entry_theta_puente.delete(0, tk.END)
|
|
|
|
|
+ self.entry_theta_puente.insert(0, f"{params['theta']:.6f}")
|
|
|
|
|
+ # Mostrar frame puente nuevo
|
|
|
|
|
+ self.frame_ipe.pack_forget()
|
|
|
|
|
+ self.frame_personalizada.pack_forget()
|
|
|
|
|
+ self.frame_puente_nuevo.pack(fill=tk.BOTH, expand=True, pady=(0, 10))
|
|
|
|
|
+ else:
|
|
|
|
|
+ # Mostrar frame personalizado
|
|
|
|
|
+ self.frame_ipe.pack_forget()
|
|
|
|
|
+ self.frame_puente_nuevo.pack_forget()
|
|
|
|
|
+ self.frame_personalizada.pack(fill=tk.BOTH, expand=True)
|
|
|
|
|
+ import json
|
|
|
|
|
+ puntos_json = json.dumps(self.current_points.tolist(), indent=2)
|
|
|
|
|
+ self.text_puntos.delete(1.0, tk.END)
|
|
|
|
|
+ self.text_puntos.insert(1.0, puntos_json)
|
|
|
|
|
+ # Si solo tiene parámetros IPE, generar puntos
|
|
|
|
|
+ elif section_type == 'ipe' and 'parametros' in seccion:
|
|
|
|
|
+ params = seccion['parametros']
|
|
|
|
|
+ self.combo_tipo.set("IPE")
|
|
|
|
|
+ self.entry_H.delete(0, tk.END)
|
|
|
|
|
+ self.entry_H.insert(0, f"{params['H']:.6f}")
|
|
|
|
|
+ self.entry_b.delete(0, tk.END)
|
|
|
|
|
+ self.entry_b.insert(0, f"{params['b']:.6f}")
|
|
|
|
|
+ self.entry_tf.delete(0, tk.END)
|
|
|
|
|
+ self.entry_tf.insert(0, f"{params['tf']:.6f}")
|
|
|
|
|
+ self.entry_tw.delete(0, tk.END)
|
|
|
|
|
+ self.entry_tw.insert(0, f"{params['tw']:.6f}")
|
|
|
|
|
+ self.on_parameter_change_ipe(None)
|
|
|
|
|
+ elif section_type == 'puente_nuevo' and 'parametros' in seccion:
|
|
|
|
|
+ params = seccion['parametros']
|
|
|
|
|
+ self.combo_tipo.set("Puente nuevo")
|
|
|
|
|
+ self.entry_h_puente.delete(0, tk.END)
|
|
|
|
|
+ self.entry_h_puente.insert(0, f"{params['h']:.6f}")
|
|
|
|
|
+ self.entry_b_puente.delete(0, tk.END)
|
|
|
|
|
+ self.entry_b_puente.insert(0, f"{params['b']:.6f}")
|
|
|
|
|
+ self.entry_tf_puente.delete(0, tk.END)
|
|
|
|
|
+ self.entry_tf_puente.insert(0, f"{params['tf']:.6f}")
|
|
|
|
|
+ self.entry_tw_puente.delete(0, tk.END)
|
|
|
|
|
+ self.entry_tw_puente.insert(0, f"{params['tw']:.6f}")
|
|
|
|
|
+ self.entry_ha_puente.delete(0, tk.END)
|
|
|
|
|
+ self.entry_ha_puente.insert(0, f"{params['ha']:.6f}")
|
|
|
|
|
+ self.entry_ta_puente.delete(0, tk.END)
|
|
|
|
|
+ self.entry_ta_puente.insert(0, f"{params['ta']:.6f}")
|
|
|
|
|
+ self.entry_tr_puente.delete(0, tk.END)
|
|
|
|
|
+ self.entry_tr_puente.insert(0, f"{params['tr']:.6f}")
|
|
|
|
|
+ self.entry_theta_puente.delete(0, tk.END)
|
|
|
|
|
+ self.entry_theta_puente.insert(0, f"{params['theta']:.6f}")
|
|
|
|
|
+ self.on_parameter_change_puente_nuevo(None)
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ def on_parameter_change_ipe(self, event=None):
|
|
|
|
|
+ """Se ejecuta cuando cambien los valores de entrada"""
|
|
|
|
|
+ try:
|
|
|
|
|
+ H = float(self.entry_H.get())
|
|
|
|
|
+ b = float(self.entry_b.get())
|
|
|
|
|
+ tf = float(self.entry_tf.get())
|
|
|
|
|
+ tw = float(self.entry_tw.get())
|
|
|
|
|
+
|
|
|
|
|
+ # Validar valores básicos
|
|
|
|
|
+ if H <= 0 or b <= 0 or tf <= 0 or tw <= 0:
|
|
|
|
|
+ return
|
|
|
|
|
+
|
|
|
|
|
+ # Generar puntos y calcular
|
|
|
|
|
+ self.current_points = generate_ipe_points(H, b, tf, tw)
|
|
|
|
|
+ self.current_properties = calculate_section_properties(self.current_points)
|
|
|
|
|
+
|
|
|
|
|
+ # Actualizar visualización
|
|
|
|
|
+ self.update_plot()
|
|
|
|
|
+ self.update_results()
|
|
|
|
|
+ except ValueError:
|
|
|
|
|
+ # Ignorar si los valores no son números válidos
|
|
|
|
|
+ pass
|
|
|
|
|
+
|
|
|
|
|
+ def on_parameter_change_puente_nuevo(self, event=None):
|
|
|
|
|
+ """Se ejecuta cuando cambien los valores de entrada"""
|
|
|
|
|
+ try:
|
|
|
|
|
+ h = float(self.entry_h_puente.get())
|
|
|
|
|
+ b = float(self.entry_b_puente.get())
|
|
|
|
|
+ tf = float(self.entry_tf_puente.get())
|
|
|
|
|
+ tw = float(self.entry_tw_puente.get())
|
|
|
|
|
+ ha = float(self.entry_ha_puente.get())
|
|
|
|
|
+ ta = float(self.entry_ta_puente.get())
|
|
|
|
|
+ tr = float(self.entry_tr_puente.get())
|
|
|
|
|
+ theta = float(self.entry_theta_puente.get())
|
|
|
|
|
+
|
|
|
|
|
+ # Validar valores básicos
|
|
|
|
|
+ if h <= 0 or b <= 0 or tf <= 0 or tw <= 0 or ha <= 0 or ta <= 0 or tr <= 0:
|
|
|
|
|
+ return
|
|
|
|
|
+
|
|
|
|
|
+ # Generar puntos y calcular
|
|
|
|
|
+ self.current_points = generate_puente_nuevo_points(h, b, tf, tw, ha, ta, tr, theta)
|
|
|
|
|
+ self.current_properties = calculate_section_properties(self.current_points)
|
|
|
|
|
+
|
|
|
|
|
+ # Actualizar visualización
|
|
|
|
|
+ self.update_plot()
|
|
|
|
|
+ self.update_results()
|
|
|
|
|
+ except ValueError:
|
|
|
|
|
+ # Ignorar si los valores no son números válidos
|
|
|
|
|
+ pass
|
|
|
|
|
+
|
|
|
|
|
+ def update_plot(self):
|
|
|
|
|
+ """Actualiza el gráfico de la sección"""
|
|
|
|
|
+ self.ax.clear()
|
|
|
|
|
+
|
|
|
|
|
+ points = self.current_points
|
|
|
|
|
+ props = self.current_properties
|
|
|
|
|
+
|
|
|
|
|
+ px = points[:, 0]
|
|
|
|
|
+ py = points[:, 1]
|
|
|
|
|
+ xg = props['xg_orig']
|
|
|
|
|
+ yg = props['yg_orig']
|
|
|
|
|
+ bmax = props['bmax']
|
|
|
|
|
+ hmax = props['hmax']
|
|
|
|
|
+ tetha = props['tetha']
|
|
|
|
|
+
|
|
|
|
|
+ # Dibujar sección
|
|
|
|
|
+ self.ax.plot(px, py, 'b-', linewidth=2)
|
|
|
|
|
+ self.ax.fill(px, py, facecolor="lightblue", alpha=0.5)
|
|
|
|
|
+
|
|
|
|
|
+ # Centro de gravedad
|
|
|
|
|
+ self.ax.plot(xg, yg, 'ro', markersize=8, label='CDG')
|
|
|
|
|
+
|
|
|
|
|
+ # Ejes principales
|
|
|
|
|
+ tt = min(bmax, hmax)
|
|
|
|
|
+ self.ax.arrow(xg, yg, tt/3, 0, head_width=tt/30, head_length=tt/30, fc='green', ec='green')
|
|
|
|
|
+ self.ax.arrow(xg, yg, 0, tt/3, head_width=tt/30, head_length=tt/30, fc='green', ec='green')
|
|
|
|
|
+
|
|
|
|
|
+ tet = tetha * (math.pi) / 180
|
|
|
|
|
+ self.ax.arrow(xg, yg, tt/3*math.cos(tet), tt/3*math.sin(tet), head_width=tt/30, head_length=tt/30, fc='red', ec='red')
|
|
|
|
|
+ self.ax.arrow(xg, yg, -tt/3*math.sin(tet), tt/3*math.cos(tet), head_width=tt/30, head_length=tt/30, fc='red', ec='red')
|
|
|
|
|
+
|
|
|
|
|
+ self.ax.set_aspect('equal')
|
|
|
|
|
+ self.ax.grid(True, alpha=0.3)
|
|
|
|
|
+ self.ax.set_xlabel('X (m)', fontweight='bold')
|
|
|
|
|
+ self.ax.set_ylabel('Y (m)', fontweight='bold')
|
|
|
|
|
+ self.ax.set_title(self.current_section_name or 'Sección IPE', fontweight='bold')
|
|
|
|
|
+ self.ax.legend()
|
|
|
|
|
+
|
|
|
|
|
+ self.figure.tight_layout()
|
|
|
|
|
+ self.canvas.draw()
|
|
|
|
|
+
|
|
|
|
|
+ def update_results(self):
|
|
|
|
|
+ """Actualiza el panel de resultados"""
|
|
|
|
|
+ props = self.current_properties
|
|
|
|
|
+
|
|
|
|
|
+ results_text = f"""PROPIEDADES CALCULADAS
|
|
|
|
|
+
|
|
|
|
|
+GEOMÉTRICAS:
|
|
|
|
|
+ Área: {props['area']:.2f} cm²
|
|
|
|
|
+ Perímetro: {props['perimetro']:.2f} cm
|
|
|
|
|
+ b_max: {props['bmax']*100:.2f} cm
|
|
|
|
|
+ h_max: {props['hmax']*100:.2f} cm
|
|
|
|
|
+
|
|
|
|
|
+CDG (ref. origen):
|
|
|
|
|
+ X: {props['xg']:.2f} cm
|
|
|
|
|
+ Y: {props['yg']:.2f} cm
|
|
|
|
|
+
|
|
|
|
|
+INERCIA (eje Xg-Yg):
|
|
|
|
|
+ Ixg: {props['ixg']:.2f} cm⁴
|
|
|
|
|
+ Iyg: {props['iyg']:.2f} cm⁴
|
|
|
|
|
+ Pxyg: {props['pxyg']:.2f} cm⁴
|
|
|
|
|
+ rx: {props['rx']:.2f} cm
|
|
|
|
|
+ ry: {props['ry']:.2f} cm
|
|
|
|
|
+
|
|
|
|
|
+INERCIA (ejes principales):
|
|
|
|
|
+ Imax: {props['imax']:.2f} cm⁴
|
|
|
|
|
+ Imin: {props['imin']:.2f} cm⁴
|
|
|
|
|
+ θ: {props['tetha']:.2f}°
|
|
|
|
|
+ rmax: {props['rmax']:.2f} cm
|
|
|
|
|
+ rmin: {props['rmin']:.2f} cm
|
|
|
|
|
+
|
|
|
|
|
+MÓDULO RESISTENTE:
|
|
|
|
|
+ Wel1x: {props['wel1x']:.2f} cm³
|
|
|
|
|
+ Wel2x: {props['wel2x']:.2f} cm³
|
|
|
|
|
+ Wel1y: {props['wel1y']:.2f} cm³
|
|
|
|
|
+ Wel2y: {props['wel2y']:.2f} cm³
|
|
|
|
|
+
|
|
|
|
|
+MECÁNICAS:
|
|
|
|
|
+ Peso: {props['peso']:.2f} kg/m
|
|
|
|
|
+ Nel: {props['nel']:.2f} kN
|
|
|
|
|
+ Melx: {props['melx']:.2f} kN·m
|
|
|
|
|
+ Mely: {props['mely']:.2f} kN·m
|
|
|
|
|
+"""
|
|
|
|
|
+
|
|
|
|
|
+ self.text_results.config(state=tk.NORMAL)
|
|
|
|
|
+ self.text_results.delete(1.0, tk.END)
|
|
|
|
|
+ self.text_results.insert(1.0, results_text)
|
|
|
|
|
+ self.text_results.config(state=tk.DISABLED)
|
|
|
|
|
+
|
|
|
|
|
+ def save_new_section(self):
|
|
|
|
|
+ """Guarda la sección actual en el JSON"""
|
|
|
|
|
+ if not self.current_section_name or self.current_points is None:
|
|
|
|
|
+ messagebox.showerror("Error", "Selecciona una sección primero")
|
|
|
|
|
+ return
|
|
|
|
|
+
|
|
|
|
|
+ # Preguntar nombre de la nueva sección
|
|
|
|
|
+ new_name = tk.simpledialog.askstring("Guardar sección", "Nombre de la nueva sección:")
|
|
|
|
|
+ if not new_name:
|
|
|
|
|
+ return
|
|
|
|
|
+
|
|
|
|
|
+ # Verificar si ya existe
|
|
|
|
|
+ exists = any(s['nombre'] == new_name for s in self.secciones_data['secciones'])
|
|
|
|
|
+ if exists:
|
|
|
|
|
+ should_overwrite = messagebox.askyesno("Ya existe", f"La sección '{new_name}' ya existe. ¿Sobreescribir?")
|
|
|
|
|
+ if not should_overwrite:
|
|
|
|
|
+ return
|
|
|
|
|
+
|
|
|
|
|
+ # Detectar tipo de sección
|
|
|
|
|
+ section_type = self.combo_tipo.get().lower()
|
|
|
|
|
+ if section_type == "ipe":
|
|
|
|
|
+ section_type = "ipe"
|
|
|
|
|
+ elif section_type == "puente nuevo":
|
|
|
|
|
+ section_type = "puente_nuevo"
|
|
|
|
|
+ else:
|
|
|
|
|
+ section_type = "otro"
|
|
|
|
|
+
|
|
|
|
|
+ # Crear nueva sección con puntos
|
|
|
|
|
+ new_section = {
|
|
|
|
|
+ 'nombre': new_name,
|
|
|
|
|
+ 'tipo': section_type,
|
|
|
|
|
+ 'puntos': self.current_points.tolist() # Guardar los puntos actuales
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ # Si es IPE, guardar también los parámetros
|
|
|
|
|
+ if section_type == "ipe":
|
|
|
|
|
+ try:
|
|
|
|
|
+ H = float(self.entry_H.get())
|
|
|
|
|
+ b = float(self.entry_b.get())
|
|
|
|
|
+ tf = float(self.entry_tf.get())
|
|
|
|
|
+ tw = float(self.entry_tw.get())
|
|
|
|
|
+
|
|
|
|
|
+ if H > 0 and b > 0 and tf > 0 and tw > 0: # Validar que sean mayores a 0
|
|
|
|
|
+ new_section['parametros'] = {
|
|
|
|
|
+ 'H': H,
|
|
|
|
|
+ 'b': b,
|
|
|
|
|
+ 'tf': tf,
|
|
|
|
|
+ 'tw': tw
|
|
|
|
|
+ }
|
|
|
|
|
+ except:
|
|
|
|
|
+ pass # Si no se pueden leer, solo guardamos puntos
|
|
|
|
|
+ elif section_type == "puente_nuevo":
|
|
|
|
|
+ try:
|
|
|
|
|
+ h = float(self.entry_h_puente.get())
|
|
|
|
|
+ b = float(self.entry_b_puente.get())
|
|
|
|
|
+ tf = float(self.entry_tf_puente.get())
|
|
|
|
|
+ tw = float(self.entry_tw_puente.get())
|
|
|
|
|
+ ha = float(self.entry_ha_puente.get())
|
|
|
|
|
+ ta = float(self.entry_ta_puente.get())
|
|
|
|
|
+ tr = float(self.entry_tr_puente.get())
|
|
|
|
|
+ theta = float(self.entry_theta_puente.get())
|
|
|
|
|
+
|
|
|
|
|
+ if all(v > 0 for v in [h, b, tf, tw, ha, ta, tr]) and 0 <= theta <= 360:
|
|
|
|
|
+ new_section['parametros'] = {
|
|
|
|
|
+ 'h': h,
|
|
|
|
|
+ 'b': b,
|
|
|
|
|
+ 'tf': tf,
|
|
|
|
|
+ 'tw': tw,
|
|
|
|
|
+ 'ha': ha,
|
|
|
|
|
+ 'ta': ta,
|
|
|
|
|
+ 'tr': tr,
|
|
|
|
|
+ 'theta': theta
|
|
|
|
|
+ }
|
|
|
|
|
+ except:
|
|
|
|
|
+ pass # Si no se pueden leer, solo guardamos puntos
|
|
|
|
|
+
|
|
|
|
|
+ # Eliminar si existe
|
|
|
|
|
+ self.secciones_data['secciones'] = [s for s in self.secciones_data['secciones'] if s['nombre'] != new_name]
|
|
|
|
|
+ self.secciones_data['secciones'].append(new_section)
|
|
|
|
|
+
|
|
|
|
|
+ self.save_json()
|
|
|
|
|
+
|
|
|
|
|
+ # Actualizar combo
|
|
|
|
|
+ self.combo_seccion['values'] = [s['nombre'] for s in self.secciones_data['secciones']]
|
|
|
|
|
+ self.combo_seccion.set(new_name)
|
|
|
|
|
+
|
|
|
|
|
+ messagebox.showinfo("Éxito", f"Sección '{new_name}' guardada correctamente")
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+if __name__ == "__main__":
|
|
|
|
|
+ app = SectionDesignerApp()
|
|
|
|
|
+ app.mainloop()
|