diff --git a/app/src/components/three/Shelter3D.tsx b/app/src/components/three/Shelter3D.tsx index 523f5d8..fb45b77 100644 --- a/app/src/components/three/Shelter3D.tsx +++ b/app/src/components/three/Shelter3D.tsx @@ -5,7 +5,7 @@ import { Text, Grid, Environment } from '@react-three/drei'; import { ViewerOrbitControls as OrbitControls } from './ViewerOrbitControls'; import SceneCanvas from '../SceneCanvas'; import { useCanvasTheme } from '@/lib/theme'; -import type { ShelterCondition, OpeningPosition } from '@/lib/nbr-tables/table-shelters'; +import type { ShelterCondition, OpeningPosition, ShelterWindAngle } from '@/lib/nbr-tables/table-shelters'; export interface Shelter3DProps { condition: ShelterCondition; @@ -18,6 +18,7 @@ export interface Shelter3DProps { rightClosure: number; openingPos: OpeningPosition; backRange?: [number, number]; + windAngle?: ShelterWindAngle; viewMode: '3d' | 'elevation'; } @@ -66,6 +67,7 @@ function ShelterModel({ rightClosure, openingPos, backRange, + windAngle = 0, viewMode, }: Shelter3DProps) { const isElevation = viewMode === 'elevation'; @@ -160,8 +162,8 @@ function ShelterModel({ {renderWallWithGap(depth, height, rightClosure, 'distributed', Math.PI / 2, [halfW, 0, 0])} {/* 4. Linhas de Corrente e Vetores de Vento (Ilustrativo e Didático) */} - - {/* Vento de entrada (Barlavento) entrando pela frente em direção ao abrigo */} + + {/* Vento de entrada (Barlavento) entrando em direção ao abrigo */} @@ -195,7 +197,13 @@ function ShelterModel({ {isElevation ? 'CORTE A-A (Elevação)' : 'Abrigo / Pórtico (NBR 6123)'} - VENTO FRONTAL + {`VENTO ${windAngle === 0 ? 'FRONTAL (0°)' : windAngle === 45 ? 'OBLÍQUO (45°)' : 'LATERAL (90°)'}`} + + + Arrancamento [+Fz ↑] + + + Empuxo no Fundo [+Fx →] ); diff --git a/app/src/data/manuals/shelter.tsx b/app/src/data/manuals/shelter.tsx index 9dfb2c2..3dc6bd4 100644 --- a/app/src/data/manuals/shelter.tsx +++ b/app/src/data/manuals/shelter.tsx @@ -1,4 +1,3 @@ -import React from 'react'; export const shelterManual = { title: 'Abrigos, Marquises e Pórticos Fechados', diff --git a/app/src/lib/__tests__/table-shelters.test.ts b/app/src/lib/__tests__/table-shelters.test.ts new file mode 100644 index 0000000..96df3e1 --- /dev/null +++ b/app/src/lib/__tests__/table-shelters.test.ts @@ -0,0 +1,55 @@ +import { describe, it, expect } from 'vitest'; +import { + calculateShelterWind, + calculateShelterWindAllAngles, + type ShelterInput, +} from '../nbr-tables/table-shelters'; + +describe('NBR 6123 - Ações de Vento em Abrigos e Pórticos (table-shelters)', () => { + const baseInput: ShelterInput = { + condition: 'cond1', + depth: 6, + width: 10, + height: 4.8, + theta: 5, + backClosure: 100, + leftClosure: 100, + rightClosure: 100, + openingPos: 'distributed', + }; + + it('calcula corretamente o abrigo para incidência frontal a 0°', () => { + const res = calculateShelterWind({ ...baseInput, windAngle: 0 }, 1.0); + expect(res.windAngle).toBe(0); + expect(res.cpeTop).toBe(-1.0); + expect(res.cpiBottom).toBe(0.75); + expect(res.cnfVertical).toBe(-1.75); + expect(res.cfBack).toBe(1.3); + expect(res.cfSide).toBe(0.9); + }); + + it('calcula corretamente o abrigo para incidência oblíqua a 45° com vórtice crítico de canto', () => { + const res = calculateShelterWind({ ...baseInput, windAngle: 45 }, 1.0); + expect(res.windAngle).toBe(45); + expect(res.cpeTop).toBe(-1.3); // picos de sucção de canto + expect(res.cfBack).toBe(1.05); + expect(res.cfSide).toBe(1.05); + }); + + it('calcula corretamente o abrigo para incidência lateral a 90°', () => { + const res = calculateShelterWind({ ...baseInput, windAngle: 90 }, 1.0); + expect(res.windAngle).toBe(90); + expect(res.cpeTop).toBe(-1.1); + expect(res.cfSide).toBe(1.3); // lateral atua como parede barlavento frontal + }); + + it('calcula a envoltória de todas as 3 direções (0°, 45°, 90°)', () => { + const env = calculateShelterWindAllAngles(baseInput, 1.0); + expect(env.res0.windAngle).toBe(0); + expect(env.res45.windAngle).toBe(45); + expect(env.res90.windAngle).toBe(90); + expect(env.criticalUp.value).toBeGreaterThan(0); + expect(env.criticalFx.angle).toBe(0); // 0° é crítico para FxBack em cond1 + expect(env.criticalFy.angle).toBe(90); // 90° é crítico para FySide em cond1 + }); +}); diff --git a/app/src/lib/export-generic-pdf.tsx b/app/src/lib/export-generic-pdf.tsx index 185f7b0..ef653a7 100644 --- a/app/src/lib/export-generic-pdf.tsx +++ b/app/src/lib/export-generic-pdf.tsx @@ -102,7 +102,13 @@ function generateRecommendations(moduleName: string, sections: GenericPDFSection } const nameLower = moduleName.toLowerCase(); - if (nameLower.includes('cobertura') || nameLower.includes('isolada')) { + if (nameLower.includes('abrigo') || nameLower.includes('pórtico') || nameLower.includes('marquise')) { + recs.push("Em abrigos e pórticos em balanço, o arrancamento vertical (Fz) na cobertura é intensificado quando a parede de fundo é fechada devido ao aprisionamento de pressão (+Cpi)."); + recs.push("Verifique criteriosamente as ligações pilar-viga e a ancoragem das telhas na região da testeira, onde as tensões de arrancamento atingem picos críticos."); + if (hasSuction && forceValue > 15) { + recs.push(`A força de arrancamento calculada (${forceValue.toFixed(1)} kN) requer verificação da fundação dos pilares contra arrancamento e tombamento.`); + } + } else if (nameLower.includes('cobertura') || nameLower.includes('isolada')) { recs.push("Para coberturas isoladas, o vento gera sucções severas de arrancamento nas bordas. Verifique criteriosamente o dimensionamento e ancoragem das terças."); if (hasSuction && forceValue > 20) { recs.push(`A força de arrancamento vertical calculada (${forceValue.toFixed(1)} kN) exige detalhamento especial de fundação resistente a tração.`); diff --git a/app/src/lib/nbr-tables/table-shelters.ts b/app/src/lib/nbr-tables/table-shelters.ts index 215e9a6..e6265cb 100644 --- a/app/src/lib/nbr-tables/table-shelters.ts +++ b/app/src/lib/nbr-tables/table-shelters.ts @@ -1,11 +1,12 @@ /** * Módulo de Ações de Vento para Abrigos, Marquises e Pórticos com Fechamentos Parciais * NBR 6123:2023 — Combinação de Coberturas Isoladas (Tab. 24 e 25), Pressão Interna e Aberturas (sec. 6.3) - * e Placas / Painéis (Tab. 23). + * e Placas / Painéis (Tab. 23), suportando incidências do vento a 0°, 45° e 90°. */ export type ShelterCondition = 'cond1' | 'cond2' | 'cond3' | 'cond4'; export type OpeningPosition = 'top' | 'bottom' | 'distributed'; +export type ShelterWindAngle = 0 | 45 | 90; export interface ShelterInput { /** Condição topológica padrão (1=Estanque 3 lados, 2=Fundo fechado, 3=Túnel, 4=Aberto) */ @@ -28,9 +29,13 @@ export interface ShelterInput { rightClosure: number; /** Posição da abertura na parede de fundo ('top' | 'bottom' | 'distributed') */ openingPos: OpeningPosition; + /** Ângulo de incidência do vento (0° = frontal, 45° = oblíquo, 90° = lateral) */ + windAngle?: ShelterWindAngle; } export interface ShelterResult { + /** Ângulo de incidência do vento calculado (0, 45 ou 90) */ + windAngle: ShelterWindAngle; /** Coeficiente de pressão superior da cobertura (Ce superior) */ cpeTop: number; /** Coeficiente de pressão interna efetivo sob a cobertura (Cpi inferior) */ @@ -61,14 +66,25 @@ export interface ShelterResult { /** Carga vertical/horizontal na viga em balanço (kN/m) */ beamLoad: number; }; - /** Alívio percentual de arrancamento obtido pela posição da abertura no topo (%) */ + /** Alívio percentual de arrancamento obtido pela posição da abertura (%) */ reliefPercentage: number; /** Explicação normativa do comportamento */ statusText: string; } +export interface ShelterEnvelopeResult { + res0: ShelterResult; + res45: ShelterResult; + res90: ShelterResult; + criticalUp: { angle: ShelterWindAngle; value: number }; + criticalFx: { angle: ShelterWindAngle; value: number }; + criticalFy: { angle: ShelterWindAngle; value: number }; + criticalColumnLoad: { angle: ShelterWindAngle; value: number }; +} + /** - * Calcula os coeficientes e forças para Abrigos, Marquises e Pórticos. + * Calcula os coeficientes e forças para Abrigos, Marquises e Pórticos + * considerando incidência do vento a 0° (frontal), 45° (oblíquo) ou 90° (lateral). */ export function calculateShelterWind(input: ShelterInput, q: number): ShelterResult { const { @@ -84,6 +100,8 @@ export function calculateShelterWind(input: ShelterInput, q: number): ShelterRes openingPos, } = input; + const windAngle: ShelterWindAngle = input.windAngle ?? 0; + const areaRoof = depth * width; const areaBack = width * height; const areaSide = depth * height; @@ -92,26 +110,58 @@ export function calculateShelterWind(input: ShelterInput, q: number): ShelterRes const bClose = Math.max(0, Math.min(100, backClosure)) / 100; const lClose = Math.max(0, Math.min(100, leftClosure)) / 100; const rClose = Math.max(0, Math.min(100, rightClosure)) / 100; + const sideAvgClose = (lClose + rClose) / 2; - // 1. Coeficiente externo superior na cobertura (Tabela 24 para uma água) - // Varia entre -0.8 a -1.5 de sucção de barlavento, dependendo de theta - const cpeTop = theta <= 5 ? -1.0 : theta <= 15 ? -1.2 : -1.4; + // 1. Coeficiente externo superior na cobertura (Ce superior) + // Varia conforme o ângulo de incidência (0°, 45° ou 90°) + let cpeTop = -1.0; + if (windAngle === 0) { + // Frontal: -1.0 a -1.4 de sucção de barlavento, dependendo de theta + cpeTop = theta <= 5 ? -1.0 : theta <= 15 ? -1.2 : -1.4; + } else if (windAngle === 45) { + // Oblíquo 45°: forte vórtice cônico de canto nas bordas de barlavento (aumento de sucção em ~20% a 30%) + cpeTop = theta <= 5 ? -1.3 : theta <= 15 ? -1.4 : -1.6; + } else { + // Lateral 90°: escoamento transversal sobre a cobertura, sucção constante por descolamento + cpeTop = -1.1; + } // 2. Cálculo do Cpi sob a cobertura (efeito estagnação da parede traseira / laterais) - // Quando o fundo é fechado e o ar é contido, cpiBottom torna-se positivo e elevado let baseCpi = 0.0; - if (condition === 'cond1') { - // 3 lados fechados -> grande armadilha aerodinâmica (+0.75 estanque) - baseCpi = 0.75 * bClose * ((lClose + rClose) / 2); - } else if (condition === 'cond2') { - // Fundo fechado, lados abertos -> sobrepressão moderada (+0.45) - baseCpi = 0.45 * bClose; - } else if (condition === 'cond3') { - // Túnel (fundo aberto, lados fechados) -> efeito Venturi / sucção inferior (-0.35) - baseCpi = -0.35 * ((lClose + rClose) / 2); + if (windAngle === 0) { + if (condition === 'cond1') { + baseCpi = 0.75 * bClose * sideAvgClose; + } else if (condition === 'cond2') { + baseCpi = 0.45 * bClose; + } else if (condition === 'cond3') { + baseCpi = -0.35 * sideAvgClose; + } else { + baseCpi = -0.1 * bClose; + } + } else if (windAngle === 45) { + if (condition === 'cond1') { + baseCpi = 0.68 * bClose * sideAvgClose; + } else if (condition === 'cond2') { + baseCpi = 0.35 * bClose; + } else if (condition === 'cond3') { + baseCpi = 0.25 * sideAvgClose; + } else { + baseCpi = -0.1 * bClose; + } } else { - // Condição 4: Livre (apenas obstrução leve ou nula) - baseCpi = -0.1 * bClose; + // windAngle === 90 + if (condition === 'cond1') { + // Estanque 3 lados: vento a 90° incide sobre lateral fechada com fundo fechado retendo o ar + baseCpi = 0.65 * sideAvgClose * bClose; + } else if (condition === 'cond2') { + // Fundo fechado, mas laterais ABERTAS: vento a 90° passa livremente sob o telhado -> sucção + baseCpi = -0.25; + } else if (condition === 'cond3') { + // Túnel (laterais fechadas, fundo aberto): lateral se comporta como parede frontal + baseCpi = 0.50 * sideAvgClose; + } else { + baseCpi = -0.1 * bClose; + } } // 3. Efeito da posição da abertura (Topo vs Base vs Distribuída) @@ -120,67 +170,101 @@ export function calculateShelterWind(input: ShelterInput, q: number): ShelterRes if (backRange && bClose > 0.05 && bClose < 0.99) { const topGapPct = Math.max(0, Math.min(100, 100 - backRange[1])) / 100; - // O alívio de arrancamento da cobertura depende diretamente da abertura no topo (fresta superior) reliefPercentage = Math.round(35 * Math.min(1, topGapPct * 2.5)); effectiveCpi = baseCpi * (1 - reliefPercentage / 100); } else if (bClose > 0.05 && bClose < 0.99) { - const openRatio = 1 - bClose; // fração aberta da parede de fundo + const openRatio = 1 - bClose; if (openingPos === 'top') { - // Fresta superior permite o escape de sobrepressão debaixo do telhado - // Reduz o Cpi positivo de forma proporcional ao tamanho da fresta no topo reliefPercentage = Math.round(35 * openRatio); effectiveCpi = baseCpi * (1 - reliefPercentage / 100); } else if (openingPos === 'bottom') { - // Abertura inferior: o ar flui embaixo, mantendo pressão sob o telhado reliefPercentage = 0; effectiveCpi = baseCpi * 1.05; } else { - // Distribuída: alívio proporcional à permeabilidade aberta reliefPercentage = Math.round(18 * openRatio); effectiveCpi = baseCpi * (1 - reliefPercentage / 100); } } // Cnf vertical total na cobertura (arrancamento para cima = negativo) - // Sugção para cima (FzUp): cpeTop (-) - effectiveCpi (+) => força de sucção efetiva total const cnfUp = cpeTop - Math.max(0, effectiveCpi); - const cnfDown = 0.2 - Math.min(0, effectiveCpi); // Carregamento descendente + const cnfDown = 0.2 - Math.min(0, effectiveCpi); - // 4. Coeficientes em paredes de fundo e laterais (Tabela 23 / 6) - const cfBack = 1.3 * bClose; - const cfSide = 0.9 * ((lClose + rClose) / 2); + // 4. Coeficientes horizontais em paredes de fundo e laterais + let cfBack = 1.3 * bClose; + let cfSide = 0.9 * sideAvgClose; + + if (windAngle === 45) { + cfBack = 1.05 * bClose; + cfSide = 1.05 * sideAvgClose; + } else if (windAngle === 90) { + // Vento a 90°: as paredes laterais viram superfície frontal barlavento + cfSide = 1.3 * sideAvgClose; + cfBack = 0.7 * bClose; + } // 5. Forças globais (kN) const fzUp = Math.abs(cnfUp) * q * areaRoof; const fzDown = Math.abs(cnfDown) * q * areaRoof; const fxBack = cfBack * q * areaBack; const fySide = cfSide * q * (areaSide * 2); - const fFriction = 0.04 * q * areaRoof; // Atrito no telhado + const fFriction = 0.04 * q * areaRoof; - // 6. Carga linear em pórticos (assumindo 2 pórticos principais como no croqui, ex: vão b/2) - // Carga no pilar (kN/m) + // 6. Carga linear em pórticos (2 pórticos principais) const numPorticos = 2; - const columnLoad = (fxBack / height / numPorticos) + (q * 1.8 * 0.15); // arrasto do tubo 150x150 + let columnLoad = 0; + if (windAngle === 0) { + columnLoad = (fxBack / height / numPorticos) + (q * 1.8 * 0.15); + } else if (windAngle === 45) { + const combForce = Math.sqrt(fxBack * fxBack + fySide * fySide); + columnLoad = (combForce / height / numPorticos) + (q * 1.8 * 0.15); + } else { + // Em 90°, a carga transversal principal atua nas paredes laterais (Fy) + columnLoad = (fySide / height / numPorticos) + (q * 1.8 * 0.15); + } + const beamLoad = (fzUp / depth / numPorticos); - // Texto explicativo do regime + // Texto explicativo por condição e ângulo let statusText = ''; + const anglePrefix = + windAngle === 0 + ? 'Vento Frontal (0°)' + : windAngle === 45 + ? 'Vento Oblíquo (45°)' + : 'Vento Transversal / Lateral (90°)'; + switch (condition) { case 'cond1': - statusText = 'Abrigo estanque em 3 lados (Condição 1): Forte acúmulo de pressão positiva sob a cobertura, gerando pico crítico de arrancamento vertical e empuxo na parede de fundo.'; + statusText = `${anglePrefix} — Abrigo estanque em 3 lados (Condição 1): Forte retenção de pressão positiva sob a cobertura. ${ + windAngle === 45 + ? 'O vento a 45° gera pico crítico de arrancamento devido a vórtices de canto em barlavento.' + : windAngle === 90 + ? 'O vento a 90° incide diretamente na parede lateral com acúmulo contra o fundo fechado.' + : 'Gera pico crítico de arrancamento vertical e empuxo na parede de fundo.' + }`; break; case 'cond2': - statusText = 'Abrigo com fundo fechado e laterais abertas (Condição 2): A parede de fundo atua como anteparo, elevando a sobrepressão sob o balanço junto à testeira traseira.'; + statusText = `${anglePrefix} — Abrigo com fundo fechado e laterais abertas (Condição 2): ${ + windAngle === 90 + ? 'Com incidência a 90°, o vento escoa livremente entre as laterais abertas sem gerar sobrepressão (+Cpi) sob o telhado.' + : 'A parede de fundo atua como anteparo, elevando a sobrepressão sob o balanço.' + }`; break; case 'cond3': - statusText = 'Abrigo tipo túnel (Condição 3): Abertura frontal e traseira provocam efeito Venturi, com sucção interna na cobertura e paredes laterais sujeitas a arrasto.'; + statusText = `${anglePrefix} — Abrigo tipo túnel (Condição 3): ${ + windAngle === 90 + ? 'Com vento a 90°, o fechamento lateral comporta-se como anteparo barlavento, gerando empuxo transversal elevado.' + : 'Aberturas frontal e traseira provocam efeito Venturi, com sucção interna na cobertura.' + }`; break; case 'cond4': - statusText = 'Cobertura livre / aberta em 4 lados (Condição 4): Escoamento desimpedido ao redor e sob o telhado, seguindo os coeficientes líquidos da Tabela 24 da NBR 6123.'; + statusText = `${anglePrefix} — Cobertura livre (Condição 4): Escoamento desimpedido ao redor e sob o telhado, com ações orientadas pela Tabela 24 da NBR 6123.`; break; } return { + windAngle, cpeTop: Number(cpeTop.toFixed(2)), cpiBottom: Number(effectiveCpi.toFixed(2)), cnfVertical: Number(cnfUp.toFixed(2)), @@ -201,3 +285,38 @@ export function calculateShelterWind(input: ShelterInput, q: number): ShelterRes statusText, }; } + +/** + * Calcula a envoltória e os resultados para as 3 direções de vento (0°, 45° e 90°). + */ +export function calculateShelterWindAllAngles(input: ShelterInput, q: number): ShelterEnvelopeResult { + const res0 = calculateShelterWind({ ...input, windAngle: 0 }, q); + const res45 = calculateShelterWind({ ...input, windAngle: 45 }, q); + const res90 = calculateShelterWind({ ...input, windAngle: 90 }, q); + + const angles: ShelterWindAngle[] = [0, 45, 90]; + const results = [res0, res45, res90]; + + let critUpIdx = 0; + let critFxIdx = 0; + let critFyIdx = 0; + let critColIdx = 0; + + for (let i = 1; i < 3; i++) { + if (results[i].forces.fzUp > results[critUpIdx].forces.fzUp) critUpIdx = i; + if (results[i].forces.fxBack > results[critFxIdx].forces.fxBack) critFxIdx = i; + if (results[i].forces.fySide > results[critFyIdx].forces.fySide) critFyIdx = i; + if (results[i].lineLoads.columnLoad > results[critColIdx].lineLoads.columnLoad) critColIdx = i; + } + + return { + res0, + res45, + res90, + criticalUp: { angle: angles[critUpIdx], value: results[critUpIdx].forces.fzUp }, + criticalFx: { angle: angles[critFxIdx], value: results[critFxIdx].forces.fxBack }, + criticalFy: { angle: angles[critFyIdx], value: results[critFyIdx].forces.fySide }, + criticalColumnLoad: { angle: angles[critColIdx], value: results[critColIdx].lineLoads.columnLoad }, + }; +} + diff --git a/app/src/pages/ShelterModule.tsx b/app/src/pages/ShelterModule.tsx index a4fc645..6e6da49 100644 --- a/app/src/pages/ShelterModule.tsx +++ b/app/src/pages/ShelterModule.tsx @@ -6,8 +6,10 @@ import { Button } from '@/components/ui/button'; import { useWindStore } from '@/store/appStore'; import { calculateShelterWind, + calculateShelterWindAllAngles, type ShelterCondition, type OpeningPosition, + type ShelterWindAngle, } from '@/lib/nbr-tables/table-shelters'; import Shelter3D from '@/components/three/Shelter3D'; import { WindFlowGrid2D } from '@/components/WindFlowGrid2D'; @@ -30,6 +32,7 @@ const conditionLabels: Record = { const ShelterModule: React.FC = () => { const { q } = useWindStore(); const [condition, setCondition] = useState('cond1'); + const [windAngle, setWindAngle] = useState(0); const [depth, setDepth] = useState(6); const [width, setWidth] = useState(10); const [height, setHeight] = useState(4.8); @@ -75,8 +78,8 @@ const ShelterModule: React.FC = () => { } }; - const result = useMemo(() => { - return calculateShelterWind({ + const { result, envelope } = useMemo(() => { + const input = { condition, depth, width, @@ -87,31 +90,151 @@ const ShelterModule: React.FC = () => { leftClosure, rightClosure, openingPos, - }, q); - }, [condition, q, depth, width, height, theta, backClosure, backRange, leftClosure, rightClosure, openingPos]); + }; + return { + result: calculateShelterWind({ ...input, windAngle }, q), + envelope: calculateShelterWindAllAngles(input, q), + }; + }, [condition, q, depth, width, height, theta, backClosure, backRange, leftClosure, rightClosure, openingPos, windAngle]); const handleExportPDF = () => { + const angleLabel = windAngle === 0 ? '0° (Frontal)' : windAngle === 45 ? '45° (Oblíquo)' : '90° (Lateral)'; const sections: GenericPDFSection[] = [ { - title: 'Geometria e Fechamentos do Abrigo', + title: 'Geometria, Inclinação e Fechamentos do Abrigo', type: 'grid', gridItems: [ { label: 'Comprimento (d / balanço)', value: `${depth} m` }, { label: 'Largura (b)', value: `${width} m` }, - { label: 'Altura (h)', value: `${height} m` }, - { label: 'Inclinação (θ)', value: `${theta}°` }, - { label: 'Fechamento Fundo', value: `${backClosure}%` }, + { label: 'Altura do Pilar (h)', value: `${height} m` }, + { label: 'Inclinação da Cobertura (θ)', value: `${theta}°` }, + { label: 'Incidência do Vento (Direção)', value: angleLabel }, + { label: 'Fechamento da Parede Traseira', value: `${backClosure}%` }, + { label: 'Faixa de Parede [Base, Topo]', value: backRange ? `${backRange[0]}% até ${backRange[1]}%` : '0% até 100%' }, { label: 'Fechamento Lateral Esq / Dir', value: `${leftClosure}% / ${rightClosure}%` }, - { label: 'Posição da Abertura', value: openingPos }, ], }, { - title: 'Forças Resultantes (kN)', + title: `Coeficientes Aerodinâmicos e Pressões Líquidas - Direção ${angleLabel}`, type: 'grid', gridItems: [ - { label: 'Força Vertical (Fz)', value: `${result.forces.fzUp} kN` }, - { label: 'Empuxo Frontal (Fx)', value: `${result.forces.fxBack} kN` }, - { label: 'Empuxo Laterais (Fy)', value: `${result.forces.fySide} kN` }, + { label: 'Ce Superior (Cobertura - Tab. 24)', value: result.cpeTop }, + { label: 'Cpi Inferior (Sobrepressão sob Telhado)', value: result.cpiBottom }, + { label: 'Cnf Resultante (Ce - Cpi)', value: result.cnfVertical }, + { label: 'Cf Parede de Fundo (Tab. 23 / 6)', value: result.cfBack }, + { label: 'Cf Paredes Laterais', value: result.cfSide }, + { label: 'Alívio por Fresta Superior', value: `${result.reliefPercentage}%` }, + ], + }, + { + title: 'Envoltória e Comparativo Normativo — As 3 Incidências de Vento (0° • 45° • 90°)', + type: 'table', + tableHeaders: ['Parâmetro / Força', '0° (Frontal)', '45° (Oblíquo)', '90° (Lateral)', 'Envoltória / Crítico'], + tableRows: [ + [ + 'Ce Superior (Cobertura)', + String(envelope.res0.cpeTop), + String(envelope.res45.cpeTop), + String(envelope.res90.cpeTop), + `Máx: ${Math.min(envelope.res0.cpeTop, envelope.res45.cpeTop, envelope.res90.cpeTop)} (Sucção)` + ], + [ + 'Cpi Inferior (Sob Telhado)', + String(envelope.res0.cpiBottom), + String(envelope.res45.cpiBottom), + String(envelope.res90.cpiBottom), + `Máx: +${Math.max(envelope.res0.cpiBottom, envelope.res45.cpiBottom, envelope.res90.cpiBottom).toFixed(2)}` + ], + [ + 'Cnf Vertical Líquido', + String(envelope.res0.cnfVertical), + String(envelope.res45.cnfVertical), + String(envelope.res90.cnfVertical), + `Crítico: ${Math.min(envelope.res0.cnfVertical, envelope.res45.cnfVertical, envelope.res90.cnfVertical)}` + ], + [ + 'Força Vertical Telhado [Fz ↑]', + `${envelope.res0.forces.fzUp} kN`, + `${envelope.res45.forces.fzUp} kN`, + `${envelope.res90.forces.fzUp} kN`, + `${envelope.criticalUp.value} kN (${envelope.criticalUp.angle}°)` + ], + [ + 'Empuxo Parede Traseira [Fx →]', + `${envelope.res0.forces.fxBack} kN`, + `${envelope.res45.forces.fxBack} kN`, + `${envelope.res90.forces.fxBack} kN`, + `${envelope.criticalFx.value} kN (${envelope.criticalFx.angle}°)` + ], + [ + 'Empuxo Paredes Laterais [Fy ↗]', + `${envelope.res0.forces.fySide} kN`, + `${envelope.res45.forces.fySide} kN`, + `${envelope.res90.forces.fySide} kN`, + `${envelope.criticalFy.value} kN (${envelope.criticalFy.angle}°)` + ], + [ + 'Carga Horiz. no Pilar', + `${envelope.res0.lineLoads.columnLoad} kN/m`, + `${envelope.res45.lineLoads.columnLoad} kN/m`, + `${envelope.res90.lineLoads.columnLoad} kN/m`, + `${envelope.criticalColumnLoad.value} kN/m (${envelope.criticalColumnLoad.angle}°)` + ], + [ + 'Carga Vert. na Viga', + `${envelope.res0.lineLoads.beamLoad} kN/m`, + `${envelope.res45.lineLoads.beamLoad} kN/m`, + `${envelope.res90.lineLoads.beamLoad} kN/m`, + `${Math.max(envelope.res0.lineLoads.beamLoad, envelope.res45.lineLoads.beamLoad, envelope.res90.lineLoads.beamLoad).toFixed(2)} kN/m` + ], + ], + }, + { + title: `Forças Resultantes no Pórtico (${angleLabel}) (kN)`, + type: 'grid', + gridItems: [ + { label: 'Força Vertical no Telhado [Fz ↑]', value: `${result.forces.fzUp} kN` }, + { label: 'Empuxo na Parede Traseira [Fx →]', value: `${result.forces.fxBack} kN` }, + { label: 'Empuxo nas Paredes Laterais [Fy ↗]', value: `${result.forces.fySide} kN` }, + { label: 'Força de Atrito no Telhado [F_atrito]', value: `${result.forces.fFriction} kN` }, + ], + }, + { + title: `Cargas Lineares para Dimensionamento (${angleLabel}) (kN/m)`, + type: 'grid', + gridItems: [ + { label: 'Carga Horizontal no Pilar Traseiro', value: `${result.lineLoads.columnLoad} kN/m` }, + { label: 'Carga Vertical na Viga em Balanço', value: `${result.lineLoads.beamLoad} kN/m` }, + ], + }, + { + title: 'Parecer Técnico do Regime Topológico e Aerodinâmico', + type: 'text', + content: `${result.statusText} Permeabilidade líquida da parede de fundo: ${100 - backClosure}%. Alívio de arrancamento obtido pela fresta superior: ${result.reliefPercentage}%. O relatório acima apresenta a tabela comparativa das 3 incidências de vento (0°, 45° e 90°) para garantir a verificação da envoltória mais desfavorável conforme a NBR 6123:2023. As cargas lineares calculadas (kN/m) podem ser aplicadas diretamente em softwares de pórtico plano como Ftool, SAP2000 ou CYPE 3D.`, + }, + { + title: 'Guia de Aplicação dos Eixos e Resultantes no Croqui', + type: 'table', + tableHeaders: ['Eixo / Componente', 'Símbolo', 'Sentido e Aplicação no Croqui', 'Carga Distribuída'], + tableRows: [ + [ + 'Eixo Z (Vertical)', + 'Fz (↑)', + 'Perpendicular ao telhado. Valor (+) indica sucção para cima (arrancamento); (-) indica carga descendente.', + `${result.lineLoads.beamLoad} kN/m na viga` + ], + [ + 'Eixo X (Longitudinal)', + 'Fx (→)', + 'Na direção do vento (frente -> fundo). Atua como empuxo horizontal contra a parede traseira.', + `${result.lineLoads.columnLoad} kN/m no pilar` + ], + [ + 'Eixo Y (Transversal)', + 'Fy (↗)', + 'Perpendicular ao vento. Atua horizontalmente contra o fechamento das paredes laterais.', + '—' + ], ], }, ]; @@ -133,7 +256,7 @@ const ShelterModule: React.FC = () => { @@ -160,6 +283,27 @@ const ShelterModule: React.FC = () => { +
+ +
+ {([0, 45, 90] as ShelterWindAngle[]).map((ang) => ( + + ))} +
+
+