feat: aprimoramento didatico dos SVGs, rotulagem de vistas, legendas e explicacao contextualizada
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import { BookOpen, HelpCircle } from 'lucide-react';
|
import React from 'react';
|
||||||
|
import { HelpCircle, Info, Settings2 } from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import {
|
import {
|
||||||
Sheet,
|
Sheet,
|
||||||
@@ -23,6 +24,144 @@ export function EducationalManual({ type, params }: EducationalManualProps) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Gera explicações interpretativas dinâmicas com base nas opções selecionadas no painel
|
||||||
|
const renderConfigurationExplanation = () => {
|
||||||
|
const explanations: React.ReactNode[] = [];
|
||||||
|
|
||||||
|
// --- Abóbadas (vault) ---
|
||||||
|
if (type === 'vault') {
|
||||||
|
const regime = params.regime || 'laminar-rough';
|
||||||
|
const cpi = Number(params.localCpi || 0);
|
||||||
|
|
||||||
|
// Regime de escoamento
|
||||||
|
if (regime === 'laminar-rough') {
|
||||||
|
explanations.push(
|
||||||
|
<div key="vault-regime" className="text-xs border-l-2 border-sky-400 pl-3 py-1 space-y-1">
|
||||||
|
<span className="font-bold text-foreground block">Escoamento: Baixa Turbulência (Tabelas 15-17)</span>
|
||||||
|
<p>Representa vento uniforme sobre terreno desobstruído. O escoamento flui de forma limpa sobre o arco, gerando os valores tradicionais de sucção no topo.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
explanations.push(
|
||||||
|
<div key="vault-regime" className="text-xs border-l-2 border-red-500 pl-3 py-1 space-y-1">
|
||||||
|
<span className="font-bold text-foreground block">Escoamento: Regime Turbulento (Tabelas 18-20)</span>
|
||||||
|
<p>O vento apresenta alta turbulência devido a obstáculos no terreno. As correntes de vento ondulantes (exibidas no simulador) induzem picos de pressões dinâmicas flutuantes.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Permeabilidade (Cpi)
|
||||||
|
if (cpi === 0) {
|
||||||
|
explanations.push(
|
||||||
|
<div key="vault-cpi" className="text-xs border-l-2 border-slate-400 pl-3 py-1 space-y-1">
|
||||||
|
<span className="font-bold text-foreground block">Pressão Interna Neutralizada (Cpi = 0.0)</span>
|
||||||
|
<p>As aberturas são inexistentes ou estão distribuídas igualmente nas faces. A força resultante no arco depende exclusivamente da sucção externa.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else if (cpi > 0) {
|
||||||
|
explanations.push(
|
||||||
|
<div key="vault-cpi" className="text-xs border-l-2 border-orange-500 pl-3 py-1 space-y-1">
|
||||||
|
<span className="font-bold text-orange-500 block">Abóbada Inflada (Cpi = +{cpi})</span>
|
||||||
|
<p>Uma abertura dominante na face de barlavento permite a entrada do ar, "inflando" a abóbada de dentro para fora (indicado pelas setas laranjas). Essa pressão interna empurra a cobertura para cima, somando-se à sucção externa do vento e aumentando de forma crítica a força de arrancamento do telhado!</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
explanations.push(
|
||||||
|
<div key="vault-cpi" className="text-xs border-l-2 border-blue-500 pl-3 py-1 space-y-1">
|
||||||
|
<span className="font-bold text-blue-500 block">Abóbada Sugada / Vácuo (Cpi = {cpi})</span>
|
||||||
|
<p>Com aberturas dominantes nas faces protegidas (laterais ou sotavento), o ar interno é sugado para fora, criando um vácuo interno (setas azuis). Esse efeito de vácuo ajuda a compensar e aliviar a sucção externa no topo, mas puxa as paredes para dentro.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Cilindros (cylinder) ---
|
||||||
|
if (type === 'cylinder') {
|
||||||
|
const isSmooth = params.surface === 'smooth';
|
||||||
|
const cpi = Number(params.cpi || 0);
|
||||||
|
|
||||||
|
explanations.push(
|
||||||
|
<div key="cyl-surface" className="text-xs border-l-2 border-sky-400 pl-3 py-1 space-y-1">
|
||||||
|
<span className="font-bold text-foreground block">Superfície: {isSmooth ? 'Lisa' : 'Rugosa'}</span>
|
||||||
|
<p>{isSmooth
|
||||||
|
? 'Superfície de baixo atrito. No regime supercrítico, o descolamento da camada limite é atrasado significativamente, fazendo o coeficiente de arrasto Ca cair para valores muito baixos (~0.4).'
|
||||||
|
: 'Casca áspera. A rugosidade gera microturbulências na superfície do cilindro que impedem a esteira de estreitar excessivamente, mantendo o arrasto Ca em níveis elevados no regime crítico/supercrítico.'
|
||||||
|
}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (cpi > 0) {
|
||||||
|
explanations.push(
|
||||||
|
<div key="cyl-cpi" className="text-xs border-l-2 border-orange-500 pl-3 py-1 space-y-1">
|
||||||
|
<span className="font-bold text-orange-500 block">Reservatório Inflado (Cpi positivo)</span>
|
||||||
|
<p>O ar interno pressiona as paredes do silo/chaminé de dentro para fora, somando-se às sucções externas nas laterais.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Muros e Placas (sign) ---
|
||||||
|
if (type === 'sign') {
|
||||||
|
const hasEndPlates = params.hasEndPlates === true;
|
||||||
|
const alpha = Number(params.alpha || 90);
|
||||||
|
|
||||||
|
explanations.push(
|
||||||
|
<div key="sign-alpha" className="text-xs border-l-2 border-sky-400 pl-3 py-1 space-y-1">
|
||||||
|
<span className="font-bold text-foreground block">Incidência do Vento: {alpha}º</span>
|
||||||
|
<p>{alpha === 90
|
||||||
|
? 'Vento Perpendicular: o arrasto atua uniformemente, concentrando a força resultante exatamente no centro da placa (sem torque de torção nas fundações).'
|
||||||
|
: 'Vento Oblíquo: o fluxo se concentra na borda esquerda de ataque. Isso gera uma excentricidade horizontal na força (e = 0.25 L), introduzindo um momento torsor crítico que deve ser suportado pelos pilares.'
|
||||||
|
}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
explanations.push(
|
||||||
|
<div key="sign-plates" className="text-xs border-l-2 border-red-500 pl-3 py-1 space-y-1">
|
||||||
|
<span className="font-bold text-foreground block">Abas Laterais (End Plates): {hasEndPlates ? 'Com abas' : 'Sem abas'}</span>
|
||||||
|
<p>{hasEndPlates
|
||||||
|
? 'As abas vermelhas mostradas no simulador impedem o ar de escapar pelas laterais do muro, gerando um escoamento bidimensional plano que eleva a força de arrasto Cf ao máximo.'
|
||||||
|
: 'O vento escoa tridimensionalmente pelas bordas laterais livres, criando um efeito de alívio aerodinâmico que reduz a força resultante nas pontas do muro.'
|
||||||
|
}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Galpões (warehouse) ---
|
||||||
|
if (type === 'warehouse') {
|
||||||
|
const cpi = Number(params.cpi || 0);
|
||||||
|
|
||||||
|
if (cpi > 0) {
|
||||||
|
explanations.push(
|
||||||
|
<div key="wh-cpi" className="text-xs border-l-2 border-orange-500 pl-3 py-1 space-y-1">
|
||||||
|
<span className="font-bold text-orange-500 block">Galpão Inflado (Cpi = +{cpi})</span>
|
||||||
|
<p>Ocorre quando há aberturas dominantes na parede de barlavento (frente). O ar "sopra" para dentro e expande o galpão de dentro para fora, somando-se à sucção externa do telhado e aumentando de forma gravíssima o risco de arrancamento da cobertura!</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else if (cpi < 0) {
|
||||||
|
explanations.push(
|
||||||
|
<div key="wh-cpi" className="text-xs border-l-2 border-blue-500 pl-3 py-1 space-y-1">
|
||||||
|
<span className="font-bold text-blue-500 block">Galpão Desinflado / Vácuo (Cpi = {cpi})</span>
|
||||||
|
<p>Ocorre com aberturas nas paredes laterais ou de trás (sotavento). O ar interno é sugado, puxando as chapas do telhado para dentro e ajudando a contrapor a sucção do vento externo.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (explanations.length === 0) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-3 bg-slate-900/10 p-4 border border-border/60 rounded-xl my-4">
|
||||||
|
<div className="flex items-center gap-2 text-primary border-b border-border/40 pb-1.5">
|
||||||
|
<Settings2 className="size-4" />
|
||||||
|
<h4 className="text-xs font-bold uppercase tracking-wider">Análise das Suas Configurações</h4>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
{explanations}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sheet>
|
<Sheet>
|
||||||
<SheetTrigger asChild>
|
<SheetTrigger asChild>
|
||||||
@@ -32,7 +171,7 @@ export function EducationalManual({ type, params }: EducationalManualProps) {
|
|||||||
className="size-10 bg-background/80 backdrop-blur-xs border border-border shadow-md hover:bg-accent/80 transition-all rounded-full flex items-center justify-center"
|
className="size-10 bg-background/80 backdrop-blur-xs border border-border shadow-md hover:bg-accent/80 transition-all rounded-full flex items-center justify-center"
|
||||||
title="Manual Didático da Norma NBR 6123"
|
title="Manual Didático da Norma NBR 6123"
|
||||||
>
|
>
|
||||||
<BookOpen className="size-5 text-primary animate-pulse" />
|
<Info className="size-5 text-primary animate-pulse" />
|
||||||
</Button>
|
</Button>
|
||||||
</SheetTrigger>
|
</SheetTrigger>
|
||||||
<SheetContent className="overflow-y-auto w-full sm:max-w-xl p-6 flex flex-col gap-6 border-l border-border bg-card">
|
<SheetContent className="overflow-y-auto w-full sm:max-w-xl p-6 flex flex-col gap-6 border-l border-border bg-card">
|
||||||
@@ -49,6 +188,9 @@ export function EducationalManual({ type, params }: EducationalManualProps) {
|
|||||||
{/* Simulador Interativo 2D no topo do manual */}
|
{/* Simulador Interativo 2D no topo do manual */}
|
||||||
<WindFlowGrid2D type={type} params={params} />
|
<WindFlowGrid2D type={type} params={params} />
|
||||||
|
|
||||||
|
{/* Quadro Dinâmico explicativo dos seletores */}
|
||||||
|
{renderConfigurationExplanation()}
|
||||||
|
|
||||||
{/* Seções de Texto e Fórmulas */}
|
{/* Seções de Texto e Fórmulas */}
|
||||||
<div className="flex flex-col gap-6 text-foreground leading-relaxed">
|
<div className="flex flex-col gap-6 text-foreground leading-relaxed">
|
||||||
{manual.sections.map((section, idx) => (
|
{manual.sections.map((section, idx) => (
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
|
|
||||||
|
|
||||||
interface WindFlowGrid2DProps {
|
interface WindFlowGrid2DProps {
|
||||||
type: string;
|
type: string;
|
||||||
params: Record<string, any>;
|
params: Record<string, any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function WindFlowGrid2D({ type, params }: WindFlowGrid2DProps) {
|
export function WindFlowGrid2D({ type, params }: WindFlowGrid2DProps) {
|
||||||
// Estilo global de animação inserido no próprio SVG
|
// Estilos globais e animações integrados no SVG
|
||||||
const styleBlock = (
|
const styleBlock = (
|
||||||
<style>{`
|
<style>{`
|
||||||
@keyframes windFlow {
|
@keyframes windFlow {
|
||||||
@@ -22,7 +21,7 @@ export function WindFlowGrid2D({ type, params }: WindFlowGrid2DProps) {
|
|||||||
50% { transform: translateY(-8px); }
|
50% { transform: translateY(-8px); }
|
||||||
}
|
}
|
||||||
.wind-line {
|
.wind-line {
|
||||||
stroke: #60a5fa;
|
stroke: #3b82f6;
|
||||||
stroke-width: 1.8;
|
stroke-width: 1.8;
|
||||||
stroke-dasharray: 8 16;
|
stroke-dasharray: 8 16;
|
||||||
animation: windFlow 1.8s linear infinite;
|
animation: windFlow 1.8s linear infinite;
|
||||||
@@ -30,7 +29,7 @@ export function WindFlowGrid2D({ type, params }: WindFlowGrid2DProps) {
|
|||||||
opacity: 0.85;
|
opacity: 0.85;
|
||||||
}
|
}
|
||||||
.wind-line-fast {
|
.wind-line-fast {
|
||||||
stroke: #38bdf8;
|
stroke: #0ea5e9;
|
||||||
stroke-width: 2.2;
|
stroke-width: 2.2;
|
||||||
stroke-dasharray: 6 12;
|
stroke-dasharray: 6 12;
|
||||||
animation: windFlow 0.9s linear infinite;
|
animation: windFlow 0.9s linear infinite;
|
||||||
@@ -45,11 +44,12 @@ export function WindFlowGrid2D({ type, params }: WindFlowGrid2DProps) {
|
|||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
.vortex-line {
|
.vortex-line {
|
||||||
stroke: #f87171;
|
stroke: #ef4444;
|
||||||
stroke-width: 1.5;
|
stroke-width: 1.5;
|
||||||
stroke-dasharray: 4 8;
|
stroke-dasharray: 4 8;
|
||||||
animation: windFlow 2.5s linear infinite;
|
animation: windFlow 2.5s linear infinite;
|
||||||
fill: none;
|
fill: none;
|
||||||
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
.vortex-spin-cw {
|
.vortex-spin-cw {
|
||||||
transform-origin: center;
|
transform-origin: center;
|
||||||
@@ -62,46 +62,167 @@ export function WindFlowGrid2D({ type, params }: WindFlowGrid2DProps) {
|
|||||||
.vibe-cyl {
|
.vibe-cyl {
|
||||||
animation: vibration 1.5s ease-in-out infinite;
|
animation: vibration 1.5s ease-in-out infinite;
|
||||||
}
|
}
|
||||||
|
.cpi-arrow-pos {
|
||||||
|
stroke: #f97316;
|
||||||
|
stroke-width: 2;
|
||||||
|
fill: none;
|
||||||
|
}
|
||||||
|
.cpi-arrow-neg {
|
||||||
|
stroke: #2563eb;
|
||||||
|
stroke-width: 2;
|
||||||
|
fill: none;
|
||||||
|
}
|
||||||
`}</style>
|
`}</style>
|
||||||
);
|
);
|
||||||
|
|
||||||
// Renderizadores específicos de fluxo ativo com base nos sliders da página
|
const getViewLabel = () => {
|
||||||
|
switch (type) {
|
||||||
|
case 'tower':
|
||||||
|
case 'cylinder':
|
||||||
|
case 'bar':
|
||||||
|
return 'Vista Aérea (Superior / Planta Baixa)';
|
||||||
|
case 'bridge':
|
||||||
|
case 'sign':
|
||||||
|
case 'warehouse':
|
||||||
|
case 'vault':
|
||||||
|
case 'dome':
|
||||||
|
case 'dynamics':
|
||||||
|
case 'isolated-roof':
|
||||||
|
return 'Vista Lateral (Corte Transversal)';
|
||||||
|
default:
|
||||||
|
return 'Vista Esquemática';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Desenha os vetores de Cpi (pressão interna) dentro do perfil do galpão/cilindro/abóbada
|
||||||
|
const renderCpiVectors = (cpi: number, shape: 'rect' | 'arch' | 'circle' | 'dome') => {
|
||||||
|
if (cpi === 0) return null;
|
||||||
|
const isPositive = cpi > 0;
|
||||||
|
const marker = isPositive ? 'url(#cpi-pos)' : 'url(#cpi-neg)';
|
||||||
|
const strokeClass = isPositive ? 'cpi-arrow-pos' : 'cpi-arrow-neg';
|
||||||
|
|
||||||
|
if (shape === 'arch') {
|
||||||
|
// Abóbada: 3 vetores radiais
|
||||||
|
return (
|
||||||
|
<g>
|
||||||
|
{isPositive ? (
|
||||||
|
<>
|
||||||
|
<path d="M 190,140 L 165,115" className={strokeClass} markerEnd={marker} />
|
||||||
|
<path d="M 200,135 L 200,105" className={strokeClass} markerEnd={marker} />
|
||||||
|
<path d="M 210,140 L 235,115" className={strokeClass} markerEnd={marker} />
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<path d="M 165,115 L 190,140" className={strokeClass} markerEnd={marker} />
|
||||||
|
<path d="M 200,105 L 200,135" className={strokeClass} markerEnd={marker} />
|
||||||
|
<path d="M 235,115 L 210,140" className={strokeClass} markerEnd={marker} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</g>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shape === 'rect') {
|
||||||
|
// Galpão: paredes e telhado
|
||||||
|
return (
|
||||||
|
<g>
|
||||||
|
{isPositive ? (
|
||||||
|
<>
|
||||||
|
{/* Paredes */}
|
||||||
|
<path d="M 170,145 L 145,145" className={strokeClass} markerEnd={marker} />
|
||||||
|
<path d="M 230,145 L 255,145" className={strokeClass} markerEnd={marker} />
|
||||||
|
{/* Telhado */}
|
||||||
|
<path d="M 180,105 L 165,85" className={strokeClass} markerEnd={marker} />
|
||||||
|
<path d="M 220,105 L 235,85" className={strokeClass} markerEnd={marker} />
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<path d="M 145,145 L 170,145" className={strokeClass} markerEnd={marker} />
|
||||||
|
<path d="M 255,145 L 230,145" className={strokeClass} markerEnd={marker} />
|
||||||
|
<path d="M 165,85 L 180,105" className={strokeClass} markerEnd={marker} />
|
||||||
|
<path d="M 235,85 L 220,105" className={strokeClass} markerEnd={marker} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</g>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shape === 'circle') {
|
||||||
|
// Cilindro: 4 direções
|
||||||
|
return (
|
||||||
|
<g>
|
||||||
|
{isPositive ? (
|
||||||
|
<>
|
||||||
|
<path d="M 190,100 L 170,100" className={strokeClass} markerEnd={marker} />
|
||||||
|
<path d="M 210,100 L 230,100" className={strokeClass} markerEnd={marker} />
|
||||||
|
<path d="M 200,90 L 200,70" className={strokeClass} markerEnd={marker} />
|
||||||
|
<path d="M 200,110 L 200,130" className={strokeClass} markerEnd={marker} />
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<path d="M 170,100 L 190,100" className={strokeClass} markerEnd={marker} />
|
||||||
|
<path d="M 230,100 L 210,100" className={strokeClass} markerEnd={marker} />
|
||||||
|
<path d="M 200,70 L 200,90" className={strokeClass} markerEnd={marker} />
|
||||||
|
<path d="M 200,130 L 200,110" className={strokeClass} markerEnd={marker} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</g>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shape === 'dome') {
|
||||||
|
// Cúpula
|
||||||
|
return (
|
||||||
|
<g>
|
||||||
|
{isPositive ? (
|
||||||
|
<>
|
||||||
|
<path d="M 170,145 L 155,145" className={strokeClass} markerEnd={marker} />
|
||||||
|
<path d="M 230,145 L 245,145" className={strokeClass} markerEnd={marker} />
|
||||||
|
<path d="M 200,115 L 200,85" className={strokeClass} markerEnd={marker} />
|
||||||
|
<path d="M 185,100 L 165,80" className={strokeClass} markerEnd={marker} />
|
||||||
|
<path d="M 215,100 L 235,80" className={strokeClass} markerEnd={marker} />
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<path d="M 155,145 L 170,145" className={strokeClass} markerEnd={marker} />
|
||||||
|
<path d="M 245,145 L 230,145" className={strokeClass} markerEnd={marker} />
|
||||||
|
<path d="M 200,85 L 200,115" className={strokeClass} markerEnd={marker} />
|
||||||
|
<path d="M 165,80 L 185,100" className={strokeClass} markerEnd={marker} />
|
||||||
|
<path d="M 235,80 L 215,100" className={strokeClass} markerEnd={marker} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</g>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
const renderActiveSimulation = () => {
|
const renderActiveSimulation = () => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'bridge': {
|
case 'bridge': {
|
||||||
const alpha = Number(params.alpha || 0);
|
const alpha = Number(params.alpha || 0);
|
||||||
// O vento muda de ângulo
|
const rotationAngle = alpha * 3;
|
||||||
const rotationAngle = alpha * 3; // amplifica o ângulo para ficar visível
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<h4 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">Simulação Ativa (com suas variáveis)</h4>
|
|
||||||
<div className="relative aspect-video w-full bg-slate-950 border border-border rounded-lg overflow-hidden">
|
|
||||||
<svg viewBox="0 0 400 200" className="w-full h-full">
|
<svg viewBox="0 0 400 200" className="w-full h-full">
|
||||||
{styleBlock}
|
{styleBlock}
|
||||||
{/* Linhas de vento de fundo */}
|
|
||||||
<g transform={`rotate(${rotationAngle}, 200, 100)`}>
|
<g transform={`rotate(${rotationAngle}, 200, 100)`}>
|
||||||
<path d="M -50,30 L 450,30" className="wind-line-slow" />
|
<path d="M -50,30 L 450,30" className="wind-line-slow" />
|
||||||
<path d="M -50,70 Q 150,50 450,70" className="wind-line" />
|
<path d="M -50,70 Q 150,50 450,70" className="wind-line" />
|
||||||
<path d="M -50,130 Q 150,150 450,130" className="wind-line" />
|
<path d="M -50,130 Q 150,150 450,130" className="wind-line" />
|
||||||
<path d="M -50,170 L 450,170" className="wind-line-slow" />
|
<path d="M -50,170 L 450,170" className="wind-line-slow" />
|
||||||
|
|
||||||
{/* Linhas aceleradas acima e abaixo */}
|
|
||||||
<path d="M -50,90 Q 200,60 450,90" className="wind-line-fast" />
|
<path d="M -50,90 Q 200,60 450,90" className="wind-line-fast" />
|
||||||
<path d="M -50,110 Q 200,140 450,110" className="wind-line-fast" />
|
<path d="M -50,110 Q 200,140 450,110" className="wind-line-fast" />
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
{/* Tabuleiro da ponte */}
|
<g transform="translate(140, 85)">
|
||||||
<g transform="translate(150, 85)">
|
<rect x="0" y="0" width="120" height="30" rx="3" fill="#334155" stroke="#475569" strokeWidth="2" />
|
||||||
<rect x="0" y="0" width="100" height="30" rx="4" fill="#1e293b" stroke="#334155" strokeWidth="2" />
|
<rect x="5" y="-6" width="8" height="6" fill="#1e293b" />
|
||||||
{/* Barreiras de proteção */}
|
<rect x="107" y="-6" width="8" height="6" fill="#1e293b" />
|
||||||
<rect x="5" y="-6" width="6" height="6" fill="#64748b" />
|
|
||||||
<rect x="89" y="-6" width="6" height="6" fill="#64748b" />
|
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
{/* Vetores de Força baseados no ângulo */}
|
|
||||||
{alpha !== 0 && (
|
{alpha !== 0 && (
|
||||||
<g>
|
<g>
|
||||||
{/* Vetor de sustentação (Fz) */}
|
|
||||||
<path
|
<path
|
||||||
d={alpha > 0 ? "M 200,75 L 200,30" : "M 200,125 L 200,170"}
|
d={alpha > 0 ? "M 200,75 L 200,30" : "M 200,125 L 200,170"}
|
||||||
stroke={alpha > 0 ? "#10b981" : "#ef4444"}
|
stroke={alpha > 0 ? "#10b981" : "#ef4444"}
|
||||||
@@ -109,24 +230,17 @@ export function WindFlowGrid2D({ type, params }: WindFlowGrid2DProps) {
|
|||||||
fill="none"
|
fill="none"
|
||||||
markerEnd="url(#arrow)"
|
markerEnd="url(#arrow)"
|
||||||
/>
|
/>
|
||||||
<text x="210" y={alpha > 0 ? 45 : 160} fill={alpha > 0 ? "#10b981" : "#ef4444"} className="text-xs font-bold font-mono">
|
<text x="215" y={alpha > 0 ? 45 : 160} fill={alpha > 0 ? "#10b981" : "#ef4444"} className="text-xs font-bold font-mono">
|
||||||
{alpha > 0 ? 'Sustentação (+Fz)' : 'Downforce (-Fz)'}
|
{alpha > 0 ? '+Fz (Sustentação)' : '-Fz (Abaixamento)'}
|
||||||
</text>
|
</text>
|
||||||
</g>
|
</g>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Marcadores SVG de setas */}
|
|
||||||
<defs>
|
<defs>
|
||||||
<marker id="arrow" viewBox="0 0 10 10" refX="5" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
|
<marker id="arrow" viewBox="0 0 10 10" refX="5" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
|
||||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="context-stroke" />
|
<path d="M 0 0 L 10 5 L 0 10 z" fill="context-stroke" />
|
||||||
</marker>
|
</marker>
|
||||||
</defs>
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
<div className="absolute bottom-2 right-2 bg-slate-900/80 backdrop-blur px-2 py-1 rounded text-[10px] text-sky-400 font-mono">
|
|
||||||
Ângulo: {alpha}º
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,243 +248,384 @@ export function WindFlowGrid2D({ type, params }: WindFlowGrid2DProps) {
|
|||||||
const section = params.section || 'square';
|
const section = params.section || 'square';
|
||||||
const alphaWind = Number(params.alphaWind || 0);
|
const alphaWind = Number(params.alphaWind || 0);
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<h4 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">Simulação Ativa (com suas variáveis)</h4>
|
|
||||||
<div className="relative aspect-video w-full bg-slate-950 border border-border rounded-lg overflow-hidden">
|
|
||||||
<svg viewBox="0 0 400 200" className="w-full h-full">
|
<svg viewBox="0 0 400 200" className="w-full h-full">
|
||||||
{styleBlock}
|
{styleBlock}
|
||||||
|
|
||||||
{/* Linhas de vento fluindo */}
|
|
||||||
<g>
|
<g>
|
||||||
<path d="M -20,20 L 420,20" className="wind-line-slow" />
|
<path d="M -20,20 L 420,20" className="wind-line-slow" />
|
||||||
<path d="M -20,60 C 150,60 170,40 230,40 C 270,40 320,60 420,60" className="wind-line" />
|
<path d="M -20,60 C 150,60 170,40 230,40 C 270,40 320,60 420,60" className="wind-line" />
|
||||||
<path d="M -20,140 C 150,140 170,160 230,160 C 270,160 320,140 420,140" className="wind-line" />
|
<path d="M -20,140 C 150,140 170,160 230,160 C 270,160 320,140 420,140" className="wind-line" />
|
||||||
<path d="M -20,180 L 420,180" className="wind-line-slow" />
|
<path d="M -20,180 L 420,180" className="wind-line-slow" />
|
||||||
|
<path d="M 230,85 Q 310,70 420,85" className="vortex-line" />
|
||||||
{/* Turbulência traseira (esteira) */}
|
<path d="M 230,115 Q 310,130 420,115" className="vortex-line" />
|
||||||
<path d="M 230,90 Q 300,75 420,90" className="vortex-line" />
|
|
||||||
<path d="M 230,110 Q 300,125 420,110" className="vortex-line" />
|
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
{/* Perfil da Seção da Torre (Centro) */}
|
|
||||||
<g transform={`translate(200, 100) rotate(${alphaWind})`}>
|
<g transform={`translate(200, 100) rotate(${alphaWind})`}>
|
||||||
{section === 'square' ? (
|
|
||||||
<rect x="-25" y="-25" width="50" height="50" fill="none" stroke="#64748b" strokeWidth="4" />
|
|
||||||
) : (
|
|
||||||
<polygon points="0,-28 25,15 -25,15" fill="none" stroke="#64748b" strokeWidth="4" />
|
|
||||||
)}
|
|
||||||
{/* Barras internas treliçadas */}
|
|
||||||
{section === 'square' ? (
|
{section === 'square' ? (
|
||||||
<>
|
<>
|
||||||
<line x1="-25" y1="-25" x2="25" y2="25" stroke="#475569" strokeWidth="1.5" />
|
<rect x="-25" y="-25" width="50" height="50" fill="none" stroke="#64748b" strokeWidth="3" />
|
||||||
<line x1="25" y1="-25" x2="-25" y2="25" stroke="#475569" strokeWidth="1.5" />
|
<line x1="-25" y1="-25" x2="25" y2="25" stroke="#64748b" strokeWidth="1.5" />
|
||||||
|
<line x1="25" y1="-25" x2="-25" y2="25" stroke="#64748b" strokeWidth="1.5" />
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<line x1="0" y1="-28" x2="0" y2="15" stroke="#475569" strokeWidth="1.5" />
|
<polygon points="0,-28 25,15 -25,15" fill="none" stroke="#64748b" strokeWidth="3" />
|
||||||
<line x1="-25" y1="15" x2="12.5" y2="-6.5" stroke="#475569" strokeWidth="1.5" />
|
<line x1="0" y1="-28" x2="0" y2="15" stroke="#64748b" strokeWidth="1.5" />
|
||||||
<line x1="25" y1="15" x2="-12.5" y2="-6.5" stroke="#475569" strokeWidth="1.5" />
|
<line x1="-25" y1="15" x2="12.5" y2="-6.5" stroke="#64748b" strokeWidth="1.5" />
|
||||||
|
<line x1="25" y1="15" x2="-12.5" y2="-6.5" stroke="#64748b" strokeWidth="1.5" />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
<div className="absolute bottom-2 right-2 bg-slate-900/80 backdrop-blur px-2 py-1 rounded text-[10px] text-sky-400 font-mono">
|
|
||||||
{section === 'square' ? 'Quadrada' : 'Triangular'} | {alphaWind}º
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'cylinder': {
|
case 'cylinder': {
|
||||||
const isSmooth = params.roughness === 'smooth' || params.d_over_k > 1e5;
|
const isSmooth = params.surface === 'smooth';
|
||||||
|
const cpi = Number(params.cpi || 0);
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<h4 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">Simulação Ativa (com suas variáveis)</h4>
|
|
||||||
<div className="relative aspect-video w-full bg-slate-950 border border-border rounded-lg overflow-hidden">
|
|
||||||
<svg viewBox="0 0 400 200" className="w-full h-full">
|
<svg viewBox="0 0 400 200" className="w-full h-full">
|
||||||
{styleBlock}
|
{styleBlock}
|
||||||
|
{/* Marcadores de setas Cpi */}
|
||||||
|
<defs>
|
||||||
|
<marker id="cpi-pos" viewBox="0 0 10 10" refX="5" refY="5" markerWidth="6" markerHeight="6" orient="auto">
|
||||||
|
<path d="M 0 0 L 10 5 L 0 10 z" fill="#f97316" />
|
||||||
|
</marker>
|
||||||
|
<marker id="cpi-neg" viewBox="0 0 10 10" refX="5" refY="5" markerWidth="6" markerHeight="6" orient="auto">
|
||||||
|
<path d="M 0 0 L 10 5 L 0 10 z" fill="#2563eb" />
|
||||||
|
</marker>
|
||||||
|
</defs>
|
||||||
|
|
||||||
{/* Fluxo contornando cilindro */}
|
|
||||||
<g>
|
<g>
|
||||||
<path d="M -20,20 L 420,20" className="wind-line-slow" />
|
<path d="M -20,20 L 420,20" className="wind-line-slow" />
|
||||||
<path d="M -20,60 C 150,55 160,35 200,35 C 240,35 250,55 420,55" className="wind-line-fast" />
|
<path d="M -20,60 C 150,55 160,30 200,30 C 240,30 250,55 420,55" className="wind-line-fast" />
|
||||||
<path d="M -20,140 C 150,145 160,165 200,165 C 240,165 250,145 420,145" className="wind-line-fast" />
|
<path d="M -20,140 C 150,145 160,170 200,170 C 240,170 250,145 420,145" className="wind-line-fast" />
|
||||||
<path d="M -20,180 L 420,180" className="wind-line-slow" />
|
<path d="M -20,180 L 420,180" className="wind-line-slow" />
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
{/* Esteira de Vórtices de Von Kármán */}
|
|
||||||
<g transform="translate(250, 100)">
|
<g transform="translate(250, 100)">
|
||||||
<circle cx="20" cy="-25" r="10" fill="none" stroke="#ef4444" strokeWidth="1.5" strokeDasharray="3 6" className="vortex-spin-cw" />
|
<circle cx="20" cy="-25" r="10" fill="none" stroke="#ef4444" strokeWidth="1.5" strokeDasharray="3 6" className="vortex-spin-cw" />
|
||||||
<circle cx="60" cy="25" r="12" fill="none" stroke="#ef4444" strokeWidth="1.5" strokeDasharray="3 6" className="vortex-spin-ccw" />
|
<circle cx="60" cy="25" r="12" fill="none" stroke="#ef4444" strokeWidth="1.5" strokeDasharray="3 6" className="vortex-spin-ccw" />
|
||||||
<circle cx="100" cy="-20" r="14" fill="none" stroke="#ef4444" strokeWidth="1.5" strokeDasharray="3 6" className="vortex-spin-cw" />
|
|
||||||
<path d="M 0,0 Q 40,-35 80,0 Q 120,35 160,0" className="vortex-line" />
|
<path d="M 0,0 Q 40,-35 80,0 Q 120,35 160,0" className="vortex-line" />
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
{/* Cilindro */}
|
{/* Cilindro */}
|
||||||
<circle cx="200" cy="100" r="35" fill="#334155" stroke="#475569" strokeWidth={isSmooth ? "2" : "5"} />
|
<circle cx="200" cy="100" r="35" fill="#334155" stroke="#475569" strokeWidth={isSmooth ? "2" : "5"} />
|
||||||
|
|
||||||
|
{/* Pressão Interna */}
|
||||||
|
{renderCpiVectors(cpi, 'circle')}
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'sign': {
|
||||||
|
const hasEndPlates = params.hasEndPlates === true;
|
||||||
|
return (
|
||||||
|
<svg viewBox="0 0 400 200" className="w-full h-full">
|
||||||
|
{styleBlock}
|
||||||
|
<line x1="0" y1="170" x2="400" y2="170" stroke="#475569" strokeWidth="3" />
|
||||||
|
|
||||||
|
<g>
|
||||||
|
<path d="M -20,30 L 420,30" className="wind-line-slow" />
|
||||||
|
{/* O vento passa reto ou desvia mais dependendo das abas laterais */}
|
||||||
|
<path
|
||||||
|
d={hasEndPlates
|
||||||
|
? "M -20,70 C 130,60 160,20 200,20 C 230,20 260,80 420,80"
|
||||||
|
: "M -20,80 C 150,70 170,40 210,40 C 260,40 300,80 420,80"
|
||||||
|
}
|
||||||
|
className="wind-line"
|
||||||
|
/>
|
||||||
|
<path d="M -20,130 C 140,120 160,50 200,50 C 250,50 310,120 420,120" className="wind-line-fast" />
|
||||||
|
|
||||||
|
{/* Maior turbulência se houver placas de extremidade */}
|
||||||
|
<circle cx="260" cy="130" r={hasEndPlates ? "22" : "15"} fill="none" stroke="#ef4444" strokeWidth="1.5" strokeDasharray="2 4" className="vortex-spin-cw" />
|
||||||
|
<path d="M 210,140 Q 280,90 420,155" className="vortex-line" />
|
||||||
|
</g>
|
||||||
|
|
||||||
|
{/* Muro */}
|
||||||
|
<rect x="195" y="70" width="10" height="100" fill="#334155" stroke="#475569" strokeWidth="2" />
|
||||||
|
{/* Placas de extremidade (abas de borda) */}
|
||||||
|
{hasEndPlates && (
|
||||||
|
<g>
|
||||||
|
<line x1="190" y1="70" x2="210" y2="70" stroke="#ef4444" strokeWidth="4" />
|
||||||
|
<line x1="190" y1="170" x2="210" y2="170" stroke="#ef4444" strokeWidth="4" />
|
||||||
|
</g>
|
||||||
|
)}
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'warehouse': {
|
||||||
|
const cpi = Number(params.cpi || 0);
|
||||||
|
return (
|
||||||
|
<svg viewBox="0 0 400 200" className="w-full h-full">
|
||||||
|
{styleBlock}
|
||||||
|
<defs>
|
||||||
|
<marker id="cpi-pos" viewBox="0 0 10 10" refX="5" refY="5" markerWidth="6" markerHeight="6" orient="auto">
|
||||||
|
<path d="M 0 0 L 10 5 L 0 10 z" fill="#f97316" />
|
||||||
|
</marker>
|
||||||
|
<marker id="cpi-neg" viewBox="0 0 10 10" refX="5" refY="5" markerWidth="6" markerHeight="6" orient="auto">
|
||||||
|
<path d="M 0 0 L 10 5 L 0 10 z" fill="#2563eb" />
|
||||||
|
</marker>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<line x1="0" y1="170" x2="400" y2="170" stroke="#475569" strokeWidth="3" />
|
||||||
|
|
||||||
|
<g>
|
||||||
|
<path d="M -20,30 L 420,30" className="wind-line-slow" />
|
||||||
|
<path d="M -20,80 C 130,80 140,40 200,40 C 260,40 270,90 420,90" className="wind-line-fast" />
|
||||||
|
<path d="M -20,130 C 120,120 130,95 150,95 L 250,95 C 270,95 280,130 420,130" className="wind-line" />
|
||||||
|
<path d="M 250,110 Q 300,100 420,140" className="vortex-line" />
|
||||||
|
</g>
|
||||||
|
|
||||||
|
{/* Galpão */}
|
||||||
|
<path
|
||||||
|
d="M 140,170 L 140,110 L 200,70 L 260,110 L 260,170 Z"
|
||||||
|
fill="#334155"
|
||||||
|
stroke="#475569"
|
||||||
|
strokeWidth="2.5"
|
||||||
|
/>
|
||||||
|
{/* Pressão Interna */}
|
||||||
|
{renderCpiVectors(cpi, 'rect')}
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'vault': {
|
||||||
|
const regime = params.regime || 'laminar-rough';
|
||||||
|
const cpi = Number(params.localCpi || 0);
|
||||||
|
const isTurbulent = regime.startsWith('turbulent');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<svg viewBox="0 0 400 200" className="w-full h-full">
|
||||||
|
{styleBlock}
|
||||||
|
<defs>
|
||||||
|
<marker id="cpi-pos" viewBox="0 0 10 10" refX="5" refY="5" markerWidth="6" markerHeight="6" orient="auto">
|
||||||
|
<path d="M 0 0 L 10 5 L 0 10 z" fill="#f97316" />
|
||||||
|
</marker>
|
||||||
|
<marker id="cpi-neg" viewBox="0 0 10 10" refX="5" refY="5" markerWidth="6" markerHeight="6" orient="auto">
|
||||||
|
<path d="M 0 0 L 10 5 L 0 10 z" fill="#2563eb" />
|
||||||
|
</marker>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<line x1="0" y1="170" x2="400" y2="170" stroke="#475569" strokeWidth="3" />
|
||||||
|
|
||||||
|
<g>
|
||||||
|
<path d="M -20,30 L 420,30" className="wind-line-slow" />
|
||||||
|
{/* Linha de vento ondulante / caótica se for turbulento */}
|
||||||
|
<path
|
||||||
|
d={isTurbulent
|
||||||
|
? "M -20,75 C 130,65 140,45 180,45 Q 210,30 240,75 Q 275,120 420,110"
|
||||||
|
: "M -20,80 C 130,70 140,40 200,40 C 260,40 270,90 420,90"
|
||||||
|
}
|
||||||
|
className="wind-line-fast"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d={isTurbulent
|
||||||
|
? "M -20,125 C 120,115 130,95 180,95 Q 200,80 230,115 Q 260,140 420,135"
|
||||||
|
: "M -20,130 C 120,120 130,85 200,85 C 270,85 280,130 420,130"
|
||||||
|
}
|
||||||
|
className="wind-line"
|
||||||
|
/>
|
||||||
|
{isTurbulent && (
|
||||||
|
<path d="M 230,105 Q 300,90 420,145" className="vortex-line" />
|
||||||
|
)}
|
||||||
|
</g>
|
||||||
|
|
||||||
|
{/* Abóbada */}
|
||||||
|
<path
|
||||||
|
d="M 130,170 A 70,70 0 0 1 270,170 Z"
|
||||||
|
fill="#334155"
|
||||||
|
stroke="#475569"
|
||||||
|
strokeWidth="2.5"
|
||||||
|
/>
|
||||||
|
{/* Pressão Interna */}
|
||||||
|
{renderCpiVectors(cpi, 'arch')}
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'dome': {
|
||||||
|
const cpi = Number(params.cpi || 0);
|
||||||
|
return (
|
||||||
|
<svg viewBox="0 0 400 200" className="w-full h-full">
|
||||||
|
{styleBlock}
|
||||||
|
<defs>
|
||||||
|
<marker id="cpi-pos" viewBox="0 0 10 10" refX="5" refY="5" markerWidth="6" markerHeight="6" orient="auto">
|
||||||
|
<path d="M 0 0 L 10 5 L 0 10 z" fill="#f97316" />
|
||||||
|
</marker>
|
||||||
|
<marker id="cpi-neg" viewBox="0 0 10 10" refX="5" refY="5" markerWidth="6" markerHeight="6" orient="auto">
|
||||||
|
<path d="M 0 0 L 10 5 L 0 10 z" fill="#2563eb" />
|
||||||
|
</marker>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<line x1="0" y1="170" x2="400" y2="170" stroke="#475569" strokeWidth="3" />
|
||||||
|
|
||||||
|
<g>
|
||||||
|
<path d="M -20,30 L 420,30" className="wind-line-slow" />
|
||||||
|
<path d="M -20,70 C 120,60 140,30 200,30 C 260,30 280,75 420,75" className="wind-line-fast" />
|
||||||
|
<path d="M -20,120 C 120,110 140,80 200,80 C 260,80 280,125 420,125" className="wind-line" />
|
||||||
|
</g>
|
||||||
|
|
||||||
|
{/* Cúpula */}
|
||||||
|
<g>
|
||||||
|
<rect x="150" y="120" width="100" height="50" fill="#1e293b" stroke="#334155" strokeWidth="2" />
|
||||||
|
<path
|
||||||
|
d="M 150,120 A 50,50 0 0 1 250,120 Z"
|
||||||
|
fill="#334155"
|
||||||
|
stroke="#475569"
|
||||||
|
strokeWidth="2.5"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
{/* Pressão Interna */}
|
||||||
|
{renderCpiVectors(cpi, 'dome')}
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'isolated-roof': {
|
||||||
|
const typeRoof = params.type || 'shed';
|
||||||
|
return (
|
||||||
|
<svg viewBox="0 0 400 200" className="w-full h-full">
|
||||||
|
{styleBlock}
|
||||||
|
<line x1="0" y1="170" x2="400" y2="170" stroke="#475569" strokeWidth="3" />
|
||||||
|
|
||||||
|
<g>
|
||||||
|
<path d="M -20,30 Q 200,20 420,30" className="wind-line-slow" />
|
||||||
|
<path d="M -20,70 Q 200,60 420,70" className="wind-line-fast" />
|
||||||
|
<path d="M -20,110 Q 200,105 420,110" className="wind-line" />
|
||||||
|
<path d="M -20,145 L 420,145" className="wind-line-slow" />
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g>
|
||||||
|
<line x1="160" y1="85" x2="160" y2="170" stroke="#475569" strokeWidth="3" />
|
||||||
|
<line x1="240" y1="85" x2="240" y2="170" stroke="#475569" strokeWidth="3" />
|
||||||
|
{typeRoof === 'shed' ? (
|
||||||
|
<line x1="130" y1="75" x2="270" y2="90" stroke="#f43f5e" strokeWidth="5" strokeLinecap="round" />
|
||||||
|
) : (
|
||||||
|
<path d="M 130,90 L 200,70 L 270,90" fill="none" stroke="#f43f5e" strokeWidth="5" strokeLinecap="round" />
|
||||||
|
)}
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'bar': {
|
||||||
|
const barType = params.barType || 'flat';
|
||||||
|
const section = params.section || 'l';
|
||||||
|
const alpha = Number(params.alpha || 0);
|
||||||
|
return (
|
||||||
|
<svg viewBox="0 0 400 200" className="w-full h-full">
|
||||||
|
{styleBlock}
|
||||||
|
<g transform={`rotate(${alpha}, 200, 100)`}>
|
||||||
|
<path d="M -20,30 L 420,30" className="wind-line-slow" />
|
||||||
|
<path d="M -20,70 C 150,60 170,40 230,40 Q 320,70 420,70" className="wind-line" />
|
||||||
|
<path d="M -20,130 C 150,140 170,160 230,160 Q 320,130 420,130" className="wind-line" />
|
||||||
|
<path d="M -20,170 L 420,170" className="wind-line-slow" />
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<g transform="translate(200, 100)">
|
||||||
|
{barType === 'circular' ? (
|
||||||
|
<circle cx="0" cy="0" r="22" fill="#334155" stroke="#475569" strokeWidth="2.5" />
|
||||||
|
) : (
|
||||||
|
<g transform="translate(-20, -20)">
|
||||||
|
{section === 'l' && (
|
||||||
|
<path d="M 0,0 L 12,0 L 12,28 L 40,28 L 40,40 L 0,40 Z" fill="#334155" stroke="#475569" strokeWidth="2" />
|
||||||
|
)}
|
||||||
|
{section === 'u' && (
|
||||||
|
<path d="M 0,0 L 12,0 L 12,28 L 28,28 L 28,0 L 40,0 L 40,40 L 0,40 Z" fill="#334155" stroke="#475569" strokeWidth="2" />
|
||||||
|
)}
|
||||||
|
{section === 'i' && (
|
||||||
|
<path d="M 0,0 L 40,0 L 40,10 L 26,10 L 26,30 L 40,30 L 40,40 L 0,40 L 0,30 L 14,30 L 14,10 L 0,10 Z" fill="#334155" stroke="#475569" strokeWidth="2" />
|
||||||
|
)}
|
||||||
|
</g>
|
||||||
|
)}
|
||||||
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
<div className="absolute bottom-2 right-2 bg-slate-900/80 backdrop-blur px-2 py-1 rounded text-[10px] text-sky-400 font-mono">
|
|
||||||
Superfície: {isSmooth ? 'Lisa (Menor Arrasto)' : 'Rugosa (Maior Arrasto)'}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'dynamics': {
|
case 'dynamics': {
|
||||||
const fn = Number(params.fn || 0.5);
|
|
||||||
// Frequência regula a velocidade da vibração visual
|
|
||||||
const vibeDuration = Math.max(0.5, Math.min(2.5, 1 / fn));
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<h4 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">Simulação Ativa (com suas variáveis)</h4>
|
|
||||||
<div className="relative aspect-video w-full bg-slate-950 border border-border rounded-lg overflow-hidden">
|
|
||||||
<svg viewBox="0 0 400 200" className="w-full h-full">
|
<svg viewBox="0 0 400 200" className="w-full h-full">
|
||||||
{styleBlock}
|
{styleBlock}
|
||||||
|
|
||||||
{/* Linhas de vento */}
|
|
||||||
<g>
|
<g>
|
||||||
<path d="M -20,20 L 420,20" className="wind-line-slow" />
|
<path d="M -20,25 L 420,25" className="wind-line-slow" />
|
||||||
<path d="M -20,60 C 150,55 160,35 200,35 C 240,35 250,55 420,55" className="wind-line" />
|
<path d="M -20,60 C 150,55 160,35 200,35 C 240,35 250,60 420,60" className="wind-line" />
|
||||||
<path d="M -20,140 C 150,145 160,165 200,165 C 240,165 250,145 420,145" className="wind-line" />
|
<path d="M -20,140 C 150,145 160,165 200,165 C 240,165 250,140 420,140" className="wind-line" />
|
||||||
<path d="M -20,180 L 420,180" className="wind-line-slow" />
|
<path d="M -20,175 L 420,175" className="wind-line-slow" />
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
{/* Vórtices alternados desalinhados de Von Kármán */}
|
<g transform="translate(240, 100)">
|
||||||
<g transform="translate(250, 100)">
|
<circle cx="20" cy="-25" r="9" fill="none" stroke="#ef4444" strokeWidth="1.5" strokeDasharray="3 6" className="vortex-spin-cw" />
|
||||||
<path d="M 0,0 Q 40,-25 80,10 Q 120,-15 160,5" className="vortex-line" />
|
<circle cx="60" cy="25" r="11" fill="none" stroke="#ef4444" strokeWidth="1.5" strokeDasharray="3 6" className="vortex-spin-ccw" />
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
{/* Cilindro que VIBRA verticalmente na ressonância */}
|
<g className="vibe-cyl">
|
||||||
<g className="vibe-cyl" style={{ animationDuration: `${vibeDuration}s` }}>
|
<circle cx="200" cy="100" r="28" fill="#ef4444" stroke="#b91c1c" strokeWidth="2.5" />
|
||||||
<circle cx="200" cy="100" r="30" fill="#ef4444" stroke="#f87171" strokeWidth="2" opacity="0.9" />
|
<line x1="200" y1="100" x2="200" y2="40" stroke="#ef4444" strokeWidth="2.5" strokeDasharray="2 4" />
|
||||||
<line x1="200" y1="100" x2="200" y2="40" stroke="#f87171" strokeWidth="2" strokeDasharray="2 4" />
|
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
<div className="absolute bottom-2 right-2 bg-slate-900/80 backdrop-blur px-2 py-1 rounded text-[10px] text-sky-400 font-mono">
|
|
||||||
fn: {fn} Hz | Oscilação Ativa
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// Padrão genérico de fluxo para os demais módulos
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<h4 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">Visualização do Escoamento</h4>
|
|
||||||
<div className="relative aspect-video w-full bg-slate-950 border border-border rounded-lg overflow-hidden">
|
|
||||||
<svg viewBox="0 0 400 200" className="w-full h-full">
|
<svg viewBox="0 0 400 200" className="w-full h-full">
|
||||||
{styleBlock}
|
{styleBlock}
|
||||||
<path d="M -20,30 L 420,30" className="wind-line-slow" />
|
<path d="M -20,30 L 420,30" className="wind-line-slow" />
|
||||||
<path d="M -20,70 Q 200,40 420,70" className="wind-line" />
|
<path d="M -20,100 L 420,100" className="wind-line" />
|
||||||
<path d="M -20,130 Q 200,160 420,130" className="wind-line" />
|
|
||||||
<path d="M -20,170 L 420,170" className="wind-line-slow" />
|
<path d="M -20,170 L 420,170" className="wind-line-slow" />
|
||||||
|
<circle cx="200" cy="100" r="25" fill="#334155" />
|
||||||
{/* Elemento genérico no centro */}
|
|
||||||
<rect x="180" y="80" width="40" height="40" rx="3" fill="#334155" stroke="#475569" strokeWidth="2" />
|
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Comparativos estáticos baseados em grids como sugerido pelo usuário
|
|
||||||
const renderComparisonGrid = () => {
|
|
||||||
if (type === 'bridge') {
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-2 gap-4 mt-4">
|
<div className="flex flex-col gap-4 my-6 bg-slate-900/40 p-4 border border-border/60 rounded-xl">
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex justify-between items-center border-b border-border/40 pb-2">
|
||||||
<span className="text-[10px] font-bold text-muted-foreground uppercase">Incidência Descendente (α = -5º)</span>
|
<div>
|
||||||
<div className="aspect-video w-full bg-slate-950 border border-border rounded-lg overflow-hidden">
|
<h3 className="text-sm font-semibold">Túnel de Vento Virtual 2D</h3>
|
||||||
<svg viewBox="0 0 400 200" className="w-full h-full opacity-90">
|
<span className="text-[10px] text-sky-400 font-medium font-mono">{getViewLabel()}</span>
|
||||||
{styleBlock}
|
|
||||||
<g transform="rotate(-15, 200, 100)">
|
|
||||||
<path d="M -50,50 L 450,50" className="wind-line-slow" />
|
|
||||||
<path d="M -50,100 L 450,100" className="wind-line" />
|
|
||||||
<path d="M -50,150 L 450,150" className="wind-line-slow" />
|
|
||||||
</g>
|
|
||||||
<rect x="150" y="85" width="100" height="30" rx="4" fill="#334155" />
|
|
||||||
{/* Seta para baixo */}
|
|
||||||
<path d="M 200,125 L 200,170" stroke="#ef4444" strokeWidth="3" markerEnd="url(#arrow)" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
</div>
|
||||||
<p className="text-[10px] text-muted-foreground">Vento empurra o tabuleiro para baixo, aumentando os esforços de compressão nos pilares.</p>
|
<span className="text-[9px] bg-sky-500/10 text-sky-400 font-bold border border-sky-500/20 px-2 py-0.5 rounded-full">SVG Animado</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-1">
|
|
||||||
<span className="text-[10px] font-bold text-muted-foreground uppercase">Incidência Ascendente (α = +5º)</span>
|
|
||||||
<div className="aspect-video w-full bg-slate-950 border border-border rounded-lg overflow-hidden">
|
|
||||||
<svg viewBox="0 0 400 200" className="w-full h-full opacity-90">
|
|
||||||
{styleBlock}
|
|
||||||
<g transform="rotate(15, 200, 100)">
|
|
||||||
<path d="M -50,50 L 450,50" className="wind-line-slow" />
|
|
||||||
<path d="M -50,100 L 450,100" className="wind-line" />
|
|
||||||
<path d="M -50,150 L 450,150" className="wind-line-slow" />
|
|
||||||
</g>
|
|
||||||
<rect x="150" y="85" width="100" height="30" rx="4" fill="#334155" />
|
|
||||||
{/* Seta para cima */}
|
|
||||||
<path d="M 200,75 L 200,30" stroke="#10b981" strokeWidth="3" markerEnd="url(#arrow)" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<p className="text-[10px] text-muted-foreground">Efeito aerofólio levanta a ponte. Crítico para pontes estaiadas ou suspensas.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === 'tower') {
|
<div className="relative aspect-video w-full bg-slate-950 border border-border/80 rounded-lg overflow-hidden shadow-inner">
|
||||||
return (
|
|
||||||
<div className="grid grid-cols-2 gap-4 mt-4">
|
|
||||||
<div className="flex flex-col gap-1">
|
|
||||||
<span className="text-[10px] font-bold text-muted-foreground uppercase">Vento Normal (0º)</span>
|
|
||||||
<div className="aspect-video w-full bg-slate-950 border border-border rounded-lg overflow-hidden">
|
|
||||||
<svg viewBox="0 0 400 200" className="w-full h-full">
|
|
||||||
{styleBlock}
|
|
||||||
<path d="M -20,60 C 150,60 170,45 230,45 Q 320,60 420,60" className="wind-line" />
|
|
||||||
<path d="M -20,140 C 150,140 170,155 230,155 Q 320,140 420,140" className="wind-line" />
|
|
||||||
<g transform="translate(180, 80)">
|
|
||||||
<rect x="0" y="0" width="40" height="40" fill="none" stroke="#64748b" strokeWidth="3" />
|
|
||||||
<line x1="0" y1="0" x2="40" y2="40" stroke="#475569" />
|
|
||||||
<line x1="40" y1="0" x2="0" y2="40" stroke="#475569" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<p className="text-[10px] text-muted-foreground">Vento incidindo perpendicularmente à face frontal. Menor área exposta efetiva.</p>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-1">
|
|
||||||
<span className="text-[10px] font-bold text-muted-foreground uppercase">Vento na Diagonal (45º)</span>
|
|
||||||
<div className="aspect-video w-full bg-slate-950 border border-border rounded-lg overflow-hidden">
|
|
||||||
<svg viewBox="0 0 400 200" className="w-full h-full">
|
|
||||||
{styleBlock}
|
|
||||||
<path d="M -20,40 C 150,40 170,30 230,30 Q 320,40 420,40" className="wind-line" />
|
|
||||||
<path d="M -20,160 C 150,160 170,170 230,170 Q 320,160 420,160" className="wind-line" />
|
|
||||||
<g transform="translate(200, 100) rotate(45)">
|
|
||||||
<rect x="-20" y="-20" width="40" height="40" fill="none" stroke="#64748b" strokeWidth="3" />
|
|
||||||
<line x1="-20" y1="-20" x2="20" y2="20" stroke="#475569" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<p className="text-[10px] text-muted-foreground">Aumenta a esteira e o coeficiente de arrasto Ca nas torres de seção quadrada.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Grid padrão se não houver um caso comparativo customizado
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col gap-4 my-6 bg-slate-900/40 p-4 border rounded-xl">
|
|
||||||
<div className="flex justify-between items-center">
|
|
||||||
<h3 className="text-sm font-semibold">Túnel de Vento Virtual (Escoamento 2D)</h3>
|
|
||||||
<span className="text-[10px] bg-sky-500/10 text-sky-400 font-bold border border-sky-500/20 px-2 py-0.5 rounded-full">SVG Interativo</span>
|
|
||||||
</div>
|
|
||||||
{renderActiveSimulation()}
|
{renderActiveSimulation()}
|
||||||
{renderComparisonGrid()}
|
</div>
|
||||||
|
|
||||||
|
{/* Caixa de Legenda Didática Expandida */}
|
||||||
|
<div className="bg-slate-950/60 p-3 rounded-lg border border-border/40 text-[11px] text-muted-foreground flex flex-col gap-2">
|
||||||
|
<span className="font-bold text-xs text-foreground uppercase tracking-wider">Legenda do Escoamento:</span>
|
||||||
|
<div className="grid grid-cols-2 gap-x-4 gap-y-2">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="w-6 h-0.5 bg-blue-500 inline-block border-b border-dashed" />
|
||||||
|
<span>Linhas de Corrente de Vento</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="w-6 h-0.5 bg-sky-400 inline-block" />
|
||||||
|
<span>Fluxo Acelerado (Alta Vel. / Sucção)</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="w-6 h-0.5 bg-red-500 inline-block border-b border-dotted" />
|
||||||
|
<span>Esteira Turbulenta / Recirculação</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="w-4 h-4 rounded-full border border-dashed border-red-500 inline-block" />
|
||||||
|
<span>Vórtices de Desprendimento</span>
|
||||||
|
</div>
|
||||||
|
{/* Legenda de Pressão Interna */}
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-orange-500 text-xs font-bold font-sans">→</span>
|
||||||
|
<span>Seta Laranja: Pressão Interna (+Cpi)</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-blue-500 text-xs font-bold font-sans">←</span>
|
||||||
|
<span>Seta Azul Escura: Sucção Interna (-Cpi)</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ const CylinderModule: React.FC = () => {
|
|||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
<div className="absolute top-4 right-4 z-10">
|
<div className="absolute top-4 right-4 z-10">
|
||||||
<EducationalManual type="cylinder" params={{ roughness: surface }} />
|
<EducationalManual type="cylinder" params={{ surface, endType, cpi: result.cpi }} />
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full h-full bg-muted/20">
|
<div className="w-full h-full bg-muted/20">
|
||||||
<Cylinder3DViewer
|
<Cylinder3DViewer
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ const DomeModule: React.FC = () => {
|
|||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
<div className="absolute top-4 right-4 z-10">
|
<div className="absolute top-4 right-4 z-10">
|
||||||
<EducationalManual type="dome" params={{ rise, diameter }} />
|
<EducationalManual type="dome" params={{ rise, diameter, cpi }} />
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full h-full bg-muted/20">
|
<div className="w-full h-full bg-muted/20">
|
||||||
<Dome3DViewer
|
<Dome3DViewer
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ const VaultModule: React.FC = () => {
|
|||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
<div className="absolute top-4 right-4 z-10">
|
<div className="absolute top-4 right-4 z-10">
|
||||||
<EducationalManual type="vault" params={{ rise, span }} />
|
<EducationalManual type="vault" params={{ rise, span, regime, localCpi }} />
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full h-full bg-muted/20">
|
<div className="w-full h-full bg-muted/20">
|
||||||
<Vault3DViewer span={span} length={length} rise={rise} cpi={localCpi} cpeProfile={{ ...result.windPerpendicular }} />
|
<Vault3DViewer span={span} length={length} rise={rise} cpi={localCpi} cpeProfile={{ ...result.windPerpendicular }} />
|
||||||
|
|||||||
Reference in New Issue
Block a user