|
|
@@ -0,0 +1,715 @@
|
|
|
+import os
|
|
|
+import sys
|
|
|
+import comtypes.client
|
|
|
+import pandas as pd
|
|
|
+from shapely import points
|
|
|
+pd.set_option('future.no_silent_downcasting', True)
|
|
|
+import itertools
|
|
|
+import tkinter as tk
|
|
|
+from tkinter import *
|
|
|
+from tkinter import ttk, messagebox, scrolledtext
|
|
|
+from tkinter.filedialog import askopenfilename
|
|
|
+import math
|
|
|
+import numpy as np
|
|
|
+import warnings
|
|
|
+warnings.filterwarnings('ignore', category=UserWarning, module='openpyxl')
|
|
|
+import threading
|
|
|
+import time
|
|
|
+
|
|
|
+nombre_archivo = "resultados1.txt"
|
|
|
+
|
|
|
+f = open(nombre_archivo, "w")
|
|
|
+f.write("H\tb\ttf\ttw\tFrecuencia\tCoef_Impacto\tFlecha_38D\tFlecha_86E\tFlecha_Media\tArea\tIxg\tIyg\tImax\tImin\tPeso\n")
|
|
|
+f.close() # Limpiar el archivo al inicio
|
|
|
+
|
|
|
+class TimeoutException(Exception):
|
|
|
+ pass
|
|
|
+
|
|
|
+def raise_timeout():
|
|
|
+ raise TimeoutException()
|
|
|
+
|
|
|
+timer = threading.Timer(20.0, raise_timeout) #se crea un timer de 20 segundos
|
|
|
+timer.start()
|
|
|
+
|
|
|
+try:
|
|
|
+ #Conexion con SAP2000
|
|
|
+ helper = comtypes.client.CreateObject('SAP2000v1.Helper')
|
|
|
+ helper = helper.QueryInterface(comtypes.gen.SAP2000v1.cHelper)
|
|
|
+ mySapObject = helper.GetObject("CSI.SAP2000.API.SapObject")
|
|
|
+ SapModel = mySapObject.SapModel
|
|
|
+ if SapModel.GetModelisLocked(): SapModel.SetModelisLocked(False)
|
|
|
+
|
|
|
+except TimeoutException as exc:
|
|
|
+ messagebox.showerror(
|
|
|
+ "Error",
|
|
|
+ "No se encuentra una instancia de SAP2000 abierta. Por favor, abra SAP2000 e intente de nuevo."
|
|
|
+ )
|
|
|
+ sys.exit(1)
|
|
|
+
|
|
|
+finally:
|
|
|
+ timer.cancel()
|
|
|
+
|
|
|
+
|
|
|
+class SAPSectionDesignerGUI:
|
|
|
+ def __init__(self):
|
|
|
+ self.root = tk.Tk()
|
|
|
+ self.root.title("Diseñador de Secciones SAP2000 - Python API")
|
|
|
+ self.root.geometry("600x800")
|
|
|
+
|
|
|
+ self.SapModel = None
|
|
|
+
|
|
|
+ self.crear_interfaz()
|
|
|
+
|
|
|
+ def crear_interfaz(self):
|
|
|
+ # === Datos de la sección ===
|
|
|
+ tk.Label(self.root, text="Datos de la Sección", font=("Arial", 12, "bold")).pack(pady=(20,5))
|
|
|
+
|
|
|
+ frame_datos = tk.Frame(self.root)
|
|
|
+ frame_datos.pack(pady=5, padx=20, fill="x")
|
|
|
+
|
|
|
+ # Frame para parámetros fijos
|
|
|
+ frame_fixed = ttk.LabelFrame(self.root, text="Parámetros Fijos", padding=5)
|
|
|
+ frame_fixed.pack(fill=tk.X, pady=(0, 10))
|
|
|
+
|
|
|
+ # Variables para toggle fijo/barrido
|
|
|
+ self.sweep_toggle_vars = {}
|
|
|
+ self.sweep_fixed_entries = {}
|
|
|
+ self.sweep_range_entries = {}
|
|
|
+
|
|
|
+ params_ipe = ['H', 'b', 'tf', 'tw']
|
|
|
+ for param in params_ipe:
|
|
|
+ container = ttk.Frame(frame_fixed)
|
|
|
+ container.pack(pady=2)
|
|
|
+
|
|
|
+ var = tk.BooleanVar(value=True)
|
|
|
+ self.sweep_toggle_vars[param] = var
|
|
|
+ chk = ttk.Checkbutton(container, text=f"Fijo: {param}", variable=var,
|
|
|
+ command=lambda p=param: self.on_sweep_toggle(p))
|
|
|
+ chk.pack(side=tk.LEFT, fill=tk.X, expand=True)
|
|
|
+
|
|
|
+ entry = ttk.Spinbox(container, from_=0.0, to=1000.0, increment=0.001, width=10)
|
|
|
+ entry.pack(side=tk.LEFT, padx=5)
|
|
|
+ self.sweep_fixed_entries[param] = entry
|
|
|
+
|
|
|
+ # Cargar valores actuales
|
|
|
+ self.sweep_fixed_entries['H'].set("1.0")
|
|
|
+ self.sweep_fixed_entries['b'].set("0.45")
|
|
|
+ self.sweep_fixed_entries['tf'].set("0.01")
|
|
|
+ self.sweep_fixed_entries['tw'].set("0.01")
|
|
|
+
|
|
|
+ # Frame para parámetros a barrer
|
|
|
+ frame_sweep = ttk.LabelFrame(self.root, text="Parámetros a Barrer", padding=5)
|
|
|
+ frame_sweep.pack(fill=tk.X, pady=(0, 10))
|
|
|
+
|
|
|
+ self.sweep_range_labels = {}
|
|
|
+ self.sweep_range_entries = {}
|
|
|
+
|
|
|
+ for param in params_ipe:
|
|
|
+ container = ttk.Frame(frame_sweep)
|
|
|
+ container.pack(fill=tk.X, pady=2)
|
|
|
+
|
|
|
+ lbl = ttk.Label(container, text=f"{param}:", width=5)
|
|
|
+ lbl.pack(side=tk.LEFT)
|
|
|
+ self.sweep_range_labels[param] = lbl
|
|
|
+
|
|
|
+ ttk.Label(container, text="min:").pack(side=tk.LEFT, padx=(10, 2))
|
|
|
+ min_entry = ttk.Spinbox(container, from_=0.0, to=1000.0, increment=0.001, width=8)
|
|
|
+ min_entry.pack(side=tk.LEFT, padx=2)
|
|
|
+
|
|
|
+ ttk.Label(container, text="max:").pack(side=tk.LEFT, padx=(10, 2))
|
|
|
+ max_entry = ttk.Spinbox(container, from_=0.0, to=1000.0, increment=0.001, width=8)
|
|
|
+ max_entry.pack(side=tk.LEFT, padx=2)
|
|
|
+
|
|
|
+ ttk.Label(container, text="pasos:").pack(side=tk.LEFT, padx=(10, 2))
|
|
|
+ steps_entry = ttk.Spinbox(container, from_=2, to=20, increment=1, width=8)
|
|
|
+ steps_entry.pack(side=tk.LEFT, padx=2)
|
|
|
+ steps_entry.set(5)
|
|
|
+
|
|
|
+ self.sweep_range_entries[param] = {
|
|
|
+ 'min': min_entry,
|
|
|
+ 'max': max_entry,
|
|
|
+ 'steps': steps_entry
|
|
|
+ }
|
|
|
+
|
|
|
+ # Botones de control
|
|
|
+ button_frame = ttk.Frame(self.root)
|
|
|
+ button_frame.pack(fill=tk.X, pady=(10, 0))
|
|
|
+
|
|
|
+ # === Botón Crear ===
|
|
|
+ btn_crear = tk.Button(self.root, text="🚀 Crear Sección en Section Designer",
|
|
|
+ command=self.execute_parametric_sweep, width=40, height=2,
|
|
|
+ bg="#2196F3", fg="white", font=("Arial", 10, "bold"))
|
|
|
+ btn_crear.pack(pady=20)
|
|
|
+
|
|
|
+ tk.Label(self.root, text="Nota: Todas las unidades estan en [m].",
|
|
|
+ fg="gray").pack()
|
|
|
+
|
|
|
+ def on_sweep_toggle(self, param):
|
|
|
+ """Alterna parámetro entre fijo y barrido"""
|
|
|
+ is_fixed = self.sweep_toggle_vars[param].get()
|
|
|
+ # Los rangos se mostrarán solo si no está fijo
|
|
|
+ # Esta lógica se puede mejorar si es necesario
|
|
|
+
|
|
|
+ def execute_parametric_sweep(self):
|
|
|
+ """Ejecuta el barrido paramétrico"""
|
|
|
+
|
|
|
+ if SapModel.GetModelisLocked(): SapModel.SetModelisLocked(False)
|
|
|
+
|
|
|
+ try:
|
|
|
+ # Recopilar configuración
|
|
|
+ fixed_params = {}
|
|
|
+ sweep_configs = {}
|
|
|
+
|
|
|
+ for param in ['H', 'b', 'tf', 'tw']:
|
|
|
+ is_fixed = self.sweep_toggle_vars[param].get()
|
|
|
+
|
|
|
+ if is_fixed:
|
|
|
+ value = float(self.sweep_fixed_entries[param].get())
|
|
|
+ fixed_params[param] = value
|
|
|
+ else:
|
|
|
+ min_val = float(self.sweep_range_entries[param]['min'].get())
|
|
|
+ max_val = float(self.sweep_range_entries[param]['max'].get())
|
|
|
+ steps = int(self.sweep_range_entries[param]['steps'].get())
|
|
|
+
|
|
|
+ if steps < 1:
|
|
|
+ messagebox.showerror("Error", f"El número de pasos para {param} debe ser al menos 1")
|
|
|
+ return
|
|
|
+
|
|
|
+ if min_val <= 0:
|
|
|
+ messagebox.showerror("Error", f"Valores de {param} deben ser positivos")
|
|
|
+ return
|
|
|
+
|
|
|
+ if min_val >= max_val:
|
|
|
+ messagebox.showerror("Error", f"Rango inválido para {param}: min >= max")
|
|
|
+ return
|
|
|
+
|
|
|
+ sweep_configs[param] = {
|
|
|
+ 'min': min_val,
|
|
|
+ 'max': max_val,
|
|
|
+ 'steps': steps
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ if not sweep_configs:
|
|
|
+ messagebox.showerror("Error", "Debe barrer al menos un parámetro")
|
|
|
+ return
|
|
|
+
|
|
|
+ if not fixed_params:
|
|
|
+ messagebox.showwarning("Advertencia", "No hay parámetros fijos")
|
|
|
+
|
|
|
+
|
|
|
+ combinations = self.generar_matriz_sweep(sweep_configs, fixed_params)
|
|
|
+
|
|
|
+ if not combinations:
|
|
|
+ messagebox.showerror("Error", "No se generaron combinaciones para el barrido")
|
|
|
+ return
|
|
|
+
|
|
|
+ results = []
|
|
|
+
|
|
|
+ for params in combinations:
|
|
|
+ SapModel.SetModelisLocked(False)
|
|
|
+ SapModel.SetPresentUnits(10) #unidades en niutons metros
|
|
|
+ points_ipe = self.generate_ipe_points(params['H'], params['b'], params['tf'], params['tw'])
|
|
|
+ properties_ipe = self.calculate_section_properties(points_ipe)
|
|
|
+ points_hueco_inf = self.generate_hueco_inf_points(params['H'], params['b'], params['tf'], params['tw'])
|
|
|
+ points_hueco_sup = self.generate_hueco_sup_points(params['H'], params['b'], params['tf'], params['tw'])
|
|
|
+ points_completo = self.generate_completo_points(params['H'], params['b'], params['tf'], params['tw'])
|
|
|
+
|
|
|
+ self.crear_seccion(points_ipe, tipo="ipe", nombre_poligono = "Polygon1")
|
|
|
+ self.crear_seccion(points_hueco_inf, tipo="hueco", nombre_poligono = "Polygon1")
|
|
|
+ self.crear_seccion(points_hueco_sup, tipo="hueco", nombre_poligono = "Polygon2")
|
|
|
+ self.crear_seccion(points_completo, tipo="completo", nombre_poligono = "Polygon1")
|
|
|
+
|
|
|
+ ret = SapModel.Analyze.RunAnalysis()
|
|
|
+
|
|
|
+ frequency = self.obtain_frequency()
|
|
|
+
|
|
|
+ coef_impacto = self.calc_coef_impacto(frequency)
|
|
|
+
|
|
|
+ ret = SapModel.SetModelisLocked(False)
|
|
|
+
|
|
|
+ ret = SapModel.LoadCases.StaticLinear.SetLoads("H3. Sobrecarga UIC 71", 1, ["Load"],
|
|
|
+ ["H3. Sobrecarga UIC"], [coef_impacto])
|
|
|
+
|
|
|
+ if ret[3] != 0:
|
|
|
+ messagebox.showerror("Error de carga", f"Error al aplicar cargas para la combinación: {params}\nCódigo de error: {ret}")
|
|
|
+ return
|
|
|
+
|
|
|
+ ret = SapModel.Analyze.RunAnalysis()
|
|
|
+
|
|
|
+ SapModel.SetPresentUnits(9) # Unidades niutons milimetos
|
|
|
+ ret = SapModel.Results.Setup.DeselectAllCasesAndCombosForOutput()
|
|
|
+ ret = SapModel.Results.Setup.SetComboSelectedForOutput("1. ENV ELS FREQ")
|
|
|
+
|
|
|
+ FieldKeyList = []
|
|
|
+ GroupName = 'All'
|
|
|
+ TableVersion = 1
|
|
|
+ FieldsKeysIncluded = []
|
|
|
+ NumberRecords = 1
|
|
|
+ TableData = []
|
|
|
+
|
|
|
+ ret = SapModel.DatabaseTables.GetTableforDisplayArray("Joint Displacements", FieldKeyList, GroupName,
|
|
|
+ TableVersion, FieldsKeysIncluded, NumberRecords, TableData)
|
|
|
+
|
|
|
+ if ret[-1] != 0:
|
|
|
+ messagebox.showerror("Error de resultados", f"Error al obtener resultados para la combinación: {params}\nCódigo de error: {ret}")
|
|
|
+ return
|
|
|
+
|
|
|
+ big_tuple = ret[4]
|
|
|
+ items = list(big_tuple)
|
|
|
+
|
|
|
+ filas = []
|
|
|
+
|
|
|
+ j = 0
|
|
|
+ while j < len(items):
|
|
|
+ if j + 10 <= len(items):
|
|
|
+ fila = items[j:j+10]
|
|
|
+ filas.append(fila)
|
|
|
+ j += 10
|
|
|
+ else:
|
|
|
+ break
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ flecha1 = self.filtrar_por_joint(filas, "38D")
|
|
|
+ flecha2 = self.filtrar_por_joint(filas, "86E")
|
|
|
+
|
|
|
+ flecha_media = (flecha1 + flecha2) / 2
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ f = open(nombre_archivo, "a")
|
|
|
+ string = (f"{params['H'].item() if isinstance(params['H'], np.ndarray) else params['H']}\
|
|
|
+ {params['b'].item() if isinstance(params['b'], np.ndarray) else params['b']}\
|
|
|
+ {params['tf'].item() if isinstance(params['tf'], np.ndarray) else params['tf']}\
|
|
|
+ {params['tw'].item() if isinstance(params['tw'], np.ndarray) else params['tw']}\
|
|
|
+ {frequency}\
|
|
|
+ {coef_impacto}\
|
|
|
+ {flecha1}\
|
|
|
+ {flecha2}\
|
|
|
+ {flecha_media}\
|
|
|
+ {properties_ipe['area'].item()}\
|
|
|
+ {properties_ipe['ixg'].item()}\
|
|
|
+ {properties_ipe['iyg'].item()}\
|
|
|
+ {properties_ipe['imax'].item()}\
|
|
|
+ {properties_ipe['imin'].item()}\
|
|
|
+ {properties_ipe['peso'].item()}\n").replace(".", ",")
|
|
|
+ f.write(string)
|
|
|
+ f.close()
|
|
|
+
|
|
|
+
|
|
|
+ return
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ messagebox.showerror("Error en configuración", f"Revisa los valores ingresados:\n{e}")
|
|
|
+ return
|
|
|
+
|
|
|
+
|
|
|
+ def filtrar_por_joint(self, filas, joint_id):
|
|
|
+
|
|
|
+ for fila in filas:
|
|
|
+ if fila[0] == joint_id and fila[3] == "Min":
|
|
|
+ return float(fila[6].replace(",", "."))
|
|
|
+
|
|
|
+ return None
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ def calc_coef_impacto(self, frecuencia):
|
|
|
+
|
|
|
+ #calculo segun EC1.2 trenes reales
|
|
|
+
|
|
|
+ L_phi = 15 # m
|
|
|
+ v = 80 # km/h
|
|
|
+ r = 0.5 # calidad de mantenimiento de la vía
|
|
|
+
|
|
|
+ alpha = 1 if v/3.6 > 22 else v/(3.6*22)
|
|
|
+
|
|
|
+ K = v/(3.6*L_phi*frecuencia)
|
|
|
+
|
|
|
+ phi1 = 1.325 if K >= 0.76 else K/(1-K+K**4)
|
|
|
+
|
|
|
+ phi2 = alpha*(56*math.exp(-((L_phi/10)**2))+50*(L_phi*frecuencia/80-1)*math.exp(-((L_phi/20)**2)))/100
|
|
|
+
|
|
|
+ return 1.21 * (1 + phi1 + r * phi2) #aplicado el factor alpha
|
|
|
+
|
|
|
+
|
|
|
+ def obtain_frequency(self):
|
|
|
+ Num_results = 0
|
|
|
+ LoadCase = ""
|
|
|
+ Steptype = []
|
|
|
+ Stepnum = []
|
|
|
+ Period = []
|
|
|
+ Ux = []
|
|
|
+ Uy = []
|
|
|
+ Uz = []
|
|
|
+ SumUx = []
|
|
|
+ SumUy = []
|
|
|
+ SumUz = []
|
|
|
+ Rx = []
|
|
|
+ Ry = []
|
|
|
+ Rz = []
|
|
|
+ SumRx = []
|
|
|
+ SumRy = []
|
|
|
+ SumRz = []
|
|
|
+
|
|
|
+ ret = SapModel.Results.ModalParticipatingMassRatios(Num_results, LoadCase, Steptype, Stepnum, Period,
|
|
|
+ Ux, Uy, Uz, SumUx, SumUy, SumUz,
|
|
|
+ Rx, Ry, Rz, SumRx, SumRy, SumRz)
|
|
|
+
|
|
|
+ if ret[17] != 0:
|
|
|
+ messagebox.showerror("Error al obtener frecuencias", f"Código de error: {ret}")
|
|
|
+ return None
|
|
|
+
|
|
|
+ return 1/ret[4][min(ret[7].index(max(ret[7])), ret[12].index(max(ret[12])))]
|
|
|
+
|
|
|
+
|
|
|
+ def calculate_section_properties(self, points):
|
|
|
+ """Calcula propiedades de la sección a partir de puntos"""
|
|
|
+ # Constantes de material
|
|
|
+ DENS = 7850 # kg/m3
|
|
|
+ FY = 355 # MPa
|
|
|
+ GAMMAS = 1.05
|
|
|
+ EYOUNG = 210000 # MPa
|
|
|
+ NU = 0.3
|
|
|
+ FYD = FY * 10**6 / GAMMAS
|
|
|
+
|
|
|
+ npuntos = len(points)
|
|
|
+ px = points[:, 0]
|
|
|
+ py = points[:, 1]
|
|
|
+
|
|
|
+ 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] = ((points[i+1, 0] - points[i, 0])**2 + (points[i+1, 1] - points[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] = (points[i+1, 0] - points[i, 0]) * (points[i+1, 1] + points[i, 1]) / 2
|
|
|
+ area = abs(sum(area_i))
|
|
|
+
|
|
|
+ # Peso
|
|
|
+ peso = area * DENS
|
|
|
+
|
|
|
+ # Centro de gravedad
|
|
|
+ cdg_i = np.zeros([npuntos, 2])
|
|
|
+ for i in range(npuntos - 1):
|
|
|
+ h1 = points[i, 1]
|
|
|
+ h2 = points[i+1, 1]
|
|
|
+ b = points[i+1, 0] - points[i, 0]
|
|
|
+ d = points[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)
|
|
|
+
|
|
|
+ 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 = points[i, 1]
|
|
|
+ h2 = points[i+1, 1]
|
|
|
+ b = points[i+1, 0] - points[i, 0]
|
|
|
+ d = points[i, 0]
|
|
|
+ xgi = cdg_i[i, 0]
|
|
|
+ ygi = cdg_i[i, 1]
|
|
|
+ ai = area_i[i]
|
|
|
+
|
|
|
+ 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
|
|
|
+
|
|
|
+ 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
|
|
|
+
|
|
|
+ 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
|
|
|
+ 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
|
|
|
+
|
|
|
+ # Conversión a unidades prácticas (cm, cm2, cm3, cm4, kN)
|
|
|
+ pot = 2
|
|
|
+ area_cm2 = area * 10**(pot*2)
|
|
|
+ ixg_cm4 = ixg * 10**(pot*4)
|
|
|
+ iyg_cm4 = iyg * 10**(pot*4)
|
|
|
+ imax_cm4 = imax * 10**(pot*4)
|
|
|
+ imin_cm4 = imin * 10**(pot*4)
|
|
|
+
|
|
|
+ return {
|
|
|
+ 'area': area_cm2,
|
|
|
+ 'ixg': ixg_cm4,
|
|
|
+ 'iyg': iyg_cm4,
|
|
|
+ 'imax': imax_cm4,
|
|
|
+ 'imin': imin_cm4,
|
|
|
+ 'peso': peso,
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ def generate_completo_points(self, H, b, tf, tw):
|
|
|
+ """Genera los puntos de una sección IPE a partir de parámetros normalizados"""
|
|
|
+ h_alma = H - 2*tf
|
|
|
+ x_alma_ini = (b - .3) / 2
|
|
|
+ x_alma_fin = (b + .3) / 2
|
|
|
+
|
|
|
+ points = np.array([
|
|
|
+ [0, 0],
|
|
|
+ [b, 0],
|
|
|
+ [b, tf],
|
|
|
+ [x_alma_fin, 0.169],
|
|
|
+ [x_alma_fin, H - .169],
|
|
|
+ [b, H - tf],
|
|
|
+ [b, H],
|
|
|
+ [0, H],
|
|
|
+ [0, H - tf],
|
|
|
+ [x_alma_ini, H - .169],
|
|
|
+ [x_alma_ini, 0.169],
|
|
|
+ [0, tf],
|
|
|
+ ])
|
|
|
+
|
|
|
+ return points
|
|
|
+
|
|
|
+
|
|
|
+ def generate_hueco_inf_points(self, H, b, tf, tw):
|
|
|
+ """Genera los puntos de una sección IPE a partir de parámetros normalizados"""
|
|
|
+ h_alma = H - 2*tf
|
|
|
+ x_alma_ini = (b - tw) / 2
|
|
|
+ x_alma_fin = (b + tw) / 2
|
|
|
+
|
|
|
+ points = np.array([
|
|
|
+ [0, 0],
|
|
|
+ [b, 0],
|
|
|
+ [b, tf],
|
|
|
+ [x_alma_fin, tf],
|
|
|
+ [x_alma_fin, H - tf -.36 - .169],
|
|
|
+ [x_alma_ini, H - tf - .36 - .169],
|
|
|
+ [x_alma_ini, tf],
|
|
|
+ [0, tf],
|
|
|
+ ])
|
|
|
+
|
|
|
+ return points
|
|
|
+
|
|
|
+ def generate_hueco_sup_points(self, H, b, tf, tw):
|
|
|
+ """Genera los puntos de una sección IPE a partir de parámetros normalizados"""
|
|
|
+ h_alma = H - 2*tf
|
|
|
+ x_alma_ini = (b - tw) / 2
|
|
|
+ x_alma_fin = (b + tw) / 2
|
|
|
+
|
|
|
+ points = np.array([
|
|
|
+ [x_alma_fin, H - tf - .169],
|
|
|
+ [x_alma_fin, H - tf],
|
|
|
+ [b, H - tf],
|
|
|
+ [b, H],
|
|
|
+ [0, H],
|
|
|
+ [0, H - tf],
|
|
|
+ [x_alma_ini, H - tf],
|
|
|
+ [x_alma_ini, H - tf - .169],
|
|
|
+ ])
|
|
|
+
|
|
|
+ return points
|
|
|
+
|
|
|
+ def generate_ipe_points(self, H, b, tf, tw):
|
|
|
+ """Genera los puntos de una sección IPE a partir de parámetros normalizados"""
|
|
|
+ h_alma = H - 2*tf
|
|
|
+ x_alma_ini = (b - tw) / 2
|
|
|
+ x_alma_fin = (b + tw) / 2
|
|
|
+
|
|
|
+ points = np.array([
|
|
|
+ [0, 0],
|
|
|
+ [b, 0],
|
|
|
+ [b, tf],
|
|
|
+ [x_alma_fin, tf],
|
|
|
+ [x_alma_fin, H - tf],
|
|
|
+ [b, H - tf],
|
|
|
+ [b, H],
|
|
|
+ [0, H],
|
|
|
+ [0, H - tf],
|
|
|
+ [x_alma_ini, H - tf],
|
|
|
+ [x_alma_ini, tf],
|
|
|
+ [0, tf],
|
|
|
+ ])
|
|
|
+
|
|
|
+ return points
|
|
|
+
|
|
|
+ def _valid_geometry(self, params):
|
|
|
+ """Valida que la combinación de parámetros genere una geometría válida"""
|
|
|
+ H = params.get('H', 0)
|
|
|
+ b = params.get('b', 0)
|
|
|
+ tf = params.get('tf', 0)
|
|
|
+ tw = params.get('tw', 0)
|
|
|
+
|
|
|
+ if H <= 0 or b <= 0 or tf <= 0 or tw <= 0:
|
|
|
+ return False
|
|
|
+ if H < 0.360 + 0.169 + tf:
|
|
|
+ return False
|
|
|
+ if b < 0.3:
|
|
|
+ return False
|
|
|
+ if tf > H/2 or tf > 0.169:
|
|
|
+ return False
|
|
|
+ if tw > b:
|
|
|
+ return False
|
|
|
+
|
|
|
+ return True
|
|
|
+
|
|
|
+ def generar_matriz_sweep(self, sweep_configs, fixed_params):
|
|
|
+ sweep_params = list(sweep_configs.keys())
|
|
|
+
|
|
|
+ param_values = {}
|
|
|
+ for param in sweep_params:
|
|
|
+ config = sweep_configs[param]
|
|
|
+ values = np.linspace(config['min'], config['max'], config['steps'])
|
|
|
+ param_values[param] = values
|
|
|
+
|
|
|
+ combinations = []
|
|
|
+ for combo in itertools.product(*param_values.values()):
|
|
|
+ params = fixed_params.copy()
|
|
|
+ for i, param in enumerate(sweep_params):
|
|
|
+ params[param] = combo[i]
|
|
|
+ if self._valid_geometry(params):
|
|
|
+ combinations.append(params)
|
|
|
+
|
|
|
+ return combinations
|
|
|
+
|
|
|
+
|
|
|
+ def crear_seccion(self, puntos = [], tipo="ipe", nombre_poligono="Nueva sección"):
|
|
|
+
|
|
|
+ material = "S355"
|
|
|
+
|
|
|
+ if not len(puntos) or not tipo:
|
|
|
+ messagebox.showerror("Error", "Faltan puntos o tipos de sección")
|
|
|
+ return
|
|
|
+
|
|
|
+ # Leer coordenadas
|
|
|
+ try:
|
|
|
+ X = [float(p[0]) for p in puntos]
|
|
|
+ Y = [float(p[1]) for p in puntos]
|
|
|
+
|
|
|
+ num_points = len(X)
|
|
|
+ Radius = [0.0] * num_points
|
|
|
+
|
|
|
+ if num_points < 3:
|
|
|
+ raise ValueError("Se necesitan al menos 3 puntos")
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ messagebox.showerror("Error en coordenadas", str(e))
|
|
|
+ return
|
|
|
+
|
|
|
+ if tipo == "hueco":
|
|
|
+ nombre = "hueca"
|
|
|
+ elif tipo == "completo":
|
|
|
+ nombre = "rigida"
|
|
|
+ elif tipo == "ipe":
|
|
|
+ nombre = "entera"
|
|
|
+ else:
|
|
|
+ messagebox.showerror("Error", f"Tipo de sección desconocido: {tipo}")
|
|
|
+ return
|
|
|
+
|
|
|
+ try:
|
|
|
+
|
|
|
+ ret = SapModel.PropFrame.SetSDSection(
|
|
|
+ nombre, material, 1, -1, "", "Default"
|
|
|
+ )
|
|
|
+
|
|
|
+ if not (ret == 0 or ret == 1): # 1 = sección ya existe, lo cual está bien
|
|
|
+ messagebox.showerror("Error SetSDSection", f"Código: {ret}")
|
|
|
+ return
|
|
|
+
|
|
|
+ if not (nombre == "hueca" and nombre_poligono == "Polygon2"): #si es la sección hueca, el polígono 2 es el hueco, no se borra
|
|
|
+ ret = SapModel.PropFrame.SDShape.Delete(
|
|
|
+ nombre,
|
|
|
+ "",
|
|
|
+ True
|
|
|
+ )
|
|
|
+
|
|
|
+ if ret != 0 :
|
|
|
+ messagebox.showerror("Error Delete", f"Código: {ret}")
|
|
|
+ return
|
|
|
+
|
|
|
+ ret = SapModel.PropFrame.SDShape.SetPolygon(
|
|
|
+ nombre, # Nombre de la sección SD
|
|
|
+ nombre_poligono, # Nombre del shape
|
|
|
+ material, # Material
|
|
|
+ "Default",
|
|
|
+ num_points, # Número de puntos
|
|
|
+ X, # Lista normal de Python (X)
|
|
|
+ Y, # Lista normal (Y)
|
|
|
+ Radius, # Lista normal (Radius)
|
|
|
+ -1, # Color
|
|
|
+ False,
|
|
|
+ ""
|
|
|
+ )
|
|
|
+
|
|
|
+ if ret[4] != 0:
|
|
|
+ messagebox.showerror("Error SAP",
|
|
|
+ f"SetPolygon devolvió código de error: {ret}\n\nRevisa que el material exista y las coordenadas sean correctas.")
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ messagebox.showerror("Error SAP", str(e))
|
|
|
+
|
|
|
+
|
|
|
+ def run(self):
|
|
|
+ self.root.mainloop()
|
|
|
+
|
|
|
+if __name__ == "__main__":
|
|
|
+ app = SAPSectionDesignerGUI()
|
|
|
+ app.run()
|