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

+ + +
+
+ + {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')}

+ + +
+
+ + setPp(Number(e.target.value))} className="h-8" /> +
+
+ + setSc(Number(e.target.value))} className="h-8" /> +
+
+ +
+ + + + + + + + + + + {[ + { 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 ( + + + + + + + ); + })} + +
Elemento (Têrça)Vento (kN/m)ELU (kN/m)ELS (kN/m)
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