🚀 Auto-deploy: BrainWind atualizado em 26/08/2026 10:57:31
This commit is contained in:
@@ -42,6 +42,8 @@ export function GlobalWindSettingsModal({ trigger }: GlobalWindSettingsModalProp
|
|||||||
setTerrainCategory,
|
setTerrainCategory,
|
||||||
locality,
|
locality,
|
||||||
setLocality,
|
setLocality,
|
||||||
|
fviz,
|
||||||
|
setFviz,
|
||||||
} = useWindStore();
|
} = useWindStore();
|
||||||
|
|
||||||
const [stationQuery, setStationQuery] = useState('');
|
const [stationQuery, setStationQuery] = useState('');
|
||||||
@@ -77,6 +79,7 @@ export function GlobalWindSettingsModal({ trigger }: GlobalWindSettingsModalProp
|
|||||||
<TabsList className="flex w-full mb-4 bg-muted p-1 text-xs min-w-0">
|
<TabsList className="flex w-full mb-4 bg-muted p-1 text-xs min-w-0">
|
||||||
<TabsTrigger value="norma" className="text-xs py-1 px-1.5 flex-1">NBR 6123</TabsTrigger>
|
<TabsTrigger value="norma" className="text-xs py-1 px-1.5 flex-1">NBR 6123</TabsTrigger>
|
||||||
<TabsTrigger value="local" className="text-xs py-1 px-1.5 flex-1">Localidade</TabsTrigger>
|
<TabsTrigger value="local" className="text-xs py-1 px-1.5 flex-1">Localidade</TabsTrigger>
|
||||||
|
<TabsTrigger value="vizinhanca" className="text-xs py-1 px-1.5 flex-1">Vizinhança 3D</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
<TabsContent value="norma" className="space-y-4 min-w-0">
|
<TabsContent value="norma" className="space-y-4 min-w-0">
|
||||||
@@ -215,6 +218,21 @@ export function GlobalWindSettingsModal({ trigger }: GlobalWindSettingsModalProp
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="vizinhanca" className="space-y-4 min-w-0">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<label className="text-xs font-medium text-foreground">Fator de Vizinhança (Fviz)</label>
|
||||||
|
<span className="text-xs text-muted-foreground font-mono">{fviz.toFixed(2)}</span>
|
||||||
|
</div>
|
||||||
|
<Slider min={0.7} max={1.3} step={0.01} value={[fviz]} onValueChange={(vals) => setFviz(vals[0])} className="py-1 cursor-pointer" />
|
||||||
|
<p className="text-[10px] text-muted-foreground">
|
||||||
|
Modifica diretamente a pressão dinâmica (q).<br/>
|
||||||
|
• <b>< 1.0</b>: Efeito de sombreamento (edifícios ao redor protegem a estrutura).<br/>
|
||||||
|
• <b>> 1.0</b>: Efeito funil ou fenda (edifícios ao redor aceleram o vento).
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import {
|
|||||||
getPillarBaseMoment,
|
getPillarBaseMoment,
|
||||||
getDragForce,
|
getDragForce,
|
||||||
} from '../lib/line-loads';
|
} 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 { Card, CardContent, CardHeader, CardTitle, CardDescription } from './ui/card';
|
||||||
import { Input } from './ui/input';
|
import { Input } from './ui/input';
|
||||||
import { Badge } from './ui/badge';
|
import { Badge } from './ui/badge';
|
||||||
@@ -24,6 +26,10 @@ const LinearLoadsTable: React.FC = () => {
|
|||||||
const { q, cpi, windAngle, frameSpacing, setFrameSpacing } = wind;
|
const { q, cpi, windAngle, frameSpacing, setFrameSpacing } = wind;
|
||||||
|
|
||||||
const [purlinSpacing, setPurlinSpacing] = useState<number>(1.5);
|
const [purlinSpacing, setPurlinSpacing] = useState<number>(1.5);
|
||||||
|
|
||||||
|
// Gravitational Loads (kN/m²)
|
||||||
|
const [pp, setPp] = useState<number>(0.15); // Peso Próprio típico
|
||||||
|
const [sc, setSc] = useState<number>(0.25); // Sobrecarga típica de cobertura
|
||||||
|
|
||||||
const columnLoads = useMemo(
|
const columnLoads = useMemo(
|
||||||
() => getColumnLinearLoads(cpi, q, wallCpe, frameSpacing, windAngle),
|
() => getColumnLinearLoads(cpi, q, wallCpe, frameSpacing, windAngle),
|
||||||
@@ -92,10 +98,11 @@ const LinearLoadsTable: React.FC = () => {
|
|||||||
<Separator />
|
<Separator />
|
||||||
|
|
||||||
<Tabs defaultValue="pilares" className="w-full">
|
<Tabs defaultValue="pilares" className="w-full">
|
||||||
<TabsList className="grid w-full grid-cols-3">
|
<TabsList className="grid w-full grid-cols-4">
|
||||||
<TabsTrigger value="pilares">{t('linear_loads_tab_pillars')}</TabsTrigger>
|
<TabsTrigger value="pilares">{t('linear_loads_tab_pillars')}</TabsTrigger>
|
||||||
<TabsTrigger value="tercas">{t('linear_loads_tab_purlins')}</TabsTrigger>
|
<TabsTrigger value="tercas">{t('linear_loads_tab_purlins')}</TabsTrigger>
|
||||||
<TabsTrigger value="reacoes">{t('linear_loads_tab_reactions')}</TabsTrigger>
|
<TabsTrigger value="reacoes">{t('linear_loads_tab_reactions')}</TabsTrigger>
|
||||||
|
<TabsTrigger value="combinacoes" className="text-xs">Combinações</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
<TabsContent value="pilares" className="space-y-3">
|
<TabsContent value="pilares" className="space-y-3">
|
||||||
@@ -202,6 +209,59 @@ const LinearLoadsTable: React.FC = () => {
|
|||||||
{t('linear_loads_warning_simplified')}
|
{t('linear_loads_warning_simplified')}
|
||||||
</p>
|
</p>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="combinacoes" className="space-y-4">
|
||||||
|
<div className="grid grid-cols-2 gap-4 bg-muted/20 p-3 rounded-md border">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<label className="text-xs font-medium text-foreground">Peso Próprio (kN/m²)</label>
|
||||||
|
<Input type="number" step={0.05} value={pp} onChange={e => setPp(Number(e.target.value))} className="h-8" />
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1">
|
||||||
|
<label className="text-xs font-medium text-foreground">Sobrecarga (kN/m²)</label>
|
||||||
|
<Input type="number" step={0.05} value={sc} onChange={e => setSc(Number(e.target.value))} className="h-8" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="rounded-md border overflow-hidden">
|
||||||
|
<table className="w-full text-sm">
|
||||||
|
<thead className="bg-muted/40">
|
||||||
|
<tr>
|
||||||
|
<th className="px-3 py-2 text-left font-medium">Elemento (Têrça)</th>
|
||||||
|
<th className="px-3 py-2 text-right font-medium">Vento (kN/m)</th>
|
||||||
|
<th className="px-3 py-2 text-right font-medium">ELU (kN/m)</th>
|
||||||
|
<th className="px-3 py-2 text-right font-medium text-muted-foreground">ELS (kN/m)</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{[
|
||||||
|
{ 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 (
|
||||||
|
<tr key={row.zona} className="border-t">
|
||||||
|
<td className="px-3 py-2 font-mono font-semibold">Zona {row.zona}</td>
|
||||||
|
<td className="px-3 py-2 text-right font-mono">{fmtSigned(row.w)}</td>
|
||||||
|
<td className="px-3 py-2 text-right font-mono font-bold text-primary">{fmtSigned(calcELU(inputs))}</td>
|
||||||
|
<td className="px-3 py-2 text-right font-mono text-muted-foreground">{fmtSigned(calcELS(inputs))}</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<p className="text-[11px] text-muted-foreground leading-relaxed">
|
||||||
|
* Combinações simplificadas para NBR 8681. Assumido vento principal ou sobrecarga principal (pega o pior caso ELU Normal).
|
||||||
|
</p>
|
||||||
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
<Separator />
|
<Separator />
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -8,13 +8,14 @@ import { Button } from '@/components/ui/button';
|
|||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { useWindStore } from '@/store/appStore';
|
import { useWindStore } from '@/store/appStore';
|
||||||
import { calculatePiperack, type PipeDef } from '@/lib/modules/piperack';
|
import { calculatePiperack, type PipeDef } from '@/lib/modules/piperack';
|
||||||
|
import { isResonantRisk } from '@/lib/dynamics';
|
||||||
import { Piperack3D } from '@/components/three/Piperack3D';
|
import { Piperack3D } from '@/components/three/Piperack3D';
|
||||||
import SceneCapturePanel from '../components/SceneCapturePanel';
|
import SceneCapturePanel from '../components/SceneCapturePanel';
|
||||||
import ExportMenu from '../components/ExportMenu';
|
import ExportMenu from '../components/ExportMenu';
|
||||||
import { EducationalManual } from '@/components/EducationalManual';
|
import { EducationalManual } from '@/components/EducationalManual';
|
||||||
import { WindParametersSummary } from '@/components/WindParametersSummary';
|
import { WindParametersSummary } from '@/components/WindParametersSummary';
|
||||||
import { exportGenericToPDF, type GenericPDFSection } from '../lib/export-generic-pdf';
|
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 { Tooltip, TooltipContent, TooltipTrigger, TooltipProvider } from '@/components/ui/tooltip';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
|
|
||||||
@@ -26,6 +27,7 @@ const PiperackModule: React.FC = () => {
|
|||||||
const [spacing, setSpacing] = useState(2);
|
const [spacing, setSpacing] = useState(2);
|
||||||
const [numFrames, setNumFrames] = useState(2);
|
const [numFrames, setNumFrames] = useState(2);
|
||||||
const [phiStruct, setPhiStruct] = useState(0.5);
|
const [phiStruct, setPhiStruct] = useState(0.5);
|
||||||
|
const [f1, setF1] = useState(1.5); // Frequência natural (Hz)
|
||||||
const [pipes, setPipes] = useState<PipeDef[]>([]);
|
const [pipes, setPipes] = useState<PipeDef[]>([]);
|
||||||
|
|
||||||
const pendingLoad = useHydrationStore((s) => s.pendingLoad);
|
const pendingLoad = useHydrationStore((s) => s.pendingLoad);
|
||||||
@@ -41,6 +43,7 @@ const PiperackModule: React.FC = () => {
|
|||||||
if (p.numFrames !== undefined) setNumFrames(p.numFrames);
|
if (p.numFrames !== undefined) setNumFrames(p.numFrames);
|
||||||
if (p.pipes !== undefined) setPipes(p.pipes);
|
if (p.pipes !== undefined) setPipes(p.pipes);
|
||||||
if (p.phiStruct !== undefined) setPhiStruct(p.phiStruct);
|
if (p.phiStruct !== undefined) setPhiStruct(p.phiStruct);
|
||||||
|
if (p.f1 !== undefined) setF1(p.f1);
|
||||||
clearPendingLoad();
|
clearPendingLoad();
|
||||||
}
|
}
|
||||||
}, [pendingLoad, clearPendingLoad]);
|
}, [pendingLoad, clearPendingLoad]);
|
||||||
@@ -157,7 +160,7 @@ const PiperackModule: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
<SaveModuleDialog
|
<SaveModuleDialog
|
||||||
moduleType="piperack"
|
moduleType="piperack"
|
||||||
inputs={{ elevation, width, height, spacing, numFrames, pipes, phiStruct }}
|
inputs={{ elevation, width, height, spacing, numFrames, pipes, phiStruct, f1 }}
|
||||||
/>
|
/>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-6">
|
<CardContent className="space-y-6">
|
||||||
@@ -219,6 +222,33 @@ const PiperackModule: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
<Slider value={[phiStruct]} min={0.1} max={1.0} step={0.05} onValueChange={v => setPhiStruct(v[0])} />
|
<Slider value={[phiStruct]} min={0.1} max={1.0} step={0.05} onValueChange={v => setPhiStruct(v[0])} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-sm font-medium">Freq. Natural (f₁)</span>
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Info className="w-4 h-4 text-muted-foreground cursor-help" />
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="top" className="max-w-xs">
|
||||||
|
<p>Frequência fundamental no sentido do vento (Hz). Valores abaixo de 1 Hz indicam risco dinâmico elevado.</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
</div>
|
||||||
|
<span className="text-sm text-muted-foreground">{f1.toFixed(1)} Hz</span>
|
||||||
|
</div>
|
||||||
|
<Slider value={[f1]} min={0.1} max={5.0} step={0.1} onValueChange={v => setF1(v[0])} />
|
||||||
|
|
||||||
|
{isResonantRisk(f1) && (
|
||||||
|
<div className="mt-2 p-2 bg-destructive/10 border border-destructive/20 rounded text-xs text-destructive flex gap-2 items-start">
|
||||||
|
<AlertTriangle className="w-4 h-4 shrink-0 mt-0.5" />
|
||||||
|
<p>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.</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import { EducationalManual } from '@/components/EducationalManual';
|
|||||||
import { WindParametersSummary } from '@/components/WindParametersSummary';
|
import { WindParametersSummary } from '@/components/WindParametersSummary';
|
||||||
import { SaveModuleDialog } from '@/components/SaveModuleDialog';
|
import { SaveModuleDialog } from '@/components/SaveModuleDialog';
|
||||||
import { exportGenericToPDF, type GenericPDFSection } from '../lib/export-generic-pdf';
|
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';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
const conditionLabels: Record<ShelterCondition, string> = {
|
const conditionLabels: Record<ShelterCondition, string> = {
|
||||||
@@ -41,6 +41,7 @@ const ShelterModule: React.FC = () => {
|
|||||||
const [backRange, setBackRange] = useState<[number, number]>([0, 75]);
|
const [backRange, setBackRange] = useState<[number, number]>([0, 75]);
|
||||||
const [leftClosure, setLeftClosure] = useState(100);
|
const [leftClosure, setLeftClosure] = useState(100);
|
||||||
const [rightClosure, setRightClosure] = 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');
|
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)
|
// 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 effAreaRoof = depth * width;
|
||||||
const effAreaBack = (width * height) * (backClosure / 100);
|
const effAreaBack = (width * height) * (backClosure / 100);
|
||||||
const effAreaSideTotal = (depth * height) * ((leftClosure + rightClosure) / 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 handleExportPDF = () => {
|
||||||
const angleLabel = windAngle === 0 ? '0° (Frontal)' : windAngle === 45 ? '45° (Oblíquo)' : '90° (Lateral)';
|
const angleLabel = windAngle === 0 ? '0° (Frontal)' : windAngle === 45 ? '45° (Oblíquo)' : '90° (Lateral)';
|
||||||
@@ -351,7 +355,7 @@ const ShelterModule: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
<SaveModuleDialog
|
<SaveModuleDialog
|
||||||
moduleType="shelter"
|
moduleType="shelter"
|
||||||
inputs={{ condition, depth, width, height, theta, backClosure, leftClosure, rightClosure, openingPos, windAngle }}
|
inputs={{ condition, depth, width, height, theta, backClosure, leftClosure, rightClosure, openingPos, windAngle, surfaceMass }}
|
||||||
/>
|
/>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-5">
|
<CardContent className="space-y-5">
|
||||||
@@ -476,6 +480,21 @@ const ShelterModule: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
<Slider min={0} max={30} step={1} value={[theta]} onValueChange={(v) => setTheta(v[0])} />
|
<Slider min={0} max={30} step={1} value={[theta]} onValueChange={(v) => setTheta(v[0])} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2 pt-2 border-t">
|
||||||
|
<div className="flex justify-between text-xs">
|
||||||
|
<span>Massa Superficial (kg/m²):</span>
|
||||||
|
<span className="font-mono font-medium">{surfaceMass} kg/m²</span>
|
||||||
|
</div>
|
||||||
|
<Slider min={2} max={100} step={1} value={[surfaceMass]} onValueChange={(v) => setSurfaceMass(v[0])} />
|
||||||
|
<p className="text-[10px] text-muted-foreground mt-1">Lonas ~3kg/m², Telhas Metálicas ~10-15kg/m²</p>
|
||||||
|
{isUpliftCritical && (
|
||||||
|
<div className="mt-2 p-2 bg-destructive/10 border border-destructive/20 rounded text-xs text-destructive flex gap-2 items-start">
|
||||||
|
<AlertTriangle className="w-4 h-4 shrink-0 mt-0.5" />
|
||||||
|
<p>Alerta de Arrancamento: Força (↑) {envelope.criticalUp.value.toFixed(1)} kN > Peso ({gravityForce.toFixed(1)} kN). Exige ancoragem/estaiamento.</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export interface GlobalWindState {
|
|||||||
largestDimension: number;
|
largestDimension: number;
|
||||||
heightZ: number;
|
heightZ: number;
|
||||||
locality: string | null;
|
locality: string | null;
|
||||||
|
fviz: number; // Fator de vizinhança 3D (0.7 a 1.3)
|
||||||
|
|
||||||
// Saídas (Calculados)
|
// Saídas (Calculados)
|
||||||
structureClass: StructureClass;
|
structureClass: StructureClass;
|
||||||
@@ -42,6 +43,7 @@ export interface GlobalWindState {
|
|||||||
setPermeabilityCase: (c: PermeabilityCase) => void;
|
setPermeabilityCase: (c: PermeabilityCase) => void;
|
||||||
setCpiRatio: (r: number) => void;
|
setCpiRatio: (r: number) => void;
|
||||||
setCpiManual: (c: number) => void;
|
setCpiManual: (c: number) => void;
|
||||||
|
setFviz: (f: number) => void;
|
||||||
companyLogo: string | null;
|
companyLogo: string | null;
|
||||||
setCompanyLogo: (logo: string | null) => void;
|
setCompanyLogo: (logo: string | null) => void;
|
||||||
|
|
||||||
@@ -81,7 +83,7 @@ export const useWindStore = create<GlobalWindState>((set, get) => {
|
|||||||
structureClass: calc.structClass,
|
structureClass: calc.structClass,
|
||||||
s2: calc.s2,
|
s2: calc.s2,
|
||||||
vk: calc.vk,
|
vk: calc.vk,
|
||||||
q: calc.q,
|
q: calc.q * (state.fviz ?? 1.0),
|
||||||
cpi: calcCpi(state.permeabilityCase, state.cpiRatio, state.windAngle),
|
cpi: calcCpi(state.permeabilityCase, state.cpiRatio, state.windAngle),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -95,6 +97,7 @@ export const useWindStore = create<GlobalWindState>((set, get) => {
|
|||||||
largestDimension: 30,
|
largestDimension: 30,
|
||||||
heightZ: 10,
|
heightZ: 10,
|
||||||
locality: null,
|
locality: null,
|
||||||
|
fviz: 1.0,
|
||||||
|
|
||||||
structureClass: 'B',
|
structureClass: 'B',
|
||||||
s2: 1.06,
|
s2: 1.06,
|
||||||
@@ -169,9 +172,10 @@ export const useWindStore = create<GlobalWindState>((set, get) => {
|
|||||||
},
|
},
|
||||||
setCpiManual: (c) => {
|
setCpiManual: (c) => {
|
||||||
set({ cpi: clampCpi(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' ?
|
setFviz: (f) => {
|
||||||
// Wait, there is no 'custom'. We just set the value.
|
set({ fviz: f });
|
||||||
|
get().updateCalculations();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
Reference in New Issue
Block a user