🚀 Auto-deploy: BrainWind atualizado em 13/07/2026 20:57:51

This commit is contained in:
2026-07-13 20:57:51 +00:00
parent d4e0abe260
commit c184b2211c
4 changed files with 84 additions and 28 deletions
+63 -22
View File
@@ -25,15 +25,15 @@ const PiperackModule: React.FC = () => {
const [pipes, setPipes] = useState<PipeDef[]>([]);
const addPipe = () => {
setPipes([...pipes, { id: Math.random().toString(36).substr(2, 9), diameter: 0.5 }]);
setPipes([...pipes, { id: Math.random().toString(36).substr(2, 9), diameter: 0.5, elevationOffset: height / 2 }]);
};
const removePipe = (id: string) => {
setPipes(pipes.filter(p => p.id !== id));
};
const updatePipe = (id: string, diam: number) => {
setPipes(pipes.map(p => p.id === id ? { ...p, diameter: diam } : p));
const updatePipe = (id: string, diam: number, yOffset: number) => {
setPipes(pipes.map(p => p.id === id ? { ...p, diameter: diam, elevationOffset: yOffset } : p));
};
const result = useMemo(() => {
@@ -80,7 +80,8 @@ const PiperackModule: React.FC = () => {
{ label: 'Carga Linear Pórtico Frontal', value: `${result.linearLoadFront.toFixed(2)} kN/m` },
{ label: 'Carga Linear Pórticos Traseiros (cada)', value: `${result.linearLoadBack.toFixed(2)} kN/m` },
{ label: 'Força Global Total', value: `${result.globalForce.toFixed(2)} kN` },
{ label: 'Momento Total de Tombamento', value: `${(result.linearLoadFront * width * elevation + (numFrames > 1 ? (numFrames - 1) * result.linearLoadBack * width * elevation : 0)).toFixed(2)} kN·m` },
{ label: 'Centro de Pressão Efetivo (Z_eff)', value: `${result.effectiveElevation.toFixed(2)} m` },
{ label: 'Momento Total de Tombamento', value: `${(result.globalForce * result.effectiveElevation).toFixed(2)} kN·m` },
],
}
];
@@ -182,22 +183,40 @@ const PiperackModule: React.FC = () => {
Nenhuma tubulação adicionada.
</div>
) : (
<div className="space-y-2">
<div className="space-y-4">
{pipes.map((pipe, index) => (
<div key={pipe.id} className="flex items-center gap-2 p-2 rounded-md border bg-muted/20">
<label className="w-20 text-xs text-muted-foreground shrink-0">Tubo {index + 1}</label>
<Input
type="number"
value={pipe.diameter}
onChange={(e) => updatePipe(pipe.id, Number(e.target.value))}
step={0.1}
min={0.1}
className="h-7 text-xs font-mono"
/>
<span className="text-xs text-muted-foreground">m</span>
<Button variant="ghost" size="icon" className="h-7 w-7 text-destructive shrink-0" onClick={() => removePipe(pipe.id)}>
<Trash2 className="w-4 h-4" />
</Button>
<div key={pipe.id} className="flex flex-col gap-2 p-3 rounded-md border bg-muted/20">
<div className="flex items-center justify-between">
<label className="text-xs font-semibold text-foreground">Tubo {index + 1}</label>
<Button variant="ghost" size="icon" className="h-6 w-6 text-destructive" onClick={() => removePipe(pipe.id)}>
<Trash2 className="w-3 h-3" />
</Button>
</div>
<div className="flex items-center gap-3">
<div className="flex-1 space-y-1">
<label className="text-[10px] text-muted-foreground uppercase tracking-wider">Diâmetro (m)</label>
<Input
type="number"
value={pipe.diameter}
onChange={(e) => updatePipe(pipe.id, Number(e.target.value), pipe.elevationOffset)}
step={0.1}
min={0.1}
className="h-8 text-xs font-mono"
/>
</div>
<div className="flex-1 space-y-1">
<label className="text-[10px] text-muted-foreground uppercase tracking-wider">Alt. Pórtico (0 a {height})</label>
<Input
type="number"
value={pipe.elevationOffset}
onChange={(e) => updatePipe(pipe.id, pipe.diameter, Number(e.target.value))}
step={0.1}
min={0}
max={height}
className="h-8 text-xs font-mono"
/>
</div>
</div>
</div>
))}
</div>
@@ -326,14 +345,35 @@ const PiperackModule: React.FC = () => {
return pipes.map((p, idx) => {
const cx = pipes.length === 1 ? (startX + endX)/2 : startX + idx * gap;
const r = Math.max(4, Math.min(15, p.diameter * 8)); // escala visual
// y=25 para repousar sobre o suporte (y=35 para n=1) ou viga (y=40 para n>1)
const cy = numFrames === 1 ? 35 - r : 40 - r;
// p.elevationOffset varia de 0 (base) a height (topo)
// no SVG: Y cresce para baixo. y=110 (chão). Vigas y=40, y=65
// Para não complicar a matemática com proporções da viga,
// Mapeamos a elevação local para a região do pórtico no SVG (y=110 chão, base=110 - (elevation - height/2) - isso é dinâmico)
// Vamos fazer simples: The drawn structural portico is roughly Y=35 (top) to Y=110 (bottom).
// If n=1, structure top is Y=35.
// Actually the pipe is attached to the column at a relative height.
const localRatio = Math.max(0, Math.min(1, p.elevationOffset / height));
// No desenho, a altura reticulada é desenhada num "y" aproximado entre Y=35 (topo) e Y=110 (base da coluna inteira).
// Mas o pórtico real desenhado ali tem topo em 35 e altura 75.
// Então base do reticulado = 35 + 75 = 110. (Não, isso é pro solo).
// Supondo que a altura h desenhada seja o topo da coluna Y=35 até uns 2/3 (Y=60),
// Y invertido no SVG: base da seção = 65, topo = 35 (altura de 30 px).
const svgTop = 35;
const svgBottom = 65;
const cy = svgBottom - localRatio * (svgBottom - svgTop) - r;
return (
<circle key={p.id} cx={cx} cy={cy} r={r} fill="#3b82f6" fillOpacity="0.8" />
);
});
})()}
{/* Eixo indicativo de elevação Z_eff */}
<g opacity="0.6">
<line x1="20" y1={110 - Math.min(90, (result.effectiveElevation/elevation)*60)} x2="280" y2={110 - Math.min(90, (result.effectiveElevation/elevation)*60)} stroke="#f59e0b" strokeWidth="1" strokeDasharray="3 3" />
<text x="25" y={110 - Math.min(90, (result.effectiveElevation/elevation)*60) - 3} fontSize="8" fill="#d97706" fontWeight="bold">Z_eff</text>
</g>
<defs>
<marker id="arrowRight" markerWidth="6" markerHeight="6" refX="6" refY="3" orient="auto">
<path d="M0,0 L6,3 L0,6" fill="none" stroke="#10b981" strokeWidth="1.5" />
@@ -347,10 +387,11 @@ const PiperackModule: React.FC = () => {
<div className="space-y-1">
<div className="flex justify-between text-muted-foreground"><span>F. Frontal:</span> <span className="font-mono">{(result.linearLoadFront * width).toFixed(2)} kN</span></div>
{numFrames > 1 && <div className="flex justify-between text-muted-foreground"><span>F. Traseiros (Total):</span> <span className="font-mono">{((numFrames - 1) * result.linearLoadBack * width).toFixed(2)} kN</span></div>}
<div className="flex justify-between text-primary font-medium mt-1"><span>Z efetivo (Z_eff):</span> <span className="font-mono">{result.effectiveElevation.toFixed(2)} m</span></div>
</div>
<div className="space-y-1">
<div className="flex justify-between text-primary font-medium"><span>Momento Global de Tombamento:</span></div>
<div className="flex justify-end font-mono font-bold text-base">{(result.linearLoadFront * width * elevation + (numFrames > 1 ? (numFrames - 1) * result.linearLoadBack * width * elevation : 0)).toFixed(2)} kN·m</div>
<div className="flex justify-end font-mono font-bold text-base">{(result.globalForce * result.effectiveElevation).toFixed(2)} kN·m</div>
</div>
</div>
<p className="text-[10px] text-muted-foreground leading-tight text-center">