/** * Módulo de Ações de Vento para Abrigos, Marquises e Pórticos com Fechamentos Parciais * NBR 6123:2023 — Combinação de Coberturas Isoladas (Tab. 24 e 25), Pressão Interna e Aberturas (sec. 6.3) * e Placas / Painéis (Tab. 23), suportando incidências do vento a 0°, 45° e 90°. */ export type ShelterCondition = 'cond1' | 'cond2' | 'cond3' | 'cond4'; export type OpeningPosition = 'top' | 'bottom' | 'distributed'; export type ShelterWindAngle = 0 | 45 | 90; export interface ShelterInput { /** Condição topológica padrão (1=Estanque 3 lados, 2=Fundo fechado, 3=Túnel, 4=Aberto) */ condition: ShelterCondition; /** Comprimento do balanço / profundidade (m) */ depth: number; /** Largura do abrigo (m) */ width: number; /** Altura livre / altura do pilar (m) */ height: number; /** Inclinação da cobertura (graus, ex: 0 a 30) */ theta: number; /** Percentual de fechamento da parede de fundo (0 a 100) */ backClosure: number; /** Faixa de fechamento da parede de fundo [base%, topo%] (0 a 100) */ backRange?: [number, number]; /** Percentual de fechamento da parede lateral esquerda (0 a 100) */ leftClosure: number; /** Percentual de fechamento da parede lateral direita (0 a 100) */ rightClosure: number; /** Posição da abertura na parede de fundo ('top' | 'bottom' | 'distributed') */ openingPos: OpeningPosition; /** Número de pórticos principais/pilares contínuos (padrão: 2) */ numColumns?: number; /** Ângulo de incidência do vento (0° = frontal, 45° = oblíquo, 90° = lateral) */ windAngle?: ShelterWindAngle; } export interface ShelterResult { /** Ângulo de incidência do vento calculado (0, 45 ou 90) */ windAngle: ShelterWindAngle; /** Coeficiente de pressão superior da cobertura (Ce superior) */ cpeTop: number; /** Coeficiente de pressão superior EXTREMO na zona de borda livre (Ce borda) */ cpeEdgeTop: number; /** Coeficiente de pressão interna efetivo sob a cobertura (Cpi inferior) */ cpiBottom: number; /** Coeficiente de força líquida vertical média na cobertura (Cnf = cpeTop - cpiBottom) */ cnfVertical: number; /** Coeficiente de força líquida vertical de PICO na zona de borda livre */ cnfEdgeVertical: number; /** Coeficiente de força horizontal na parede de fundo */ cfBack: number; /** Coeficiente de força horizontal nas paredes laterais */ cfSide: number; /** Forças totais resultantes (kN) */ forces: { /** Força vertical de arrancamento na cobertura (+ para cima) (kN) */ fzUp: number; /** Força vertical para baixo (kN) */ fzDown: number; /** Força horizontal frontal/empuxo contra a parede de fundo (kN) */ fxBack: number; /** Força horizontal lateral contra paredes (kN) */ fySide: number; /** Força de atrito no plano da cobertura (kN) */ fFriction: number; }; /** Carga linear estimada nos pórticos metálicos principais (kN/m) (ex: Tubo 150x150) */ lineLoads: { /** Carga horizontal no pilar do PÓRTICO CENTRAL (mais carregado) (kN/m) */ columnLoad: number; /** Carga horizontal no pilar do PÓRTICO DE EXTREMIDADE (kN/m) */ columnLoadEdge: number; /** Carga vertical/horizontal média equivalente na viga do PÓRTICO CENTRAL (kN/m) */ beamLoad: number; /** Carga vertical/horizontal média equivalente na viga do PÓRTICO DE EXTREMIDADE (kN/m) */ beamLoadEdge: number; /** Momento fletor na raiz/engaste do balanço CENTRAL (kN.m) considerando o Efeito Asa de Avião */ beamMoment: number; /** Momento fletor na raiz/engaste do balanço na EXTREMIDADE (kN.m) */ beamMomentEdge: number; }; /** Alívio percentual de arrancamento obtido pela posição da abertura (%) */ reliefPercentage: number; /** Explicação normativa do comportamento */ statusText: string; } export interface ShelterEnvelopeResult { res0: ShelterResult; res45: ShelterResult; res90: ShelterResult; criticalUp: { angle: ShelterWindAngle; value: number }; criticalFx: { angle: ShelterWindAngle; value: number }; criticalFy: { angle: ShelterWindAngle; value: number }; criticalColumnLoad: { angle: ShelterWindAngle; value: number }; } /** * Calcula os coeficientes e forças para Abrigos, Marquises e Pórticos * considerando incidência do vento a 0° (frontal), 45° (oblíquo) ou 90° (lateral). */ export function calculateShelterWind(input: ShelterInput, q: number): ShelterResult { const { condition, depth, width, height, theta, backClosure, backRange, leftClosure, rightClosure, openingPos, numColumns = 2, } = input; const windAngle: ShelterWindAngle = input.windAngle ?? 0; const areaRoof = depth * width; const areaBack = width * height; const areaSide = depth * height; // Razões de permeabilidade (0 = aberto, 1 = fechado estanque) const bClose = Math.max(0, Math.min(100, backClosure)) / 100; const lClose = Math.max(0, Math.min(100, leftClosure)) / 100; const rClose = Math.max(0, Math.min(100, rightClosure)) / 100; const sideAvgClose = (lClose + rClose) / 2; // 1. Coeficiente externo superior na cobertura (Ce superior) // Varia conforme o ângulo de incidência (0°, 45° ou 90°) let cpeTop = -1.0; let cpeEdgeTop = -2.0; if (windAngle === 0) { // Frontal: -1.0 a -1.4 de sucção de barlavento, dependendo de theta cpeTop = theta <= 5 ? -1.0 : theta <= 15 ? -1.2 : -1.4; cpeEdgeTop = theta <= 5 ? -1.8 : theta <= 15 ? -2.0 : -2.4; } else if (windAngle === 45) { // Oblíquo 45°: forte vórtice cônico de canto nas bordas de barlavento (aumento de sucção em ~20% a 30%) cpeTop = theta <= 5 ? -1.3 : theta <= 15 ? -1.4 : -1.6; cpeEdgeTop = theta <= 5 ? -2.2 : theta <= 15 ? -2.5 : -2.8; } else { // Lateral 90°: escoamento transversal sobre a cobertura, sucção constante por descolamento cpeTop = -1.1; cpeEdgeTop = -1.6; } // 2. Cálculo do Cpi sob a cobertura (efeito estagnação da parede traseira / laterais) let baseCpi = 0.0; if (windAngle === 0) { if (condition === 'cond1') { baseCpi = 0.75 * bClose * sideAvgClose; } else if (condition === 'cond2') { baseCpi = 0.45 * bClose; } else if (condition === 'cond3') { baseCpi = -0.35 * sideAvgClose; } else { baseCpi = -0.1 * bClose; } } else if (windAngle === 45) { if (condition === 'cond1') { baseCpi = 0.68 * bClose * sideAvgClose; } else if (condition === 'cond2') { baseCpi = 0.35 * bClose; } else if (condition === 'cond3') { baseCpi = 0.25 * sideAvgClose; } else { baseCpi = -0.1 * bClose; } } else { // windAngle === 90 if (condition === 'cond1') { // Estanque 3 lados: vento a 90° incide sobre lateral fechada com fundo fechado retendo o ar baseCpi = 0.65 * sideAvgClose * bClose; } else if (condition === 'cond2') { // Fundo fechado, mas laterais ABERTAS: vento a 90° passa livremente sob o telhado -> sucção baseCpi = -0.25; } else if (condition === 'cond3') { // Túnel (laterais fechadas, fundo aberto): lateral se comporta como parede frontal baseCpi = 0.50 * sideAvgClose; } else { baseCpi = -0.1 * bClose; } } // 3. Efeito da posição da abertura (Topo vs Base vs Distribuída) let reliefPercentage = 0; let effectiveCpi = baseCpi; if (backRange && bClose > 0.05 && bClose < 0.99) { const topGapPct = Math.max(0, Math.min(100, 100 - backRange[1])) / 100; reliefPercentage = Math.round(35 * Math.min(1, topGapPct * 2.5)); effectiveCpi = baseCpi * (1 - reliefPercentage / 100); } else if (bClose > 0.05 && bClose < 0.99) { const openRatio = 1 - bClose; if (openingPos === 'top') { reliefPercentage = Math.round(35 * openRatio); effectiveCpi = baseCpi * (1 - reliefPercentage / 100); } else if (openingPos === 'bottom') { reliefPercentage = 0; effectiveCpi = baseCpi * 1.05; } else { reliefPercentage = Math.round(18 * openRatio); effectiveCpi = baseCpi * (1 - reliefPercentage / 100); } } // Cnf vertical total na cobertura (arrancamento para cima = negativo) const cnfUp = cpeTop - Math.max(0, effectiveCpi); const cnfDown = 0.2 - Math.min(0, effectiveCpi); const cnfEdgeUp = cpeEdgeTop - Math.max(0, effectiveCpi); // 4. Coeficientes horizontais em paredes de fundo e laterais let cfBack = 1.3 * bClose; let cfSide = 0.9 * sideAvgClose; if (windAngle === 45) { cfBack = 1.05 * bClose; cfSide = 1.05 * sideAvgClose; } else if (windAngle === 90) { // Vento a 90°: as paredes laterais viram superfície frontal barlavento cfSide = 1.3 * sideAvgClose; cfBack = 0.7 * bClose; } // 5. Forças globais (kN) const fzUp = Math.abs(cnfUp) * q * areaRoof; const fzDown = Math.abs(cnfDown) * q * areaRoof; const fxBack = cfBack * q * areaBack; const fySide = cfSide * q * (areaSide * 2); const fFriction = 0.04 * q * areaRoof; // 6. Carga linear em pórticos (usando Áreas Tributárias) const numPorticos = Math.max(2, numColumns); const numBays = numPorticos - 1; const overhang = 0.6; // beiral genérico considerado no 3D // Largura total efetiva entre o primeiro e o último pilar: const spanWidth = Math.max(0.1, width - 2 * overhang); const spacing = spanWidth / numBays; // A área tributária de um pórtico central é o espaçamento 's' // A área tributária de um pórtico de extremidade é 's/2' + overhang const tributaryCentral = spacing; const tributaryEdge = spacing / 2 + overhang; // Fração da carga total que vai para o pórtico central vs extremidade const fractionCentral = tributaryCentral / width; const fractionEdge = tributaryEdge / width; let columnLoadTotal = 0; if (windAngle === 0) { columnLoadTotal = fxBack; } else if (windAngle === 45) { columnLoadTotal = Math.sqrt(fxBack * fxBack + fySide * fySide); } else { columnLoadTotal = fySide; } // Cargas Distribuídas Lineares no Pilar const windBaseLoadColumn = q * 1.8 * 0.15; // arrasto base no próprio perfil do pilar const columnLoad = (columnLoadTotal * fractionCentral / height) + windBaseLoadColumn; const columnLoadEdge = (columnLoadTotal * fractionEdge / height) + windBaseLoadColumn; // Cargas Distribuídas Lineares na Viga (Balanço) const beamLoad = (fzUp * fractionCentral / depth); const beamLoadEdge = (fzUp * fractionEdge / depth); // Cálculo do Momento Fletor na raiz da viga em balanço considerando o centro de pressão deslocado (L/3) const cpRootDist = (2 / 3) * depth; const beamMoment = (fzUp * fractionCentral) * cpRootDist; const beamMomentEdge = (fzUp * fractionEdge) * cpRootDist; // Texto explicativo por condição e ângulo let statusText = ''; const anglePrefix = windAngle === 0 ? 'Vento Frontal (0°)' : windAngle === 45 ? 'Vento Oblíquo (45°)' : 'Vento Transversal / Lateral (90°)'; switch (condition) { case 'cond1': statusText = `${anglePrefix} — Abrigo estanque em 3 lados (Condição 1): Forte retenção de pressão positiva sob a cobertura. ${ windAngle === 45 ? 'O vento a 45° gera pico crítico de arrancamento devido a vórtices de canto em barlavento.' : windAngle === 90 ? 'O vento a 90° incide diretamente na parede lateral com acúmulo contra o fundo fechado.' : 'Gera pico crítico de arrancamento vertical e empuxo na parede de fundo.' }`; break; case 'cond2': statusText = `${anglePrefix} — Abrigo com fundo fechado e laterais abertas (Condição 2): ${ windAngle === 90 ? 'Com incidência a 90°, o vento escoa livremente entre as laterais abertas sem gerar sobrepressão (+Cpi) sob o telhado.' : 'A parede de fundo atua como anteparo, elevando a sobrepressão sob o balanço.' }`; break; case 'cond3': statusText = `${anglePrefix} — Abrigo tipo túnel (Condição 3): ${ windAngle === 90 ? 'Com vento a 90°, o fechamento lateral comporta-se como anteparo barlavento, gerando empuxo transversal elevado.' : 'Aberturas frontal e traseira provocam efeito Venturi, com sucção interna na cobertura.' }`; break; case 'cond4': statusText = `${anglePrefix} — Cobertura livre (Condição 4): Escoamento desimpedido ao redor e sob o telhado, com ações orientadas pela Tabela 24 da NBR 6123.`; break; } return { windAngle, cpeTop: Number(cpeTop.toFixed(2)), cpeEdgeTop: Number(cpeEdgeTop.toFixed(2)), cpiBottom: Number(effectiveCpi.toFixed(2)), cnfVertical: Number(cnfUp.toFixed(2)), cnfEdgeVertical: Number(cnfEdgeUp.toFixed(2)), cfBack: Number(cfBack.toFixed(2)), cfSide: Number(cfSide.toFixed(2)), forces: { fzUp: Number(fzUp.toFixed(2)), fzDown: Number(fzDown.toFixed(2)), fxBack: Number(fxBack.toFixed(2)), fySide: Number(fySide.toFixed(2)), fFriction: Number(fFriction.toFixed(3)), }, lineLoads: { columnLoad: Number(columnLoad.toFixed(2)), columnLoadEdge: Number(columnLoadEdge.toFixed(2)), beamLoad: Number(beamLoad.toFixed(2)), beamLoadEdge: Number(beamLoadEdge.toFixed(2)), beamMoment: Number(beamMoment.toFixed(2)), beamMomentEdge: Number(beamMomentEdge.toFixed(2)), }, reliefPercentage, statusText, }; } /** * Calcula a envoltória e os resultados para as 3 direções de vento (0°, 45° e 90°). */ export function calculateShelterWindAllAngles(input: ShelterInput, q: number): ShelterEnvelopeResult { const res0 = calculateShelterWind({ ...input, windAngle: 0 }, q); const res45 = calculateShelterWind({ ...input, windAngle: 45 }, q); const res90 = calculateShelterWind({ ...input, windAngle: 90 }, q); const angles: ShelterWindAngle[] = [0, 45, 90]; const results = [res0, res45, res90]; let critUpIdx = 0; let critFxIdx = 0; let critFyIdx = 0; let critColIdx = 0; for (let i = 1; i < 3; i++) { if (results[i].forces.fzUp > results[critUpIdx].forces.fzUp) critUpIdx = i; if (results[i].forces.fxBack > results[critFxIdx].forces.fxBack) critFxIdx = i; if (results[i].forces.fySide > results[critFyIdx].forces.fySide) critFyIdx = i; if (results[i].lineLoads.columnLoad > results[critColIdx].lineLoads.columnLoad) critColIdx = i; } return { res0, res45, res90, criticalUp: { angle: angles[critUpIdx], value: results[critUpIdx].forces.fzUp }, criticalFx: { angle: angles[critFxIdx], value: results[critFxIdx].forces.fxBack }, criticalFy: { angle: angles[critFyIdx], value: results[critFyIdx].forces.fySide }, criticalColumnLoad: { angle: angles[critColIdx], value: results[critColIdx].lineLoads.columnLoad }, }; }