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" 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") con_ref = tk.BooleanVar(value=False) chk_con_ref = ttk.Checkbutton(frame_datos, text="Con Refuerzos verticales", variable=con_ref) chk_con_ref.pack(anchor="w") self.con_ref = con_ref # 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', 'e_ref', 'L_ref'] 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") self.sweep_fixed_entries['e_ref'].set("0.01") self.sweep_fixed_entries['L_ref'].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', 'e_ref', 'L_ref'] if self.con_ref.get() else ['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 = [] f = open(nombre_archivo, "w") if self.con_ref.get(): f.write("H\tb\ttf\ttw\te\tL\tFrecuencia\tCoef_Impacto\tFlecha_38D\tFlecha_86E\tFlecha_Media\tArea\tIxg\tIyg\tImax\tImin\tPeso\n") else: f.write("H\tb\ttf\ttw\tFrecuencia\tCoef_Impacto\tFlecha_38D\tFlecha_86E\tFlecha_Media\tArea\tIxg\tIyg\tImax\tImin\tPeso\n") for params in combinations: SapModel.SetModelisLocked(False) SapModel.SetPresentUnits(10) #unidades en niutons metros if self.con_ref.get(): points_ipe = self.generate_ipe_points_ref(params['H'], params['b'], params['tf'], params['tw'], params['e_ref'], params['L_ref']) points_hueco_inf = self.generate_hueco_inf_points_ref(params['H'], params['b'], params['tf'], params['tw'], params['e_ref'], params['L_ref']) points_hueco_sup = self.generate_hueco_sup_points_ref(params['H'], params['b'], params['tf'], params['tw'], params['e_ref'], params['L_ref']) points_completo = self.generate_completo_points_ref(params['H'], params['b'], params['tf'], params['tw'], params['e_ref'], params['L_ref']) else: points_ipe = self.generate_ipe_points(params['H'], params['b'], params['tf'], params['tw']) 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']) properties_ipe = self.calculate_section_properties(points_ipe) 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("3. ENV ELS CAR") 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") if self.con_ref.get(): 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']}\ {params['e_ref'].item() if isinstance(params['e_ref'], np.ndarray) else params['e_ref']}\ {params['L_ref'].item() if isinstance(params['L_ref'], np.ndarray) else params['L_ref']}\ {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(".", ",") else: 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 = 1 # 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_completo_points_ref(self, H, b, tf, tw, e_ref, L_ref): """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 x = (0.169 - tf) * e_ref / (b - x_alma_fin) + tf points = np.array([ [0, 0], [b, 0], [b, L_ref], [b - e_ref, L_ref], [b - e_ref, x], [x_alma_fin, 0.169], [x_alma_fin, H - .169], [b - e_ref, H - x], [b - e_ref, H - L_ref], [b, H - L_ref], [b, H], [0, H], [0, H - L_ref], [e_ref, H - L_ref], [e_ref, H - x], [x_alma_ini, H - 0.169], [x_alma_ini, 0.169], [e_ref, x], [e_ref, L_ref], [0, L_ref], ]) 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 -.36 - .169], [x_alma_ini, H - .36 - .169], [x_alma_ini, tf], [0, tf], ]) return points def generate_hueco_inf_points_ref(self, H, b, tf, tw, e_ref, L_ref): """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, L_ref], [b - e_ref, L_ref], [b - e_ref, tf], [x_alma_fin, tf], [x_alma_fin, H -.36 - .169], [x_alma_ini, H - .36 - .169], [x_alma_ini, tf], [e_ref, tf], [e_ref, L_ref], [0, L_ref], ]) 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_hueco_sup_points_ref(self, H, b, tf, tw, e_ref, L_ref): """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 - .169], [x_alma_fin, H - tf], [b - e_ref, H - tf], [b - e_ref, H - L_ref], [b, H - L_ref], [b, H], [0, H], [0, H - L_ref], [e_ref, H - L_ref], [e_ref, H - tf], [x_alma_ini, H - tf], [x_alma_ini, H - .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 generate_ipe_points_ref(self, H, b, tf, tw, e_ref, L_ref): """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, L_ref], [b - e_ref, L_ref], [b - e_ref, tf], [x_alma_fin, tf], [x_alma_fin, H - tf], [b - e_ref, H - tf], [b - e_ref, H - L_ref], [b, H - L_ref], [b, H], [0, H], [0, H - L_ref], [e_ref, H - L_ref], [e_ref, H - tf], [x_alma_ini, H - tf], [x_alma_ini, tf], [e_ref, tf], [e_ref, L_ref], [0, L_ref], ]) 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 if self.con_ref.get(): e_ref = params.get('e_ref', 0) L_ref = params.get('L_ref', 0) if e_ref > 0.3: return False if L_ref > 0.169: return False if L_ref < tf: 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()