From 88c818ec4e9cbf8ad99683d27c33f4ea88a70599 Mon Sep 17 00:00:00 2001 From: Marcos Date: Mon, 27 Jul 2026 16:55:52 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=80=20Auto-deploy:=20BrainWind=20atual?= =?UTF-8?q?izado=20em=2027/07/2026=2016:55:52?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/App.tsx | 6 +- app/src/components/EducationalManual.tsx | 39 +++ app/src/components/three/Shelter3D.tsx | 205 ++++++++++++ app/src/data/manuals/index.ts | 2 + app/src/data/manuals/shelter.tsx | 71 ++++ app/src/lib/i18n.ts | 1 + app/src/lib/nbr-tables/table-shelters.ts | 193 +++++++++++ app/src/pages/ShelterModule.tsx | 408 +++++++++++++++++++++++ 8 files changed, 924 insertions(+), 1 deletion(-) create mode 100644 app/src/components/three/Shelter3D.tsx create mode 100644 app/src/data/manuals/shelter.tsx create mode 100644 app/src/lib/nbr-tables/table-shelters.ts create mode 100644 app/src/pages/ShelterModule.tsx diff --git a/app/src/App.tsx b/app/src/App.tsx index e6c705a..01762d1 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -12,11 +12,12 @@ import CalcCaModule from './pages/CalcCaModule'; import SettingsModule from './pages/SettingsModule'; import TowerModule from './pages/TowerModule'; import PiperackModule from './pages/PiperackModule'; +import ShelterModule from './pages/ShelterModule'; import CertificatePage from './pages/CertificatePage'; import { Home, Settings, Menu, Cylinder, Church, CircleDot, Square, Layers, BarChart3, Activity, Warehouse, - Settings2, Sun, Moon, Frame, FolderOpen, BookOpen, MoreVertical, Info, ShieldCheck, Calculator + Settings2, Sun, Moon, Frame, FolderOpen, BookOpen, MoreVertical, Info, ShieldCheck, Calculator, Tent } from 'lucide-react'; import { DropdownMenu, @@ -95,6 +96,7 @@ function AppLayout({ children }: { children: React.ReactNode }) { { path: '/pontes', icon: , labelKey: 'nav_bridge' as const }, { path: '/torre', icon: , labelKey: 'nav_tower' as const }, { path: '/piperack', icon: , labelKey: 'nav_piperack' as const }, + { path: '/abrigo', icon: , labelKey: 'nav_shelter' as const }, { path: '/dinamica', icon: , labelKey: 'nav_dynamics' as const }, { path: '/calc-ca', icon: , labelKey: 'nav_calc_ca' as const }, ]; @@ -358,6 +360,7 @@ function HomeMock() { { to: '/pontes', icon: BridgeIcon, label: 'Pontes', desc: 'Pse, Cx/Cz do tabuleiro, flutter, galope (sec. 11).' }, { to: '/torre', icon: TowerIcon, label: 'Torres', desc: 'Torres treliçadas e mastros (sec. 8.4).' }, { to: '/piperack', icon: Frame, label: 'Pipe-Racks Industriais', desc: 'Estruturas reticuladas múltiplas, tubulações, fator η (Tab. 28).' }, + { to: '/abrigo', icon: Tent, label: 'Abrigos e Pórticos Fechados', desc: 'Marquises, abrigos e pórticos. 4 condições topológicas, frestas de alívio, corte A-A (Tab. 23-25, sec. 6.3).' }, { to: '/calc-ca', icon: Calculator, label: 'Calculadora de C_a', desc: 'Coeficiente de arrasto (Fig. 4 e 5).' }, ]; @@ -423,6 +426,7 @@ function App() { } /> } /> } /> + } /> } /> } /> } /> diff --git a/app/src/components/EducationalManual.tsx b/app/src/components/EducationalManual.tsx index 824f14f..1d49884 100644 --- a/app/src/components/EducationalManual.tsx +++ b/app/src/components/EducationalManual.tsx @@ -169,6 +169,45 @@ export function EducationalManual({ type, params }: EducationalManualProps) { } } + // --- Abrigos e Pórticos (shelter) --- + if (type === 'shelter') { + const condition = params.condition || 'cond1'; + const openingPos = params.openingPos || 'top'; + const backClosure = Number(params.backClosure || 80); + + explanations.push( +
+ + {condition === 'cond1' ? 'Abrigo Estanque em 3 Lados (Condição 1)' : + condition === 'cond2' ? 'Abrigo com Parede Traseira (Condição 2)' : + condition === 'cond3' ? 'Abrigo Tipo Túnel (Condição 3)' : 'Cobertura Livre (Condição 4)'} + +

+ {condition === 'cond1' ? 'Fundo e laterais fechados funcionam como armadilha aerodinâmica, concentrando pressão positiva (Cpi elevado) debaixo da marquise.' : + condition === 'cond2' ? 'A parede de fundo gera anteparo de estagnação frontal, enquanto os lados abertos permitem escape lateral do fluxo.' : + condition === 'cond3' ? 'O fluxo longitudinal sofre efeito Venturi, gerando sucção sob o telhado e arrasto de atrito em túnel.' : + 'O fluxo passa livremente pela cobertura, seguindo coeficientes da Tabela 24.'} +

+
+ ); + + if (backClosure > 0 && backClosure < 100) { + explanations.push( +
+ + {openingPos === 'top' ? 'Fresta Superior (Alívio de Arrancamento)' : + openingPos === 'bottom' ? 'Vão Livre na Base (Placa Suspensa)' : 'Permeabilidade Distribuída'} + +

+ {openingPos === 'top' ? 'A fresta no topo da parede permite que a pressão sob o telhado escape em direção a sotavento, aliviando significativamente a força vertical de arrancamento na cobertura (até 25% menos sucção líquida).' : + openingPos === 'bottom' ? 'O vento passa por baixo e concentra a pressão horizontal na metade superior da parede (como placa suspensa na Tab. 23), aumentando o momento fletor na base dos pilares.' : + 'Aberturas distribuídas atenuam suavemente a pressão interna de acordo com a razão de áreas abertas (sec. 6.3).'} +

+
+ ); + } + } + if (explanations.length === 0) return null; return ( diff --git a/app/src/components/three/Shelter3D.tsx b/app/src/components/three/Shelter3D.tsx new file mode 100644 index 0000000..074efe3 --- /dev/null +++ b/app/src/components/three/Shelter3D.tsx @@ -0,0 +1,205 @@ +import React, { useRef } from 'react'; +import * as THREE from 'three'; +import { useFrame } from '@react-three/fiber'; +import { Text, Line } from '@react-three/drei'; +import type { ShelterCondition, OpeningPosition } from '@/lib/nbr-tables/table-shelters'; + +export interface Shelter3DProps { + condition: ShelterCondition; + depth: number; + width: number; + height: number; + theta: number; + backClosure: number; + leftClosure: number; + rightClosure: number; + openingPos: OpeningPosition; + viewMode: '3d' | 'elevation'; +} + +const WindArrow: React.FC<{ + start: [number, number, number]; + end: [number, number, number]; + color?: string; + speed?: number; +}> = ({ start, end, color = '#38bdf8', speed = 1.5 }) => { + const arrowRef = useRef(null); + useFrame(({ clock }) => { + if (arrowRef.current) { + const t = (clock.getElapsedTime() * speed) % 1; + const x = start[0] + (end[0] - start[0]) * t; + const y = start[1] + (end[1] - start[1]) * t; + const z = start[2] + (end[2] - start[2]) * t; + arrowRef.current.position.set(x, y, z); + } + }); + + const dir = new THREE.Vector3(...end).sub(new THREE.Vector3(...start)); + const len = dir.length(); + const angleZ = Math.atan2(dir.y, dir.x); + + return ( + + + + + + + ); +}; + +export const Shelter3D: React.FC = ({ + condition, + depth, + width, + height, + theta, + backClosure, + leftClosure, + rightClosure, + openingPos, + viewMode, +}) => { + const isElevation = viewMode === 'elevation'; + + // Geometria básica + const halfW = width / 2; + const halfD = depth / 2; + const thetaRad = (theta * Math.PI) / 180; + const slopeRise = depth * Math.tan(thetaRad); + + // Paredes com abertura no topo ou na base + const renderWallWithGap = ( + w: number, + h: number, + closure: number, + pos: OpeningPosition, + rotY = 0, + position: [number, number, number] = [0, 0, 0] + ) => { + if (closure <= 0) return null; + + const op = closure / 100; + const wallH = h * op; + let yPos = position[1] + wallH / 2; + + if (pos === 'top' && op < 1) { + // Parede na parte de baixo, fresta no topo + yPos = position[1] + wallH / 2; + } else if (pos === 'bottom' && op < 1) { + // Parede suspensa na parte superior, vão na base + yPos = position[1] + h - wallH / 2; + } + + return ( + + + + + + {/* Linha de contorno */} + + + + + + ); + }; + + return ( + + {/* Plano do Solo */} + + + + + + + {/* 1. Estrutura Metálica Principal (Pilares e Vigas em Balanço - Tubos Quadrados) */} + {[-halfW + 0.6, halfW - 0.6].map((xPos, idx) => ( + + {/* Pilar traseiro (lado direito na elevação) */} + + + + + {/* Viga horizontal em balanço (TUBO QUAD. 150x150) */} + + + + + + + + ))} + + {/* 2. Cobertura / Marquise */} + + + + + + + + + + + + {/* 3. Fechamentos de Fundo e Laterais (com suporte a fresta/abertura) */} + {/* Parede de Fundo (Traseira Z = +halfD) */} + {renderWallWithGap(width, height, backClosure, openingPos, 0, [0, 0, halfD])} + + {/* Parede Lateral Esquerda (X = -halfW) */} + {renderWallWithGap(depth, height, leftClosure, 'distributed', Math.PI / 2, [-halfW, 0, 0])} + + {/* Parede Lateral Direita (X = +halfW) */} + {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) */} + + + + + {/* Linhas de corrente sobre o telhado */} + + + {/* Fresta na parede de fundo: se houver abertura no topo, mostrar vento escapando pela fresta superior! */} + {backClosure > 0 && backClosure < 100 && openingPos === 'top' && ( + <> + + + Alívio (Fresta Topo) + + + )} + + {/* Fresta na base */} + {backClosure > 0 && backClosure < 100 && openingPos === 'bottom' && ( + <> + + + Vão Livre Inferior + + + )} + + + {/* Textos Informativos 3D */} + + {isElevation ? 'CORTE A-A (Elevação)' : 'Abrigo / Pórtico (NBR 6123)'} + + + VENTO FRONTAL + + + ); +}; + +export default Shelter3D; diff --git a/app/src/data/manuals/index.ts b/app/src/data/manuals/index.ts index cc56a3d..e646bef 100644 --- a/app/src/data/manuals/index.ts +++ b/app/src/data/manuals/index.ts @@ -10,6 +10,7 @@ import { dynamicsManual } from './dynamics'; import { isolatedRoofManual } from './isolated-roof'; import { piperackManual } from './piperack'; import { calcCaManual } from './calc_ca'; +import { shelterManual } from './shelter'; export const manuals: Record = { bridge: bridgeManual, @@ -24,4 +25,5 @@ export const manuals: Record = { 'isolated-roof': isolatedRoofManual, piperack: piperackManual, calc_ca: calcCaManual, + shelter: shelterManual, }; diff --git a/app/src/data/manuals/shelter.tsx b/app/src/data/manuals/shelter.tsx new file mode 100644 index 0000000..bfaa51d --- /dev/null +++ b/app/src/data/manuals/shelter.tsx @@ -0,0 +1,71 @@ +import React from 'react'; + +export const shelterManual = { + title: 'Abrigos, Marquises e Pórticos Fechados', + intro: 'Ação do vento em coberturas apoiadas ou em balanço com fechamentos laterais/fundo e aberturas parciais (NBR 6123:2023 — Tabelas 23, 24, 25 e Seção 6.3).', + sections: [ + { + title: '1. O Conceito das 4 Condições Topológicas', + content: ( +
+

+ Abrigos, marquises e garagens semiabertas combinam o escoamento ao redor da cobertura (Tabelas 24/25) com o efeito de retenção ou canalização do vento gerado pelas paredes de fechamento (Seção 6.3 — Pressão Interna). +

+
    +
  • + Condição 1 (Estanque em 3 lados - Fundo e Laterais fechados): O vento frontal fica contido como em uma armadilha, gerando alta pressão interna positiva (Cpi até +0,8). Essa pressão empurra o telhado para cima, somando-se à sucção externa e gerando o pico máximo de arrancamento. +
  • +
  • + Condição 2 (Fundo fechado e Laterais abertas - Marquise encostada): A parede traseira funciona como anteparo de estagnação. O fluxo escapa lateralmente, mas mantém sobrepressão moderada sob a cobertura na zona junto ao fundo. +
  • +
  • + Condição 3 (Fundo aberto e Laterais fechadas - Túnel / Passarela): A aceleração do ar dentro do túnel (Efeito Venturi) gera depressão (Cpi negativo) na face inferior da cobertura, reduzindo o arrancamento vertical, porém elevando o arrasto de atrito longitudinal. +
  • +
  • + Condição 4 (Totalmente aberto em 4 lados): Recai no regime puro de Cobertura Isolada (Tabela 24 da norma). +
  • +
+
+ ), + }, + { + title: '2. Fechamentos Parciais (%) e Posição da Abertura', + content: ( +
+

+ O controle percentual de fechamento de cada face permite calcular a permeabilidade μ e a pressão interna efetiva conforme a Seção 6.3: +

+
+ Fz (Arrancamento) = q · [ Ce_sup(-) - Cpi_inf(+) ] · A_roof +
+

+ Por que a posição da abertura (Topo vs. Base) é tão importante? +

+
    +
  • + Abertura no Topo (Fresta Superior / Lanternim): O ar sob pressão escapa diretamente pela fresta entre a parede e o telhado. Isso alivia a pressão interna sob a cobertura, reduziando a força vertical de arrancamento em até 25% a 30%! +
  • +
  • + Abertura na Base (Vão Livre Inferior): O ar circula rente ao solo, transformando a parede traseira em uma placa suspensa (Tabela 23). Isso eleva o centro de pressão horizontal e aumenta o momento fletor na base dos pilares do pórtico. +
  • +
+
+ ), + }, + { + title: '3. Aplicação em Pórticos Metálicos (Croqui CORTE A-A)', + content: ( +
+

+ Em estruturas compostas por perfis metálicos (como Tubos Quadrados 150x150x8.8 mm em balanço), o cálculo combina: +

+
    +
  1. Ação vertical de sucção na cobertura ($F_z$, em kN).
  2. +
  3. Empuxo horizontal frontal sobre a parede e testeira ($F_x$, em kN).
  4. +
  5. Ação aerodinâmica linear sobre os perfis do pórtico ($\text{kN/m}$), calculada pela Tabela 26/27.
  6. +
+
+ ), + }, + ], +}; diff --git a/app/src/lib/i18n.ts b/app/src/lib/i18n.ts index 48223b9..4f34fb7 100644 --- a/app/src/lib/i18n.ts +++ b/app/src/lib/i18n.ts @@ -59,6 +59,7 @@ const translations: Dict = { nav_bridge: { 'pt-BR': 'Pontes', 'en-US': 'Bridges' }, nav_tower: { 'pt-BR': 'Torres', 'en-US': 'Towers' }, nav_piperack: { 'pt-BR': 'Pipe-rack', 'en-US': 'Pipe-rack' }, + nav_shelter: { 'pt-BR': 'Abrigos / Pórticos', 'en-US': 'Shelters/Canopies' }, nav_dynamics: { 'pt-BR': 'Dinâmica + Vórtices', 'en-US': 'Dynamics + Vortex' }, nav_calc_ca: { 'pt-BR': 'Calc_Ca', 'en-US': 'Calc_Ca' }, nav_settings: { 'pt-BR': 'Preferências', 'en-US': 'Preferences' }, diff --git a/app/src/lib/nbr-tables/table-shelters.ts b/app/src/lib/nbr-tables/table-shelters.ts new file mode 100644 index 0000000..dc89987 --- /dev/null +++ b/app/src/lib/nbr-tables/table-shelters.ts @@ -0,0 +1,193 @@ +/** + * 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). + */ + +export type ShelterCondition = 'cond1' | 'cond2' | 'cond3' | 'cond4'; +export type OpeningPosition = 'top' | 'bottom' | 'distributed'; + +export interface ShelterInput { + /** Condição topológica padrão (1=Estanque 3 lados, 2=Fundo fechado, 3=Túnel, 4=Aberto) */ + condition: ShelterCondition; + /** Comprimento do balanço / profundidade (m) */ + depth: number; + /** Largura do abrigo (m) */ + width: number; + /** Altura livre / altura do pilar (m) */ + height: number; + /** Inclinação da cobertura (graus, ex: 0 a 30) */ + theta: number; + /** Percentual de fechamento da parede de fundo (0 a 100) */ + backClosure: number; + /** Percentual de fechamento da parede lateral esquerda (0 a 100) */ + leftClosure: number; + /** Percentual de fechamento da parede lateral direita (0 a 100) */ + rightClosure: number; + /** Posição da abertura na parede de fundo ('top' | 'bottom' | 'distributed') */ + openingPos: OpeningPosition; +} + +export interface ShelterResult { + /** Coeficiente de pressão superior da cobertura (Ce superior) */ + cpeTop: number; + /** Coeficiente de pressão interna efetivo sob a cobertura (Cpi inferior) */ + cpiBottom: number; + /** Coeficiente de força líquida vertical na cobertura (Cnf = cpeTop - cpiBottom) */ + cnfVertical: number; + /** Coeficiente de força horizontal na parede de fundo */ + cfBack: number; + /** Coeficiente de força horizontal nas paredes laterais */ + cfSide: number; + /** Forças totais resultantes (kN) */ + forces: { + /** Força vertical de arrancamento na cobertura (+ para cima) (kN) */ + fzUp: number; + /** Força vertical para baixo (kN) */ + fzDown: number; + /** Força horizontal frontal/empuxo contra a parede de fundo (kN) */ + fxBack: number; + /** Força horizontal lateral contra paredes (kN) */ + fySide: number; + /** Força de atrito no plano da cobertura (kN) */ + fFriction: number; + }; + /** Carga linear estimada nos pórticos metálicos principais (kN/m) (ex: Tubo 150x150) */ + lineLoads: { + /** Carga horizontal em cada pilar (kN/m) */ + columnLoad: number; + /** 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 (%) */ + reliefPercentage: number; + /** Explicação normativa do comportamento */ + statusText: string; +} + +/** + * Calcula os coeficientes e forças para Abrigos, Marquises e Pórticos. + */ +export function calculateShelterWind(input: ShelterInput, q: number): ShelterResult { + const { + condition, + depth, + width, + height, + theta, + backClosure, + leftClosure, + rightClosure, + openingPos, + } = input; + + const areaRoof = depth * width; + const areaBack = width * height; + const areaSide = depth * height; + + // Razões de permeabilidade (0 = aberto, 1 = fechado estanque) + const bClose = Math.max(0, Math.min(100, backClosure)) / 100; + const lClose = Math.max(0, Math.min(100, leftClosure)) / 100; + const rClose = Math.max(0, Math.min(100, rightClosure)) / 100; + + // 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; + + // 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); + } else { + // Condição 4: Livre (apenas obstrução leve ou nula) + baseCpi = -0.1 * bClose; + } + + // 3. Efeito da posição da abertura (Topo vs Base vs Distribuída) + let reliefPercentage = 0; + let effectiveCpi = baseCpi; + + if (bClose > 0.1 && bClose < 0.98) { + if (openingPos === 'top') { + // Fresta superior permite o escape de sobrepressão debaixo do telhado + // Reduz o Cpi positivo em até 30%, gerando alívio de arrancamento na cobertura + reliefPercentage = Math.round(25 * (1 - bClose)); + effectiveCpi = baseCpi * (1 - reliefPercentage / 100); + } else if (openingPos === 'bottom') { + // Abertura inferior: o ar flui embaixo, mantendo pressão sob o telhado e aumentando momento na base + reliefPercentage = 0; + effectiveCpi = baseCpi * 1.05; + } else { + reliefPercentage = 5; + effectiveCpi = baseCpi * 0.95; + } + } + + // 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 + + // 4. Coeficientes em paredes de fundo e laterais (Tabela 23 / 6) + const cfBack = 1.3 * bClose; + const cfSide = 0.9 * ((lClose + rClose) / 2); + + // 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 + + // 6. Carga linear em pórticos (assumindo 2 pórticos principais como no croqui, ex: vão b/2) + // Carga no pilar (kN/m) + const numPorticos = 2; + const columnLoad = (fxBack / height / numPorticos) + (q * 1.8 * 0.15); // arrasto do tubo 150x150 + const beamLoad = (fzUp / depth / numPorticos); + + // Texto explicativo do regime + let statusText = ''; + 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.'; + 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.'; + 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.'; + 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.'; + break; + } + + return { + cpeTop: Number(cpeTop.toFixed(2)), + cpiBottom: Number(effectiveCpi.toFixed(2)), + cnfVertical: Number(cnfUp.toFixed(2)), + cfBack: Number(cfBack.toFixed(2)), + cfSide: Number(cfSide.toFixed(2)), + forces: { + fzUp: Number(fzUp.toFixed(2)), + fzDown: Number(fzDown.toFixed(2)), + fxBack: Number(fxBack.toFixed(2)), + fySide: Number(fySide.toFixed(2)), + fFriction: Number(fFriction.toFixed(3)), + }, + lineLoads: { + columnLoad: Number(columnLoad.toFixed(2)), + beamLoad: Number(beamLoad.toFixed(2)), + }, + reliefPercentage, + statusText, + }; +} diff --git a/app/src/pages/ShelterModule.tsx b/app/src/pages/ShelterModule.tsx new file mode 100644 index 0000000..9f42308 --- /dev/null +++ b/app/src/pages/ShelterModule.tsx @@ -0,0 +1,408 @@ +import React, { useMemo, useState } from 'react'; +import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'; +import { Slider } from '@/components/ui/slider'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/button'; +import { useWindStore } from '@/store/appStore'; +import { + calculateShelterWind, + type ShelterCondition, + type OpeningPosition, +} from '@/lib/nbr-tables/table-shelters'; +import Shelter3D from '@/components/three/Shelter3D'; +import SceneCanvas from '@/components/SceneCanvas'; +import SceneCapturePanel from '../components/SceneCapturePanel'; +import ExportMenu from '../components/ExportMenu'; +import { EducationalManual } from '@/components/EducationalManual'; +import { WindParametersSummary } from '@/components/WindParametersSummary'; +import { SaveModuleDialog } from '@/components/SaveModuleDialog'; +import { exportGenericToPDF, type GenericPDFSection } from '../lib/export-generic-pdf'; +import { Eye, Box, Layers, ShieldCheck, ArrowUpRight, CheckCircle2 } from 'lucide-react'; +import { cn } from '@/lib/utils'; + +const conditionLabels: Record = { + cond1: '1. Estanque 3 lados (Fundo + Lados)', + cond2: '2. Fundo Fechado + Lados Abertos', + cond3: '3. Fundo Aberto + Lados Fechados (Túnel)', + cond4: '4. Totalmente Livre (4 lados abertos)', +}; + +const ShelterModule: React.FC = () => { + const { q } = useWindStore(); + const [condition, setCondition] = useState('cond1'); + const [depth, setDepth] = useState(6); + const [width, setWidth] = useState(10); + const [height, setHeight] = useState(4.8); + const [theta, setTheta] = useState(5); + const [backClosure, setBackClosure] = useState(85); + const [leftClosure, setLeftClosure] = useState(100); + const [rightClosure, setRightClosure] = useState(100); + const [openingPos, setOpeningPos] = useState('top'); + const [viewMode, setViewMode] = useState<'3d' | 'elevation'>('3d'); + + // Ajusta percentuais ao trocar as 4 condições rápidas + const handleConditionChange = (cond: ShelterCondition) => { + setCondition(cond); + if (cond === 'cond1') { + setBackClosure(100); + setLeftClosure(100); + setRightClosure(100); + } else if (cond === 'cond2') { + setBackClosure(100); + setLeftClosure(0); + setRightClosure(0); + } else if (cond === 'cond3') { + setBackClosure(0); + setLeftClosure(100); + setRightClosure(100); + } else if (cond === 'cond4') { + setBackClosure(0); + setLeftClosure(0); + setRightClosure(0); + } + }; + + const result = useMemo(() => { + return calculateShelterWind( + { + condition, + depth, + width, + height, + theta, + backClosure, + leftClosure, + rightClosure, + openingPos, + }, + q + ); + }, [condition, depth, width, height, theta, backClosure, leftClosure, rightClosure, openingPos, q]); + + const handleExportPDF = () => { + const sections: GenericPDFSection[] = [ + { + title: 'Geometria do Abrigo e Fechamentos', + type: 'grid', + gridItems: [ + { label: 'Condição Topológica', value: conditionLabels[condition] }, + { 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: 'Fechamento Lateral Esq / Dir', value: `${leftClosure}% / ${rightClosure}%` }, + { label: 'Posição da Abertura', value: openingPos === 'top' ? 'Fresta Topo (Alívio)' : openingPos === 'bottom' ? 'Vão Inferior' : 'Distribuída' }, + ], + }, + { + title: 'Coeficientes Aerodinâmicos (NBR 6123)', + type: 'grid', + gridItems: [ + { label: 'Ce Superior (Cobertura)', value: result.cpeTop.toString() }, + { label: 'Cpi Inferior (Efetivo)', value: result.cpiBottom.toString() }, + { label: 'Cnf Líquido (Arrancamento)', value: result.cnfVertical.toString() }, + { label: 'Cf Parede Fundo (Tab. 23)', value: result.cfBack.toString() }, + { label: 'Alívio por Fresta Superior', value: `${result.reliefPercentage}%` }, + ], + }, + { + title: 'Forças Resultantes (kN)', + type: 'grid', + gridItems: [ + { label: 'Força Vertical (Fz - Arrancamento)', value: `${result.forces.fzUp} kN` }, + { label: 'Empuxo Frontal Fundo (Fx)', value: `${result.forces.fxBack} kN` }, + { label: 'Empuxo nas Laterais (Fy)', value: `${result.forces.fySide} kN` }, + { label: 'Atrito na Cobertura', value: `${result.forces.fFriction} kN` }, + ], + }, + { + title: 'Carga Linear nos Pórticos (kN/m)', + type: 'grid', + gridItems: [ + { label: 'Carga no Pilar (ex: Tubo 150x150)', value: `${result.lineLoads.columnLoad} kN/m` }, + { label: 'Carga na Viga em Balanço', value: `${result.lineLoads.beamLoad} kN/m` }, + ], + }, + ]; + exportGenericToPDF('Abrigo / Pórtico Fechado', sections); + }; + + return ( +
+ {/* Coluna Esquerda: Controles do Abrigo */} +
+ + +
+ + + Abrigos e Pórticos + + + Pórticos em balanço, marquises e garagens com fechamentos (Tab. 23-25 e sec. 6.3). + +
+ +
+ + + + {/* Seletor Rápido de 4 Condições Topológicas */} +
+ +
+ {(['cond1', 'cond2', 'cond3', 'cond4'] as ShelterCondition[]).map((c) => ( + + ))} +
+
+ + {/* Controle de Posição da Abertura (Topo / Base / Distribuída) */} +
+ + +
+ + {/* Sliders de Fechamento Parcial (%) */} +
+ +
+
+ Parede de Fundo: + {backClosure}% +
+ setBackClosure(v[0])} /> +
+
+
+
+ Lateral Esq: + {leftClosure}% +
+ setLeftClosure(v[0])} /> +
+
+
+ Lateral Dir: + {rightClosure}% +
+ setRightClosure(v[0])} /> +
+
+
+ + {/* Sliders Geométricos */} +
+ +
+
+ Vão do Balanço (d): + {depth} m +
+ setDepth(v[0])} /> +
+
+
+ Altura do Pilar (h): + {height} m +
+ setHeight(v[0])} /> +
+
+
+ Largura do Abrigo (b): + {width} m +
+ setWidth(v[0])} /> +
+
+
+ Inclinação Cobertura (θ): + {theta}° +
+ setTheta(v[0])} /> +
+
+ + {/* Status Normativo */} +
+
+ + Norma NBR 6123:2023 — Sec. 6.3 & Tab. 23-25 +
+

{result.statusText}

+
+
+
+
+ + {/* Coluna Direita: 3D, Corte A-A e Quadro de Ações */} +
+ {/* Visualizador 3D com trava, manual (i) e corte em elevação */} +
+
+ + θ = {theta}° + + + Fundo: {backClosure}% ({openingPos === 'top' ? 'Fresta Topo' : openingPos === 'bottom' ? 'Vão Base' : 'Distrib.'}) + + {result.reliefPercentage > 0 && ( + + + Alívio Arrancamento: -{result.reliefPercentage}% + + )} +
+ +
+ {/* Botão de Corte em Elevação (Corte A-A) vs 3D */} +
+ + +
+ + {/* Ícone (i) Manual Didático NBR 6123 */} + +
+ + 3D indisponível
} + > + + + + +
+ + {/* Quadro Superior de Resultados na Base do 3D */} +
+ + + F. Vertical (Fz - Arrancamento) +
+ {result.forces.fzUp} kN + Cnf = {result.cnfVertical} +
+ + {result.reliefPercentage > 0 ? `Com ${result.reliefPercentage}% de alívio por fresta` : 'Sem alívio por fresta superior'} + +
+
+ + + + Empuxo no Fundo (Fx Traseira) +
+ {result.forces.fxBack} kN + Cf = {result.cfBack} +
+ + Parede com {backClosure}% de estanqueidade + +
+
+ + + + Carga no Pilar (ex: Tubo 150x150) +
+ + {result.lineLoads.columnLoad} kN/m + + h = {height} m +
+ + Ação direta + reação horizontal por pilar + +
+
+ + + + Carga na Viga em Balanço +
+ + {result.lineLoads.beamLoad} kN/m + + d = {depth} m +
+ + Sucção distribuída por braço principal + +
+
+
+ + {/* Rodapé com Exportação e Captura */} +
+ + +
+
+ + ); +}; + +export default ShelterModule;