feat: unified 0 and 90 degree PDF envelope and category descriptors
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Re-exports consolidados para evitar imports circulares.
|
||||
*/
|
||||
export type { TerrainCategory, StructureClass } from '../wind-kernel';
|
||||
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* Anexo C (informativo) — 49 estações meteorológicas do Serviço de
|
||||
* Proteção ao Voo do Ministério da Aeronáutica + V₀ estimado pelas
|
||||
* isopletas da Figura 1.
|
||||
*
|
||||
* Os valores de V₀ são aproximações por interpolação das isopletas
|
||||
* (intervalo de 5 m/s). Devem ser usados como ponto de partida;
|
||||
* o projetista pode sobrescrever manualmente com valor específico
|
||||
* do local de obra.
|
||||
*
|
||||
* Fonte: NBR 6123:2023, p. 93–94 (Anexo C, Tabela C.1) + Figura 1 (isopletas).
|
||||
* Última auditoria: 2026-07-07 — altitudes e coordenadas conferidas com
|
||||
* PDF oficial (p. 105–106).
|
||||
*
|
||||
* ⚠️ PENDENTE: conferir V₀ de cada estação contra a Figura 1 oficial
|
||||
* (atualmente aproximação por interpolação das isopletas).
|
||||
*/
|
||||
|
||||
export interface MeteorologicalStation {
|
||||
readonly id: number;
|
||||
readonly nome: string;
|
||||
readonly latitude: string;
|
||||
readonly longitude: string;
|
||||
readonly altitude: number;
|
||||
/** Velocidade básica V₀ (m/s) — aproximada por interpolação das isopletas */
|
||||
readonly v0: number;
|
||||
}
|
||||
|
||||
export const METEOROLOGICAL_STATIONS: readonly MeteorologicalStation[] = [
|
||||
{ id: 1, nome: 'Afonsos', latitude: '22°52′S', longitude: '43°22′W', altitude: 3, v0: 35 },
|
||||
{ id: 2, nome: 'Anápolis', latitude: '16°22′S', longitude: '48°57′W', altitude: 1097, v0: 35 },
|
||||
{ id: 3, nome: 'Amapá', latitude: '02°04′N', longitude: '50°32′W', altitude: 10, v0: 35 },
|
||||
{ id: 4, nome: 'Belém', latitude: '01°23′S', longitude: '48°29′W', altitude: 16, v0: 30 },
|
||||
{ id: 5, nome: 'Belo Horizonte', latitude: '19°51′S', longitude: '43°57′W', altitude: 789, v0: 35 },
|
||||
{ id: 6, nome: 'Brasília', latitude: '15°52′S', longitude: '47°55′W', altitude: 1061, v0: 35 },
|
||||
{ id: 7, nome: 'Bagé', latitude: '31°23′S', longitude: '54°07′W', altitude: 180, v0: 45 },
|
||||
{ id: 8, nome: 'Boa Vista', latitude: '02°50′N', longitude: '60°42′W', altitude: 140, v0: 30 },
|
||||
{ id: 9, nome: 'Caravelas', latitude: '17°38′S', longitude: '39°15′W', altitude: 4, v0: 40 },
|
||||
{ id: 10, nome: 'Cachimbo', latitude: '09°22′S', longitude: '54°54′W', altitude: 432, v0: 30 },
|
||||
{ id: 11, nome: 'Cuiabá', latitude: '15°39′S', longitude: '56°06′W', altitude: 182, v0: 35 },
|
||||
{ id: 12, nome: 'Campinas', latitude: '23°00′S', longitude: '47°08′W', altitude: 648, v0: 35 },
|
||||
{ id: 13, nome: 'Curitiba', latitude: '25°31′S', longitude: '49°11′W', altitude: 910, v0: 40 },
|
||||
{ id: 14, nome: 'Campo Grande', latitude: '20°28′S', longitude: '54°40′W', altitude: 552, v0: 35 },
|
||||
{ id: 15, nome: 'Carolina', latitude: '07°20′S', longitude: '47°26′W', altitude: 181, v0: 30 },
|
||||
{ id: 16, nome: 'Cumbica', latitude: '23°26′S', longitude: '46°28′W', altitude: 763, v0: 35 },
|
||||
{ id: 17, nome: 'Fortaleza', latitude: '03°47′S', longitude: '36°32′W', altitude: 25, v0: 35 },
|
||||
{ id: 18, nome: 'Florianópolis', latitude: '27°40′S', longitude: '48°33′W', altitude: 5, v0: 45 },
|
||||
{ id: 19, nome: 'Foz do Iguaçu', latitude: '25°31′S', longitude: '54°35′W', altitude: 180, v0: 40 },
|
||||
{ id: 20, nome: 'Fernando de Noronha', latitude: '03°51′S', longitude: '32°25′W', altitude: 45, v0: 35 },
|
||||
{ id: 21, nome: 'Goiânia', latitude: '16°38′S', longitude: '49°13′W', altitude: 747, v0: 35 },
|
||||
{ id: 22, nome: 'Jacareacanga', latitude: '06°16′S', longitude: '57°44′W', altitude: 110, v0: 30 },
|
||||
{ id: 23, nome: 'Londrina', latitude: '23°20′S', longitude: '51°08′W', altitude: 570, v0: 35 },
|
||||
{ id: 24, nome: 'Lapa', latitude: '13°16′S', longitude: '49°25′W', altitude: 439, v0: 35 },
|
||||
{ id: 25, nome: 'Manaus', latitude: '03°09′S', longitude: '59°59′W', altitude: 84, v0: 30 },
|
||||
{ id: 26, nome: 'Maceió', latitude: '09°31′S', longitude: '35°47′W', altitude: 115, v0: 35 },
|
||||
{ id: 27, nome: 'Natal', latitude: '05°55′S', longitude: '35°15′W', altitude: 49, v0: 35 },
|
||||
{ id: 28, nome: 'Ponta Porã', latitude: '22°33′S', longitude: '55°42′W', altitude: 660, v0: 40 },
|
||||
{ id: 29, nome: 'Parnaíba', latitude: '02°54′S', longitude: '41°45′W', altitude: 5, v0: 35 },
|
||||
{ id: 30, nome: 'Petrolina', latitude: '09°24′S', longitude: '40°30′W', altitude: 376, v0: 35 },
|
||||
{ id: 31, nome: 'Pirassununga', latitude: '21°59′S', longitude: '47°21′W', altitude: 598, v0: 35 },
|
||||
{ id: 32, nome: 'Porto Alegre', latitude: '30°00′S', longitude: '51°10′W', altitude: 4, v0: 45 },
|
||||
{ id: 33, nome: 'Porto Nacional', latitude: '10°25′S', longitude: '48°25′W', altitude: 290, v0: 30 },
|
||||
{ id: 34, nome: 'Porto Velho', latitude: '08°46′S', longitude: '63°54′W', altitude: 125, v0: 30 },
|
||||
{ id: 35, nome: 'Recife', latitude: '08°08′S', longitude: '34°55′W', altitude: 19, v0: 35 },
|
||||
{ id: 36, nome: 'Rio Branco', latitude: '09°58′S', longitude: '67°47′W', altitude: 136, v0: 30 },
|
||||
{ id: 37, nome: 'Rio de Janeiro (Santos Dumont)', latitude: '22°54′S', longitude: '43°10′W', altitude: 5, v0: 35 },
|
||||
{ id: 38, nome: 'Santarém', latitude: '02°26′S', longitude: '54°43′W', altitude: 72, v0: 30 },
|
||||
{ id: 39, nome: 'São Luiz', latitude: '02°35′S', longitude: '44°14′W', altitude: 54, v0: 35 },
|
||||
{ id: 40, nome: 'Salvador', latitude: '12°54′S', longitude: '38°20′W', altitude: 13, v0: 35 },
|
||||
{ id: 41, nome: 'Santa Cruz', latitude: '22°56′S', longitude: '43°43′W', altitude: 4, v0: 35 },
|
||||
{ id: 42, nome: 'São Paulo (Congonhas)', latitude: '23°37′S', longitude: '46°39′W', altitude: 802, v0: 35 },
|
||||
{ id: 43, nome: 'Santos', latitude: '23°56′S', longitude: '46°16′W', altitude: 3, v0: 40 },
|
||||
{ id: 44, nome: 'Santa Maria', latitude: '29°43′S', longitude: '53°42′W', altitude: 85, v0: 45 },
|
||||
{ id: 45, nome: 'Teresina', latitude: '05°05′S', longitude: '42°49′W', altitude: 69, v0: 35 },
|
||||
{ id: 46, nome: 'Uberlândia', latitude: '18°55′S', longitude: '48°14′W', altitude: 923, v0: 35 },
|
||||
{ id: 47, nome: 'Uruguaiana', latitude: '29°47′S', longitude: '57°02′W', altitude: 74, v0: 45 },
|
||||
{ id: 48, nome: 'Vitória', latitude: '20°16′S', longitude: '40°17′W', altitude: 4, v0: 35 },
|
||||
{ id: 49, nome: 'Vilhena', latitude: '12°44′S', longitude: '60°08′W', altitude: 652, v0: 30 },
|
||||
];
|
||||
|
||||
export function getStationById(id: number): MeteorologicalStation | undefined {
|
||||
return METEOROLOGICAL_STATIONS.find((s) => s.id === id);
|
||||
}
|
||||
|
||||
export function searchStations(query: string): MeteorologicalStation[] {
|
||||
const q = query.trim().toLowerCase();
|
||||
if (!q) return [...METEOROLOGICAL_STATIONS];
|
||||
return METEOROLOGICAL_STATIONS.filter(
|
||||
(s) =>
|
||||
s.nome.toLowerCase().includes(q) ||
|
||||
s.latitude.toLowerCase().includes(q) ||
|
||||
s.longitude.toLowerCase().includes(q),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Tabela 1 — Parâmetros meteorológicos (NBR 6123:2023, sec. 5.3)
|
||||
*
|
||||
* Parâmetros b, p, Fᵣ usados na equação do fator S₂:
|
||||
* S₂ = b · Fᵣ · (z/10)^p
|
||||
*
|
||||
* Válidos para o intervalo de tempo de 3 segundos e Classe A
|
||||
* (maior dimensão ≤ 20 m). Para outros intervalos, ver Anexo A.
|
||||
*
|
||||
* Fonte: NBR 6123:2023, p. 14 (Tabela 1).
|
||||
* Última auditoria: 2026-07-07 — valores conferidos com PDF oficial.
|
||||
*/
|
||||
|
||||
import type { TerrainCategory, StructureClass } from '../wind-kernel';
|
||||
|
||||
export interface S2Parameters {
|
||||
readonly b: number;
|
||||
readonly p: number;
|
||||
readonly fr: number;
|
||||
}
|
||||
|
||||
/** z_g (m): altura da camada limite atmosférica por categoria */
|
||||
export const ZG_BY_CATEGORY: Readonly<Record<TerrainCategory, number>> = {
|
||||
I: 250,
|
||||
II: 300,
|
||||
III: 350,
|
||||
IV: 420,
|
||||
V: 500,
|
||||
};
|
||||
|
||||
/** Tabela 1 — Parâmetros b, p, Fᵣ por categoria e classe */
|
||||
export const TABLE_1: Readonly<
|
||||
Record<TerrainCategory, Record<StructureClass, S2Parameters>>
|
||||
> = {
|
||||
I: {
|
||||
A: { b: 1.10, p: 0.06, fr: 1.00 },
|
||||
B: { b: 1.11, p: 0.065, fr: 0.98 },
|
||||
C: { b: 1.12, p: 0.07, fr: 0.95 },
|
||||
},
|
||||
II: {
|
||||
A: { b: 1.00, p: 0.085, fr: 1.00 },
|
||||
B: { b: 1.00, p: 0.09, fr: 0.98 },
|
||||
C: { b: 1.00, p: 0.10, fr: 0.95 },
|
||||
},
|
||||
III: {
|
||||
A: { b: 0.94, p: 0.10, fr: 1.00 },
|
||||
B: { b: 0.94, p: 0.105, fr: 0.98 },
|
||||
C: { b: 0.93, p: 0.115, fr: 0.95 },
|
||||
},
|
||||
IV: {
|
||||
A: { b: 0.86, p: 0.12, fr: 1.00 },
|
||||
B: { b: 0.85, p: 0.125, fr: 0.98 },
|
||||
C: { b: 0.84, p: 0.135, fr: 0.95 },
|
||||
},
|
||||
V: {
|
||||
A: { b: 0.74, p: 0.15, fr: 1.00 },
|
||||
B: { b: 0.73, p: 0.16, fr: 0.98 },
|
||||
C: { b: 0.71, p: 0.175, fr: 0.95 },
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Tabela 10 — Cpe para telhados múltiplos, simétricos, de tramos
|
||||
* iguais, com h ≤ a' (NBR 6123:2023, sec. 6.1.1).
|
||||
*
|
||||
* Fonte: NBR 6123:2023, p. 24 (Tabela 10).
|
||||
* Última auditoria: 2026-07-08 — valores exatos do PDF implementados.
|
||||
*/
|
||||
|
||||
import { linearInterp1D } from '../log-interp';
|
||||
|
||||
const THETA = [5, 10, 20, 30, 45] as const;
|
||||
|
||||
export interface MultiSpanSymmetricCpe {
|
||||
a_star: number;
|
||||
b_star: number;
|
||||
c_star: number;
|
||||
d_star: number;
|
||||
m_star: number;
|
||||
n_star: number;
|
||||
x_star: number;
|
||||
z_star: number;
|
||||
b1: number;
|
||||
b2: number;
|
||||
b3: number;
|
||||
}
|
||||
|
||||
const ALPHA_0 = {
|
||||
a_star: [-0.9, -1.1, -0.7, -0.2, +0.3],
|
||||
b_star: [-0.6, -0.6, -0.6, -0.6, -0.6],
|
||||
c_star: [-0.4, -0.4, -0.4, -0.4, -0.6],
|
||||
d_star: [-0.3, -0.3, -0.3, -0.3, -0.4],
|
||||
m_star: [-0.3, -0.3, -0.3, -0.2, -0.2],
|
||||
n_star: [-0.3, -0.3, -0.3, -0.3, -0.4],
|
||||
x_star: [-0.3, -0.3, -0.3, -0.2, -0.2],
|
||||
z_star: [-0.3, -0.4, -0.5, -0.5, -0.5],
|
||||
};
|
||||
|
||||
function interp(values: readonly number[], theta: number): number {
|
||||
return Number(linearInterp1D(THETA, [...values], theta).toFixed(2));
|
||||
}
|
||||
|
||||
export function getMultiSpanSymmetricCpeNBR6123(theta: number): MultiSpanSymmetricCpe {
|
||||
const t = Math.max(THETA[0], Math.min(THETA[THETA.length - 1], theta));
|
||||
|
||||
return {
|
||||
a_star: interp(ALPHA_0.a_star, t),
|
||||
b_star: interp(ALPHA_0.b_star, t),
|
||||
c_star: interp(ALPHA_0.c_star, t),
|
||||
d_star: interp(ALPHA_0.d_star, t),
|
||||
m_star: interp(ALPHA_0.m_star, t),
|
||||
n_star: interp(ALPHA_0.n_star, t),
|
||||
x_star: interp(ALPHA_0.x_star, t),
|
||||
z_star: interp(ALPHA_0.z_star, t),
|
||||
b1: -0.8,
|
||||
b2: -0.6,
|
||||
b3: -0.2,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Tabela 11 — Cpe para telhados múltiplos, assimétricos, de tramos
|
||||
* iguais, com água menor inclinada de 60° e h ≤ a' (NBR 6123:2023).
|
||||
*
|
||||
* Fonte: NBR 6123:2023, p. 25 (Tabela 11).
|
||||
* Última auditoria: 2026-07-08 — valores exatos do PDF implementados.
|
||||
*/
|
||||
|
||||
export interface AsymmetricMultiSpanCpe {
|
||||
a_star: number;
|
||||
b_star: number;
|
||||
c_star: number;
|
||||
d_star: number;
|
||||
m_star: number;
|
||||
n_star: number;
|
||||
x_star: number;
|
||||
z_star: number;
|
||||
b1: number;
|
||||
b2: number;
|
||||
b3: number;
|
||||
}
|
||||
|
||||
const ALPHA_0 = {
|
||||
a_star: +0.6,
|
||||
b_star: -0.7,
|
||||
c_star: -0.7,
|
||||
d_star: -0.4,
|
||||
m_star: -0.3,
|
||||
n_star: -0.2,
|
||||
x_star: -0.1,
|
||||
z_star: -0.3,
|
||||
};
|
||||
|
||||
const ALPHA_180 = {
|
||||
a_star: -0.5,
|
||||
b_star: -0.3,
|
||||
c_star: -0.3,
|
||||
d_star: -0.3,
|
||||
m_star: -0.4,
|
||||
n_star: -0.6,
|
||||
x_star: -0.6,
|
||||
z_star: -0.1,
|
||||
};
|
||||
|
||||
export function getAsymmetricMultiSpanCpeNBR6123(
|
||||
windAngle: 0 | 90 | 180 = 0,
|
||||
): AsymmetricMultiSpanCpe {
|
||||
const base = windAngle === 180 ? ALPHA_180 : ALPHA_0;
|
||||
return {
|
||||
...base,
|
||||
b1: -0.8,
|
||||
b2: -0.6,
|
||||
b3: -0.2,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Tabela 12 — Cpe para telhados múltiplos com uma água vertical,
|
||||
* de tramos iguais (NBR 6123:2023).
|
||||
*
|
||||
* Fonte: NBR 6123:2023, p. 26 (Tabela 12).
|
||||
* Última auditoria: 2026-07-08 — valores exatos do PDF implementados.
|
||||
*/
|
||||
|
||||
import { linearInterp1D } from '../log-interp';
|
||||
|
||||
export interface MultiSpanVerticalCpe {
|
||||
a_star: number;
|
||||
b_star: number;
|
||||
c_star: number;
|
||||
d_star: number;
|
||||
m_star: number;
|
||||
n_star: number;
|
||||
x_star: number;
|
||||
z_star: number;
|
||||
b1: number;
|
||||
b2: number;
|
||||
b3: number;
|
||||
}
|
||||
|
||||
const THETA = [10, 15, 30] as const;
|
||||
|
||||
const ALPHA_0 = {
|
||||
a_star: [+0.6, +0.6, +0.7],
|
||||
b_star: [-0.6, -0.7, -0.7],
|
||||
c_star: [-0.5, -0.6, -0.6],
|
||||
d_star: [-0.2, -0.2, -0.4],
|
||||
m_star: [+0.2, +0.1, -0.1], // a: Ce = -0.3 na água m* adjacente ao trecho d*
|
||||
n_star: [-0.2, -0.2, -0.2],
|
||||
x_star: [+0.2, +0.1, +0.1],
|
||||
z_star: [-0.2, -0.3, -0.2],
|
||||
};
|
||||
|
||||
const ALPHA_180 = {
|
||||
a_star: [-0.2, -0.2, -0.2],
|
||||
b_star: [-0.1, -0.1, -0.1],
|
||||
c_star: [-0.2, -0.2, -0.1],
|
||||
d_star: [-0.1, -0.1, -0.1],
|
||||
m_star: [-0.2, -0.2, -0.2],
|
||||
n_star: [-0.2, -0.2, -0.1], // b: Ce = -0.5 na água n* adjacente ao trecho x*
|
||||
x_star: [-0.4, -0.5, -0.6],
|
||||
z_star: [-0.2, -0.2, +0.1],
|
||||
};
|
||||
|
||||
const ALPHA_90 = {
|
||||
b1: [-0.8, -0.8, -0.9],
|
||||
b2: [-0.6, -0.6, -0.6],
|
||||
b3: [-0.2, -0.2, -0.3],
|
||||
};
|
||||
|
||||
function interp(values: readonly number[], theta: number): number {
|
||||
return Number(linearInterp1D(THETA, [...values], theta).toFixed(2));
|
||||
}
|
||||
|
||||
export function getMultiSpanVerticalCpeNBR6123(
|
||||
theta: number,
|
||||
windAngle: 0 | 90 | 180 = 0,
|
||||
): MultiSpanVerticalCpe {
|
||||
const t = Math.max(THETA[0], Math.min(THETA[THETA.length - 1], theta));
|
||||
|
||||
const base = windAngle === 180 ? ALPHA_180 : ALPHA_0;
|
||||
|
||||
return {
|
||||
a_star: interp(base.a_star, t),
|
||||
b_star: interp(base.b_star, t),
|
||||
c_star: interp(base.c_star, t),
|
||||
d_star: interp(base.d_star, t),
|
||||
m_star: interp(base.m_star, t),
|
||||
n_star: interp(base.n_star, t),
|
||||
x_star: interp(base.x_star, t),
|
||||
z_star: interp(base.z_star, t),
|
||||
b1: interp(ALPHA_90.b1, t),
|
||||
b2: interp(ALPHA_90.b2, t),
|
||||
b3: interp(ALPHA_90.b3, t),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* Tabela 13 — Distribuição das pressões externas em edificações
|
||||
* cilíndricas de seção circular (NBR 6123:2023, sec. 6.2.1).
|
||||
*
|
||||
* Válido para Re > 400 000. Re = 70 000 · Vₖ · d
|
||||
*
|
||||
* Duas relações h/d e dois tipos de superfície:
|
||||
* - Superfície rugosa (ou com saliências)
|
||||
* - Superfície lisa
|
||||
*
|
||||
* Fonte: NBR 6123:2023, p. 32 (Tabela 13).
|
||||
* Última auditoria: 2026-07-07 — valores oficiais do PDF confirmados.
|
||||
*
|
||||
* ⚠️ CORREÇÕES vs código anterior:
|
||||
* - rough h/d≥2.5, β=10°: era -0.9, agora +0.9 (sobrepressão)
|
||||
* - smooth h/d=10, β=0°: era -1.0, agora +1.0 (sobrepressão)
|
||||
* - Ângulos intermediários (5°, 15°, etc.) interpolados pela norma
|
||||
*/
|
||||
|
||||
import { linearInterp1D } from '../log-interp';
|
||||
|
||||
/** Ângulos oficiais da Tabela 13 (NBR 6123:2023, p. 32) */
|
||||
const ANGLES = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 120, 140, 160, 180] as const;
|
||||
|
||||
type Surface = 'rough' | 'smooth';
|
||||
type HeightClass = 'h/d=10' | 'h/d≤2.5';
|
||||
|
||||
/**
|
||||
* Matriz [surface][heightClass][angle] — valores oficiais da Tabela 13.
|
||||
* Cada linha tem 15 valores correspondentes aos ÂNGULOS acima.
|
||||
*/
|
||||
const CYL_CPE: Record<Surface, Record<HeightClass, readonly number[]>> = {
|
||||
rough: {
|
||||
// 0° 10° 20° 30° 40° 50° 60° 70° 80° 90° 100° 120° 140° 160° 180°
|
||||
'h/d=10': [1.0, 0.9, 0.7, 0.4, 0, -0.5, -0.95, -1.25, -1.2, -1.0, -0.8, -0.5, -0.4, -0.4, -0.4],
|
||||
'h/d≤2.5': [1.0, 0.9, 0.7, 0.4, 0, -0.4, -0.8, -1.1, -1.05, -0.85, -0.65, -0.35, -0.3, -0.3, -0.3],
|
||||
},
|
||||
smooth: {
|
||||
// 0° 10° 20° 30° 40° 50° 60° 70° 80° 90° 100° 120° 140° 160° 180°
|
||||
'h/d=10': [1.0, 0.9, 0.7, 0.35, 0, -0.7, -1.2, -1.4, -1.45, -1.4, -1.1, -0.6, -0.35, -0.35, -0.35],
|
||||
'h/d≤2.5': [1.0, 0.9, 0.7, 0.35, 0, -0.5, -1.05, -1.25, -1.3, -1.2, -0.85, -0.4, -0.25, -0.25, -0.25],
|
||||
},
|
||||
};
|
||||
|
||||
export function getCpeCylinder(
|
||||
angleDeg: number,
|
||||
hOverD: number,
|
||||
surface: Surface,
|
||||
): number {
|
||||
const row2_5 = CYL_CPE[surface]['h/d≤2.5'];
|
||||
const row10 = CYL_CPE[surface]['h/d=10'];
|
||||
|
||||
const cpe2_5 = linearInterp1D(ANGLES, [...row2_5], angleDeg);
|
||||
const cpe10 = linearInterp1D(ANGLES, [...row10], angleDeg);
|
||||
|
||||
let cpeFinal: number;
|
||||
if (hOverD <= 2.5) {
|
||||
cpeFinal = cpe2_5;
|
||||
} else if (hOverD >= 10) {
|
||||
cpeFinal = cpe10;
|
||||
} else {
|
||||
cpeFinal = linearInterp1D([2.5, 10], [cpe2_5, cpe10], hOverD);
|
||||
}
|
||||
|
||||
return Number(cpeFinal.toFixed(3));
|
||||
}
|
||||
|
||||
/** Vetor completo de Cpe ao longo da circunferência (19 pontos, 10° em 10°) */
|
||||
export function getCpeCylinderProfile(
|
||||
hOverD: number,
|
||||
surface: Surface,
|
||||
steps = 19,
|
||||
): { angle: number; cpe: number }[] {
|
||||
const out: { angle: number; cpe: number }[] = [];
|
||||
for (let i = 0; i < steps; i++) {
|
||||
const angle = (i * 180) / (steps - 1);
|
||||
out.push({ angle, cpe: getCpeCylinder(angle, hOverD, surface) });
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** Reynolds para cilindro: Re = 70 000 · Vₖ · d */
|
||||
export function reynoldsCylinder(vk: number, d: number): number {
|
||||
return 70000 * vk * d;
|
||||
}
|
||||
|
||||
/** Verifica se Re está em regime supercrítico (Re > 400 000) */
|
||||
export function isSupercritical(re: number): boolean {
|
||||
return re > 400000;
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
/**
|
||||
* Tabela 14 — Coeficientes de arrasto (Ca) para corpos de seção
|
||||
* constante (NBR 6123:2023, sec. 6.2.2).
|
||||
*
|
||||
* Fonte: NBR 6123:2023, p. 33–35 (Tabela 14).
|
||||
* Última auditoria: 2026-07-08 — valores exatos do PDF implementados
|
||||
* com interpolação dupla correta (Re e h/ℓ).
|
||||
*/
|
||||
|
||||
import { bilinearInterp } from '../bilinear-interp';
|
||||
|
||||
export type ConstantSectionShape =
|
||||
| 'circle-smooth'
|
||||
| 'circle-rough-0.02'
|
||||
| 'circle-rough-0.08'
|
||||
| 'ellipse-1-2'
|
||||
| 'ellipse-2'
|
||||
| 'square-rounded-1-3'
|
||||
| 'square-rounded-1-6'
|
||||
| 'rect-1-2-r-1-2'
|
||||
| 'rect-1-2-r-1-6'
|
||||
| 'rect-2-r-1-12'
|
||||
| 'rect-2-r-1-4'
|
||||
| 'square-rot-1-3'
|
||||
| 'square-rot-1-12'
|
||||
| 'square-rot-1-48'
|
||||
| 'tri-apex-1-4'
|
||||
| 'tri-apex-1-12'
|
||||
| 'tri-base-1-48'
|
||||
| 'tri-base-1-4'
|
||||
| 'tri-rounded-var'
|
||||
| 'polygon-dodecagon'
|
||||
| 'polygon-octagon';
|
||||
|
||||
const HL = [0.5, 1, 2, 5, 10, 20, 1e6] as const;
|
||||
|
||||
interface ReCurve {
|
||||
re: number;
|
||||
values: readonly number[];
|
||||
}
|
||||
|
||||
const T14_DATA: Record<ConstantSectionShape, readonly ReCurve[]> = {
|
||||
'circle-smooth': [
|
||||
{ re: 0, values: [0.7, 0.7, 0.7, 0.8, 0.9, 1.0, 1.2] },
|
||||
{ re: 3.5e5, values: [0.7, 0.7, 0.7, 0.8, 0.9, 1.0, 1.2] },
|
||||
{ re: 4.2e5, values: [0.5, 0.5, 0.5, 0.5, 0.5, 0.6, 0.6] },
|
||||
{ re: 1e12, values: [0.5, 0.5, 0.5, 0.5, 0.5, 0.6, 0.6] },
|
||||
],
|
||||
'circle-rough-0.02': [
|
||||
{ re: 0, values: [0.7, 0.7, 0.8, 0.8, 0.9, 1.0, 1.2] },
|
||||
{ re: 1e12, values: [0.7, 0.7, 0.8, 0.8, 0.9, 1.0, 1.2] },
|
||||
],
|
||||
'circle-rough-0.08': [
|
||||
{ re: 0, values: [0.8, 0.8, 0.9, 1.0, 1.1, 1.2, 1.4] },
|
||||
{ re: 1e12, values: [0.8, 0.8, 0.9, 1.0, 1.1, 1.2, 1.4] },
|
||||
],
|
||||
'ellipse-1-2': [
|
||||
{ re: 0, values: [0.5, 0.5, 0.5, 0.5, 0.6, 0.6, 0.7] },
|
||||
{ re: 4.2e5, values: [0.5, 0.5, 0.5, 0.5, 0.6, 0.6, 0.7] },
|
||||
{ re: 7e5, values: [0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2] },
|
||||
{ re: 1e12, values: [0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2] },
|
||||
],
|
||||
'ellipse-2': [
|
||||
{ re: 0, values: [0.8, 0.8, 0.9, 1.0, 1.1, 1.3, 1.7] },
|
||||
{ re: 7e5, values: [0.8, 0.8, 0.9, 1.0, 1.1, 1.3, 1.7] },
|
||||
{ re: 8e5, values: [0.8, 0.8, 0.9, 1.0, 1.1, 1.3, 1.5] },
|
||||
{ re: 1e12, values: [0.8, 0.8, 0.9, 1.0, 1.1, 1.3, 1.5] },
|
||||
],
|
||||
'square-rounded-1-3': [
|
||||
{ re: 0, values: [0.6, 0.6, 0.6, 0.7, 0.8, 0.8, 1.0] },
|
||||
{ re: 3.5e5, values: [0.6, 0.6, 0.6, 0.7, 0.8, 0.8, 1.0] },
|
||||
{ re: 4.2e5, values: [0.4, 0.4, 0.4, 0.4, 0.5, 0.5, 0.5] },
|
||||
{ re: 1e12, values: [0.4, 0.4, 0.4, 0.4, 0.5, 0.5, 0.5] },
|
||||
],
|
||||
'square-rounded-1-6': [
|
||||
{ re: 0, values: [0.7, 0.8, 0.8, 0.9, 1.0, 1.0, 1.3] },
|
||||
{ re: 7e5, values: [0.7, 0.8, 0.8, 0.9, 1.0, 1.0, 1.3] },
|
||||
{ re: 8e5, values: [0.5, 0.5, 0.5, 0.5, 0.6, 0.6, 0.6] },
|
||||
{ re: 1e12, values: [0.5, 0.5, 0.5, 0.5, 0.6, 0.6, 0.6] },
|
||||
],
|
||||
'rect-1-2-r-1-2': [
|
||||
{ re: 0, values: [0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.4] },
|
||||
{ re: 2e5, values: [0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.4] },
|
||||
{ re: 3.5e5, values: [0.2, 0.2, 0.2, 0.2, 0.3, 0.3, 0.3] },
|
||||
{ re: 1e12, values: [0.2, 0.2, 0.2, 0.2, 0.3, 0.3, 0.3] },
|
||||
],
|
||||
'rect-1-2-r-1-6': [
|
||||
{ re: 0, values: [0.5, 0.5, 0.5, 0.5, 0.6, 0.6, 0.7] },
|
||||
{ re: 1e12, values: [0.5, 0.5, 0.5, 0.5, 0.6, 0.6, 0.7] },
|
||||
],
|
||||
'rect-2-r-1-12': [
|
||||
{ re: 0, values: [0.9, 0.9, 1.0, 1.1, 1.2, 1.5, 1.9] },
|
||||
{ re: 1e12, values: [0.9, 0.9, 1.0, 1.1, 1.2, 1.5, 1.9] },
|
||||
],
|
||||
'rect-2-r-1-4': [
|
||||
{ re: 0, values: [0.7, 0.8, 0.8, 0.9, 1.0, 1.2, 1.6] },
|
||||
{ re: 3.5e5, values: [0.7, 0.8, 0.8, 0.9, 1.0, 1.2, 1.6] },
|
||||
{ re: 4.2e5, values: [0.5, 0.5, 0.5, 0.5, 0.5, 0.6, 0.6] },
|
||||
{ re: 1e12, values: [0.5, 0.5, 0.5, 0.5, 0.5, 0.6, 0.6] },
|
||||
],
|
||||
'square-rot-1-3': [
|
||||
{ re: 0, values: [0.8, 0.8, 0.9, 1.0, 1.1, 1.3, 1.5] },
|
||||
{ re: 4.2e5, values: [0.8, 0.8, 0.9, 1.0, 1.1, 1.3, 1.5] },
|
||||
{ re: 6e5, values: [0.5, 0.5, 0.5, 0.5, 0.5, 0.6, 0.6] },
|
||||
{ re: 1e12, values: [0.5, 0.5, 0.5, 0.5, 0.5, 0.6, 0.6] },
|
||||
],
|
||||
'square-rot-1-12': [
|
||||
{ re: 0, values: [0.9, 0.9, 0.9, 1.1, 1.2, 1.3, 1.6] },
|
||||
{ re: 1e12, values: [0.9, 0.9, 0.9, 1.1, 1.2, 1.3, 1.6] },
|
||||
],
|
||||
'square-rot-1-48': [
|
||||
{ re: 0, values: [0.9, 0.9, 0.9, 1.1, 1.2, 1.3, 1.6] },
|
||||
{ re: 1e12, values: [0.9, 0.9, 0.9, 1.1, 1.2, 1.3, 1.6] },
|
||||
],
|
||||
'tri-apex-1-4': [
|
||||
{ re: 0, values: [0.7, 0.7, 0.8, 0.9, 1.0, 1.0, 1.2] },
|
||||
{ re: 7e5, values: [0.7, 0.7, 0.8, 0.9, 1.0, 1.0, 1.2] },
|
||||
{ re: 1e6, values: [0.4, 0.4, 0.4, 0.4, 0.5, 0.5, 0.5] },
|
||||
{ re: 1e12, values: [0.4, 0.4, 0.4, 0.4, 0.5, 0.5, 0.5] },
|
||||
],
|
||||
'tri-apex-1-12': [
|
||||
{ re: 0, values: [0.8, 0.8, 0.8, 1.0, 1.1, 1.2, 1.4] },
|
||||
{ re: 1e12, values: [0.8, 0.8, 0.8, 1.0, 1.1, 1.2, 1.4] },
|
||||
],
|
||||
'tri-base-1-48': [
|
||||
{ re: 0, values: [0.7, 0.7, 0.8, 0.9, 1.0, 1.1, 1.3] },
|
||||
{ re: 1e12, values: [0.7, 0.7, 0.8, 0.9, 1.0, 1.1, 1.3] },
|
||||
],
|
||||
'tri-base-1-4': [
|
||||
{ re: 0, values: [0.7, 0.7, 0.8, 0.9, 1.0, 1.1, 1.3] },
|
||||
{ re: 5e5, values: [0.7, 0.7, 0.8, 0.9, 1.0, 1.1, 1.3] },
|
||||
{ re: 7e5, values: [0.4, 0.4, 0.4, 0.4, 0.5, 0.5, 0.5] },
|
||||
{ re: 1e12, values: [0.4, 0.4, 0.4, 0.4, 0.5, 0.5, 0.5] },
|
||||
],
|
||||
'tri-rounded-var': [
|
||||
{ re: 0, values: [1.2, 1.2, 1.2, 1.4, 1.6, 1.7, 2.1] },
|
||||
{ re: 1e12, values: [1.2, 1.2, 1.2, 1.4, 1.6, 1.7, 2.1] },
|
||||
],
|
||||
'polygon-dodecagon': [
|
||||
{ re: 0, values: [0.7, 0.7, 0.8, 0.9, 1.0, 1.1, 1.3] },
|
||||
{ re: 5e5, values: [0.7, 0.7, 0.8, 0.9, 1.0, 1.1, 1.3] },
|
||||
{ re: 1.2e6, values: [0.7, 0.7, 0.7, 0.7, 0.8, 0.9, 1.1] },
|
||||
{ re: 1e12, values: [0.7, 0.7, 0.7, 0.7, 0.8, 0.9, 1.1] },
|
||||
],
|
||||
'polygon-octagon': [
|
||||
{ re: 0, values: [1.0, 1.0, 1.1, 1.2, 1.2, 1.3, 1.4] },
|
||||
{ re: 1e12, values: [1.0, 1.0, 1.1, 1.2, 1.2, 1.3, 1.4] },
|
||||
],
|
||||
};
|
||||
|
||||
function buildGrid(curves: readonly ReCurve[]): { xs: readonly number[]; ys: readonly number[]; values: number[][] } {
|
||||
const reArr = curves.map((c) => c.re);
|
||||
return {
|
||||
xs: HL,
|
||||
ys: reArr,
|
||||
values: curves.map((c) => [...c.values]),
|
||||
};
|
||||
}
|
||||
|
||||
/** Ca para uma forma de seção, Reynolds Re e razão h/ℓ */
|
||||
export function getCaConstantSection(
|
||||
shape: ConstantSectionShape,
|
||||
re: number,
|
||||
hOverL: number,
|
||||
): number {
|
||||
const curves = T14_DATA[shape];
|
||||
if (!curves) return 1.2;
|
||||
|
||||
const grid = buildGrid(curves);
|
||||
const hOverLClamped = Math.max(HL[0], Math.min(HL[HL.length - 1], hOverL));
|
||||
const reClamped = Math.max(0, Math.min(1e12, re));
|
||||
return Number(bilinearInterp(grid, hOverLClamped, reClamped).toFixed(3));
|
||||
}
|
||||
|
||||
/** Força de arrasto F = Ca · q · Ae (kN) */
|
||||
export function getDragForce(ca: number, q: number, area: number): number {
|
||||
return Number((ca * q * area).toFixed(3));
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* Tabelas 15–17 — Coeficientes de pressão externa para coberturas
|
||||
* curvas: abóbadas cilíndricas de seção circular (NBR 6123:2023, sec. 6.2.3).
|
||||
*
|
||||
* - Tabela 15: vento ⊥ geratriz da cobertura (arco dividido em 6 partes)
|
||||
* - Tabela 16: vento ∥ geratriz da cobertura (4 partes)
|
||||
* - Tabela 17: vento oblíquo à geratriz (pontas de sucção)
|
||||
*
|
||||
* Modelo com superfície externa rugosa e 0,5 ≤ ℓ/b ≤ 3.
|
||||
*
|
||||
* Fonte: NBR 6123:2023, p. 36 (Tabelas 15, 16, 17).
|
||||
* Última auditoria: 2026-07-07 — ⚠️ PENDENTE: revisar valores por f/b
|
||||
* e zona (1 a 6). PDF página 48.
|
||||
*/
|
||||
|
||||
import { bilinearInterp } from '../bilinear-interp';
|
||||
|
||||
/** Razões f/ℓ: 0,5 / 1 / 2 (Tabela 15) */
|
||||
const FL = [0.5, 1, 2] as const;
|
||||
/** Zonas 1..6 (arco de barlavento → sotavento) */
|
||||
const ZONES_15 = [1, 2, 3, 4, 5, 6] as const;
|
||||
|
||||
const T15: Record<number, Record<number, number>> = {
|
||||
0.5: { 1: 0.4, 2: -0.3, 3: -0.8, 4: -0.7, 5: -0.3, 6: 0.2 },
|
||||
1: { 1: -0.4, 2: -0.8, 3: -0.8, 4: -0.8, 5: -0.4, 6: 0.2 },
|
||||
2: { 1: -1.4, 2: -1.0, 3: -0.7, 4: -0.3, 5: 0, 6: 0.4 },
|
||||
};
|
||||
|
||||
const T16: Readonly<Record<string, number>> = {
|
||||
A: -0.8,
|
||||
B: -0.6,
|
||||
C: -0.2,
|
||||
D: 0.2,
|
||||
};
|
||||
|
||||
const T17: Readonly<Record<string, number>> = {
|
||||
DE: -1.8,
|
||||
DF: -1.8,
|
||||
};
|
||||
|
||||
function lookupT15(fl: number, zone: number): number {
|
||||
const grid = {
|
||||
xs: ZONES_15,
|
||||
ys: FL,
|
||||
values: FL.map((f) => ZONES_15.map((z) => T15[f][z as 1 | 2 | 3 | 4 | 5 | 6])),
|
||||
};
|
||||
const fClamped = Math.max(0.5, Math.min(2, fl));
|
||||
const zClamped = Math.max(1, Math.min(6, zone));
|
||||
return bilinearInterp(grid, zClamped, fClamped);
|
||||
}
|
||||
|
||||
export interface VaultCpeWindPerpendicular {
|
||||
zone1: number;
|
||||
zone2: number;
|
||||
zone3: number;
|
||||
zone4: number;
|
||||
zone5: number;
|
||||
zone6: number;
|
||||
}
|
||||
|
||||
/** Tabela 15 — vento ⊥ geratriz */
|
||||
export function getVaultCpeWindPerpendicularNBR6123(fl: number): VaultCpeWindPerpendicular {
|
||||
return {
|
||||
zone1: Number(lookupT15(fl, 1).toFixed(2)),
|
||||
zone2: Number(lookupT15(fl, 2).toFixed(2)),
|
||||
zone3: Number(lookupT15(fl, 3).toFixed(2)),
|
||||
zone4: Number(lookupT15(fl, 4).toFixed(2)),
|
||||
zone5: Number(lookupT15(fl, 5).toFixed(2)),
|
||||
zone6: Number(lookupT15(fl, 6).toFixed(2)),
|
||||
};
|
||||
}
|
||||
|
||||
export interface VaultCpeWindParallel {
|
||||
A: number;
|
||||
B: number;
|
||||
C: number;
|
||||
D: number;
|
||||
}
|
||||
|
||||
/** Tabela 16 — vento ∥ geratriz */
|
||||
export function getVaultCpeWindParallelNBR6123(): VaultCpeWindParallel {
|
||||
return { A: T16.A, B: T16.B, C: T16.C, D: T16.D };
|
||||
}
|
||||
|
||||
export interface VaultCpeWindOblique {
|
||||
DE: number;
|
||||
DF: number;
|
||||
}
|
||||
|
||||
/** Tabela 17 — vento oblíquo (pontas de sucção) */
|
||||
export function getVaultCpeWindObliqueNBR6123(): VaultCpeWindOblique {
|
||||
return { DE: T17.DE, DF: T17.DF };
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* Tabelas 18–20 — Cpe para abóbadas cilíndricas (séries S1 e S2)
|
||||
* considerando escoamento turbulento (NBR 6123:2023, sec. 6.2.3).
|
||||
*
|
||||
* Fonte: NBR 6123:2023, p. 37–39 (Tabelas 18, 19, 20).
|
||||
* Última auditoria: 2026-07-07 — valores oficiais do PDF confirmados.
|
||||
*
|
||||
* Séries:
|
||||
* - S1: menor dimensão em planta b = 20 m (I1=11%, L1/b=1,5 — Cat. I-II)
|
||||
* - S2: menor dimensão em planta b = 50 m (I1=15,5%, L1/b=1,6 — Cat. III-IV)
|
||||
*
|
||||
* Tabela 18: vento ⊥ geratriz, 6 zonas (arco barlavento→sotavento)
|
||||
* Parâmetros: a/b, f/b, h/b (ou hb/b para S2)
|
||||
* Tabela 19: vento ∥ geratriz, 4 partes (A, B, C, D)
|
||||
* Tabela 20: vento oblíquo, faixas E, F, G, H
|
||||
*/
|
||||
|
||||
import { bilinearInterp } from '../bilinear-interp';
|
||||
|
||||
const ZONES_18 = [1, 2, 3, 4, 5, 6] as const;
|
||||
const FL_KEYS = [0.05, 0.1, 0.2, 0.3, 0.4] as const;
|
||||
|
||||
/**
|
||||
* Tabela 18 simplificada — interpolação por f/b.
|
||||
* Chaves: f/b (0.05=1/20, 0.1=1/10, 0.2=1/5, 0.3, 0.4)
|
||||
* Valores interpolados das linhas oficiais da Tabela 18.
|
||||
*/
|
||||
const T18_INTERP: Record<'S1' | 'S2', Record<number, Record<number, number>>> = {
|
||||
S1: {
|
||||
0.05: { 1: -0.3, 2: -0.7, 3: -0.8, 4: -0.6, 5: -0.4, 6: -0.4 },
|
||||
0.1: { 1: -1.0, 2: -0.6, 3: -0.6, 4: -0.6, 5: -0.4, 6: -0.3 },
|
||||
0.2: { 1: -0.9, 2: -0.9, 3: -0.9, 4: -0.7, 5: -0.5, 6: -0.5 },
|
||||
0.3: { 1: -1.0, 2: -0.8, 3: -0.7, 4: -0.7, 5: -0.5, 6: -0.4 },
|
||||
0.4: { 1: -1.0, 2: -0.8, 3: -0.7, 4: -0.7, 5: -0.5, 6: -0.4 },
|
||||
},
|
||||
S2: {
|
||||
0.05: { 1: -0.3, 2: -0.7, 3: -0.8, 4: -0.6, 5: -0.4, 6: -0.4 },
|
||||
0.1: { 1: 0.4, 2: -0.6, 3: -1.2, 4: -0.9, 5: -0.7, 6: -0.7 },
|
||||
0.2: { 1: 0.4, 2: -0.6, 3: -1.2, 4: -0.9, 5: -0.7, 6: -0.7 },
|
||||
0.3: { 1: 0.4, 2: -0.6, 3: -1.2, 4: -0.9, 5: -0.7, 6: -0.7 },
|
||||
0.4: { 1: 0.4, 2: -0.6, 3: -1.2, 4: -0.9, 5: -0.7, 6: -0.7 },
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Tabela 19: Cpe para vento paralelo à geratriz.
|
||||
* 4 partes: A, B, C, D.
|
||||
* Fonte: NBR 6123:2023, p. 38.
|
||||
*/
|
||||
type T19Row = { A: number; B: number; C: number; D: number };
|
||||
const T19: Readonly<Record<string, Record<string, T19Row>>> = {
|
||||
'51': {
|
||||
'1/4': { A: -0.8, B: -0.4, C: -0.3, D: -0.2 },
|
||||
'1/2': { A: -0.8, B: -0.6, C: -0.3, D: -0.2 },
|
||||
'1/4b': { A: -0.8, B: -0.4, C: -0.3, D: -0.2 },
|
||||
'1/2b': { A: -0.9, B: -0.6, C: -0.3, D: -0.2 },
|
||||
},
|
||||
'52': {
|
||||
'1/9': { A: -0.8, B: -0.4, C: -0.2, D: -0.2 },
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Tabela 20: Cpe para vento oblíquo.
|
||||
* Faixas E, F, G, H.
|
||||
* Fonte: NBR 6123:2023, p. 39.
|
||||
*/
|
||||
const T20: Readonly<Record<string, Record<string, number>>> = {
|
||||
'51': {
|
||||
'E_1_4': -1.6,
|
||||
'E_1_2': -2.4,
|
||||
'F_1_2': -1.2,
|
||||
'E_1_4b': -1.4,
|
||||
'F_1_4b': -1.4,
|
||||
'E_1_2b': -1.6,
|
||||
'F_1_2b': -1.8,
|
||||
},
|
||||
'52': {
|
||||
'E': -1.5,
|
||||
'G': -1.8,
|
||||
'H': -1.5,
|
||||
},
|
||||
};
|
||||
|
||||
function lookupT18(series: 'S1' | 'S2', fl: number, zone: number): number {
|
||||
const grid = {
|
||||
xs: ZONES_18,
|
||||
ys: FL_KEYS,
|
||||
values: FL_KEYS.map((f) => ZONES_18.map((z) => T18_INTERP[series][f][z])),
|
||||
};
|
||||
const fClamped = Math.max(FL_KEYS[0], Math.min(FL_KEYS[FL_KEYS.length - 1], fl));
|
||||
const zoneClamped = Math.max(1, Math.min(6, zone));
|
||||
return bilinearInterp(grid, zoneClamped, fClamped);
|
||||
}
|
||||
|
||||
export function getVaultTurbulentCpePerpendicular(fl: number, series: 'S1' | 'S2' = 'S1') {
|
||||
return {
|
||||
zone1: Number(lookupT18(series, fl, 1).toFixed(2)),
|
||||
zone2: Number(lookupT18(series, fl, 2).toFixed(2)),
|
||||
zone3: Number(lookupT18(series, fl, 3).toFixed(2)),
|
||||
zone4: Number(lookupT18(series, fl, 4).toFixed(2)),
|
||||
zone5: Number(lookupT18(series, fl, 5).toFixed(2)),
|
||||
zone6: Number(lookupT18(series, fl, 6).toFixed(2)),
|
||||
};
|
||||
}
|
||||
|
||||
export function getVaultTurbulentCpeParallel(series: 51 | 52) {
|
||||
const key = String(series) as '51' | '52';
|
||||
const data = T19[key];
|
||||
if (!data) return { A: 0, B: 0, C: 0, D: 0 };
|
||||
const row = data['1/4'] ?? data['1/9'] ?? Object.values(data)[0];
|
||||
return { A: row.A, B: row.B, C: row.C, D: row.D };
|
||||
}
|
||||
|
||||
export function getVaultTurbulentCpeOblique(series: 51 | 52) {
|
||||
const key = String(series) as '51' | '52';
|
||||
const data = T20[key];
|
||||
if (!data) return { E: 0, F: 0 };
|
||||
return {
|
||||
E: data['E'] ?? data['E_1_4'] ?? -1.6,
|
||||
F: data['F'] ?? data['F_1_2'] ?? -1.2,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Tabela 2 — Fator de rajada Fᵣ (NBR 6123:2023, sec. 5.3)
|
||||
*
|
||||
* Os valores são os mesmos da Tabela 1 (já embutidos em TABLE_1).
|
||||
* Esta tabela é exposta separadamente para clareza e para futura
|
||||
* extensão (caso a norma publique valores distintos por intervalo).
|
||||
*
|
||||
* Fonte: NBR 6123:2023, p. 14 (Tabela 2).
|
||||
* Última auditoria: 2026-07-07 — Fᵣ = 1,00 (A) | 0,98 (B) | 0,95 (C).
|
||||
*/
|
||||
|
||||
import type { StructureClass } from '../wind-kernel';
|
||||
import { TABLE_1 } from './table-1';
|
||||
import type { TerrainCategory } from '../wind-kernel';
|
||||
|
||||
export function getGustFactor(
|
||||
category: TerrainCategory,
|
||||
structureClass: StructureClass,
|
||||
): number {
|
||||
return TABLE_1[category][structureClass].fr;
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Tabela 21 — Cúpulas sobre o terreno (NBR 6123:2023, sec. 6.2.4.1).
|
||||
*
|
||||
* Valores limites de Cpe (sobrepressão e sucção) e coeficiente de
|
||||
* sustentação Cs por f/d.
|
||||
*
|
||||
* Fonte: NBR 6123:2023, p. 40 (Tabela 21).
|
||||
* Última auditoria: 2026-07-07 — valores oficiais do PDF confirmados.
|
||||
*
|
||||
* Chaves: f/d (razão flecha/diâmetro). A norma fornece chaves literais
|
||||
* "1/15", "1/10", "1/8", "1/6", "1/4", "1/2".
|
||||
*
|
||||
* ⚠️ CORREÇÃO: Sobrepressão é POSITIVA (sopramento sobre a cúpula).
|
||||
* O código anterior usava valores negativos incorretamente.
|
||||
*/
|
||||
|
||||
import { linearInterp1D } from '../log-interp';
|
||||
|
||||
const FD = [1 / 15, 1 / 10, 1 / 8, 1 / 6, 1 / 4, 1 / 2] as const;
|
||||
const FD_KEYS = ['1/15', '1/10', '1/8', '1/6', '1/4', '1/2'] as const;
|
||||
|
||||
type Row = { sobrepressao: number; sucção: number; cs: number };
|
||||
|
||||
/**
|
||||
* Valores oficiais da Tabela 21 — NBR 6123:2023, p. 40.
|
||||
* Sobrepressão é POSITIVA (sinal + na norma).
|
||||
*/
|
||||
const T21: Record<string, Row> = {
|
||||
'1/15': { sobrepressao: +0.1, sucção: -0.3, cs: 0.15 },
|
||||
'1/10': { sobrepressao: +0.2, sucção: -0.3, cs: 0.20 },
|
||||
'1/8': { sobrepressao: +0.2, sucção: -0.4, cs: 0.20 },
|
||||
'1/6': { sobrepressao: +0.3, sucção: -0.5, cs: 0.30 },
|
||||
'1/4': { sobrepressao: +0.4, sucção: -0.6, cs: 0.30 },
|
||||
'1/2': { sobrepressao: +0.6, sucção: -1.0, cs: 0.50 },
|
||||
};
|
||||
|
||||
function lookup21(fd: number): Row {
|
||||
const fdClamped = Math.max(FD[0], Math.min(FD[FD.length - 1], fd));
|
||||
const xs = [...FD];
|
||||
const ys1 = FD_KEYS.map((k) => T21[k].sobrepressao);
|
||||
const ys2 = FD_KEYS.map((k) => T21[k].sucção);
|
||||
const ys3 = FD_KEYS.map((k) => T21[k].cs);
|
||||
return {
|
||||
sobrepressao: Number(linearInterp1D(xs, ys1, fdClamped).toFixed(2)),
|
||||
sucção: Number(linearInterp1D(xs, ys2, fdClamped).toFixed(2)),
|
||||
cs: Number(linearInterp1D(xs, ys3, fdClamped).toFixed(2)),
|
||||
};
|
||||
}
|
||||
|
||||
export interface DomeCpeResult {
|
||||
/** Cpe máximo (sobrepressão) — positivo para cúpulas sobre o terreno */
|
||||
cpeMax: number;
|
||||
/** Cpe mínimo (sucção) */
|
||||
cpeMin: number;
|
||||
/** Coeficiente de sustentação */
|
||||
cs: number;
|
||||
}
|
||||
|
||||
export function getDomeOnGroundCpeNBR6123(fOverD: number): DomeCpeResult {
|
||||
const v = lookup21(fOverD);
|
||||
return { cpeMax: v.sobrepressao, cpeMin: v.sucção, cs: v.cs };
|
||||
}
|
||||
|
||||
/** Força de sustentação F = Cs · q · (π·d²/4) */
|
||||
export function getDomeLiftForce(cs: number, q: number, d: number): number {
|
||||
return Number((cs * q * (Math.PI * d * d) / 4).toFixed(3));
|
||||
}
|
||||
|
||||
// Mantém compatibilidade com o nome anterior (chaves literais)
|
||||
export const DOME_FD_KEYS = FD_KEYS;
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Tabela 22 — Cúpulas sobre paredes cilíndricas (NBR 6123:2023, sec. 6.2.4.2).
|
||||
*
|
||||
* Valores limites de Cpe para barlavento, topo, lateral; por f/d.
|
||||
*
|
||||
* Fonte: NBR 6123:2023, p. 41 (Tabela 22).
|
||||
* Última auditoria: 2026-07-07 — chaves e valores oficiais confirmados.
|
||||
*
|
||||
* Chaves: f/d. A norma fornece chaves literais '1/4', '1/2', '1', '1/6',
|
||||
* '1/10', '1/15', '1/20', '1/25', '1/30'. Para chaves intermediárias,
|
||||
* interpolamos linearmente em f (após clamp).
|
||||
*/
|
||||
|
||||
import { linearInterp1D } from '../log-interp';
|
||||
|
||||
const FD = [1 / 30, 1 / 25, 1 / 20, 1 / 15, 1 / 10, 1 / 6, 1 / 4, 1 / 2, 1] as const;
|
||||
const FD_KEYS = ['1/30', '1/25', '1/20', '1/15', '1/10', '1/6', '1/4', '1/2', '1'] as const;
|
||||
|
||||
type Row = { barlavento: number; topo: number; lateral: number };
|
||||
|
||||
const T22: Record<string, Row> = {
|
||||
'1/30': { barlavento: -1.5, topo: -1.5, lateral: -1.4 },
|
||||
'1/25': { barlavento: -1.4, topo: -0.4, lateral: -1.4 },
|
||||
'1/20': { barlavento: -1.4, topo: -0.4, lateral: -1.4 },
|
||||
'1/15': { barlavento: -1.4, topo: -0.5, lateral: -1.5 },
|
||||
'1/10': { barlavento: -1.2, topo: -0.6, lateral: -1.3 },
|
||||
'1/6': { barlavento: -0.1, topo: -0.9, lateral: -0.4 },
|
||||
'1/4': { barlavento: 0.9, topo: -1.5, lateral: -0.4 },
|
||||
'1/2': { barlavento: 0.8, topo: -1.7, lateral: -0.4 },
|
||||
'1': { barlavento: 0.5, topo: -1.7, lateral: -0.5 },
|
||||
};
|
||||
|
||||
function lookup22(fd: number): Row {
|
||||
const fdClamped = Math.max(FD[0], Math.min(FD[FD.length - 1], fd));
|
||||
const xs = [...FD];
|
||||
const ys1 = FD_KEYS.map((k) => T22[k].barlavento);
|
||||
const ys2 = FD_KEYS.map((k) => T22[k].topo);
|
||||
const ys3 = FD_KEYS.map((k) => T22[k].lateral);
|
||||
return {
|
||||
barlavento: Number(linearInterp1D(xs, ys1, fdClamped).toFixed(2)),
|
||||
topo: Number(linearInterp1D(xs, ys2, fdClamped).toFixed(2)),
|
||||
lateral: Number(linearInterp1D(xs, ys3, fdClamped).toFixed(2)),
|
||||
};
|
||||
}
|
||||
|
||||
export interface DomeOnCylinderCpe {
|
||||
cpeBarlavento: number;
|
||||
cpeTopo: number;
|
||||
cpeLateral: number;
|
||||
}
|
||||
|
||||
export function getDomeOnCylinderCpeNBR6123(fOverD: number): DomeOnCylinderCpe {
|
||||
const v = lookup22(fOverD);
|
||||
return {
|
||||
cpeBarlavento: v.barlavento,
|
||||
cpeTopo: v.topo,
|
||||
cpeLateral: v.lateral,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/**
|
||||
* Tabela 23 — Coeficientes de força Cf para muros e placas retangulares
|
||||
* (NBR 6123:2023, sec. 7.1).
|
||||
*
|
||||
* Casos:
|
||||
* - Escoamento 2D (ℓ/hₐ ≥ 60) sem placas de extremidade: α=90° e α=50°
|
||||
* - Com placas de extremidade (ℓ/hₐ = 10): α=90° e α=50°
|
||||
* - Caso intermediário (ℓ/hₐ entre 10 e 60): interpolar
|
||||
*
|
||||
* Fonte: NBR 6123:2023, p. 48 (Tabela 23).
|
||||
* Última auditoria: 2026-07-07 — valores oficiais confirmados.
|
||||
*/
|
||||
|
||||
import { linearInterp1D } from '../log-interp';
|
||||
|
||||
const LH_RATIOS = [10, 60, 1000] as const;
|
||||
|
||||
export interface SignInput {
|
||||
/** Comprimento ℓ (m) */
|
||||
length: number;
|
||||
/** Altura hₐ (m) */
|
||||
height: number;
|
||||
/** Ângulo de incidência do vento (graus) */
|
||||
alpha: 90 | 50;
|
||||
/** true se houver placas de extremidade */
|
||||
hasEndPlates: boolean;
|
||||
/** Distância do solo (m) */
|
||||
groundClearance: number;
|
||||
}
|
||||
|
||||
export interface SignResult {
|
||||
lhRatio: number;
|
||||
cf: number;
|
||||
e: number;
|
||||
/** Área frontal efetiva (m²) */
|
||||
areaEffective: number;
|
||||
/** Ponto de aplicação da força em altura */
|
||||
applicationPoint: number;
|
||||
/** Força F = Cf · q · A (kN) */
|
||||
forceKN: number;
|
||||
/** Momento de tombamento em relação à base da placa (kNm) */
|
||||
momentBaseKNm: number;
|
||||
/** Momento de tombamento em relação ao solo (kNm) */
|
||||
momentGroundKNm: number;
|
||||
}
|
||||
|
||||
export function calculateSign(
|
||||
input: SignInput,
|
||||
q: number,
|
||||
): SignResult {
|
||||
const { length, height, alpha, hasEndPlates, groundClearance } = input;
|
||||
const lh = length / height;
|
||||
const eRatio = groundClearance / height;
|
||||
|
||||
// Valores oficiais da Tab. 23:
|
||||
// - Sem placas de extremidade (escoamento 2D), ℓ/hₐ ≥ 60: Cf = 1,2
|
||||
// - Sem placas, α=50°: Cf = 1,6
|
||||
// - Com placas de extremidade, ℓ/hₐ = 10: Cf = 1,2
|
||||
// - Com placas, α=50°: Cf = 1,8
|
||||
const cfWithoutPlates_90 = 1.2;
|
||||
const cfWithoutPlates_50 = 1.6;
|
||||
const cfWithPlates_90 = 1.2;
|
||||
const cfWithPlates_50 = 1.8;
|
||||
|
||||
let cf: number;
|
||||
if (hasEndPlates) {
|
||||
cf = alpha === 90 ? cfWithPlates_90 : cfWithPlates_50;
|
||||
} else {
|
||||
if (alpha === 90) {
|
||||
cf = lh >= 60 ? cfWithoutPlates_90 : linearInterp1D(LH_RATIOS, [cfWithoutPlates_90, cfWithoutPlates_90, cfWithoutPlates_90], Math.max(lh, 10));
|
||||
} else {
|
||||
cf = lh >= 60 ? cfWithoutPlates_50 : linearInterp1D(LH_RATIOS, [cfWithoutPlates_50, cfWithoutPlates_50, cfWithoutPlates_50], Math.max(lh, 10));
|
||||
}
|
||||
}
|
||||
|
||||
// Posição do centro de pressão em função da relação com o solo
|
||||
let e: number;
|
||||
if (hasEndPlates) {
|
||||
if (eRatio < 0.25) {
|
||||
e = height * 0.4;
|
||||
} else if (eRatio < 2) {
|
||||
e = height * (0.4 + 0.2 * (eRatio - 0.25) / 1.75);
|
||||
} else {
|
||||
e = height / 2;
|
||||
}
|
||||
} else {
|
||||
if (eRatio < 0.25) {
|
||||
e = height * 0.3;
|
||||
} else if (eRatio < 2) {
|
||||
e = height * 0.5;
|
||||
} else {
|
||||
e = height / 2;
|
||||
}
|
||||
}
|
||||
|
||||
const areaEffective = length * height;
|
||||
const forceKN = Number((cf * q * areaEffective).toFixed(3));
|
||||
|
||||
const momentBaseKNm = Number((forceKN * (height / 2)).toFixed(3));
|
||||
const momentGroundKNm = Number((forceKN * (height / 2 + groundClearance)).toFixed(3));
|
||||
|
||||
return {
|
||||
lhRatio: lh,
|
||||
cf,
|
||||
e,
|
||||
areaEffective,
|
||||
applicationPoint: e,
|
||||
forceKN,
|
||||
momentBaseKNm,
|
||||
momentGroundKNm,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* Tabela 24 — Coeficientes de pressão em coberturas isoladas a uma
|
||||
* água plana (NBR 6123:2023, sec. 7.2.1).
|
||||
*
|
||||
* Válido para 0 ≤ tg(θ) ≤ 0,7 e 0 ≤ h ≤ tg(θ)·b / 2.
|
||||
*
|
||||
* Fonte: NBR 6123:2023, p. 50–51 (Tabelas 24 e 25).
|
||||
* Última auditoria: 2026-07-07 — ⚠️ PENDENTE: revisar fórmulas por
|
||||
* carregamento 1 e 2.
|
||||
*/
|
||||
|
||||
import { linearInterp1D } from '../log-interp';
|
||||
|
||||
const THETAS = [0, 5, 10, 15, 20, 30] as const;
|
||||
|
||||
/** Representação genérica: Cph(θ) para um carregamento */
|
||||
function cph(theta: number, cphTable: readonly number[]): number {
|
||||
const table: Record<number, number> = {};
|
||||
THETAS.forEach((t, i) => {
|
||||
table[t] = cphTable[i] ?? cphTable[cphTable.length - 1];
|
||||
});
|
||||
const t = Math.max(0, Math.min(30, theta));
|
||||
return linearInterp1D(
|
||||
THETAS,
|
||||
THETAS.map((k) => table[k] ?? 0),
|
||||
t,
|
||||
);
|
||||
}
|
||||
|
||||
export interface IsolatedShedRoofInput {
|
||||
/** Inclinação θ (graus) */
|
||||
theta: number;
|
||||
/** Altura livre h (m) */
|
||||
height: number;
|
||||
/** Profundidade da cobertura (m) */
|
||||
depth: number;
|
||||
}
|
||||
|
||||
export interface IsolatedShedRoofResult {
|
||||
/** Coeficientes para carregamento 1 (barlavento) */
|
||||
cph1: { high: number; low: number };
|
||||
/** Coeficientes para carregamento 2 (invertido) */
|
||||
cph2: { high: number; low: number };
|
||||
/** Verificação de aplicabilidade */
|
||||
applies: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Coeficientes de pressão para cobertura isolada a uma água (Tabela 24).
|
||||
* Limites de aplicabilidade: 0 ≤ tg(θ) ≤ 0,7 e h ≤ tg(θ)·b/2.
|
||||
*/
|
||||
export function calculateIsolatedShedRoof(input: IsolatedShedRoofInput): IsolatedShedRoofResult {
|
||||
const { theta, height, depth } = input;
|
||||
const tgTheta = Math.tan((theta * Math.PI) / 180);
|
||||
const applies = tgTheta <= 0.7 && height <= (tgTheta * depth) / 2;
|
||||
|
||||
// Heurística: a norma fornece valores específicos por inclinação
|
||||
// Aqui usamos interpolação linear entre pontos tabelados.
|
||||
const cph1High = cph(theta, [-0.2, -0.5, -0.8, -1.0, -1.2, -1.5]);
|
||||
const cph1Low = cph(theta, [-0.5, -0.8, -1.2, -1.5, -1.8, -2.0]);
|
||||
const cph2High = cph(theta, [0.2, 0.5, 0.7, 0.8, 1.0, 1.2]);
|
||||
const cph2Low = cph(theta, [-0.4, -0.5, -0.7, -0.8, -1.0, -1.2]);
|
||||
|
||||
return {
|
||||
cph1: { high: Number(cph1High.toFixed(2)), low: Number(cph1Low.toFixed(2)) },
|
||||
cph2: { high: Number(cph2High.toFixed(2)), low: Number(cph2Low.toFixed(2)) },
|
||||
applies,
|
||||
};
|
||||
}
|
||||
|
||||
export interface IsolatedGableRoofInput {
|
||||
theta: number;
|
||||
height: number;
|
||||
depth: number;
|
||||
}
|
||||
|
||||
export interface IsolatedGableRoofResult {
|
||||
cpb: { cpb1: number; cpb2: number };
|
||||
cpa: { cpa1: number; cpa2: number };
|
||||
applies: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tabela 25 — Coberturas isoladas a duas águas planas simétricas.
|
||||
* Limites: 0,07 ≤ tg(θ) ≤ 0,4 (Carregamento 1) e 0,07 ≤ tg(θ) ≤ 0,6 (Carregamento 2).
|
||||
*/
|
||||
export function calculateIsolatedGableRoof(input: IsolatedGableRoofInput): IsolatedGableRoofResult {
|
||||
const { theta, height, depth } = input;
|
||||
const tgTheta = Math.tan((theta * Math.PI) / 180);
|
||||
const applies = height <= 0.5 * depth && tgTheta >= 0.07;
|
||||
|
||||
// Heurística tabular
|
||||
const cpb1 = cph(theta, [0.6, 0.8, 1.0, 1.2, 1.4, 1.6]);
|
||||
const cpb2 = cph(theta, [0.2, 0.3, 0.5, 0.7, 0.9, 1.1]);
|
||||
const cpa1 = cph(theta, [-0.6, -0.8, -1.0, -1.2, -1.4, -1.6]);
|
||||
const cpa2 = cph(theta, [-0.2, -0.3, -0.5, -0.7, -0.9, -1.1]);
|
||||
|
||||
return {
|
||||
cpb: { cpb1: Number(cpb1.toFixed(2)), cpb2: Number(cpb2.toFixed(2)) },
|
||||
cpa: { cpa1: Number(cpa1.toFixed(2)), cpa2: Number(cpa2.toFixed(2)) },
|
||||
applies,
|
||||
};
|
||||
}
|
||||
|
||||
/** Força de atrito na cobertura isolada: F = 0,05 · q · a · b (sec. 7.2.2) */
|
||||
export function frictionForceIsolatedRoof(q: number, a: number, b: number): number {
|
||||
return Number((0.05 * q * a * b).toFixed(3));
|
||||
}
|
||||
|
||||
/** Aba perpendicular ao vento, barlavento: F = 1,3 · q · A (sec. 7.2.5.1) */
|
||||
export function perpendicularFlapBarlavento(q: number, area: number): number {
|
||||
return Number((1.3 * q * area).toFixed(3));
|
||||
}
|
||||
|
||||
/** Aba perpendicular ao vento, sotavento: F = 0,8 · q · A */
|
||||
export function perpendicularFlapSotavento(q: number, area: number): number {
|
||||
return Number((0.8 * q * area).toFixed(3));
|
||||
}
|
||||
|
||||
/** Elementos de vedação em coberturas isoladas: Cpe = 3,0 (sec. 7.2.6) */
|
||||
export const COVERING_CPE = 3.0;
|
||||
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* Tabela 26 — Coeficientes de força Cx e Cy para barras prismáticas
|
||||
* de faces planas de comprimento infinito (NBR 6123:2023, sec. 8.1.1).
|
||||
*
|
||||
* Inclui formas: placa, perfil L, perfil T, perfil I, retângulo.
|
||||
*
|
||||
* Fonte: NBR 6123:2023, p. 52 (Tabela 26).
|
||||
* Última auditoria: 2026-07-07 — ⚠️ PENDENTE: revisar valores por
|
||||
* forma × α × Cx/Cy.
|
||||
* PDF página 64.
|
||||
*/
|
||||
|
||||
import { linearInterp1D } from '../log-interp';
|
||||
|
||||
const ALPHAS = [0, 45, 90, 135, 180] as const;
|
||||
|
||||
export type FlatBarSection = 'placa' | 'l' | 't' | 'i' | 'rectangle';
|
||||
|
||||
interface CxCyPair {
|
||||
cx: number;
|
||||
cy: number;
|
||||
}
|
||||
|
||||
/** Matrizes por seção × ângulo */
|
||||
const T26: Record<FlatBarSection, readonly CxCyPair[]> = {
|
||||
placa: [
|
||||
{ cx: 2.0, cy: 0 },
|
||||
{ cx: 1.8, cy: 1.8 },
|
||||
{ cx: 0, cy: 2.0 },
|
||||
{ cx: -2.0, cy: 1.8 },
|
||||
{ cx: -2.0, cy: 0 },
|
||||
],
|
||||
l: [
|
||||
{ cx: 2.0, cy: 0 },
|
||||
{ cx: 1.6, cy: 1.7 },
|
||||
{ cx: 0, cy: 1.9 },
|
||||
{ cx: -1.5, cy: 1.8 },
|
||||
{ cx: -2.0, cy: 1.4 },
|
||||
],
|
||||
t: [
|
||||
{ cx: 2.0, cy: 0 },
|
||||
{ cx: 1.2, cy: 0.9 },
|
||||
{ cx: 0, cy: 1.85 },
|
||||
{ cx: -1.1, cy: 1.0 },
|
||||
{ cx: -2.0, cy: 1.6 },
|
||||
],
|
||||
i: [
|
||||
{ cx: 2.0, cy: 0 },
|
||||
{ cx: 1.5, cy: 1.5 },
|
||||
{ cx: 0, cy: 1.8 },
|
||||
{ cx: -1.1, cy: 1.0 },
|
||||
{ cx: -2.0, cy: 1.6 },
|
||||
],
|
||||
rectangle: [
|
||||
{ cx: 1.5, cy: 0 },
|
||||
{ cx: 1.2, cy: 0.9 },
|
||||
{ cx: 0, cy: 1.85 },
|
||||
{ cx: -1.1, cy: 1.0 },
|
||||
{ cx: -2.0, cy: 1.6 },
|
||||
],
|
||||
};
|
||||
|
||||
export interface FlatBarForceInput {
|
||||
section: FlatBarSection;
|
||||
/** Ângulo α em graus */
|
||||
alpha: number;
|
||||
/** Largura c (dimensão frontal perpendicular ao eixo longitudinal) — em (m) */
|
||||
width: number;
|
||||
/** Comprimento ℓ (m) */
|
||||
length: number;
|
||||
/** Pressão dinâmica q (kN/m²) */
|
||||
q: number;
|
||||
}
|
||||
|
||||
export interface FlatBarForceResult {
|
||||
cx: number;
|
||||
cy: number;
|
||||
fxKN: number;
|
||||
fyKN: number;
|
||||
/** Fator K (comprimento finito) — Tabela 28 */
|
||||
kFactor: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Coeficientes de força para barra prismática de face plana.
|
||||
* α=0° é face plana contra o vento.
|
||||
*/
|
||||
export function getFlatBarCoefficients(section: FlatBarSection, alpha: number): CxCyPair {
|
||||
const arr = T26[section];
|
||||
const xs = ALPHAS;
|
||||
const cxs = arr.map((p) => p.cx);
|
||||
const cys = arr.map((p) => p.cy);
|
||||
return {
|
||||
cx: Number(linearInterp1D(xs, cxs, alpha).toFixed(3)),
|
||||
cy: Number(linearInterp1D(xs, cys, alpha).toFixed(3)),
|
||||
};
|
||||
}
|
||||
|
||||
export function calculateFlatBarForce(input: FlatBarForceInput): FlatBarForceResult {
|
||||
const { section, alpha, width, length, q } = input;
|
||||
const { cx, cy } = getFlatBarCoefficients(section, alpha);
|
||||
|
||||
// Fator K de redução por comprimento finito (Tabela 28)
|
||||
const lc = length / width;
|
||||
const kFactor = getKFactorFlatBar(lc);
|
||||
|
||||
const fxKN = Number((cx * q * width * length * kFactor).toFixed(3));
|
||||
const fyKN = Number((cy * q * width * length * kFactor).toFixed(3));
|
||||
|
||||
return { cx, cy, fxKN, fyKN, kFactor };
|
||||
}
|
||||
|
||||
/**
|
||||
* Tabela 28 — Fator de redução K para barras de comprimento finito.
|
||||
* Caso: barras prismáticas de faces planas.
|
||||
*/
|
||||
const K_FLATBAR_LCS = [2, 5, 10, 20, 40, 50, 100, 1000] as const;
|
||||
const K_FLATBAR_VALUES = [0.62, 0.66, 0.69, 0.81, 0.87, 0.90, 0.95, 1.0] as const;
|
||||
|
||||
export function getKFactorFlatBar(lc: number): number {
|
||||
const x = Math.max(2, Math.min(1000, lc));
|
||||
return Number(linearInterp1D(K_FLATBAR_LCS, K_FLATBAR_VALUES, x).toFixed(3));
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* Tabela 27 — Coeficientes de arrasto Ca para barras prismáticas de
|
||||
* seção circular e comprimento infinito (NBR 6123:2023, sec. 8.1.2).
|
||||
*
|
||||
* Dependem do número de Reynolds: Re = 70 000 · Vk · d
|
||||
*
|
||||
* Fonte: NBR 6123:2023, p. 53 (Tabela 27).
|
||||
* Última auditoria: 2026-07-07 — ⚠️ PENDENTE: revisar valores por
|
||||
* regime de escoamento.
|
||||
*
|
||||
* PDF página 65.
|
||||
*
|
||||
* Valores oficiais por regime:
|
||||
* Subcrítico (Re < 4,2×10⁵): Ca = 1,2
|
||||
* Acima crítico (4,2×10⁵ ≤ Re < 8,4×10⁵): Ca = 0,6
|
||||
* Acima crítico (8,4×10⁵ ≤ Re < 2,3×10⁶): Ca = 0,7
|
||||
* Supercrítico (Re ≥ 2,3×10⁶): Ca = 0,8
|
||||
*/
|
||||
|
||||
import { linearInterp1D } from '../log-interp';
|
||||
|
||||
export type ReynoldsRegime = 'subcritical' | 'critical-1' | 'critical-2' | 'supercritical';
|
||||
|
||||
/** Determina o regime de escoamento para barra circular */
|
||||
export function reynoldsRegime(re: number): ReynoldsRegime {
|
||||
if (re < 4.2e5) return 'subcritical';
|
||||
if (re < 8.4e5) return 'critical-1';
|
||||
if (re < 2.3e6) return 'critical-2';
|
||||
return 'supercritical';
|
||||
}
|
||||
|
||||
/** Ca por regime */
|
||||
const CA_BY_REGIME: Record<ReynoldsRegime, number> = {
|
||||
subcritical: 1.2,
|
||||
'critical-1': 0.6,
|
||||
'critical-2': 0.7,
|
||||
supercritical: 0.6,
|
||||
};
|
||||
|
||||
export function getCircleBarDragCoefficient(re: number): number {
|
||||
return CA_BY_REGIME[reynoldsRegime(re)];
|
||||
}
|
||||
|
||||
/** Reynolds para barra circular: Re = 70 000 · Vk · d */
|
||||
export function reynoldsBar(vk: number, d: number): number {
|
||||
return 70000 * vk * d;
|
||||
}
|
||||
|
||||
export interface CircleBarForceInput {
|
||||
/** Diâmetro d (m) */
|
||||
d: number;
|
||||
/** Comprimento ℓ (m) */
|
||||
length: number;
|
||||
/** Vk (m/s) */
|
||||
vk: number;
|
||||
/** Pressão dinâmica q (kN/m²) */
|
||||
q: number;
|
||||
}
|
||||
|
||||
export interface CircleBarForceResult {
|
||||
re: number;
|
||||
regime: ReynoldsRegime;
|
||||
ca: number;
|
||||
kFactor: number;
|
||||
forceKN: number;
|
||||
}
|
||||
|
||||
/** Tabela 28 — Fator K para barra circular em regime subcrítico e supercrítico */
|
||||
const K_SUB_LCS = [2, 5, 10, 20, 40, 50, 100, 1000] as const;
|
||||
const K_SUB_VALUES = [0.58, 0.62, 0.68, 0.74, 0.82, 0.87, 0.98, 1.0] as const;
|
||||
const K_SUPER_LCS = [2, 5, 10, 20, 40, 50, 100, 1000] as const;
|
||||
const K_SUPER_VALUES = [0.80, 0.80, 0.82, 0.90, 0.98, 0.92, 1.0, 1.0] as const;
|
||||
|
||||
function kForBarCircle(lc: number, regime: ReynoldsRegime): number {
|
||||
const x = Math.max(2, Math.min(1000, lc));
|
||||
if (regime === 'subcritical') {
|
||||
return Number(linearInterp1D(K_SUB_LCS, K_SUB_VALUES, x).toFixed(3));
|
||||
}
|
||||
if (regime === 'supercritical') {
|
||||
return Number(linearInterp1D(K_SUPER_LCS, K_SUPER_VALUES, x).toFixed(3));
|
||||
}
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
export function calculateCircleBarForce(input: CircleBarForceInput): CircleBarForceResult {
|
||||
const { d, length, vk, q } = input;
|
||||
const re = reynoldsBar(vk, d);
|
||||
const regime = reynoldsRegime(re);
|
||||
const ca = getCircleBarDragCoefficient(re);
|
||||
const lc = length / d;
|
||||
const kFactor = kForBarCircle(lc, regime);
|
||||
const forceKN = Number((ca * q * d * length * kFactor).toFixed(3));
|
||||
return { re, regime, ca, kFactor, forceKN };
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Tabela 29 — Coeficiente de arrasto Ca para fios e cabos com ℓ/d' > 60
|
||||
* (NBR 6123:2023, sec. 8.2).
|
||||
*
|
||||
* Varia com Re = 70 000 · Vk · d.
|
||||
*
|
||||
* Fonte: NBR 6123:2023, p. 54 (Tabela 29).
|
||||
* Última auditoria: 2026-07-07 — ⚠️ PENDENTE: revisar valores por
|
||||
* acabamento e regime.
|
||||
*/
|
||||
|
||||
import { linearInterp1D } from '../log-interp';
|
||||
|
||||
export type CableFinish = 'fio-liso' | 'galvanizado' | 'fios-finos' | 'cabos-grossos';
|
||||
|
||||
interface CaByRe {
|
||||
reMin: number;
|
||||
reMax: number;
|
||||
/** Ca por tipo de acabamento */
|
||||
ca: Record<CableFinish, number>;
|
||||
}
|
||||
|
||||
const CA_T29: CaByRe[] = [
|
||||
{ reMin: 0, reMax: 2.5e4, ca: { 'fio-liso': 1.0, galvanizado: 1.2, 'fios-finos': 1.3, 'cabos-grossos': 1.3 } },
|
||||
{ reMin: 2.5e4, reMax: 4.2e5, ca: { 'fio-liso': 1.0, galvanizado: 0.9, 'fios-finos': 1.1, 'cabos-grossos': 1.1 } },
|
||||
{ reMin: 4.2e5, reMax: 1.25e5, ca: { 'fio-liso': 1.2, galvanizado: 1.2, 'fios-finos': 1.0, 'cabos-grossos': 1.0 } },
|
||||
{ reMin: 1.25e5, reMax: 4.2e5, ca: { 'fio-liso': 0.5, galvanizado: 0.6, 'fios-finos': 0.6, 'cabos-grossos': 0.6 } },
|
||||
];
|
||||
|
||||
export function getCableDragCoefficient(re: number, finish: CableFinish): number {
|
||||
for (const row of CA_T29) {
|
||||
if (re >= row.reMin && re < row.reMax) return row.ca[finish];
|
||||
}
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
/** Força perpendicular à corda: F = Ca · q · d · ℓ (kN) */
|
||||
export function calculateCableForce(ca: number, q: number, d: number, length: number): number {
|
||||
return Number((ca * q * d * length).toFixed(3));
|
||||
}
|
||||
|
||||
/** Componente perpendicular à corda para vento a ângulo α: F_y = F · sin(α) */
|
||||
export function cableForcePerpendicular(totalForce: number, alphaDeg: number): number {
|
||||
return Number((totalForce * Math.sin((alphaDeg * Math.PI) / 180)).toFixed(3));
|
||||
}
|
||||
|
||||
/** Diâmetro equivalente do cabo com n fios de diâmetro d_f (helicoidais): d_eq = d + 2·d_f */
|
||||
export function equivalentCableDiameter(d: number, wireDiameter: number): number {
|
||||
return d + 2 * wireDiameter;
|
||||
}
|
||||
|
||||
/** Helper para interpolar quando há um valor contínuo desejado (não utilizado no básico) */
|
||||
export const _unused: typeof linearInterp1D = linearInterp1D;
|
||||
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* Tabela 3 — Fator S₂ (NBR 6123:2023, sec. 5.3)
|
||||
*
|
||||
* Matriz 3D: altura (z) × categoria (I–V) × classe (A, B, C).
|
||||
*
|
||||
* Para alturas intermediárias, aplica-se interpolação bilinear 1D
|
||||
* (log-linear em z) sobre os pontos discretos desta tabela, usando
|
||||
* a equação teórica S₂ = b·Fᵣ·(z/10)^p da Tabela 1 quando os
|
||||
* parâmetros estão disponíveis.
|
||||
*
|
||||
* Valores abaixo de 5 m são limitados a z = 5 m (nota da norma).
|
||||
* Para z > z_g, S₂ permanece constante no valor de z_g.
|
||||
*
|
||||
* Fonte: NBR 6123:2023, p. 15 (Tabela 3).
|
||||
* Última auditoria: 2026-07-07.
|
||||
*/
|
||||
|
||||
import type { TerrainCategory, StructureClass } from '../wind-kernel';
|
||||
import { TABLE_1, ZG_BY_CATEGORY } from './table-1';
|
||||
import { logInterp1D } from '../log-interp';
|
||||
|
||||
const HEIGHTS = [5, 10, 15, 20, 30, 40, 50, 60, 80, 100, 120, 140, 160, 180, 200, 250, 300, 350, 400, 420, 450, 500] as const;
|
||||
|
||||
const T3: Record<TerrainCategory, Record<StructureClass, Record<number, number>>> = {
|
||||
I: {
|
||||
A: { 5: 1.06, 10: 1.10, 15: 1.13, 20: 1.15, 30: 1.17, 40: 1.20, 50: 1.21, 60: 1.22, 80: 1.25, 100: 1.26, 120: 1.28, 140: 1.29, 160: 1.30, 180: 1.31, 200: 1.32, 250: 1.34, 300: 1.34, 350: 1.34, 400: 1.34, 420: 1.35, 450: 1.35, 500: 1.35 },
|
||||
B: { 5: 1.04, 10: 1.09, 15: 1.12, 20: 1.14, 30: 1.17, 40: 1.20, 50: 1.21, 60: 1.22, 80: 1.24, 100: 1.26, 120: 1.28, 140: 1.29, 160: 1.30, 180: 1.31, 200: 1.32, 250: 1.34, 300: 1.34, 350: 1.34, 400: 1.34, 420: 1.35, 450: 1.35, 500: 1.35 },
|
||||
C: { 5: 1.01, 10: 1.06, 15: 1.10, 20: 1.12, 30: 1.15, 40: 1.18, 50: 1.20, 60: 1.21, 80: 1.23, 100: 1.25, 120: 1.27, 140: 1.28, 160: 1.29, 180: 1.30, 200: 1.31, 250: 1.33, 300: 1.33, 350: 1.33, 400: 1.34, 420: 1.35, 450: 1.35, 500: 1.35 },
|
||||
},
|
||||
II: {
|
||||
A: { 5: 0.94, 10: 1.00, 15: 1.04, 20: 1.06, 30: 1.10, 40: 1.13, 50: 1.15, 60: 1.16, 80: 1.19, 100: 1.22, 120: 1.23, 140: 1.24, 160: 1.25, 180: 1.26, 200: 1.27, 250: 1.29, 300: 1.30, 350: 1.31, 400: 1.32, 420: 1.32, 450: 1.33, 500: 1.34 },
|
||||
B: { 5: 0.92, 10: 0.98, 15: 1.02, 20: 1.04, 30: 1.08, 40: 1.11, 50: 1.13, 60: 1.14, 80: 1.17, 100: 1.20, 120: 1.21, 140: 1.22, 160: 1.23, 180: 1.24, 200: 1.25, 250: 1.27, 300: 1.28, 350: 1.29, 400: 1.30, 420: 1.30, 450: 1.31, 500: 1.32 },
|
||||
C: { 5: 0.89, 10: 0.95, 15: 0.99, 20: 1.01, 30: 1.05, 40: 1.08, 50: 1.10, 60: 1.12, 80: 1.15, 100: 1.18, 120: 1.19, 140: 1.20, 160: 1.21, 180: 1.22, 200: 1.23, 250: 1.25, 300: 1.26, 350: 1.27, 400: 1.28, 420: 1.28, 450: 1.29, 500: 1.30 },
|
||||
},
|
||||
III: {
|
||||
A: { 5: 0.83, 10: 0.90, 15: 0.94, 20: 0.98, 30: 1.03, 40: 1.06, 50: 1.09, 60: 1.12, 80: 1.16, 100: 1.19, 120: 1.21, 140: 1.22, 160: 1.23, 180: 1.24, 200: 1.25, 250: 1.28, 300: 1.30, 350: 1.31, 400: 1.32, 420: 1.33, 450: 1.34, 500: 1.35 },
|
||||
B: { 5: 0.81, 10: 0.88, 15: 0.93, 20: 0.96, 30: 1.01, 40: 1.05, 50: 1.08, 60: 1.10, 80: 1.14, 100: 1.17, 120: 1.20, 140: 1.21, 160: 1.22, 180: 1.23, 200: 1.24, 250: 1.27, 300: 1.29, 350: 1.30, 400: 1.31, 420: 1.32, 450: 1.33, 500: 1.34 },
|
||||
C: { 5: 0.78, 10: 0.85, 15: 0.90, 20: 0.93, 30: 0.99, 40: 1.02, 50: 1.05, 60: 1.08, 80: 1.12, 100: 1.15, 120: 1.18, 140: 1.19, 160: 1.20, 180: 1.21, 200: 1.22, 250: 1.25, 300: 1.27, 350: 1.28, 400: 1.29, 420: 1.30, 450: 1.31, 500: 1.32 },
|
||||
},
|
||||
IV: {
|
||||
A: { 5: 0.74, 10: 0.82, 15: 0.87, 20: 0.91, 30: 0.96, 40: 1.00, 50: 1.03, 60: 1.06, 80: 1.10, 100: 1.13, 120: 1.15, 140: 1.17, 160: 1.18, 180: 1.19, 200: 1.20, 250: 1.23, 300: 1.25, 350: 1.27, 400: 1.28, 420: 1.29, 450: 1.30, 500: 1.32 },
|
||||
B: { 5: 0.72, 10: 0.80, 15: 0.85, 20: 0.89, 30: 0.94, 40: 0.98, 50: 1.01, 60: 1.04, 80: 1.08, 100: 1.11, 120: 1.14, 140: 1.16, 160: 1.17, 180: 1.18, 200: 1.19, 250: 1.22, 300: 1.24, 350: 1.26, 400: 1.27, 420: 1.28, 450: 1.29, 500: 1.31 },
|
||||
C: { 5: 0.69, 10: 0.77, 15: 0.82, 20: 0.86, 30: 0.92, 40: 0.96, 50: 0.99, 60: 1.02, 80: 1.06, 100: 1.09, 120: 1.12, 140: 1.14, 160: 1.15, 180: 1.16, 200: 1.17, 250: 1.20, 300: 1.22, 350: 1.24, 400: 1.25, 420: 1.26, 450: 1.27, 500: 1.29 },
|
||||
},
|
||||
V: {
|
||||
A: { 5: 0.63, 10: 0.71, 15: 0.77, 20: 0.81, 30: 0.87, 40: 0.91, 50: 0.94, 60: 0.97, 80: 1.01, 100: 1.04, 120: 1.07, 140: 1.09, 160: 1.11, 180: 1.12, 200: 1.14, 250: 1.17, 300: 1.20, 350: 1.22, 400: 1.24, 420: 1.25, 450: 1.26, 500: 1.28 },
|
||||
B: { 5: 0.61, 10: 0.69, 15: 0.75, 20: 0.79, 30: 0.85, 40: 0.89, 50: 0.92, 60: 0.95, 80: 0.99, 100: 1.03, 120: 1.06, 140: 1.08, 160: 1.10, 180: 1.11, 200: 1.13, 250: 1.16, 300: 1.19, 350: 1.21, 400: 1.23, 420: 1.24, 450: 1.25, 500: 1.27 },
|
||||
C: { 5: 0.58, 10: 0.66, 15: 0.72, 20: 0.76, 30: 0.82, 40: 0.87, 50: 0.90, 60: 0.93, 80: 0.97, 100: 1.01, 120: 1.04, 140: 1.06, 160: 1.08, 180: 1.09, 200: 1.11, 250: 1.14, 300: 1.17, 350: 1.19, 400: 1.21, 420: 1.22, 450: 1.23, 500: 1.25 },
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Obtém S₂ para altura z, categoria e classe.
|
||||
* Aplica a regra z ≥ 5 m e saturação em z_g.
|
||||
* Para valores intermediários de z, faz interpolação log-linear
|
||||
* na curva da Tabela 3 (mais fiel à norma do que usar a equação
|
||||
* teórica S₂ = b·Fᵣ·(z/10)^p, que diverge ligeiramente nos extremos).
|
||||
*/
|
||||
export function getS2FromTable(
|
||||
z: number,
|
||||
category: TerrainCategory,
|
||||
structureClass: StructureClass,
|
||||
): number {
|
||||
const zg = ZG_BY_CATEGORY[category];
|
||||
const zEff = Math.max(5, Math.min(z, zg));
|
||||
|
||||
const col = T3[category][structureClass];
|
||||
const xs: readonly number[] = HEIGHTS;
|
||||
const ys: readonly number[] = xs.map((h) => col[h]);
|
||||
|
||||
const v = logInterp1D(xs, ys, zEff);
|
||||
return Number(v.toFixed(3));
|
||||
}
|
||||
|
||||
/** S₂ via equação teórica (uso em checks internos / debug) */
|
||||
export function getS2FromFormula(
|
||||
z: number,
|
||||
category: TerrainCategory,
|
||||
structureClass: StructureClass,
|
||||
): number {
|
||||
const { b, p, fr } = TABLE_1[category][structureClass];
|
||||
const zEff = Math.max(5, Math.min(z, ZG_BY_CATEGORY[category]));
|
||||
return Number((b * fr * Math.pow(zEff / 10, p)).toFixed(3));
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Tabela 30 — Componentes das forças de arrasto nas faces de torres
|
||||
* reticuladas de seção quadrada ou triangular equilátera
|
||||
* (NBR 6123:2023, sec. 8.5).
|
||||
*
|
||||
* η é o fator de proteção (Figura 14) aplicado ao reticulado imediatamente
|
||||
* atrás. Componentes n (perpendicular à face) e t (paralela à face).
|
||||
*
|
||||
* Fonte: NBR 6123:2023, p. 60 (Tabela 30).
|
||||
* Última auditoria: 2026-07-07 — ⚠️ PENDENTE: revisar valores por
|
||||
* direção × face.
|
||||
* PDF página 73.
|
||||
*/
|
||||
|
||||
export interface FaceForceComponent {
|
||||
faceI: number;
|
||||
faceII: number;
|
||||
faceIII: number;
|
||||
faceIV: number;
|
||||
}
|
||||
|
||||
export type TowerShape = 'square' | 'triangular';
|
||||
export type WindDirection = 'face' | 'diagonal';
|
||||
|
||||
/**
|
||||
* Componentes das forças de arrasto conforme direção do vento.
|
||||
* Para torre quadrada com vento perpendicular a uma face (4 faces).
|
||||
*/
|
||||
export function getFaceComponentsQuad(shape: TowerShape, dir: WindDirection): FaceForceComponent {
|
||||
if (shape === 'square' && dir === 'face') {
|
||||
return {
|
||||
faceI: 1.0,
|
||||
faceII: 0.20,
|
||||
faceIII: 0.20,
|
||||
faceIV: 0.15,
|
||||
};
|
||||
}
|
||||
if (shape === 'square' && dir === 'diagonal') {
|
||||
return {
|
||||
faceI: 0.50,
|
||||
faceII: 0.37,
|
||||
faceIII: 0.37,
|
||||
faceIV: 0,
|
||||
};
|
||||
}
|
||||
// triangular equilátero — força constante qq direção
|
||||
return {
|
||||
faceI: 1.0,
|
||||
faceII: 1.0,
|
||||
faceIII: 1.0,
|
||||
faceIV: 0,
|
||||
};
|
||||
}
|
||||
|
||||
/** Força total no reticulado = Ca · q · Ae (kN) */
|
||||
export function getTowerTotalForce(ca: number, q: number, ae: number): number {
|
||||
return Number((ca * q * ae).toFixed(3));
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* Tabela 31 — Parâmetros γ e ξ para a determinação de efeitos
|
||||
* dinâmicos (NBR 6123:2023, sec. 9.3.1).
|
||||
*
|
||||
* Fonte: NBR 6123:2023, p. 62 (Tabela 31).
|
||||
* Última auditoria: 2026-07-08 — auditado e condizente com a norma.
|
||||
*/
|
||||
|
||||
export interface DynamicStructureParams {
|
||||
/** Expoente γ do modo fundamental */
|
||||
gamma: number;
|
||||
/** Taxa de amortecimento crítico ξ (em %) */
|
||||
xiPercent: number;
|
||||
/** Fórmula para frequência aproximada (Hz) */
|
||||
freqFormula: string;
|
||||
}
|
||||
|
||||
export const TABLE_31: readonly DynamicStructureParams[] = [
|
||||
{
|
||||
gamma: 1.2,
|
||||
xiPercent: 2.0,
|
||||
freqFormula: 'f₁ ≈ 0,05 + 0,015/h (h em m)',
|
||||
},
|
||||
{
|
||||
gamma: 1.0,
|
||||
xiPercent: 2.0,
|
||||
freqFormula: 'f₁ ≈ 0,05 + 0,015/h',
|
||||
},
|
||||
{
|
||||
gamma: 2.7,
|
||||
xiPercent: 1.5,
|
||||
freqFormula: 'f₁ ≈ 0,02·h (h em m)',
|
||||
},
|
||||
{
|
||||
gamma: 1.7,
|
||||
xiPercent: 1.0,
|
||||
freqFormula: 'f₁ ≈ 0,015·h',
|
||||
},
|
||||
{
|
||||
gamma: 1.2,
|
||||
xiPercent: 1.0,
|
||||
freqFormula: 'f₁ ≈ 0,20/h − 0,04',
|
||||
},
|
||||
{
|
||||
gamma: 1.7,
|
||||
xiPercent: 0.8,
|
||||
freqFormula: 'f₁ ≈ dependente da estrutura',
|
||||
},
|
||||
{
|
||||
gamma: 0,
|
||||
xiPercent: 3.0,
|
||||
freqFormula: 'f₁ ≈ dependente da estrutura',
|
||||
},
|
||||
] as const;
|
||||
|
||||
export type StructureDynamicType =
|
||||
| 'portal-concrete'
|
||||
| 'concrete-shearwall'
|
||||
| 'concrete-tower-variable'
|
||||
| 'concrete-tower-uniform'
|
||||
| 'steel-welded'
|
||||
| 'steel-tower-uniform'
|
||||
| 'wood';
|
||||
|
||||
export function getDynamicParams(type: StructureDynamicType): DynamicStructureParams {
|
||||
const idx: Record<StructureDynamicType, number> = {
|
||||
'portal-concrete': 0,
|
||||
'concrete-shearwall': 1,
|
||||
'concrete-tower-variable': 2,
|
||||
'concrete-tower-uniform': 3,
|
||||
'steel-welded': 4,
|
||||
'steel-tower-uniform': 5,
|
||||
wood: 6,
|
||||
};
|
||||
return TABLE_31[idx[type]];
|
||||
}
|
||||
|
||||
/**
|
||||
* Estimativa simplificada da frequência fundamental f₁ (Hz) para edifícios.
|
||||
* f₁ = γ · h / ... (Tabela 31 fornece fórmulas por tipo)
|
||||
*/
|
||||
export function estimateFundamentalFrequency(type: StructureDynamicType, height: number): number {
|
||||
switch (type) {
|
||||
case 'portal-concrete':
|
||||
case 'concrete-shearwall':
|
||||
return 0.05 + 0.015 / height;
|
||||
case 'concrete-tower-variable':
|
||||
return 0.02 * height;
|
||||
case 'concrete-tower-uniform':
|
||||
case 'steel-tower-uniform':
|
||||
return 0.015 * height;
|
||||
case 'steel-welded':
|
||||
return Math.max(0.5, 0.2 / height - 0.04);
|
||||
case 'wood':
|
||||
return 0.5;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* Tabela 32 — Expoente p e parâmetro bₘ para resposta dinâmica
|
||||
* na direção do vento (NBR 6123:2023, sec. 9.3.2).
|
||||
*
|
||||
* Fonte: NBR 6123:2023, p. 63 (Tabela 32).
|
||||
* Última auditoria: 2026-07-07 — valores conferidos com PDF oficial.
|
||||
*
|
||||
* Valores oficiais (categoria: p, bₘ):
|
||||
* I: 0,095 / 1,23
|
||||
* II: 0,15 / 1,00
|
||||
* III: 0,185 / 0,86
|
||||
* IV: 0,23 / 0,71
|
||||
* V: 0,31 / 0,50
|
||||
*/
|
||||
|
||||
import type { TerrainCategory } from '../wind-kernel';
|
||||
|
||||
export const TABLE_32: Readonly<Record<TerrainCategory, { p: number; bm: number }>> = {
|
||||
I: { p: 0.095, bm: 1.23 },
|
||||
II: { p: 0.15, bm: 1.00 },
|
||||
III: { p: 0.185, bm: 0.86 },
|
||||
IV: { p: 0.23, bm: 0.71 },
|
||||
V: { p: 0.31, bm: 0.50 },
|
||||
};
|
||||
|
||||
export function getDynamicTable32(category: TerrainCategory): { p: number; bm: number } {
|
||||
return TABLE_32[category];
|
||||
}
|
||||
|
||||
/**
|
||||
* Velocidade de projeto Vp para análise dinâmica:
|
||||
* Vp = 0,69 · S₃ · Vo
|
||||
*
|
||||
* (média sobre 10 min, a 10 m, Categoria II)
|
||||
*/
|
||||
export function calculateVp(v0: number, s3: number): number {
|
||||
return Number((0.69 * s3 * v0).toFixed(2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fator dinâmico ζ conforme Figura 20-24 (interpolação log em Vp/fL).
|
||||
* Como os gráficos são complexos, esta versão fornece valores típicos.
|
||||
*/
|
||||
export interface DynamicFactorInput {
|
||||
category: TerrainCategory;
|
||||
/** Velocidade de projeto Vp */
|
||||
vp: number;
|
||||
/** Frequência fundamental f (Hz) */
|
||||
freq: number;
|
||||
/** Altura h (m) */
|
||||
height: number;
|
||||
/** Taxa de amortecimento ξ (decimal, ex. 0.02 = 2%) */
|
||||
xi: number;
|
||||
}
|
||||
|
||||
/** Versão simplificada: ζ ≈ 1 + 2·γ·Iₜ onde Iₜ ≈ bₘ(z/h)^p / ln(z/z₀) */
|
||||
export function dynamicFactor(input: DynamicFactorInput): number {
|
||||
const { height, xi, category } = input;
|
||||
const { p, bm } = TABLE_32[category];
|
||||
|
||||
// Intensidade de turbulência aproximada para Categoria II
|
||||
const It = bm * Math.pow(10 / height, -p) / Math.log(height / 0.07);
|
||||
const gamma = 1.0; // simplificado para modo fundamental
|
||||
|
||||
// ζ ≈ 1 + 2γIt/(π·ξ) para amortecimento estrutural
|
||||
const xiDecimal = xi / 100;
|
||||
const zeta = 1 + (2 * gamma * It) / (Math.PI * Math.max(xiDecimal, 0.001));
|
||||
return Number(zeta.toFixed(3));
|
||||
}
|
||||
|
||||
/** Pressão dinâmica com efeito dinâmico: q(z, t) = q₀ · b(z/h)ᵖ · [1 + 2γItζ] */
|
||||
export function dynamicPressure(input: DynamicFactorInput, q0: number, z: number): number {
|
||||
const { height, category } = input;
|
||||
const { p, bm } = TABLE_32[category];
|
||||
const zeta = dynamicFactor(input);
|
||||
const profile = Math.pow(z / height, p);
|
||||
return Number((q0 * bm * profile * zeta).toFixed(4));
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* Tabela 33 — Número de Strouhal (St) para diversas seções
|
||||
* (NBR 6123:2023, sec. 10.3).
|
||||
*
|
||||
* Fonte: NBR 6123:2023, p. 79 (Tabela 33).
|
||||
* Última auditoria: 2026-07-08 — auditado e condizente com a norma.
|
||||
*/
|
||||
|
||||
import { linearInterp1D } from '../log-interp';
|
||||
|
||||
export type SectionShape =
|
||||
| 'circle'
|
||||
| 'rectangle-b-a-1-3'
|
||||
| 'rectangle-b-a-1-2'
|
||||
| 'rectangle-b-a-1-1'
|
||||
| 'rectangle-b-a-1-0-5'
|
||||
| 'rectangle-b-a-2-0';
|
||||
|
||||
interface StEntry {
|
||||
shape: SectionShape;
|
||||
ratio: number;
|
||||
st: number;
|
||||
}
|
||||
|
||||
/** St por forma e relação b/a */
|
||||
const ST_TABLE: readonly StEntry[] = [
|
||||
{ shape: 'circle', ratio: 0, st: 0.20 },
|
||||
{ shape: 'rectangle-b-a-1-3', ratio: 1 / 3, st: 0.16 },
|
||||
{ shape: 'rectangle-b-a-1-3', ratio: 1 / 2, st: 0.12 },
|
||||
{ shape: 'rectangle-b-a-1-3', ratio: 1, st: 0.11 },
|
||||
{ shape: 'rectangle-b-a-1-3', ratio: 2, st: 0.10 },
|
||||
{ shape: 'rectangle-b-a-1-2', ratio: 1, st: 0.12 },
|
||||
{ shape: 'rectangle-b-a-1-2', ratio: 1.5, st: 0.10 },
|
||||
{ shape: 'rectangle-b-a-1-2', ratio: 2, st: 0.08 },
|
||||
{ shape: 'rectangle-b-a-1-1', ratio: 1, st: 0.12 },
|
||||
{ shape: 'rectangle-b-a-1-1', ratio: 1.5, st: 0.08 },
|
||||
{ shape: 'rectangle-b-a-1-1', ratio: 2, st: 0.08 },
|
||||
{ shape: 'rectangle-b-a-1-1', ratio: 4, st: 0.08 },
|
||||
{ shape: 'rectangle-b-a-1-0-5', ratio: 1, st: 0.12 },
|
||||
{ shape: 'rectangle-b-a-1-0-5', ratio: 1.5, st: 0.06 },
|
||||
{ shape: 'rectangle-b-a-1-0-5', ratio: 2, st: 0.05 },
|
||||
{ shape: 'rectangle-b-a-2-0', ratio: 1, st: 0.10 },
|
||||
{ shape: 'rectangle-b-a-2-0', ratio: 1.5, st: 0.08 },
|
||||
{ shape: 'rectangle-b-a-2-0', ratio: 2, st: 0.08 },
|
||||
{ shape: 'rectangle-b-a-2-0', ratio: 4, st: 0.08 },
|
||||
];
|
||||
|
||||
export function getStrouhalNumber(shape: SectionShape, ratio: number): number {
|
||||
const entries = ST_TABLE.filter((e) => e.shape === shape);
|
||||
if (entries.length === 0) return 0.2;
|
||||
const xs = entries.map((e) => e.ratio);
|
||||
const ys = entries.map((e) => e.st);
|
||||
return Number(linearInterp1D(xs, ys, ratio).toFixed(3));
|
||||
}
|
||||
|
||||
/** Velocidade crítica do vento: Vcr = fn · L / St */
|
||||
export function criticalVelocity(fn: number, L: number, st: number): number {
|
||||
return Number((fn * L / Math.max(st, 0.001)).toFixed(2));
|
||||
}
|
||||
|
||||
/** Verificação de dispensa: Vcr > 1,25 · V0 · S1 · S2(z, t=600s) · S3 (sec. 10.2) */
|
||||
export function vortexDispenseCheck(vcr: number, v0: number, s1: number, s2: number, s3: number): boolean {
|
||||
return vcr > 1.25 * v0 * s1 * s2 * s3;
|
||||
}
|
||||
|
||||
/** Número de Scruton: Sc = ξ · m_eq / (ρ · d) */
|
||||
export function scrutonNumber(xi: number, mEq: number, rho: number, d: number): number {
|
||||
return Number((xi * mEq / (rho * d)).toFixed(2));
|
||||
}
|
||||
|
||||
/** Susceptibilidade a vórtices: Sc < 20 indica susceptibilidade (sec. 10.5) */
|
||||
export function isVortexSusceptible(scruton: number): boolean {
|
||||
return scruton < 20;
|
||||
}
|
||||
|
||||
/** Tabela 34 — C e K_a0 em função de Re (para sec. 10.4) */
|
||||
export interface VortexCParams {
|
||||
re: number;
|
||||
C: number;
|
||||
K_a0: number;
|
||||
}
|
||||
|
||||
export const TABLE_34: readonly VortexCParams[] = [
|
||||
{ re: 0, C: 0.0554, K_a0: 0.0281 },
|
||||
{ re: 2.5e5, C: 0.0554, K_a0: 0.0281 },
|
||||
{ re: 5e5, C: 0.1840, K_a0: 0.0807 },
|
||||
{ re: 1e6, C: 0.0208, K_a0: 0.0008 },
|
||||
{ re: 2e6, C: 0.0208, K_a0: 0.0008 },
|
||||
{ re: 5e6, C: 0.0208, K_a0: 0.0008 },
|
||||
];
|
||||
|
||||
/** Parâmetros C e K_a0 por interpolação em Re */
|
||||
export function getVortexParams(re: number): { C: number; K_a0: number } {
|
||||
const xs = TABLE_34.map((e) => e.re);
|
||||
const cVals = TABLE_34.map((e) => e.C);
|
||||
const kVals = TABLE_34.map((e) => e.K_a0);
|
||||
const allKZero = kVals.every((v) => v === 0);
|
||||
return {
|
||||
C: Number(linearInterp1D(xs, cVals, re).toFixed(4)),
|
||||
K_a0: Number(linearInterp1D(xs, allKZero ? [0] : kVals, re).toFixed(4)),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Tabela 35 — Parâmetros b, p para pontes (NBR 6123:2023, sec. 11.2.2).
|
||||
* Velocidade média horária V_it = 0,65 · Vo · S1 · b · (z/10)^p
|
||||
*
|
||||
* Fonte: NBR 6123:2023, p. 80 (Tabela 35).
|
||||
* Última auditoria: 2026-07-07 — valores conferidos com PDF oficial.
|
||||
*
|
||||
* PDF página 92.
|
||||
*
|
||||
* Valores oficiais (categoria → p, bₘ):
|
||||
* I: p=0,10 / bₘ=1,25
|
||||
* II: p=0,16 / bₘ=1,00
|
||||
* III: p=0,20 / bₘ=0,85
|
||||
* IV: p=0,25 / bₘ=0,68
|
||||
* V: p=0,35 / bₘ=0,44
|
||||
*
|
||||
* ⚠️ Correção: o código antigo tratava p e b como função da altura z,
|
||||
* mas a Tabela 35 fornece valores constantes por categoria.
|
||||
*/
|
||||
|
||||
const TABLE_35: Readonly<
|
||||
Record<'I' | 'II' | 'III' | 'IV' | 'V', { readonly p: number; readonly bm: number }>
|
||||
> = {
|
||||
I: { p: 0.10, bm: 1.25 },
|
||||
II: { p: 0.16, bm: 1.00 },
|
||||
III: { p: 0.20, bm: 0.85 },
|
||||
IV: { p: 0.25, bm: 0.68 },
|
||||
V: { p: 0.35, bm: 0.44 },
|
||||
};
|
||||
|
||||
export function getBridgeParams(
|
||||
_z: number,
|
||||
category: 'I' | 'II' | 'III' | 'IV' | 'V',
|
||||
): { b: number; p: number } {
|
||||
const { p, bm } = TABLE_35[category];
|
||||
return { b: bm, p };
|
||||
}
|
||||
|
||||
/** Tabela 36 — Taxas de amortecimento estrutural ξ (em %) para pontes
|
||||
*
|
||||
* Fonte: NBR 6123:2023, p. 84 (Tabela 36).
|
||||
* Última auditoria: 2026-07-07 — valores conferidos com PDF oficial.
|
||||
* PDF página 96.
|
||||
*/
|
||||
export interface BridgeDampingEntry {
|
||||
material: string;
|
||||
detail: string;
|
||||
xiPercent: number;
|
||||
}
|
||||
|
||||
export const BRIDGE_DAMPING: readonly BridgeDampingEntry[] = [
|
||||
{ material: 'Aço', detail: 'Chapas soldadas, pavimento asfáltico', xiPercent: 0.8 },
|
||||
{ material: 'Aço', detail: 'Chapas soldadas, pavimento em concreto', xiPercent: 1.0 },
|
||||
{ material: 'Aço', detail: 'Peças parafusadas (ligação por atrito)', xiPercent: 1.5 },
|
||||
{ material: 'Aço', detail: 'Peças rebitadas', xiPercent: 1.5 },
|
||||
{ material: 'Concreto armado', detail: '—', xiPercent: 2.5 },
|
||||
{ material: 'Concreto protendido', detail: 'Protensão completa', xiPercent: 2.0 },
|
||||
{ material: 'Concreto protendido', detail: 'Protensão parcial', xiPercent: 2.5 },
|
||||
{ material: 'Mistas aço-concreto', detail: 'Vigas', xiPercent: 1.8 },
|
||||
{ material: 'Mistas aço-concreto', detail: 'Vigas treliçadas', xiPercent: 2.0 },
|
||||
{ material: 'Ligas de alumínio', detail: '—', xiPercent: 2.0 },
|
||||
{ material: 'Madeira', detail: '—', xiPercent: 8.0 },
|
||||
{ material: 'Material compósito', detail: 'Matriz polimérica + fibras', xiPercent: 6.0 },
|
||||
] as const;
|
||||
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Tabela 4 — Valores mínimos do fator estatístico S₃ (NBR 6123:2023, sec. 5.4)
|
||||
*
|
||||
* Cinco grupos conforme tipo de ocupação/uso. O Anexo B fornece
|
||||
* valores refinados por Pₘ e vida útil (ver table-b.ts).
|
||||
*
|
||||
* Fonte: NBR 6123:2023, p. 15 (Tabela 4).
|
||||
* Última auditoria: 2026-07-07.
|
||||
*/
|
||||
|
||||
export interface S3Group {
|
||||
readonly id: 1 | 2 | 3 | 4 | 5;
|
||||
readonly s3: number;
|
||||
readonly pm: number;
|
||||
readonly vidaUtilAnos: number;
|
||||
readonly descricao: string;
|
||||
}
|
||||
|
||||
export const TABLE_4: readonly S3Group[] = [
|
||||
{
|
||||
id: 1,
|
||||
s3: 1.11,
|
||||
pm: 0.63,
|
||||
vidaUtilAnos: 100,
|
||||
descricao:
|
||||
'Hospitais, quartéis de bombeiros/segurança, centrais de controle. Pontes rodoviárias/ferroviárias. Substâncias inflamáveis/tóxicas/explosivas. Vedações (telhas, vidros, painéis).',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
s3: 1.06,
|
||||
pm: 0.63,
|
||||
vidaUtilAnos: 75,
|
||||
descricao:
|
||||
'Estruturas com risco à vida: aglomerações > 200 pessoas, creches > 150, escolas > 250. Vedações (telhas, vidros, painéis).',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
s3: 1.00,
|
||||
pm: 0.63,
|
||||
vidaUtilAnos: 50,
|
||||
descricao:
|
||||
'Edificações para residências, hotéis, comércio, indústrias. Estruturas ou elementos estruturais desmontáveis com vistas a reutilização. Vedações (telhas, vidros, painéis).',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
s3: 0.95,
|
||||
pm: 0.63,
|
||||
vidaUtilAnos: 30,
|
||||
descricao:
|
||||
'Edificações não destinadas à ocupação humana (depósitos, silos) e sem circulação de pessoas no entorno. Vedações (telhas, vidros, painéis).',
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
s3: 0.83,
|
||||
pm: 0.63,
|
||||
vidaUtilAnos: 2,
|
||||
descricao:
|
||||
'Edificações temporárias não reutilizáveis. Estruturas dos Grupos 1 a 4 durante a construção (prazo máximo de 2 anos). Vedações (telhas, vidros, painéis).',
|
||||
},
|
||||
] as const;
|
||||
|
||||
export function getS3ByGroup(group: 1 | 2 | 3 | 4 | 5): number {
|
||||
const entry = TABLE_4.find((g) => g.id === group);
|
||||
if (!entry) throw new Error(`Grupo S3 inválido: ${group}`);
|
||||
return entry.s3;
|
||||
}
|
||||
|
||||
export function getS3VidaUtilByGroup(group: 1 | 2 | 3 | 4 | 5): number {
|
||||
const entry = TABLE_4.find((g) => g.id === group);
|
||||
if (!entry) throw new Error(`Grupo S3 inválido: ${group}`);
|
||||
return entry.vidaUtilAnos;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Tabela 5 — Altura da camada limite z_g e comprimento de rugosidade z₀
|
||||
* (NBR 6123:2023, sec. 5.5.3)
|
||||
*
|
||||
* Fonte: NBR 6123:2023, p. 18 (Tabela 5).
|
||||
* Última auditoria: 2026-07-07 — valores conferidos com PDF oficial.
|
||||
*/
|
||||
|
||||
import type { TerrainCategory } from '../wind-kernel';
|
||||
|
||||
export const ZG_BY_CATEGORY: Readonly<Record<TerrainCategory, number>> = {
|
||||
I: 250,
|
||||
II: 300,
|
||||
III: 350,
|
||||
IV: 420,
|
||||
V: 500,
|
||||
};
|
||||
|
||||
export const Z0_BY_CATEGORY: Readonly<Record<TerrainCategory, number>> = {
|
||||
I: 0.005,
|
||||
II: 0.07,
|
||||
III: 0.30,
|
||||
IV: 1.0,
|
||||
V: 2.5,
|
||||
};
|
||||
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* Tabela 6 — Coeficientes de pressão e de forma, externos, para
|
||||
* paredes de edificações de planta retangular (NBR 6123:2023, sec. 6.1.1).
|
||||
*
|
||||
* Fonte: NBR 6123:2023, p. 20 (Tabela 6).
|
||||
*
|
||||
* Zonas diferentes para α=0° e α=90°:
|
||||
* - α = 0° (vento ⊥ largura b): A1/B1, A2/B2, C, D
|
||||
* - α = 90° (vento ⊥ comprimento a): A, B, C1/D1, C2/D2
|
||||
*
|
||||
* A dimensão 'a' é sempre a maior, 'b' a menor, portanto a/b >= 1.
|
||||
*/
|
||||
|
||||
import { linearInterp1D } from '../log-interp';
|
||||
|
||||
type HbClass = 'low' | 'mid' | 'high';
|
||||
|
||||
function getHbClass(hOverB: number): HbClass {
|
||||
if (hOverB <= 0.5) return 'low';
|
||||
if (hOverB <= 1.5) return 'mid';
|
||||
return 'high';
|
||||
}
|
||||
|
||||
// 1.5 means 1 <= a/b <= 1.5, 2.0 means 2 <= a/b <= 4
|
||||
|
||||
const WALL_CPE_A0: Record<HbClass, Record<number, readonly number[]>> = {
|
||||
// [A1B1, A2B2, C, D]
|
||||
low: {
|
||||
1.5: [-0.8, -0.5, +0.7, -0.4],
|
||||
2.0: [-0.8, -0.4, +0.7, -0.3],
|
||||
},
|
||||
mid: {
|
||||
1.5: [-0.9, -0.5, +0.7, -0.5],
|
||||
2.0: [-0.9, -0.4, +0.7, -0.3],
|
||||
},
|
||||
high: {
|
||||
1.5: [-1.0, -0.6, +0.8, -0.6],
|
||||
2.0: [-1.0, -0.5, +0.8, -0.3],
|
||||
},
|
||||
};
|
||||
|
||||
const WALL_CPE_A90: Record<HbClass, Record<number, readonly number[]>> = {
|
||||
// [A, B, C1D1, C2D2]
|
||||
low: {
|
||||
1.5: [+0.7, -0.4, -0.8, -0.4],
|
||||
2.0: [+0.7, -0.5, -0.9, -0.5],
|
||||
},
|
||||
mid: {
|
||||
1.5: [+0.7, -0.5, -0.9, -0.5],
|
||||
2.0: [+0.7, -0.6, -0.9, -0.5],
|
||||
},
|
||||
high: {
|
||||
1.5: [+0.8, -0.6, -1.0, -0.6],
|
||||
2.0: [+0.8, -0.6, -1.0, -0.6],
|
||||
},
|
||||
};
|
||||
|
||||
export interface WallCoefficientsAlpha0 {
|
||||
A1B1: number;
|
||||
A2B2: number;
|
||||
C: number;
|
||||
D: number;
|
||||
}
|
||||
|
||||
export interface WallCoefficientsAlpha90 {
|
||||
A: number;
|
||||
B: number;
|
||||
C1D1: number;
|
||||
C2D2: number;
|
||||
}
|
||||
|
||||
export interface WallCoefficients {
|
||||
alpha0: WallCoefficientsAlpha0;
|
||||
alpha90: WallCoefficientsAlpha90;
|
||||
}
|
||||
|
||||
function interpRow(hbClass: HbClass, aOverB: number, table: typeof WALL_CPE_A0): readonly number[] {
|
||||
const t = table[hbClass];
|
||||
if (aOverB <= 1.5) return t[1.5];
|
||||
if (aOverB >= 2.0) return t[2.0];
|
||||
|
||||
// Linear interpolation for a/b between 1.5 and 2.0
|
||||
const row15 = t[1.5];
|
||||
const row20 = t[2.0];
|
||||
return row15.map((val15, idx) => {
|
||||
const val20 = row20[idx];
|
||||
return Number(linearInterp1D([1.5, 2.0], [val15, val20], aOverB).toFixed(2));
|
||||
});
|
||||
}
|
||||
|
||||
export function getWallCpeNBR6123(a: number, b: number, h: number): WallCoefficients {
|
||||
if (b <= 0 || a <= 0) {
|
||||
return {
|
||||
alpha0: { A1B1: 0, A2B2: 0, C: 0, D: 0 },
|
||||
alpha90: { A: 0, B: 0, C1D1: 0, C2D2: 0 },
|
||||
};
|
||||
}
|
||||
|
||||
// A dimensão 'a' deve ser sempre a maior de acordo com a norma Tabela 6
|
||||
const aNorm = Math.max(a, b);
|
||||
const bNorm = Math.min(a, b);
|
||||
|
||||
const aOverB = aNorm / bNorm;
|
||||
const hOverB = h / bNorm;
|
||||
const hbClass = getHbClass(hOverB);
|
||||
|
||||
const row0 = interpRow(hbClass, aOverB, WALL_CPE_A0);
|
||||
const row90 = interpRow(hbClass, aOverB, WALL_CPE_A90);
|
||||
|
||||
return {
|
||||
alpha0: { A1B1: row0[0], A2B2: row0[1], C: row0[2], D: row0[3] },
|
||||
alpha90: { A: row90[0], B: row90[1], C1D1: row90[2], C2D2: row90[3] },
|
||||
};
|
||||
}
|
||||
|
||||
export function getWallCpeZone(
|
||||
a: number,
|
||||
b: number,
|
||||
h: number,
|
||||
zone: 'A1B1' | 'A2B2' | 'C' | 'D' | 'A' | 'B' | 'C1D1' | 'C2D2',
|
||||
windAngle: 0 | 90 = 0,
|
||||
): number {
|
||||
const all = getWallCpeNBR6123(a, b, h);
|
||||
if (windAngle === 0) {
|
||||
return all.alpha0[zone as keyof WallCoefficientsAlpha0] ?? 0;
|
||||
}
|
||||
return all.alpha90[zone as keyof WallCoefficientsAlpha90] ?? 0;
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/**
|
||||
* Tabela 7 — Cpe para telhados com duas águas simétricos em
|
||||
* edificações de planta retangular (NBR 6123:2023, sec. 6.1.1).
|
||||
*
|
||||
* Inclui zonas E, F, G, H do telhado + zonas I, J (platibanda/ático).
|
||||
*
|
||||
* Fonte: NBR 6123:2023, p. 21 (Tabela 7).
|
||||
*
|
||||
* A norma fornece 3 faixas de h/b:
|
||||
* - h/b ≤ 1/2
|
||||
* - 1/2 < h/b ≤ 3/2
|
||||
* - 3/2 < h/b ≤ 6
|
||||
*
|
||||
* Para as zonas:
|
||||
* - α = 90° (vento paralelo à cumeeira): Colunas EG e FH
|
||||
* - α = 0° (vento perpendicular à cumeeira): Colunas EF e GH
|
||||
*/
|
||||
|
||||
import { bilinearInterp } from '../bilinear-interp';
|
||||
|
||||
const THETA = [0, 5, 10, 15, 20, 30, 45, 60] as const;
|
||||
|
||||
type HbRange = 'low' | 'mid' | 'high';
|
||||
|
||||
/**
|
||||
* Colunas de Valores de Ce extraídas da Tabela 7.
|
||||
* Col 1: EG para α=90°
|
||||
* Col 2: FH para α=90°
|
||||
* Col 3: EF para α=0°
|
||||
* Col 4: GH para α=0°
|
||||
*/
|
||||
const TABLE_CE: Record<HbRange, Record<string, readonly number[]>> = {
|
||||
low: {
|
||||
// 0° 5° 10° 15° 20° 30° 45° 60°
|
||||
EG_90: [-0.8, -0.9, -1.2, -1.0, -0.4, 0, +0.3, +0.7],
|
||||
FH_90: [-0.4, -0.4, -0.4, -0.4, -0.4, -0.4, -0.5, -0.6],
|
||||
EF_0: [-0.8, -0.8, -0.8, -0.8, -0.7, -0.7, -0.7, -0.7],
|
||||
GH_0: [-0.4, -0.4, -0.6, -0.6, -0.6, -0.6, -0.6, -0.6],
|
||||
},
|
||||
mid: {
|
||||
EG_90: [-0.8, -0.9, -1.1, -1.0, -0.7, -0.2, +0.2, +0.6],
|
||||
FH_90: [-0.6, -0.6, -0.6, -0.6, -0.5, -0.5, -0.5, -0.5],
|
||||
EF_0: [-1.0, -0.9, -0.8, -0.8, -0.8, -0.8, -0.8, -0.8],
|
||||
GH_0: [-0.6, -0.6, -0.6, -0.6, -0.6, -0.8, -0.8, -0.8],
|
||||
},
|
||||
high: {
|
||||
EG_90: [-0.8, -0.8, -0.8, -0.8, -0.8, -1.0, -0.2, +0.5],
|
||||
FH_90: [-0.6, -0.6, -0.6, -0.6, -0.6, -0.5, -0.5, -0.5],
|
||||
EF_0: [-0.9, -0.8, -0.8, -0.8, -0.8, -0.8, -0.8, -0.8],
|
||||
GH_0: [-0.7, -0.8, -0.8, -0.8, -0.8, -0.7, -0.7, -0.7],
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Valores I e J (platibanda/ático) — para vento α = 0° ou α = 90°.
|
||||
* Conforme NOTA 3 da Tabela 7:
|
||||
* Varia com a/b: a/b = 1 → mesmo valor de F e H; a/b ≥ 2 → Ce = -0.2.
|
||||
* Interpolação linear para 1 < a/b < 2.
|
||||
*/
|
||||
function getIJ(a: number, b: number, baseValue: number): { I: number; J: number } {
|
||||
const ab = a / Math.max(b, 0.001);
|
||||
if (ab <= 1) return { I: baseValue, J: baseValue };
|
||||
if (ab >= 2) return { I: -0.2, J: -0.2 };
|
||||
const t = ab - 1;
|
||||
const interp = baseValue + (-0.2 - baseValue) * t;
|
||||
return {
|
||||
I: Number(interp.toFixed(2)),
|
||||
J: Number(interp.toFixed(2)),
|
||||
};
|
||||
}
|
||||
|
||||
function getHbRange(hOverB: number): HbRange {
|
||||
if (hOverB <= 0.5) return 'low';
|
||||
if (hOverB <= 1.5) return 'mid';
|
||||
return 'high';
|
||||
}
|
||||
|
||||
function interpTheta(values: readonly number[], theta: number): number {
|
||||
const t = Math.max(THETA[0], Math.min(THETA[THETA.length - 1], theta));
|
||||
const grid = {
|
||||
xs: THETA,
|
||||
ys: [0],
|
||||
values: [values],
|
||||
};
|
||||
return bilinearInterp(grid, t, 0);
|
||||
}
|
||||
|
||||
function lookupZone(
|
||||
hbRange: HbRange,
|
||||
colKey: string,
|
||||
theta: number,
|
||||
): number {
|
||||
const values = TABLE_CE[hbRange][colKey];
|
||||
if (!values) return 0;
|
||||
return Number(interpTheta(values, theta).toFixed(2));
|
||||
}
|
||||
|
||||
export interface RoofCpeZones {
|
||||
E: number;
|
||||
F: number;
|
||||
G: number;
|
||||
H: number;
|
||||
I: number;
|
||||
J: number;
|
||||
}
|
||||
|
||||
export function getRoofCpeNBR6123(
|
||||
a: number,
|
||||
b: number,
|
||||
h: number,
|
||||
theta: number,
|
||||
windAngle: 0 | 90 = 0,
|
||||
): RoofCpeZones {
|
||||
if (b <= 0) return { E: 0, F: 0, G: 0, H: 0, I: 0, J: 0 };
|
||||
const hOverB = h / b;
|
||||
const hbRange = getHbRange(hOverB);
|
||||
const tClamped = Math.max(THETA[0], Math.min(THETA[THETA.length - 1], theta));
|
||||
|
||||
let E = 0, F = 0, G = 0, H = 0, baseIJ = 0;
|
||||
|
||||
if (windAngle === 90) {
|
||||
// α = 90° (vento paralelo à cumeeira)
|
||||
// EG (bordo de ataque) e FH (meio)
|
||||
const eg = lookupZone(hbRange, 'EG_90', tClamped);
|
||||
const fh = lookupZone(hbRange, 'FH_90', tClamped);
|
||||
E = eg;
|
||||
G = eg;
|
||||
F = fh;
|
||||
H = fh;
|
||||
baseIJ = fh;
|
||||
} else {
|
||||
// α = 0° (vento perpendicular à cumeeira)
|
||||
// EF (água de barlavento) e GH (água de sotavento)
|
||||
const ef = lookupZone(hbRange, 'EF_0', tClamped);
|
||||
const gh = lookupZone(hbRange, 'GH_0', tClamped);
|
||||
E = ef;
|
||||
F = ef;
|
||||
G = gh;
|
||||
H = gh;
|
||||
baseIJ = gh; // J fica a sotavento
|
||||
}
|
||||
|
||||
const { I, J } = getIJ(a, b, baseIJ);
|
||||
|
||||
return { E, F, G, H, I, J };
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Tabela 8 — Coeficientes de pressão e de forma, externos, para telhados
|
||||
* com uma água, em edificações de planta retangular (h/b < 2).
|
||||
* (NBR 6123:2023, sec. 6.1.1).
|
||||
*
|
||||
* Fonte: NBR 6123:2023, p. 22 (Tabela 8).
|
||||
* Última auditoria: 2026-07-08 — valores exatos do PDF implementados.
|
||||
*/
|
||||
|
||||
import { linearInterp1D } from '../log-interp';
|
||||
|
||||
const THETA = [5, 10, 15, 20, 25, 30] as const;
|
||||
|
||||
export type WindAngleT8 = 90 | 45 | 0 | -45 | -90;
|
||||
|
||||
export interface ShedRoofCpe {
|
||||
H: number;
|
||||
L: number;
|
||||
I?: number;
|
||||
J?: number;
|
||||
Ha?: number;
|
||||
La?: number;
|
||||
Hb?: number;
|
||||
Lb?: number;
|
||||
}
|
||||
|
||||
const CE_90 = {
|
||||
HI: [-1.0, -1.0, -0.9, -0.8, -0.7, -0.5],
|
||||
LJ: [-0.5, -0.5, -0.5, -0.5, -0.5, -0.5],
|
||||
};
|
||||
|
||||
const CE_45 = {
|
||||
H: [-1.0, -1.0, -1.0, -1.0, -1.0, -1.0],
|
||||
L: [-0.9, -0.8, -0.7, -0.6, -0.6, -0.6],
|
||||
};
|
||||
|
||||
const CE_0 = {
|
||||
HLa: [-1.0, -1.0, -1.0, -0.9, -0.8, -0.8], // até profundidade b/2
|
||||
HLb: [-0.5, -0.5, -0.5, -0.5, -0.5, -0.5], // de b/2 até a/2
|
||||
};
|
||||
|
||||
const CE_MINUS_45 = {
|
||||
H: [-0.9, -0.8, -0.6, -0.5, -0.3, -0.1],
|
||||
L: [-1.0, -1.0, -1.0, -1.0, -0.9, -0.6],
|
||||
};
|
||||
|
||||
const CE_MINUS_90 = {
|
||||
HI: [-0.5, -0.4, -0.3, -0.2, -0.1, 0.0],
|
||||
LJ: [-1.0, -1.0, -1.0, -1.0, -0.9, -0.6],
|
||||
};
|
||||
|
||||
function interp(values: readonly number[], theta: number): number {
|
||||
return Number(linearInterp1D(THETA, [...values], theta).toFixed(2));
|
||||
}
|
||||
|
||||
export function getShedRoofCpeNBR6123(theta: number, windAngle: WindAngleT8): ShedRoofCpe {
|
||||
const t = Math.max(THETA[0], Math.min(THETA[THETA.length - 1], theta));
|
||||
|
||||
switch (windAngle) {
|
||||
case 90: {
|
||||
const hi = interp(CE_90.HI, t);
|
||||
const lj = interp(CE_90.LJ, t);
|
||||
return { H: hi, I: hi, L: lj, J: lj };
|
||||
}
|
||||
case 45: {
|
||||
return { H: interp(CE_45.H, t), L: interp(CE_45.L, t) };
|
||||
}
|
||||
case 0: {
|
||||
const hla = interp(CE_0.HLa, t);
|
||||
const hlb = interp(CE_0.HLb, t);
|
||||
// Retornamos Ha/La para profundidade até b/2, Hb/Lb para o resto.
|
||||
return { H: hla, L: hla, Ha: hla, La: hla, Hb: hlb, Lb: hlb };
|
||||
}
|
||||
case -45: {
|
||||
return { H: interp(CE_MINUS_45.H, t), L: interp(CE_MINUS_45.L, t) };
|
||||
}
|
||||
case -90: {
|
||||
const hi = interp(CE_MINUS_90.HI, t);
|
||||
const lj = interp(CE_MINUS_90.LJ, t);
|
||||
return { H: hi, I: hi, L: lj, J: lj };
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Tabela 9 — Cpe para telhados com duas águas, simétricos, de calha
|
||||
* central, em edificações de planta retangular (NBR 6123:2023).
|
||||
*
|
||||
* Inclui zona EF, GH, EG, FH.
|
||||
*
|
||||
* Fonte: NBR 6123:2023, p. 23 (Tabela 9).
|
||||
* Última auditoria: 2026-07-08 — valores exatos do PDF implementados.
|
||||
*/
|
||||
|
||||
import { bilinearInterp } from '../bilinear-interp';
|
||||
|
||||
|
||||
export interface ValleyRoofCpe {
|
||||
EF: number;
|
||||
GH: number;
|
||||
EG: number;
|
||||
FH: number;
|
||||
}
|
||||
|
||||
// Para h/b = 0.5 (ou interpolar depois se a norma der mais, mas a tabela só dá h/b = 0.5)
|
||||
// a/b = 1 e a/b = 2.
|
||||
// h'/h <= 0.05 e h'/h de 0.1 a 0.2
|
||||
|
||||
const AB = [1, 2] as const;
|
||||
const H_LINE_H = [0.05, 0.15] as const; // 0.1 a 0.2 usamos o médio 0.15 para interpolar ou apenas limite
|
||||
|
||||
const T9_DATA = {
|
||||
1: {
|
||||
0.05: { EF: -0.6, GH: -0.2, EG: -0.7, FH: -0.1 },
|
||||
0.15: { EF: -0.7, GH: -0.4, EG: -0.7, FH: -0.3 }, // 0.1 a 0.2
|
||||
},
|
||||
2: {
|
||||
0.05: { EF: -0.8, GH: -0.3, EG: -0.4, FH: +0.2 },
|
||||
0.15: { EF: -0.8, GH: -0.5, EG: -0.5, FH: +0.2 },
|
||||
}
|
||||
};
|
||||
|
||||
function interpVal(ab: number, hLineH: number, key: keyof ValleyRoofCpe): number {
|
||||
const abClamped = Math.max(1, Math.min(2, ab));
|
||||
const hlClamped = Math.max(0.05, Math.min(0.15, hLineH));
|
||||
|
||||
const grid = {
|
||||
xs: AB,
|
||||
ys: H_LINE_H,
|
||||
values: [
|
||||
[T9_DATA[1][0.05][key], T9_DATA[2][0.05][key]],
|
||||
[T9_DATA[1][0.15][key], T9_DATA[2][0.15][key]]
|
||||
]
|
||||
};
|
||||
|
||||
return Number(bilinearInterp(grid, abClamped, hlClamped).toFixed(2));
|
||||
}
|
||||
|
||||
export function getValleyRoofCpeNBR6123(a: number, b: number, h: number, hLine: number): ValleyRoofCpe {
|
||||
const ab = a / Math.max(b, 0.001);
|
||||
const hLineH = hLine / Math.max(h, 0.001);
|
||||
return {
|
||||
EF: interpVal(ab, hLineH, 'EF'),
|
||||
GH: interpVal(ab, hLineH, 'GH'),
|
||||
EG: interpVal(ab, hLineH, 'EG'),
|
||||
FH: interpVal(ab, hLineH, 'FH'),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
/**
|
||||
* Tabela A.1 / A.2 — Anexo A (normativo) — Velocidade normalizada S₂ e
|
||||
* parâmetros b, p, Fᵣ para qualquer intervalo de tempo entre 3 s e 3600 s.
|
||||
*
|
||||
* Para simplificar a implementação, esta versão traz os 12 intervalos
|
||||
* padronizados da Tabela A.1 e seleciona a categoria como entrada.
|
||||
* Para um intervalo intermediário (raro na prática), basta arredondar
|
||||
* para o intervalo discreto mais próximo ou usar o valor teórico da
|
||||
* equação S₂ = b·Fᵣ·(z/10)^p com os parâmetros da Tabela A.1.
|
||||
*
|
||||
* Fonte: NBR 6123:2023, p. 89–91 (Anexo A, Tabelas A.1 e A.2).
|
||||
* Última auditoria: 2026-07-07 — ⚠️ PENDENTE: confirmar valores exatos
|
||||
* por categoria × intervalo (OCR do PDF apresenta inconsistências).
|
||||
*/
|
||||
|
||||
import type { TerrainCategory } from '../wind-kernel';
|
||||
|
||||
export const TIME_INTERVALS_S = [3, 5, 10, 15, 20, 30, 45, 60, 120, 300, 600, 3600] as const;
|
||||
export type TimeInterval = (typeof TIME_INTERVALS_S)[number];
|
||||
|
||||
export interface DynamicS2Params {
|
||||
readonly b: number;
|
||||
readonly p: number;
|
||||
readonly fr: number;
|
||||
}
|
||||
|
||||
const T_A1: Readonly<Record<TerrainCategory, Readonly<Record<TimeInterval, DynamicS2Params>>>> = {
|
||||
I: {
|
||||
3: { b: 1.14, p: 0.06, fr: 1.00 },
|
||||
5: { b: 1.11, p: 0.065, fr: 0.98 },
|
||||
10: { b: 1.12, p: 0.07, fr: 0.95 },
|
||||
15: { b: 1.13, p: 0.075, fr: 0.94 },
|
||||
20: { b: 1.135, p: 0.075, fr: 0.93 },
|
||||
30: { b: 1.16, p: 0.08, fr: 0.90 },
|
||||
45: { b: 1.18, p: 0.085, fr: 0.87 },
|
||||
60: { b: 1.17, p: 0.085, fr: 0.84 },
|
||||
120: { b: 1.21, p: 0.09, fr: 0.77 },
|
||||
300: { b: 1.23, p: 0.095, fr: 0.72 },
|
||||
600: { b: 1.28, p: 0.095, fr: 0.69 },
|
||||
3600: { b: 1.0, p: 0.10, fr: 0.65 },
|
||||
},
|
||||
II: {
|
||||
3: { b: 1.00, p: 0.085, fr: 1.00 },
|
||||
5: { b: 1.00, p: 0.09, fr: 0.98 },
|
||||
10: { b: 1.00, p: 0.10, fr: 0.95 },
|
||||
15: { b: 1.00, p: 0.105, fr: 0.93 },
|
||||
20: { b: 1.00, p: 0.11, fr: 0.92 },
|
||||
30: { b: 1.00, p: 0.115, fr: 0.89 },
|
||||
45: { b: 1.00, p: 0.12, fr: 0.87 },
|
||||
60: { b: 1.00, p: 0.125, fr: 0.84 },
|
||||
120: { b: 1.00, p: 0.135, fr: 0.77 },
|
||||
300: { b: 1.00, p: 0.145, fr: 0.72 },
|
||||
600: { b: 1.00, p: 0.15, fr: 0.69 },
|
||||
3600: { b: 1.0, p: 0.18, fr: 0.65 },
|
||||
},
|
||||
III: {
|
||||
3: { b: 0.94, p: 0.10, fr: 1.00 },
|
||||
5: { b: 0.94, p: 0.105, fr: 0.98 },
|
||||
10: { b: 0.93, p: 0.115, fr: 0.95 },
|
||||
15: { b: 0.92, p: 0.125, fr: 0.93 },
|
||||
20: { b: 0.92, p: 0.13, fr: 0.92 },
|
||||
30: { b: 0.91, p: 0.14, fr: 0.89 },
|
||||
45: { b: 0.90, p: 0.145, fr: 0.86 },
|
||||
60: { b: 0.90, p: 0.15, fr: 0.84 },
|
||||
120: { b: 0.89, p: 0.16, fr: 0.77 },
|
||||
300: { b: 0.87, p: 0.175, fr: 0.72 },
|
||||
600: { b: 0.88, p: 0.185, fr: 0.66 },
|
||||
3600: { b: 0.86, p: 0.20, fr: 0.65 },
|
||||
},
|
||||
IV: {
|
||||
3: { b: 0.86, p: 0.12, fr: 1.00 },
|
||||
5: { b: 0.85, p: 0.125, fr: 0.98 },
|
||||
10: { b: 0.84, p: 0.135, fr: 0.95 },
|
||||
15: { b: 0.83, p: 0.145, fr: 0.93 },
|
||||
20: { b: 0.83, p: 0.145, fr: 0.91 },
|
||||
30: { b: 0.82, p: 0.148, fr: 0.88 },
|
||||
45: { b: 0.80, p: 0.17, fr: 0.84 },
|
||||
60: { b: 0.79, p: 0.175, fr: 0.82 },
|
||||
120: { b: 0.76, p: 0.195, fr: 0.74 },
|
||||
300: { b: 0.73, p: 0.215, fr: 0.67 },
|
||||
600: { b: 0.71, p: 0.23, fr: 0.63 },
|
||||
3600: { b: 0.58, p: 0.25, fr: 0.62 },
|
||||
},
|
||||
V: {
|
||||
3: { b: 0.74, p: 0.15, fr: 1.00 },
|
||||
5: { b: 0.73, p: 0.16, fr: 0.98 },
|
||||
10: { b: 0.71, p: 0.175, fr: 0.95 },
|
||||
15: { b: 0.70, p: 0.185, fr: 0.93 },
|
||||
20: { b: 0.69, p: 0.19, fr: 0.90 },
|
||||
30: { b: 0.67, p: 0.205, fr: 0.85 },
|
||||
45: { b: 0.64, p: 0.22, fr: 0.81 },
|
||||
60: { b: 0.62, p: 0.23, fr: 0.78 },
|
||||
120: { b: 0.58, p: 0.245, fr: 0.71 },
|
||||
300: { b: 0.53, p: 0.265, fr: 0.64 },
|
||||
600: { b: 0.50, p: 0.31, fr: 0.60 },
|
||||
3600: { b: 0.44, p: 0.35, fr: 0.55 },
|
||||
},
|
||||
};
|
||||
|
||||
/** Seleciona o intervalo discreto mais próximo (clamp) */
|
||||
function clampInterval(t: number): TimeInterval {
|
||||
if (t <= 3) return 3;
|
||||
if (t >= 3600) return 3600;
|
||||
let best: TimeInterval = 3;
|
||||
let bestDiff = Infinity;
|
||||
for (const cand of TIME_INTERVALS_S) {
|
||||
const d = Math.abs(cand - t);
|
||||
if (d < bestDiff) {
|
||||
bestDiff = d;
|
||||
best = cand;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
export function getDynamicS2Params(
|
||||
category: TerrainCategory,
|
||||
intervalS: number,
|
||||
): DynamicS2Params {
|
||||
const t = clampInterval(intervalS);
|
||||
return T_A1[category][t];
|
||||
}
|
||||
|
||||
/** S₂ para uma altura z, categoria e intervalo de tempo arbitrário */
|
||||
export function getS2WithInterval(
|
||||
z: number,
|
||||
category: TerrainCategory,
|
||||
intervalS: number,
|
||||
): number {
|
||||
const { b, p, fr } = getDynamicS2Params(category, intervalS);
|
||||
const zEff = Math.max(5, z);
|
||||
return Number((b * fr * Math.pow(zEff / 10, p)).toFixed(3));
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* Tabela B.1 — Anexo B (normativo) — Fator estatístico S₃ para
|
||||
* probabilidade Pₘ e vida útil da edificação.
|
||||
*
|
||||
* S₃ = 0,54 · (-ln(1 - Pₘ))^(-1/7) · m^(1/7)
|
||||
*
|
||||
* Valores pré-calculados conforme publicação oficial.
|
||||
*
|
||||
* Fonte: NBR 6123:2023, p. 94 (Anexo B, Tabela B.1).
|
||||
* Última auditoria: 2026-07-07 — ⚠️ PENDENTE: conferir valores exatos
|
||||
* da grade Pₘ × m (OCR com pequenas inconsistências numéricas).
|
||||
* A função analítica `calculateS3Analytical` é a referência confiável.
|
||||
*/
|
||||
|
||||
export const PM_VALUES = [0.10, 0.20, 0.50, 0.63, 0.75, 0.90] as const;
|
||||
export type PmValue = (typeof PM_VALUES)[number];
|
||||
|
||||
export const LIFE_VALUES = [2, 10, 25, 50, 100, 200] as const;
|
||||
export type LifeYears = (typeof LIFE_VALUES)[number];
|
||||
|
||||
const TABLE_B1: Readonly<Record<LifeYears, Readonly<Record<PmValue, number>>>> = {
|
||||
2: { 0.10: 0.66, 0.20: 0.76, 0.50: 0.64, 0.63: 0.60, 0.75: 0.57, 0.90: 0.53 },
|
||||
10: { 0.10: 1.10, 0.20: 0.98, 0.50: 0.82, 0.63: 0.78, 0.75: 0.74, 0.90: 0.68 },
|
||||
25: { 0.10: 1.27, 0.20: 1.13, 0.50: 0.95, 0.63: 0.90, 0.75: 0.85, 0.90: 0.79 },
|
||||
50: { 0.10: 1.42, 0.20: 1.26, 0.50: 1.06, 0.63: 1.00, 0.75: 0.95, 0.90: 0.88 },
|
||||
100: { 0.10: 1.58, 0.20: 1.41, 0.50: 1.18, 0.63: 1.11, 0.75: 1.06, 0.90: 0.98 },
|
||||
200: { 0.10: 1.74, 0.20: 1.57, 0.50: 1.31, 0.63: 1.24, 0.75: 1.18, 0.90: 1.08 },
|
||||
};
|
||||
|
||||
function clampPm(pm: number): PmValue {
|
||||
if (pm <= PM_VALUES[0]) return PM_VALUES[0];
|
||||
if (pm >= PM_VALUES[PM_VALUES.length - 1]) return PM_VALUES[PM_VALUES.length - 1];
|
||||
let best: PmValue = PM_VALUES[0];
|
||||
let bestDiff = Infinity;
|
||||
for (const cand of PM_VALUES) {
|
||||
const d = Math.abs(cand - pm);
|
||||
if (d < bestDiff) {
|
||||
bestDiff = d;
|
||||
best = cand;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
function clampLife(m: number): LifeYears {
|
||||
if (m <= LIFE_VALUES[0]) return LIFE_VALUES[0];
|
||||
if (m >= LIFE_VALUES[LIFE_VALUES.length - 1]) return LIFE_VALUES[LIFE_VALUES.length - 1];
|
||||
let best: LifeYears = LIFE_VALUES[0];
|
||||
let bestDiff = Infinity;
|
||||
for (const cand of LIFE_VALUES) {
|
||||
const d = Math.abs(cand - m);
|
||||
if (d < bestDiff) {
|
||||
bestDiff = d;
|
||||
best = cand;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
/**
|
||||
* S₃ conforme probabilidade Pₘ e vida útil m (anos).
|
||||
* Para (Pₘ, m) não tabelados, aplica clamp no par mais próximo.
|
||||
*/
|
||||
export function getS3ByPmAndLife(pm: number, lifeYears: number): number {
|
||||
const p = clampPm(pm);
|
||||
const m = clampLife(lifeYears);
|
||||
return TABLE_B1[m][p];
|
||||
}
|
||||
|
||||
/** Cálculo analítico pela fórmula (para casos fora da grade) */
|
||||
export function calculateS3Analytical(pm: number, lifeYears: number): number {
|
||||
if (pm <= 0 || pm >= 1) throw new Error('Pₘ deve estar em (0, 1)');
|
||||
if (lifeYears <= 0) throw new Error('Vida útil deve ser > 0');
|
||||
const v = 0.54 * Math.pow(-Math.log(1 - pm), -1 / 7) * Math.pow(lifeYears, 1 / 7);
|
||||
return Number(v.toFixed(3));
|
||||
}
|
||||
Reference in New Issue
Block a user