Files
BrainWind/app/src/lib/nbr-tables/table-shelters.ts
T

374 lines
14 KiB
TypeScript

/**
* 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 },
};
}