diff --git a/PLANO_NORMAS_INTERNACIONAIS.md b/PLANO_NORMAS_INTERNACIONAIS.md
new file mode 100644
index 0000000..614769f
--- /dev/null
+++ b/PLANO_NORMAS_INTERNACIONAIS.md
@@ -0,0 +1,159 @@
+# 🌍 PLANO ARQUITETURAL DE EXPANSÃO: NORMAS INTERNACIONAIS DE VENTO & BENCHMARKING MULTI-NORMA
+
+> **Documento de Visão Técnica, Segurança Arquitetural e Roadmap Futuro**
+> **Projeto:** VentoApp / BrainWind — Engenharia de Vento Avançada
+> **Estado Atual (Julho/2026):** Domínio completo da NBR 6123:2023 (100% coberta, 36 tabelas, testes unitários travados, visualizador 3D interativo, cálculo puro determinístico).
+
+---
+
+## 1. A Visão e O Sonho Técnico
+
+Transformar o **BrainWind / VentoApp** em mais do que o melhor software brasileiro de cálculo de ações do vento: elevá-lo ao status de **Plataforma de Auditoria e Benchmarking Internacional de Engenharia de Vento**.
+
+O objetivo é permitir que engenheiros estruturais, peritos e pesquisadores possam:
+1. Projetar estruturas segundo normas internacionais (**Eurocode 1 — EN 1991-1-4**, **ASCE 7-22 — EUA**, **AS/NZS 1170.2 — Austrália/Nova Zelândia**).
+2. Confrontar em **tempo real e lado a lado** os esforços gerados pela **NBR 6123:2023** contra 2, 3 ou 4 normas mundiais simultaneamente para a mesma geometria.
+3. Entender com clareza aerodinâmica e visual onde cada norma é mais conservadora ou mais econômica.
+
+---
+
+## 2. A Premissa Sagrada: Proteção do "Coração Intocável" (NBR 6123:2023)
+
+O maior risco de softwares estruturais em expansão é a **regressão funcional** — quebrar o que já está estável, testado e confiável para acomodar novos requisitos. Para que esse risco seja **ZERO**, estabelecemos o seguinte pacto arquitetural:
+
+### 2.1. Princípio Aberto-Fechado (SOLID — *Open-Closed Principle*)
+* O código atual de cálculo da NBR 6123 (`src/lib/nbr-tables/`, `src/lib/modules/`, `src/lib/wind-kernel.ts`) permanece **estritamente fechado para modificações**.
+* Nenhuma rotina, tabela ou constante existente da NBR 6123 precisará ou deverá ser editada para acomodar uma norma estrangeira.
+* Toda nova norma será implementada como um **pacote independente e isolado** em diretórios dedicados (`src/lib/eurocode-tables/`, `src/lib/asce-tables/`, `src/lib/standards-drivers/`).
+
+### 2.2. NBR 6123 como Padrão Intocável (*Default Root*)
+* Ao iniciar o aplicativo ou carregar um projeto brasileiro, o motor rodará exclusivamente no fluxo nativo da NBR 6123, preservando a experiência de uso de 100% dos engenheiros brasileiros sem latência ou burocracia adicional de parâmetros internacionais.
+
+---
+
+## 3. Arquitetura de "Drivers Normativos" (`StandardStrategy`)
+
+Para desacoplar a geometria do modelo físico de cálculo, elevamos a arquitetura para o padrão **Strategy Driver**:
+
+```
+ ┌──────────────────────────────────────────────┐
+ │ MODELO GEOMÉTRICO CANÔNICO (CORE) │
+ │ (b, d, h, θ, Permeabilidades, Topografia) │
+ └──────────────────────┬───────────────────────┘
+ │
+ ┌───────────────────────┴───────────────────────┐
+ ▼ ▼ ▼
+ ┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐
+ │ Driver NBR 6123 │ │ Driver Eurocode 1 │ │ Driver ASCE 7-22 │
+ │ (NBR6123Driver) │ │ (EN1991_1_4Driver) │ │ (ASCE7_22Driver) │
+ └──────────┬──────────┘ └──────────┬──────────┘ └──────────┬──────────┘
+ │ │ │
+ ▼ ▼ ▼
+ Tabelas NBR (36) Tabelas EN 1991-1-4 Tabelas ASCE 7-22
+ (Rajada de 3 seg) (Média de 10 min) (Rajada + Fator Kz)
+```
+
+### 3.1. Conciliação Físico-Atmosférica (Fator de Durst & Tempos de Base)
+As normas mundiais diferem no tempo de média da velocidade do vento de referência:
+* **NBR 6123 e ASCE 7-22:** Baseiam-se na velocidade de **rajada de 3 segundos**.
+* **Eurocode 1 (EN 1991-1-4):** Baseia-se na velocidade **média de 10 minutos**.
+* **Solução:** O motor canônico integrará conversores espectrais normatizados (ex.: **Fator de Durst / Curvas de Cook**) que permitem transacionar velocidades de 10 minutos para rajadas de 3 segundos com precisão matemática, colocando todas as normas na **mesma base física real de energia cinética do fluxo**.
+
+---
+
+## 4. O Segredo de UX: Mapeamento Inteligente e "Tradução Normativa"
+
+Para que o usuário **não precise preencher 4 cadastros inteiros** só para ver um comparativo, o sistema dividirá os parâmetros de entrada em duas camadas:
+
+### 4.1. Camada 1: Parâmetros Universais (Canônicos)
+Comuns a 100% das estruturas no planeta:
+* **Geometria:** Largura ($b$), Vão/Comprimento ($d$), Altura dos pilares ($h$), Inclinação da cobertura ($\theta$).
+* **Física:** Percentual de fechamento de paredes ($0\%$ a $100\%$), velocidade base de projeto ($m/s$).
+
+### 4.2. Camada 2: Tradutor Automático de Padrões (*Default Mapping Adaptor*)
+Quando o engenheiro projeta um galpão com dados brasileiros, o **Tradutor Normativo** infere automaticamente as propriedades correspondentes nas outras normas em segundo plano:
+* *Categoria de Terreno IV (Brasil) $\longrightarrow$ Terrain Category III (Eurocode 1) $\longrightarrow$ Exposure Category C (ASCE 7).*
+* *Classe de Edificação B (Brasil) $\longrightarrow$ Fator de Pico / Altura equivalente.*
+
+### 4.3. Gaveta de Ajustes Normativos Específicos (*Overrides*)
+Para engenheiros peritos que desejam refinar parâmetros exclusivos de uma norma estrangeira sem alterar a geometria global, a interface disponibilizará um painel retrátil **"Ajustes Normativos Específicos"**:
+* **Exemplo ASCE 7:** Permite alterar manualmente a *Enclosure Classification* (`Enclosed`, `Partially Enclosed`, `Partially Open`, `Open`) e o fator de altitude acima do nível do mar ($K_e$).
+* **Exemplo Eurocode 1:** Permite ajustar a densidade do ar em função de temperatura e altitude em locais específicos da Europa.
+
+---
+
+## 5. Isolamento e Blindagem de Arquivos Salvos (`Project Schema`)
+
+Para evitar mistura de referências ou corrupção de dados:
+1. **Atributo `standard` Obrigatório no Schema JSON:**
+ Todo arquivo salvo conterá explicitamente a norma de origem:
+ ```json
+ {
+ "id": "galpao-logistico-01",
+ "standard": "NBR6123_2023",
+ "version": "2.0.0",
+ "geometry": { "width": 20, "depth": 40, "height": 8, "theta": 15 },
+ "parameters": { "v0": 45, "s1": 1.0, "s2Category": 4, "s2Class": "B", "s3": 1.0 }
+ }
+ ```
+2. **Guarda de Carregamento (*Schema Guard*):**
+ Ao abrir um arquivo, o app identifica a chave `"standard"`. Se um arquivo do Eurocode for aberto por engano, a interface avisará o usuário, trocando o contexto visual para as tabelas e símbolos correspondentes sem sobrescrever o projeto brasileiro.
+
+---
+
+## 6. O Módulo de "Benchmarking Lado a Lado" (Comparativo em Tempo Real)
+
+Como cada cálculo estrutural de vento executado em TypeScript puro roda em **menos de 2 milissegundos**, o aplicativo é capaz de executar **4 motores em paralelo em tempo real** sempre que o usuário mover um slider.
+
+### 6.1. Tabela de Confronto Direto (`kN/m²` e `kN`)
+Uma tela dedicada que coloca frente a frente:
+* **Pressão Efetiva Crítica e Força Total:** Por zona do telhado, paredes frontais/traseiras e esforços na base do pilar.
+* **Mapa de Conservadorismo (Cores Críticas):** Destacando automaticamente em **Vermelho** a norma que exige a maior carga de projeto (mais conservadora) e em **Verde** a mais branda para cada elemento estrutural.
+
+### 6.2. Gráficos de Delta Percentual ($\Delta\%$)
+Gráficos de barra em SVG/Recharts mostrando os deltas em tempo real:
+* *Exemplo de insight automático:* "Para esta inclinação de $15^\circ$, a **ASCE 7-22** prescreve forças de sucção nos cantos do telhado **+14,2% maiores** que a **NBR 6123**, enquanto o **Eurocode 1** fica **-5,8% menor**."
+
+### 6.3. Modelo 3D Interativo Multi-Norma (*Split-Screen / Toggle*)
+* Opção de **Abas Instantâneas no topo do 3D:** (`[ NBR 6123 ] [ Eurocode 1 ] [ ASCE 7-22 ]`). Clicar na aba altera na hora o mapa de cores de pressão na geometria 3D.
+* **Modo Tela Dividida (*Split-Screen*):** Corta o modelo 3D ao meio, mostrando do lado esquerdo o gradiente de pressões da NBR 6123 e do lado direito o gradiente da norma comparada.
+
+---
+
+## 7. O "Selo de Garantia": Bateria de Testes contra a Literatura Estrangeira
+
+Para que qualquer norma estrangeira receba o selo de confiança técnica do **BrainWind**, ela deverá ser aprovada em uma suíte de testes unitários (`Vitest` em `src/lib/__tests__/standards/`) travada contra exercícios de referência consagrados:
+
+| Norma | Fonte Literária Oficial para Travamento de Testes Unitários |
+| :--- | :--- |
+| **ASCE 7-22 (EUA)** | • *Guide to the Wind Load Provisions of ASCE 7-22* (ASCE / SEAOC) • *SEAOC Structural/Seismic Design Manual — Wind Examples* |
+| **Eurocode 1 (EN 1991-1-4)** | • *ECCS — Designers' Guide to EN 1991-1-4 (Eurocode 1)* • *JRC Technical Reports (European Commission) — Steel & Wind Design* |
+| **AS/NZS 1170.2 (Aus/NZ)** | • *Australian Guide to the Wind Loading Standard AS/NZS 1170.2* (Engineers Australia) |
+
+### 7.1. Testes de Invariância Aerodinâmica
+Além do teste dos exemplos numéricos, a suíte rodará **testes de invariância relacional**:
+* Em qualquer das 4 normas, a face de barlavento **sempre** deve resultar em sobrepressão positiva.
+* O topo da cumeeira de telhados em duas águas **sempre** deve registrar pico de sucção na separação de escoamento.
+* O alívio aerodinâmico em abrigos com abertura superior **sempre** deve reduzir o arrancamento vertical.
+
+---
+
+## 8. Roadmap de Execução Recomendado
+
+```
+[ FASE ATUAL ] ──► CONSOLIDAÇÃO TOTAL NBR 6123:2023 (100% Norma, 100% Testes, UI/3D Impecável)
+ │
+ ▼
+[ FASE FUTURA 1 ] ──► Criação da Camada "StandardStrategy" (Isolada, sem mexer no Core NBR)
+ │
+ ▼
+[ FASE FUTURA 2 ] ──► Driver Eurocode 1 (EN 1991-1-4) + Testes de Bancada ECCS / JRC
+ │
+ ▼
+[ FASE FUTURA 3 ] ──► Driver ASCE 7-22 (EUA) + Testes de Bancada ASCE Guide / SEAOC
+ │
+ ▼
+[ FASE FUTURA 4 ] ──► Lançamento da Tela "Benchmarking Lado a Lado & Auditoria Multi-Norma"
+```
+
+> **Conclusão:** Seguindo este plano, mantemos nossa estabilidade 100% blindada e criamos uma das mais avançadas e respeitadas ferramentas de engenharia de vento da América Latina e do mundo.
diff --git a/app/src/components/SaveModuleDialog.tsx b/app/src/components/SaveModuleDialog.tsx
index 17339c4..557b5e8 100644
--- a/app/src/components/SaveModuleDialog.tsx
+++ b/app/src/components/SaveModuleDialog.tsx
@@ -15,7 +15,7 @@ import { useProjects } from '../lib/hooks/useProjects';
import { useWindStore } from '../store/appStore';
interface SaveModuleDialogProps {
- moduleType: 'galpao' | 'cilindro' | 'vault' | 'dome' | 'sign' | 'isolated-roof' | 'bar' | 'bridge' | 'dynamics' | 'piperack' | 'tower';
+ moduleType: 'galpao' | 'cilindro' | 'vault' | 'dome' | 'sign' | 'isolated-roof' | 'bar' | 'bridge' | 'dynamics' | 'piperack' | 'tower' | 'shelter';
inputs: Record;
triggerButton?: React.ReactNode;
}
diff --git a/app/src/components/WindFlowGrid2D.tsx b/app/src/components/WindFlowGrid2D.tsx
index bc07b13..c846a38 100644
--- a/app/src/components/WindFlowGrid2D.tsx
+++ b/app/src/components/WindFlowGrid2D.tsx
@@ -76,15 +76,15 @@ export function WindFlowGrid2D({ type }: WindFlowGrid2DProps) {
// Define quais itens de legenda devem ser exibidos para cada tipo de módulo
const getVisibleLegendItems = () => {
- const hasCpi = ['warehouse'].includes(type);
- const hasVortex = ['isolated-roof', 'bar', 'tower', 'vault', 'cylinder', 'sign', 'dynamics', 'piperack'].includes(type);
- const hasLift = ['bridge', 'isolated-roof'].includes(type);
+ const hasCpi = ['warehouse', 'shelter'].includes(type);
+ const hasVortex = ['isolated-roof', 'bar', 'tower', 'vault', 'cylinder', 'sign', 'dynamics', 'piperack', 'shelter'].includes(type);
+ const hasLift = ['bridge', 'isolated-roof', 'shelter'].includes(type);
return {
wind: true,
fast: true,
vortex: hasVortex,
- vortexSpin: ['cylinder', 'dynamics', 'sign', 'piperack'].includes(type),
+ vortexSpin: ['cylinder', 'dynamics', 'sign', 'piperack', 'shelter'].includes(type),
cpiPos: hasCpi,
cpiNeg: hasCpi,
lift: hasLift,
@@ -99,7 +99,8 @@ export function WindFlowGrid2D({ type }: WindFlowGrid2DProps) {
case 'dome':
case 'warehouse':
case 'piperack':
- return 'Vista Lateral (Corte Transversal)';
+ case 'shelter':
+ return 'Vista Lateral (Corte Transversal / Elevação A-A)';
case 'cylinder':
case 'sign':
case 'bar':
@@ -769,6 +770,91 @@ export function WindFlowGrid2D({ type }: WindFlowGrid2DProps) {
);
+ case 'shelter':
+ return (
+
+ {/* Caso A: Abrigo Estanque - Condição 1 */}
+
+ Caso A: Abrigo Estanque em 3 Lados (Condição 1 - Armadilha)
+
+
+
+
O vento frontal fica retido pela parede de fundo fechada, gerando sobrepressão interna (+Cpi) que empurra a cobertura retangular em balanço para cima.
+
+
+ {/* Caso B: Alívio por Fresta Superior - Condição 2 */}
+
+ Caso B: Alívio por Fresta Superior (Condição 2 + Lanternim)
+
+
+
+
A fresta aberta no topo entre a parede e o telhado permite que a sobrepressão interna escape para sotavento, reduzindo drasticamente o arrancamento vertical da cobertura.
+
+
+ );
+
case 'calc_ca':
return (
diff --git a/app/src/components/three/Shelter3D.tsx b/app/src/components/three/Shelter3D.tsx
index 074efe3..523f5d8 100644
--- a/app/src/components/three/Shelter3D.tsx
+++ b/app/src/components/three/Shelter3D.tsx
@@ -1,7 +1,10 @@
import React, { useRef } from 'react';
import * as THREE from 'three';
import { useFrame } from '@react-three/fiber';
-import { Text, Line } from '@react-three/drei';
+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';
export interface Shelter3DProps {
@@ -14,6 +17,7 @@ export interface Shelter3DProps {
leftClosure: number;
rightClosure: number;
openingPos: OpeningPosition;
+ backRange?: [number, number];
viewMode: '3d' | 'elevation';
}
@@ -24,23 +28,26 @@ const WindArrow: React.FC<{
speed?: number;
}> = ({ start, end, color = '#38bdf8', speed = 1.5 }) => {
const arrowRef = useRef(null);
- useFrame(({ clock }) => {
+
+ const quaternion = React.useMemo(() => {
+ const dir = new THREE.Vector3(...end).sub(new THREE.Vector3(...start)).normalize();
+ return new THREE.Quaternion().setFromUnitVectors(new THREE.Vector3(0, 1, 0), dir);
+ }, [start, end]);
+
+ useFrame(({ clock, invalidate }) => {
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);
+ invalidate();
}
});
- const dir = new THREE.Vector3(...end).sub(new THREE.Vector3(...start));
- const len = dir.length();
- const angleZ = Math.atan2(dir.y, dir.x);
-
return (
-
-
+
+
@@ -48,8 +55,8 @@ const WindArrow: React.FC<{
);
};
-export const Shelter3D: React.FC = ({
- condition,
+function ShelterModel({
+ condition: _condition,
depth,
width,
height,
@@ -58,8 +65,9 @@ export const Shelter3D: React.FC = ({
leftClosure,
rightClosure,
openingPos,
+ backRange,
viewMode,
-}) => {
+}: Shelter3DProps) {
const isElevation = viewMode === 'elevation';
// Geometria básica
@@ -68,25 +76,32 @@ export const Shelter3D: React.FC = ({
const thetaRad = (theta * Math.PI) / 180;
const slopeRise = depth * Math.tan(thetaRad);
- // Paredes com abertura no topo ou na base
+ // Paredes com abertura no topo ou na base (sem caixas/linhas brancas deslocadas)
const renderWallWithGap = (
w: number,
h: number,
closure: number,
pos: OpeningPosition,
rotY = 0,
- position: [number, number, number] = [0, 0, 0]
+ position: [number, number, number] = [0, 0, 0],
+ customRange?: [number, number]
) => {
if (closure <= 0) return null;
- const op = closure / 100;
- const wallH = h * op;
+ let op = closure / 100;
+ let wallH = h * op;
let yPos = position[1] + wallH / 2;
- if (pos === 'top' && op < 1) {
+ if (customRange && closure < 100) {
+ const bottomPct = Math.max(0, Math.min(100, customRange[0])) / 100;
+ const topPct = Math.max(0, Math.min(100, customRange[1])) / 100;
+ op = Math.max(0.01, topPct - bottomPct);
+ wallH = h * op;
+ yPos = position[1] + h * bottomPct + wallH / 2;
+ } else if (pos === 'top' && closure < 100) {
// Parede na parte de baixo, fresta no topo
yPos = position[1] + wallH / 2;
- } else if (pos === 'bottom' && op < 1) {
+ } else if (pos === 'bottom' && closure < 100) {
// Parede suspensa na parte superior, vão na base
yPos = position[1] + h - wallH / 2;
}
@@ -102,24 +117,12 @@ export const Shelter3D: React.FC = ({
roughness={0.4}
/>
- {/* 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) => (
@@ -144,15 +147,11 @@ export const Shelter3D: React.FC = ({
-
-
-
-
{/* 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])}
+ {renderWallWithGap(width, height, backClosure, openingPos, 0, [0, 0, halfD], backRange)}
{/* Parede Lateral Esquerda (X = -halfW) */}
{renderWallWithGap(depth, height, leftClosure, 'distributed', Math.PI / 2, [-halfW, 0, 0])}
@@ -162,44 +161,74 @@ export const Shelter3D: React.FC = ({
{/* 4. Linhas de Corrente e Vetores de Vento (Ilustrativo e Didático) */}
- {/* Vento de entrada (Barlavento) */}
-
-
-
+ {/* Vento de entrada (Barlavento) entrando pela frente em direção ao abrigo */}
+
+
+
{/* 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' && (
+ {/* Fresta na parede de fundo: se houver abertura no topo, mostrar vento escapando pela fresta superior */}
+ {backClosure > 0 && backClosure < 100 && (openingPos === 'top' || (backRange && backRange[1] < 98)) && (
<>
-
+
Alívio (Fresta Topo)
>
)}
{/* Fresta na base */}
- {backClosure > 0 && backClosure < 100 && openingPos === 'bottom' && (
+ {backClosure > 0 && backClosure < 100 && (openingPos === 'bottom' || (backRange && backRange[0] > 2)) && (
<>
-
+
Vão Livre Inferior
>
)}
- {/* Textos Informativos 3D */}
-
+ {/* Textos Informativos 3D - Virados para quem olha da frente (-Z) */}
+
{isElevation ? 'CORTE A-A (Elevação)' : 'Abrigo / Pórtico (NBR 6123)'}
-
+
VENTO FRONTAL
);
-};
+}
+
+export function Shelter3D(props: Shelter3DProps) {
+ const theme = useCanvasTheme();
+ const isDark = theme === 'dark';
+ const { width, depth, height } = props;
+ const dist = Math.max(width, depth) * 1.8 + 8;
+
+ return (
+ 3D indisponível
@@ -140,9 +129,7 @@ const ShelterModule: React.FC = () => {
Abrigos e Pórticos
-
- Pórticos em balanço, marquises e garagens com fechamentos (Tab. 23-25 e sec. 6.3).
-
+ Pórticos em balanço (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) */}
-