diff --git a/app/src/App.tsx b/app/src/App.tsx index 18b0b50..cefcab1 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -15,6 +15,7 @@ const TowerModule = lazy(() => import('./pages/TowerModule')); const PiperackModule = lazy(() => import('./pages/PiperackModule')); const ShelterModule = lazy(() => import('./pages/ShelterModule')); const CertificatePage = lazy(() => import('./pages/CertificatePage')); +const CompareModule = lazy(() => import('./pages/Compare')); import { Home, Settings, Menu, Cylinder, Church, CircleDot, Square, Layers, BarChart3, Activity, Warehouse, @@ -376,6 +377,7 @@ function App() { } /> } /> } /> + } /> diff --git a/app/src/components/Heatmap2D.tsx b/app/src/components/Heatmap2D.tsx new file mode 100644 index 0000000..555d480 --- /dev/null +++ b/app/src/components/Heatmap2D.tsx @@ -0,0 +1,200 @@ +import React from 'react'; +import { useGalpaoStore } from '../store/galpaoStore'; +import { useWindStore } from '../store/appStore'; +import type { WallCoefficients, RoofCoefficients } from '../lib/coefficients'; + +interface Heatmap2DProps { + overrideAngle?: 0 | 90; + overrideWallCpe?: WallCoefficients; + overrideRoofCpe?: RoofCoefficients; +} + +/** + * Mapeia o valor da pressão (Cpe - Cpi) para uma cor. + * Sucção (negativo) = Tons de vermelho/laranja. + * Pressão (positivo) = Tons de azul. + */ +const getPressureColor = (cpe: number, cpi: number): string => { + const p = cpe - cpi; + if (p < 0) { + const intensity = Math.min(Math.abs(p) / 1.5, 1); // Normaliza sucção até -1.5 + return `rgba(239, 68, 68, ${0.2 + intensity * 0.7})`; // Vermelho + } else if (p > 0) { + const intensity = Math.min(p / 1.0, 1); // Normaliza pressão até +1.0 + return `rgba(59, 130, 246, ${0.2 + intensity * 0.7})`; // Azul + } + return 'rgba(200, 200, 200, 0.3)'; // Neutro +}; + +const Heatmap2D: React.FC = ({ overrideAngle, overrideWallCpe, overrideRoofCpe }) => { + const storeGalpao = useGalpaoStore(); + const storeWind = useWindStore(); + + const width = storeGalpao.width; + const length = storeGalpao.length; + const wallCpe = overrideWallCpe || storeGalpao.wallCpe; + const roofCpe = overrideRoofCpe || storeGalpao.roofCpe; + const windAngle = overrideAngle !== undefined ? overrideAngle : storeWind.windAngle; + const cpi = storeWind.cpi; + + // Dimensões base do SVG + const svgW = 600; + const svgH = 600; + const margin = 100; + + // Fator de escala para caber a planta (width x length) na tela + const maxDim = Math.max(width, length); + const scale = (svgW - margin * 2) / maxDim; + + // Tamanhos desenhados + const drawW = width * scale; + const drawL = length * scale; + + // Centro do desenho + const cx = svgW / 2; + const cy = svgH / 2; + const x0 = cx - drawW / 2; + const y0 = cy - drawL / 2; + + // Determinar larguras das zonas do telhado (baseado na NBR 6123, onde as bordas têm distâncias específicas) + // Para simplificar graficamente o Heatmap, usaremos proporções visuais para E, F, G, H + const zoneSizeX = Math.min(drawW * 0.2, 40); // Representação genérica da zona E/F + const zoneSizeY = Math.min(drawL * 0.2, 40); + + // A NBR 6123 rotaciona as zonas dependendo de windAngle (0° vs 90°) + // Vento 0° = perpendicular ao comprimento (Z). A=Frente, B=Fundo, C=Lado, D=Lado + // Vento 90° = perpendicular à largura (X). A=Lado, B=Lado, C=Frente, D=Fundo + + return ( +
+
+

Planta Baixa - Telhado e Paredes

+

Vento incidindo a {windAngle}°

+ + + + + + + + + {/* Vento (Setas Dinâmicas) */} + {windAngle === 90 && ( + + + + + Vento (90°) + + )} + + {windAngle === 0 && ( + + + + + Vento (0°) + + )} + + {/* Área Total do Telhado (Fundo) */} + + + {/* Zonas do Telhado (Simplificadas para Vento a 0 ou 90) */} + {windAngle === 90 ? ( + // Vento 90° -> Zonas E, G na esquerda, F, H na direita etc. (Esquema simplificado de visualização) + <> + {/* Metade Barlavento (Esquerda) */} + + {/* Metade Sotavento (Direita) */} + + {/* Zonas Críticas */} + + + + ) : ( + // Vento 0° + <> + {/* Metade Barlavento (Topo) */} + + {/* Metade Sotavento (Baixo) */} + + {/* Zonas Críticas */} + + + + )} + + {/* Labels do Telhado (Simplificados) */} + + TELHADO + + + {/* PAREDES (Desenhadas como bordas expandidas) */} + {/* Parede A (Frente em 0°, Lado Esq em 90°) */} + {windAngle === 0 ? ( + + ) : ( + + )} + + {/* Parede B (Fundo em 0°, Lado Dir em 90°) */} + {windAngle === 0 ? ( + + ) : ( + + )} + + {/* Parede C (Lateral Dir em 0°, Frente em 90°) */} + {windAngle === 0 ? ( + + ) : ( + + )} + + {/* Parede D (Lateral Esq em 0°, Fundo em 90°) */} + {windAngle === 0 ? ( + + ) : ( + + )} + + {/* Indicadores de Paredes */} + {windAngle === 0 ? ( + <> + A + B + D + C + + ) : ( + <> + A + B + C + D + + )} + + + +
+
+
+ Sucção Máxima +
+
+
+ Neutro (0) +
+
+
+ Sobrepressão Máxima +
+
+
+
+ ); +}; + +export default Heatmap2D; diff --git a/app/src/pages/Compare.tsx b/app/src/pages/Compare.tsx new file mode 100644 index 0000000..c6f6f2e --- /dev/null +++ b/app/src/pages/Compare.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { useGalpaoStore } from '../store/galpaoStore'; +import { getWallCpeOfficial as getWallCpe, getRoofCpeOfficial as getRoofCpe } from '../lib/coefficients'; +import Heatmap2D from '../components/Heatmap2D'; + +const Compare: React.FC = () => { + const { length, width, height, roofPitch } = useGalpaoStore(); + + const wallCpe0 = getWallCpe(length, width, height, 0); + const roofCpe0 = getRoofCpe(length, width, height, roofPitch, 0); + + const wallCpe90 = getWallCpe(length, width, height, 90); + const roofCpe90 = getRoofCpe(length, width, height, roofPitch, 90); + + return ( +
+

Comparação A/B (0° vs 90°)

+

Compare visualmente a distribuição de pressões (Cpe - Cpi) nas duas direções normativas simultaneamente.

+ +
+ + +
+
+ ); +}; + +export default Compare; diff --git a/app/src/pages/GalpaoModule.tsx b/app/src/pages/GalpaoModule.tsx index 7e79cb0..5bc1e19 100644 --- a/app/src/pages/GalpaoModule.tsx +++ b/app/src/pages/GalpaoModule.tsx @@ -1,5 +1,7 @@ import React from 'react'; +import { Link } from 'react-router-dom'; import Warehouse3DViewer from '../components/Warehouse3D'; +import Heatmap2D from '../components/Heatmap2D'; import LinearLoadsTable from '../components/LinearLoadsTable'; import SceneCapturePanel from '../components/SceneCapturePanel'; import FtoolExportCard from '../components/FtoolExportCard'; @@ -12,9 +14,9 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@ import { Badge } from '@/components/ui/badge'; import { Separator } from '@/components/ui/separator'; import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; -import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs'; import { Button } from '@/components/ui/button'; -import { AlertCircle, Lightbulb, Box, Wind } from 'lucide-react'; +import { AlertCircle, Lightbulb, Box, Wind, Columns } from 'lucide-react'; import ExportMenu from '../components/ExportMenu'; import { EducationalManual } from '@/components/EducationalManual'; import { PressureLegend } from '@/components/PressureLegend'; @@ -65,18 +67,25 @@ const GalpaoModule: React.FC = () => { return (
{/* Coluna Esquerda: Controles */} -
- - -
- Geometria - Tabelas 4 e 5 — NBR 6123. -
- -
+
+ + + 1. Geometria + 2. Permeabilidade + + + + + +
+ Geometria e Vento + Tabelas 4 e 5 — NBR 6123. +
+ +
@@ -98,7 +107,7 @@ const GalpaoModule: React.FC = () => {

- A platibanda bloqueia o fluxo. Calcule os esforços sobre ela no módulo de Muros/Placas. + A platibanda bloqueia o fluxo. Calcule os esforços nela no módulo de Muros/Placas.

@@ -243,55 +252,83 @@ const GalpaoModule: React.FC = () => {
- - + + + + + +
- {/* Coluna Direita: Viewer 3D */} + {/* Coluna Central: Viewer 3D e Heatmap (Tab) */}
-
-
- - Vento a {windAngle}° - - - Cpi = {cpi.toFixed(2)} - - - q = {q.toFixed(3)} kN/m² - -
-
- - + +
+
+ + Vento a {windAngle}° + + + Cpi = {cpi.toFixed(2)} + + + q = {q.toFixed(3)} kN/m² + +
+ +
+ + Modelo 3D + Heatmap 2D + +
+ +
+ +
+ +
+ +
-
- -
-
- - - -
- -
+ +
+ + +
+ + +
+ +
+
+ + + + +
{/* Coluna Direita-Inferior: Cargas Lineares (M9.2) */}