From b842d6009f4cd8cf1e3c2e6d9404b225042f0646 Mon Sep 17 00:00:00 2001
From: Marcos
Date: Wed, 26 Aug 2026 10:57:31 +0000
Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=80=20Auto-deploy:=20BrainWind=20atual?=
=?UTF-8?q?izado=20em=2026/08/2026=2010:57:31?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../components/GlobalWindSettingsModal.tsx | 18 ++++++
app/src/components/LinearLoadsTable.tsx | 62 ++++++++++++++++++-
app/src/lib/combinations.ts | 51 +++++++++++++++
app/src/lib/dynamics.ts | 13 ++++
app/src/pages/PiperackModule.tsx | 34 +++++++++-
app/src/pages/ShelterModule.tsx | 23 ++++++-
app/src/store/appStore.ts | 12 ++--
7 files changed, 204 insertions(+), 9 deletions(-)
create mode 100644 app/src/lib/combinations.ts
create mode 100644 app/src/lib/dynamics.ts
diff --git a/app/src/components/GlobalWindSettingsModal.tsx b/app/src/components/GlobalWindSettingsModal.tsx
index 391b008..aee9191 100644
--- a/app/src/components/GlobalWindSettingsModal.tsx
+++ b/app/src/components/GlobalWindSettingsModal.tsx
@@ -42,6 +42,8 @@ export function GlobalWindSettingsModal({ trigger }: GlobalWindSettingsModalProp
setTerrainCategory,
locality,
setLocality,
+ fviz,
+ setFviz,
} = useWindStore();
const [stationQuery, setStationQuery] = useState('');
@@ -77,6 +79,7 @@ export function GlobalWindSettingsModal({ trigger }: GlobalWindSettingsModalProp
NBR 6123
Localidade
+ Vizinhança 3D
@@ -215,6 +218,21 @@ export function GlobalWindSettingsModal({ trigger }: GlobalWindSettingsModalProp
+
+
+
+
+ Fator de Vizinhança (Fviz)
+ {fviz.toFixed(2)}
+
+
setFviz(vals[0])} className="py-1 cursor-pointer" />
+
+ Modifica diretamente a pressão dinâmica (q).
+ • < 1.0 : Efeito de sombreamento (edifícios ao redor protegem a estrutura).
+ • > 1.0 : Efeito funil ou fenda (edifícios ao redor aceleram o vento).
+
+
+
diff --git a/app/src/components/LinearLoadsTable.tsx b/app/src/components/LinearLoadsTable.tsx
index c34077f..cac9d33 100644
--- a/app/src/components/LinearLoadsTable.tsx
+++ b/app/src/components/LinearLoadsTable.tsx
@@ -9,6 +9,8 @@ import {
getPillarBaseMoment,
getDragForce,
} from '../lib/line-loads';
+import { calcELU, calcELS } from '../lib/combinations';
+import type { LoadInputs } from '../lib/combinations';
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from './ui/card';
import { Input } from './ui/input';
import { Badge } from './ui/badge';
@@ -24,6 +26,10 @@ const LinearLoadsTable: React.FC = () => {
const { q, cpi, windAngle, frameSpacing, setFrameSpacing } = wind;
const [purlinSpacing, setPurlinSpacing] = useState(1.5);
+
+ // Gravitational Loads (kN/m²)
+ const [pp, setPp] = useState(0.15); // Peso Próprio típico
+ const [sc, setSc] = useState(0.25); // Sobrecarga típica de cobertura
const columnLoads = useMemo(
() => getColumnLinearLoads(cpi, q, wallCpe, frameSpacing, windAngle),
@@ -92,10 +98,11 @@ const LinearLoadsTable: React.FC = () => {
-
+
{t('linear_loads_tab_pillars')}
{t('linear_loads_tab_purlins')}
{t('linear_loads_tab_reactions')}
+ Combinações
@@ -202,6 +209,59 @@ const LinearLoadsTable: React.FC = () => {
{t('linear_loads_warning_simplified')}
+
+
+
+
+
+
+
+
+ Elemento (Têrça)
+ Vento (kN/m)
+ ELU (kN/m)
+ ELS (kN/m)
+
+
+
+ {[
+ { zona: 'E', w: roofLoads.E },
+ { zona: 'F', w: roofLoads.F },
+ { zona: 'G', w: roofLoads.G },
+ { zona: 'H', w: roofLoads.H },
+ { zona: 'I', w: roofLoads.I },
+ { zona: 'J', w: roofLoads.J },
+ ].map((row) => {
+ const inputs: LoadInputs = {
+ pp: pp * purlinSpacing, // linearizando a carga de área
+ sc: sc * purlinSpacing,
+ w: row.w
+ };
+ return (
+
+ Zona {row.zona}
+ {fmtSigned(row.w)}
+ {fmtSigned(calcELU(inputs))}
+ {fmtSigned(calcELS(inputs))}
+
+ );
+ })}
+
+
+
+
+ * Combinações simplificadas para NBR 8681. Assumido vento principal ou sobrecarga principal (pega o pior caso ELU Normal).
+
+
diff --git a/app/src/lib/combinations.ts b/app/src/lib/combinations.ts
new file mode 100644
index 0000000..b1e56b8
--- /dev/null
+++ b/app/src/lib/combinations.ts
@@ -0,0 +1,51 @@
+/**
+ * Módulo de Combinações de Ações (NBR 8681 / NBR 6118)
+ * Simplificado para uso prático com o vento.
+ */
+
+export interface LoadInputs {
+ /** Peso Próprio (Permanente) em kN/m ou kN/m² */
+ pp: number;
+ /** Sobrecarga (Acidental) em kN/m ou kN/m² */
+ sc: number;
+ /** Carga de Vento (Acidental) em kN/m ou kN/m² */
+ w: number;
+}
+
+/**
+ * Combinação ELU (Estado Limite Último) Normal
+ * Vento Principal: 1.4*PP + 1.4*W + 1.4*0.5*SC
+ * Sobrecarga Principal: 1.4*PP + 1.4*SC + 1.4*0.6*W
+ */
+export function calcELU(loads: LoadInputs): number {
+ const { pp, sc, w } = loads;
+ const g = 1.4 * pp;
+
+ // Vento como ação variável principal
+ const comb1 = g + (1.4 * w) + (1.4 * 0.5 * sc);
+
+ // Sobrecarga como ação variável principal
+ const comb2 = g + (1.4 * sc) + (1.4 * 0.6 * w);
+
+ return Math.max(Math.abs(comb1), Math.abs(comb2)) * Math.sign(w || pp || 1); // preserva direção da dominante
+}
+
+/**
+ * Combinação ELU Fiel (Arrancamento)
+ * Peso Próprio é favorável (reduz a chance de voar).
+ * Vento é desfavorável (puxa para cima, negativo).
+ * PP = 1.0, W = 1.4
+ */
+export function calcELUArrancamento(pp: number, w: number): number {
+ // pp é positivo para baixo. w é negativo para cima (sucção).
+ return (1.0 * pp) + (1.4 * w);
+}
+
+/**
+ * Combinação ELS (Estado Limite de Serviço) - Frequente
+ * PP + 0.4*SC + 0.3*W
+ */
+export function calcELS(loads: LoadInputs): number {
+ const { pp, sc, w } = loads;
+ return pp + (0.4 * sc) + (0.3 * w);
+}
diff --git a/app/src/lib/dynamics.ts b/app/src/lib/dynamics.ts
new file mode 100644
index 0000000..b8cd3a2
--- /dev/null
+++ b/app/src/lib/dynamics.ts
@@ -0,0 +1,13 @@
+/**
+ * Utilitários para Análise Dinâmica Simplificada (NBR 6123 - Anexo Dinâmico)
+ */
+
+/**
+ * Retorna verdadeiro se a frequência natural (Hz) indica que a estrutura
+ * é excessivamente flexível e exige cálculo pelo modelo dinâmico rigoroso.
+ * Uma regra de ouro da engenharia de vento brasileira é que f1 < 1Hz
+ * requer verificação atenciosa aos efeitos dinâmicos (rajadas e vórtices).
+ */
+export function isResonantRisk(f1: number): boolean {
+ return f1 > 0 && f1 < 1.0;
+}
diff --git a/app/src/pages/PiperackModule.tsx b/app/src/pages/PiperackModule.tsx
index e16a887..427855e 100644
--- a/app/src/pages/PiperackModule.tsx
+++ b/app/src/pages/PiperackModule.tsx
@@ -8,13 +8,14 @@ import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { useWindStore } from '@/store/appStore';
import { calculatePiperack, type PipeDef } from '@/lib/modules/piperack';
+import { isResonantRisk } from '@/lib/dynamics';
import { Piperack3D } from '@/components/three/Piperack3D';
import SceneCapturePanel from '../components/SceneCapturePanel';
import ExportMenu from '../components/ExportMenu';
import { EducationalManual } from '@/components/EducationalManual';
import { WindParametersSummary } from '@/components/WindParametersSummary';
import { exportGenericToPDF, type GenericPDFSection } from '../lib/export-generic-pdf';
-import { Plus, Trash2, Info } from 'lucide-react';
+import { Plus, Trash2, Info, AlertTriangle } from 'lucide-react';
import { Tooltip, TooltipContent, TooltipTrigger, TooltipProvider } from '@/components/ui/tooltip';
import { Input } from '@/components/ui/input';
@@ -26,6 +27,7 @@ const PiperackModule: React.FC = () => {
const [spacing, setSpacing] = useState(2);
const [numFrames, setNumFrames] = useState(2);
const [phiStruct, setPhiStruct] = useState(0.5);
+ const [f1, setF1] = useState(1.5); // Frequência natural (Hz)
const [pipes, setPipes] = useState([]);
const pendingLoad = useHydrationStore((s) => s.pendingLoad);
@@ -41,6 +43,7 @@ const PiperackModule: React.FC = () => {
if (p.numFrames !== undefined) setNumFrames(p.numFrames);
if (p.pipes !== undefined) setPipes(p.pipes);
if (p.phiStruct !== undefined) setPhiStruct(p.phiStruct);
+ if (p.f1 !== undefined) setF1(p.f1);
clearPendingLoad();
}
}, [pendingLoad, clearPendingLoad]);
@@ -157,7 +160,7 @@ const PiperackModule: React.FC = () => {
@@ -219,6 +222,33 @@ const PiperackModule: React.FC = () => {
setPhiStruct(v[0])} />
+
+
+
+
+
Freq. Natural (f₁)
+
+
+
+
+
+
+ Frequência fundamental no sentido do vento (Hz). Valores abaixo de 1 Hz indicam risco dinâmico elevado.
+
+
+
+
+
{f1.toFixed(1)} Hz
+
+
setF1(v[0])} />
+
+ {isResonantRisk(f1) && (
+
+
+
Estrutura flexível (f₁ < 1 Hz). O vento produzirá amplificação dinâmica (C.A.D) considerável. É exigida análise pela NBR 6123 Capítulo 9.
+
+ )}
+
diff --git a/app/src/pages/ShelterModule.tsx b/app/src/pages/ShelterModule.tsx
index ed0786c..fdb5850 100644
--- a/app/src/pages/ShelterModule.tsx
+++ b/app/src/pages/ShelterModule.tsx
@@ -19,7 +19,7 @@ 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, ArrowUpRight, CheckCircle2, Wind } from 'lucide-react';
+import { Eye, Box, Layers, ArrowUpRight, CheckCircle2, Wind, AlertTriangle } from 'lucide-react';
import { cn } from '@/lib/utils';
const conditionLabels: Record = {
@@ -41,6 +41,7 @@ const ShelterModule: React.FC = () => {
const [backRange, setBackRange] = useState<[number, number]>([0, 75]);
const [leftClosure, setLeftClosure] = useState(100);
const [rightClosure, setRightClosure] = useState(100);
+ const [surfaceMass, setSurfaceMass] = useState(15); // kg/m² para lona/telha leve
const [viewMode, setViewMode] = useState<'3d' | 'elevation' | 'airflow'>('3d');
// Determinação automática da posição da fresta/vão a partir dos cursores (base e topo)
@@ -100,6 +101,9 @@ const ShelterModule: React.FC = () => {
const effAreaRoof = depth * width;
const effAreaBack = (width * height) * (backClosure / 100);
const effAreaSideTotal = (depth * height) * ((leftClosure + rightClosure) / 100);
+
+ const gravityForce = (surfaceMass * effAreaRoof * 9.81) / 1000; // in kN
+ const isUpliftCritical = envelope.criticalUp.value > gravityForce;
const handleExportPDF = () => {
const angleLabel = windAngle === 0 ? '0° (Frontal)' : windAngle === 45 ? '45° (Oblíquo)' : '90° (Lateral)';
@@ -351,7 +355,7 @@ const ShelterModule: React.FC = () => {
@@ -476,6 +480,21 @@ const ShelterModule: React.FC = () => {
setTheta(v[0])} />
+
+
+
+ Massa Superficial (kg/m²):
+ {surfaceMass} kg/m²
+
+
setSurfaceMass(v[0])} />
+ Lonas ~3kg/m², Telhas Metálicas ~10-15kg/m²
+ {isUpliftCritical && (
+
+
+
Alerta de Arrancamento: Força (↑) {envelope.criticalUp.value.toFixed(1)} kN > Peso ({gravityForce.toFixed(1)} kN). Exige ancoragem/estaiamento.
+
+ )}
+
diff --git a/app/src/store/appStore.ts b/app/src/store/appStore.ts
index c5b53b8..2ce3bbd 100644
--- a/app/src/store/appStore.ts
+++ b/app/src/store/appStore.ts
@@ -16,6 +16,7 @@ export interface GlobalWindState {
largestDimension: number;
heightZ: number;
locality: string | null;
+ fviz: number; // Fator de vizinhança 3D (0.7 a 1.3)
// Saídas (Calculados)
structureClass: StructureClass;
@@ -42,6 +43,7 @@ export interface GlobalWindState {
setPermeabilityCase: (c: PermeabilityCase) => void;
setCpiRatio: (r: number) => void;
setCpiManual: (c: number) => void;
+ setFviz: (f: number) => void;
companyLogo: string | null;
setCompanyLogo: (logo: string | null) => void;
@@ -81,7 +83,7 @@ export const useWindStore = create((set, get) => {
structureClass: calc.structClass,
s2: calc.s2,
vk: calc.vk,
- q: calc.q,
+ q: calc.q * (state.fviz ?? 1.0),
cpi: calcCpi(state.permeabilityCase, state.cpiRatio, state.windAngle),
};
};
@@ -95,6 +97,7 @@ export const useWindStore = create((set, get) => {
largestDimension: 30,
heightZ: 10,
locality: null,
+ fviz: 1.0,
structureClass: 'B',
s2: 1.06,
@@ -169,9 +172,10 @@ export const useWindStore = create((set, get) => {
},
setCpiManual: (c) => {
set({ cpi: clampCpi(c) });
- // Not calling updateCalculations to avoid overriding it immediately if permeabilityCase applies,
- // but if the user overrides manually we might want to switch permeabilityCase to 'custom' ?
- // Wait, there is no 'custom'. We just set the value.
+ },
+ setFviz: (f) => {
+ set({ fviz: f });
+ get().updateCalculations();
},
};
});
\ No newline at end of file