feat: unified 0 and 90 degree PDF envelope and category descriptors

This commit is contained in:
2026-07-08 19:52:34 +00:00
commit 9fece3f174
170 changed files with 27177 additions and 0 deletions
+246
View File
@@ -0,0 +1,246 @@
import React, { useMemo } from 'react';
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
import { Slider } from '@/components/ui/slider';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Badge } from '@/components/ui/badge';
import { Separator } from '@/components/ui/separator';
import { useWindStore } from '@/store/appStore';
import { calculateFlatBarForce, type FlatBarSection } from '@/lib/nbr-tables/table-26';
import { calculateCircleBarForce, reynoldsBar } from '@/lib/nbr-tables/table-27';
import Bar3DViewer from '@/components/three/Bar3D';
import SceneCapturePanel from '../components/SceneCapturePanel';
import ExportMenu from '../components/ExportMenu';
import { exportGenericToPDF, type GenericPDFSection } from '../lib/export-generic-pdf';
const BarSelectorModule: React.FC = () => {
const { vk, q } = useWindStore();
const [barType, setBarType] = React.useState<'flat' | 'circular'>('flat');
const [section, setSection] = React.useState<FlatBarSection>('l');
const [alpha, setAlpha] = React.useState(45);
const [width, setWidth] = React.useState(0.1);
const [length, setLength] = React.useState(5);
const [diameter, setDiameter] = React.useState(0.05);
const flatResult = useMemo(() => {
if (barType !== 'flat') return null;
return calculateFlatBarForce({ section, alpha, width, length, q });
}, [barType, section, alpha, width, length, q]);
const circleResult = useMemo(() => {
if (barType !== 'circular') return null;
return calculateCircleBarForce({ d: diameter, length, vk, q });
}, [barType, diameter, length, vk, q]);
const reCircle = useMemo(() => reynoldsBar(vk, diameter), [vk, diameter]);
const handleExportPDF = () => {
const sections: GenericPDFSection[] = [
{
title: 'Geometria da Barra',
type: 'grid',
gridItems: [
{ label: 'Tipo', value: barType === 'flat' ? 'Faces planas' : 'Circular' },
{ label: 'Comprimento ()', value: `${length} m` },
...(barType === 'flat'
? [
{ label: 'Seção transversal', value: section },
{ label: 'Ângulo (α)', value: `${alpha}°` },
{ label: 'Largura (c)', value: `${width} m` },
]
: [
{ label: 'Diâmetro (d)', value: `${diameter} m` },
{ label: 'Reynolds (Re)', value: reCircle.toExponential(2) },
{
label: 'Regime',
value: reCircle < 4.2e5 ? 'Subcrítico' : reCircle < 2.3e6 ? 'Crítico' : 'Supercrítico',
},
]),
],
},
{
title: 'Forças e Coeficientes',
type: 'grid',
gridItems:
barType === 'flat' && flatResult
? [
{ label: 'Coeficiente Cx', value: flatResult.cx.toFixed(2) },
{ label: 'Coeficiente Cy', value: flatResult.cy.toFixed(2) },
{ label: 'Fator K', value: flatResult.kFactor.toFixed(2) },
{ label: 'Força Fx', value: `${flatResult.fxKN.toFixed(3)} kN` },
{ label: 'Força Fy', value: `${flatResult.fyKN.toFixed(3)} kN` },
]
: circleResult
? [
{ label: 'Coeficiente Ca', value: circleResult.ca.toFixed(2) },
{ label: 'Regime (tabela)', value: circleResult.regime },
{ label: 'Fator K', value: circleResult.kFactor.toFixed(2) },
{ label: 'Força de Arrasto', value: `${circleResult.forceKN.toFixed(3)} kN` },
]
: [],
},
];
exportGenericToPDF('Barra Prismática', sections);
};
return (
<div className="flex flex-col lg:flex-row h-full w-full gap-4 p-4 lg:p-6 bg-muted/30">
{/* Coluna Esquerda: Controles */}
<div className="w-full lg:w-80 shrink-0 flex flex-col gap-4 overflow-y-auto pb-8 lg:pb-0">
<Card className="shadow-sm border-border">
<CardHeader className="pb-3">
<CardTitle className="text-lg">Barra Prismática</CardTitle>
<CardDescription>Sec. 8.1 faces planas (Tab. 26) ou circulares (Tab. 27).</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div className="space-y-2">
<label className="text-sm font-medium">Tipo de Barra</label>
<Select value={barType} onValueChange={(v) => setBarType(v as 'flat' | 'circular')}>
<SelectTrigger><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem value="flat">Faces planas</SelectItem>
<SelectItem value="circular">Circular</SelectItem>
</SelectContent>
</Select>
</div>
{barType === 'flat' && (
<>
<div className="space-y-2">
<label className="text-sm font-medium">Seção Transversal</label>
<Select value={section} onValueChange={(v) => setSection(v as FlatBarSection)}>
<SelectTrigger><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem value="placa">Placa</SelectItem>
<SelectItem value="l">Perfil L (cantoneira)</SelectItem>
<SelectItem value="t">Perfil T</SelectItem>
<SelectItem value="i">Perfil I</SelectItem>
<SelectItem value="rectangle">Retângulo</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-3">
<div className="flex justify-between"><label className="text-sm font-medium">Ângulo α</label><span className="font-mono text-sm">{alpha}°</span></div>
<Slider min={0} max={180} step={5} value={[alpha]} onValueChange={(v) => setAlpha(v[0])} />
</div>
<div className="space-y-3">
<div className="flex justify-between"><label className="text-sm font-medium">Largura c</label><span className="font-mono text-sm">{width} m</span></div>
<Slider min={0.05} max={1} step={0.01} value={[width]} onValueChange={(v) => setWidth(v[0])} />
</div>
</>
)}
{barType === 'circular' && (
<div className="space-y-3">
<div className="flex justify-between"><label className="text-sm font-medium">Diâmetro d</label><span className="font-mono text-sm">{diameter} m</span></div>
<Slider min={0.01} max={0.5} step={0.01} value={[diameter]} onValueChange={(v) => setDiameter(v[0])} />
<div className="rounded-md border bg-muted/40 p-2 text-xs">
Re = {reCircle.toLocaleString('pt-BR')} · regime: {reCircle < 4.2e5 ? 'subcrítico' : reCircle < 2.3e6 ? 'crítico' : 'supercrítico'}
</div>
</div>
)}
<div className="space-y-3">
<div className="flex justify-between"><label className="text-sm font-medium">Comprimento </label><span className="font-mono text-sm">{length} m</span></div>
<Slider min={1} max={30} step={0.5} value={[length]} onValueChange={(v) => setLength(v[0])} />
</div>
<div className="rounded-md border bg-muted/40 p-3 text-xs space-y-1">
<div className="flex justify-between"><span>Vₖ:</span><span className="font-mono">{vk.toFixed(2)} m/s</span></div>
<div className="flex justify-between"><span>q:</span><span className="font-mono">{q.toFixed(4)} kN/m²</span></div>
</div>
</CardContent>
</Card>
</div>
{/* Coluna Central: 3D */}
<div className="flex-1 flex flex-col min-h-[500px] lg:min-h-0 bg-background rounded-xl border border-border shadow-sm overflow-hidden relative">
<div className="absolute top-4 left-4 z-10 flex gap-2">
<Badge variant="secondary" className="bg-background/80 backdrop-blur-sm">α = {alpha}°</Badge>
</div>
<Bar3DViewer
barType={barType}
section={barType === 'flat' ? section : undefined}
diameter={barType === 'circular' ? diameter : undefined}
width={barType === 'flat' ? width : undefined}
length={length}
alpha={alpha}
fxKN={flatResult?.fxKN ?? 0}
fyKN={flatResult?.fyKN ?? 0}
cx={flatResult?.cx ?? 0}
/>
</div>
{/* Coluna Direita: Resultados */}
<div className="w-full lg:w-96 shrink-0 flex flex-col gap-4 overflow-y-auto pb-8 lg:pb-0">
<Card className="shadow-sm border-border">
<CardHeader className="pb-3">
<CardTitle className="text-lg">Forças Cx e Cy</CardTitle>
<CardDescription>Com fator K (Tab. 28) aplicado</CardDescription>
</CardHeader>
<CardContent className="space-y-3">
{flatResult && (
<>
<div className="grid grid-cols-3 gap-2 text-sm">
<div className="rounded-md border bg-muted/40 p-2 text-center">
<div className="text-[10px] text-muted-foreground">Cx</div>
<div className="font-mono font-medium">{flatResult.cx.toFixed(2)}</div>
</div>
<div className="rounded-md border bg-muted/40 p-2 text-center">
<div className="text-[10px] text-muted-foreground">Cy</div>
<div className="font-mono font-medium">{flatResult.cy.toFixed(2)}</div>
</div>
<div className="rounded-md border bg-muted/40 p-2 text-center">
<div className="text-[10px] text-muted-foreground">K</div>
<div className="font-mono font-medium">{flatResult.kFactor.toFixed(2)}</div>
</div>
</div>
<Separator />
<div className="grid grid-cols-2 gap-2 text-sm">
<div className="rounded-md border bg-muted/40 p-2 text-center">
<div className="text-[10px] text-muted-foreground">Fx</div>
<div className="font-mono font-medium">{flatResult.fxKN.toFixed(3)} kN</div>
</div>
<div className="rounded-md border bg-muted/40 p-2 text-center">
<div className="text-[10px] text-muted-foreground">Fy</div>
<div className="font-mono font-medium">{flatResult.fyKN.toFixed(3)} kN</div>
</div>
</div>
</>
)}
{circleResult && (
<>
<div className="grid grid-cols-3 gap-2 text-sm">
<div className="rounded-md border bg-muted/40 p-2 text-center">
<div className="text-[10px] text-muted-foreground">Ca</div>
<div className="font-mono font-medium">{circleResult.ca.toFixed(2)}</div>
</div>
<div className="rounded-md border bg-muted/40 p-2 text-center">
<div className="text-[10px] text-muted-foreground">Regime</div>
<div className="font-mono font-medium text-xs">{circleResult.regime}</div>
</div>
<div className="rounded-md border bg-muted/40 p-2 text-center">
<div className="text-[10px] text-muted-foreground">K</div>
<div className="font-mono font-medium">{circleResult.kFactor.toFixed(2)}</div>
</div>
</div>
<Separator />
<div className="rounded-md border bg-muted/40 p-2 text-center">
<div className="text-[10px] text-muted-foreground">Força de arrasto</div>
<div className="font-mono font-medium text-lg">{circleResult.forceKN.toFixed(3)} kN</div>
</div>
</>
)}
</CardContent>
</Card>
<div className="flex justify-center bg-background rounded-xl border border-border shadow-sm p-4">
<ExportMenu onExportPDF={handleExportPDF} />
</div>
<SceneCapturePanel />
</div>
</div>
);
};
export default BarSelectorModule;