diff --git a/app/src/components/EducationalManual.tsx b/app/src/components/EducationalManual.tsx new file mode 100644 index 0000000..604e844 --- /dev/null +++ b/app/src/components/EducationalManual.tsx @@ -0,0 +1,74 @@ +import { BookOpen, HelpCircle } from 'lucide-react'; +import { Button } from '@/components/ui/button'; +import { + Sheet, + SheetContent, + SheetDescription, + SheetHeader, + SheetTitle, + SheetTrigger, +} from '@/components/ui/sheet'; +import { manuals } from '@/data/manuals'; +import { WindFlowGrid2D } from './WindFlowGrid2D'; + +interface EducationalManualProps { + type: string; + params: Record; +} + +export function EducationalManual({ type, params }: EducationalManualProps) { + const manual = manuals[type]; + + if (!manual) { + return null; + } + + return ( + + + + + + +
+ + {manual.title} +
+ + {manual.intro} + +
+ + {/* Simulador Interativo 2D no topo do manual */} + + + {/* Seções de Texto e Fórmulas */} +
+ {manual.sections.map((section, idx) => ( +
+

+ {section.title} +

+
+ {section.content} +
+
+ ))} +
+ +
+

+ VentoApp • Referência: ABNT NBR 6123:2023 +

+
+
+
+ ); +} diff --git a/app/src/components/FallbackDiagram.tsx b/app/src/components/FallbackDiagram.tsx index 81a1d57..29bb745 100644 --- a/app/src/components/FallbackDiagram.tsx +++ b/app/src/components/FallbackDiagram.tsx @@ -403,6 +403,7 @@ interface IsolatedRoofProps { type: 'shed' | 'gable'; theta: number; height: number; + width: number; depth: number; cpeWindward: number; cpeLeeward: number; @@ -410,7 +411,7 @@ interface IsolatedRoofProps { forceKN: number; } -function IsolatedRoofDiagram({ type, theta, height, depth, cpeWindward, cpeLeeward, cpeTop, forceKN }: IsolatedRoofProps) { +function IsolatedRoofDiagram({ type, theta, height, width, depth, cpeWindward, cpeLeeward, cpeTop, forceKN }: IsolatedRoofProps) { const vw = 400, vh = 300; const s = Math.min((vw - 100) / depth, (vh - 80) / (height + depth * Math.tan((theta * Math.PI) / 180))); const cx = vw / 2, ground = vh - 40; @@ -451,7 +452,7 @@ function IsolatedRoofDiagram({ type, theta, height, depth, cpeWindward, cpeLeewa {forceKN.toFixed(1)} kN )} - θ = {theta}° | h = {height}m | prof. = {depth}m + θ = {theta}° | h = {height}m | b = {width}m | l = {depth}m ); } diff --git a/app/src/components/SceneCanvas.tsx b/app/src/components/SceneCanvas.tsx index c4616db..1dc8a62 100644 --- a/app/src/components/SceneCanvas.tsx +++ b/app/src/components/SceneCanvas.tsx @@ -8,15 +8,35 @@ interface SceneCanvasProps extends CanvasProps { fallback: ReactNode; } -function CanvasInner({ fallback: _, ...canvasProps }: SceneCanvasProps) { +function CanvasInner({ fallback: _, shadows, ...canvasProps }: SceneCanvasProps) { const registerCanvas = useCaptureStore((s) => s.registerCanvas); const unregisterCanvas = useCaptureStore((s) => s.unregisterCanvas); useEffect(() => () => unregisterCanvas(), [unregisterCanvas]); + // Suprimir avisos de depreciação conhecidos do Three.js (r185) que o R3F ainda aciona + useEffect(() => { + const originalWarn = console.warn; + console.warn = (...args) => { + if (typeof args[0] === 'string' && (args[0].includes('THREE.Clock') || args[0].includes('THREE.WebGLShadowMap'))) { + return; + } + originalWarn(...args); + }; + return () => { + console.warn = originalWarn; + }; + }, []); + + // O R3F v9 usa PCFSoftShadowMap por padrão, que está deprecado no Three r185. + // Mudar para PCFShadowMap (1) via propriedade shadows. + const shadowConfig = shadows === true ? { type: 1 as any } : shadows; + return ( { registerCanvas(state.gl.domElement); canvasProps.onCreated?.(state); diff --git a/app/src/components/Warehouse3D.tsx b/app/src/components/Warehouse3D.tsx index 4bf4460..6c65dca 100644 --- a/app/src/components/Warehouse3D.tsx +++ b/app/src/components/Warehouse3D.tsx @@ -32,27 +32,27 @@ function PressureArrow({ }) { const { q } = useWindStore(); const force = p * q; // kN/m2 - if (Math.abs(force) < 0.05) return null; - - const length = Math.max(0.6, Math.min(3.0, Math.abs(force) * 1.5)); - const isPressure = p > 0; - const color = isPressure ? '#3b82f6' : '#ef4444'; - + const normVec = useMemo(() => new THREE.Vector3(...normal).normalize(), [normal]); const centerVec = useMemo(() => new THREE.Vector3(...center), [center]); - - const dir = isPressure ? normVec.clone().negate() : normVec.clone(); - - const start = isPressure ? centerVec.clone().sub(dir.clone().multiplyScalar(length)) : centerVec; - const end = isPressure ? centerVec : centerVec.clone().add(dir.clone().multiplyScalar(length)); - const mid = new THREE.Vector3().addVectors(start, end).multiplyScalar(0.5); - + const isPressure = p > 0; + const dir = useMemo(() => isPressure ? normVec.clone().negate() : normVec.clone(), [isPressure, normVec]); + const quat = useMemo(() => { const q = new THREE.Quaternion(); q.setFromUnitVectors(new THREE.Vector3(0, 1, 0), dir); return new THREE.Euler().setFromQuaternion(q); }, [dir]); - + + if (Math.abs(force) < 0.05) return null; + + const length = Math.max(0.6, Math.min(3.0, Math.abs(force) * 1.5)); + const color = isPressure ? '#3b82f6' : '#ef4444'; + + const start = isPressure ? centerVec.clone().sub(dir.clone().multiplyScalar(length)) : centerVec; + const end = isPressure ? centerVec : centerVec.clone().add(dir.clone().multiplyScalar(length)); + const mid = new THREE.Vector3().addVectors(start, end).multiplyScalar(0.5); + const headLen = Math.min(0.4, length * 0.4); return ( diff --git a/app/src/components/WindFlowGrid2D.tsx b/app/src/components/WindFlowGrid2D.tsx new file mode 100644 index 0000000..07bf1a9 --- /dev/null +++ b/app/src/components/WindFlowGrid2D.tsx @@ -0,0 +1,376 @@ + + +interface WindFlowGrid2DProps { + type: string; + params: Record; +} + +export function WindFlowGrid2D({ type, params }: WindFlowGrid2DProps) { + // Estilo global de animação inserido no próprio SVG + const styleBlock = ( + + ); + + // Renderizadores específicos de fluxo ativo com base nos sliders da página + const renderActiveSimulation = () => { + switch (type) { + case 'bridge': { + const alpha = Number(params.alpha || 0); + // O vento muda de ângulo + const rotationAngle = alpha * 3; // amplifica o ângulo para ficar visível + return ( +
+

Simulação Ativa (com suas variáveis)

+
+ + {styleBlock} + {/* Linhas de vento de fundo */} + + + + + + + {/* Linhas aceleradas acima e abaixo */} + + + + + {/* Tabuleiro da ponte */} + + + {/* Barreiras de proteção */} + + + + + {/* Vetores de Força baseados no ângulo */} + {alpha !== 0 && ( + + {/* Vetor de sustentação (Fz) */} + 0 ? "M 200,75 L 200,30" : "M 200,125 L 200,170"} + stroke={alpha > 0 ? "#10b981" : "#ef4444"} + strokeWidth="3" + fill="none" + markerEnd="url(#arrow)" + /> + 0 ? 45 : 160} fill={alpha > 0 ? "#10b981" : "#ef4444"} className="text-xs font-bold font-mono"> + {alpha > 0 ? 'Sustentação (+Fz)' : 'Downforce (-Fz)'} + + + )} + + {/* Marcadores SVG de setas */} + + + + + + +
+ Ângulo: {alpha}º +
+
+
+ ); + } + + case 'tower': { + const section = params.section || 'square'; + const alphaWind = Number(params.alphaWind || 0); + return ( +
+

Simulação Ativa (com suas variáveis)

+
+ + {styleBlock} + + {/* Linhas de vento fluindo */} + + + + + + + {/* Turbulência traseira (esteira) */} + + + + + {/* Perfil da Seção da Torre (Centro) */} + + {section === 'square' ? ( + + ) : ( + + )} + {/* Barras internas treliçadas */} + {section === 'square' ? ( + <> + + + + ) : ( + <> + + + + + )} + + +
+ {section === 'square' ? 'Quadrada' : 'Triangular'} | {alphaWind}º +
+
+
+ ); + } + + case 'cylinder': { + const isSmooth = params.roughness === 'smooth' || params.d_over_k > 1e5; + return ( +
+

Simulação Ativa (com suas variáveis)

+
+ + {styleBlock} + + {/* Fluxo contornando cilindro */} + + + + + + + + {/* Esteira de Vórtices de Von Kármán */} + + + + + + + + {/* Cilindro */} + + +
+ Superfície: {isSmooth ? 'Lisa (Menor Arrasto)' : 'Rugosa (Maior Arrasto)'} +
+
+
+ ); + } + + 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 ( +
+

Simulação Ativa (com suas variáveis)

+
+ + {styleBlock} + + {/* Linhas de vento */} + + + + + + + + {/* Vórtices alternados desalinhados de Von Kármán */} + + + + + {/* Cilindro que VIBRA verticalmente na ressonância */} + + + + + +
+ fn: {fn} Hz | Oscilação Ativa +
+
+
+ ); + } + + default: + // Padrão genérico de fluxo para os demais módulos + return ( +
+

Visualização do Escoamento

+
+ + {styleBlock} + + + + + + {/* Elemento genérico no centro */} + + +
+
+ ); + } + }; + + // Comparativos estáticos baseados em grids como sugerido pelo usuário + const renderComparisonGrid = () => { + if (type === 'bridge') { + return ( +
+
+ Incidência Descendente (α = -5º) +
+ + {styleBlock} + + + + + + + {/* Seta para baixo */} + + +
+

Vento empurra o tabuleiro para baixo, aumentando os esforços de compressão nos pilares.

+
+
+ Incidência Ascendente (α = +5º) +
+ + {styleBlock} + + + + + + + {/* Seta para cima */} + + +
+

Efeito aerofólio levanta a ponte. Crítico para pontes estaiadas ou suspensas.

+
+
+ ); + } + + if (type === 'tower') { + return ( +
+
+ Vento Normal (0º) +
+ + {styleBlock} + + + + + + + + +
+

Vento incidindo perpendicularmente à face frontal. Menor área exposta efetiva.

+
+
+ Vento na Diagonal (45º) +
+ + {styleBlock} + + + + + + + +
+

Aumenta a esteira e o coeficiente de arrasto Ca nas torres de seção quadrada.

+
+
+ ); + } + + // Grid padrão se não houver um caso comparativo customizado + return null; + }; + + return ( +
+
+

Túnel de Vento Virtual (Escoamento 2D)

+ SVG Interativo +
+ {renderActiveSimulation()} + {renderComparisonGrid()} +
+ ); +} diff --git a/app/src/components/three/Bar3D.tsx b/app/src/components/three/Bar3D.tsx index 5845c07..96bf9ff 100644 --- a/app/src/components/three/Bar3D.tsx +++ b/app/src/components/three/Bar3D.tsx @@ -25,11 +25,6 @@ export interface Bar3DInput { cx: number; } -/** - * Converte kN para um comprimento visual proporcional no eixo 3D. - */ -const forceToLength = (kN: number): number => Math.min(Math.max(Math.abs(kN) * 0.3, 0.3), 4); - function BarModel({ barType, section, @@ -37,94 +32,60 @@ function BarModel({ width, length, alpha, - fxKN, - fyKN, cx, }: Bar3DInput) { const barRadius = barType === 'circular' ? (diameter ?? 0.05) / 2 : Math.min(width ?? 0.1, 0.08) / 2; const barThickness = barType === 'circular' ? barRadius : barRadius * 0.5; - // Cor baseada em Cx const barColor = useMemo(() => { const intensity = Math.min(1, Math.abs(cx) / 2.5); const hue = 215 - intensity * 215; return new THREE.Color(`hsl(${hue}, ${65 + intensity * 25}%, ${45 - intensity * 10}%)`); }, [cx]); - // Rotação da barra em torno do eixo Y (alinhada com eixo X inicialmente) - // Direção do vento é +X; α é o ângulo da face da barra em relação ao vento + // Ação do vento deve rotacionar a barra em seu próprio eixo longitudinal (roll). + // O eixo longitudinal na nossa geometria é o X. const alphaRad = (alpha * Math.PI) / 180; - const barRotation = -alphaRad; // rotação em torno do eixo Y para alinhar a face - - // Direção do vetor de força resultante (na direção da força calculada) - const forceMag = Math.sqrt(fxKN * fxKN + fyKN * fyKN); - const forceAngle = Math.atan2(fyKN, fxKN); - const arrowLen = forceToLength(forceMag); - - // Centro da barra (origem) - const center = new THREE.Vector3(0, 0, 0); - - // Posição da ponta da seta - const arrowEnd = useMemo( - () => new THREE.Vector3( - Math.cos(forceAngle) * arrowLen, - Math.sin(forceAngle) * arrowLen, - 0, - ), - [forceAngle, arrowLen], - ); - const arrowMid = useMemo( - () => new THREE.Vector3(arrowEnd.x / 2, arrowEnd.y / 2, 0), - [arrowEnd], - ); - const quat = useMemo(() => { - const dir = arrowEnd.clone().normalize(); - const q = new THREE.Quaternion(); - q.setFromUnitVectors(new THREE.Vector3(0, 1, 0), dir); - return new THREE.Euler().setFromQuaternion(q); - }, [arrowEnd]); - - const headLen = 0.25; + // A rotação deve ser negativa para que o vento (vindo de -Z) atinja as faces corretas conforme a NBR. + const barRotationX = -alphaRad; return ( - - {/* Eixo principal da barra ao longo do eixo X */} - {barType === 'circular' ? ( - - - + + {/* Seta do Vento (Fixa Globalmente, soprando em +Z) */} + + + + - ) : ( - - )} - - {/* Eixos de referência */} - - - {/* Vetor de força (resultante) */} - {forceMag > 0.01 && ( - - {arrowLen - headLen > 0.01 && ( - - - - - )} - - - + + + + + + + {/* Barra Rotacionada (Roll) */} + + {/* Eixo principal da barra ao longo do eixo X */} + {barType === 'circular' ? ( + + + - - )} + ) : ( + + )} + + {/* Eixos de referência locais da barra */} + + {/* Marca de origem */} - α={alpha}° | Cx={cx.toFixed(2)} - {center && null} ); } diff --git a/app/src/components/three/Bridge3D.tsx b/app/src/components/three/Bridge3D.tsx index 1dc4ed5..a566a4b 100644 --- a/app/src/components/three/Bridge3D.tsx +++ b/app/src/components/three/Bridge3D.tsx @@ -3,6 +3,7 @@ import { OrbitControls, Grid, Environment, Text } from '@react-three/drei'; import * as THREE from 'three'; import SceneCanvas from '../SceneCanvas'; import FallbackDiagram from '../FallbackDiagram'; +import { WindArrow } from './WindArrow'; export interface Bridge3DInput { /** Maior vão Lₚ (m) */ @@ -21,6 +22,8 @@ export interface Bridge3DInput { fxPerLength: number; /** Força de sustentação por unidade de comprimento Fz (kN/m) */ fzPerLength: number; + /** Ângulo de ataque do vento (graus) */ + alpha: number; } function BridgeModel({ @@ -29,8 +32,7 @@ function BridgeModel({ deckHeight, heg, cx, - fxPerLength, - fzPerLength, + alpha, }: Bridge3DInput) { const halfL = lp / 2; const halfW = width / 2; @@ -47,13 +49,6 @@ function BridgeModel({ // Pilar heights: posicionar 3 pilares ao longo do vão const pillarHeights = useMemo(() => [deckY - 0.5, deckY - 0.5, deckY - 0.5], [deckY]); - // Vetor de força (Fx horizontal) - const fxLen = Math.min(Math.max(Math.abs(fxPerLength) * 0.5, 0.3), 4); - const fxDir = fxPerLength >= 0 ? 1 : -1; - // Vetor de força (Fz vertical) - const fzLen = Math.min(Math.max(Math.abs(fzPerLength) * 0.5, 0.3), 4); - const fzDir = fzPerLength >= 0 ? 1 : -1; - return ( {/* Tabuleiro (deck) */} @@ -89,32 +84,15 @@ function BridgeModel({ - {/* Vetor Cx (horizontal) */} - - - {/* Vetor Cz (vertical) */} - - - {/* Vetor no centro também para destacar */} - new THREE.Vector3(...start), [start]); - const dirVec = useMemo(() => new THREE.Vector3(...direction), [direction]); - const end = useMemo( - () => new THREE.Vector3( - startVec.x + dirVec.x * length, - startVec.y + dirVec.y * length, - startVec.z + dirVec.z * length, - ), - [startVec, dirVec, length], - ); - const mid = useMemo( - () => new THREE.Vector3().addVectors(startVec, end).multiplyScalar(0.5), - [startVec, end], - ); - const quat = useMemo(() => { - const dir = new THREE.Vector3().subVectors(end, startVec).normalize(); - const q = new THREE.Quaternion(); - q.setFromUnitVectors(new THREE.Vector3(0, 1, 0), dir); - return new THREE.Euler().setFromQuaternion(q); - }, [startVec, end]); - const headLen = 0.3; - return ( - - {length - headLen > 0.01 && ( - - - - - )} - - - - - - ); -} export default function Bridge3DViewer(input: Bridge3DInput) { const { lp, deckHeight, width } = input; diff --git a/app/src/components/three/Dynamics3D.tsx b/app/src/components/three/Dynamics3D.tsx index fb50408..e2f75bc 100644 --- a/app/src/components/three/Dynamics3D.tsx +++ b/app/src/components/three/Dynamics3D.tsx @@ -186,6 +186,7 @@ export default function Dynamics3DViewer(props: Dynamics3DInput) { return ( Math.min(Math.max(Math.abs(kN) * 0.15, 0.5), 6); - function pressureColor(cpe: number): THREE.Color { const clamped = Math.max(-2.5, Math.min(1.5, cpe)); const t = (clamped + 2.5) / 4.0; @@ -36,6 +37,7 @@ function IsolatedRoofModel({ type, theta, height, + width, depth, cpeWindward, cpeLeeward, @@ -43,12 +45,11 @@ function IsolatedRoofModel({ }: IsolatedRoof3DInput) { const thetaRad = (theta * Math.PI) / 180; const halfDepth = depth / 2; + const halfWidth = width / 2; const windwardColor = useMemo(() => pressureColor(cpeWindward), [cpeWindward]); const leewardColor = useMemo(() => pressureColor(cpeLeeward), [cpeLeeward]); - const arrowLen = forceToLength(forceKN); - const h_diff = depth * Math.tan(thetaRad); const h_half = (depth / 2) * Math.tan(thetaRad); @@ -60,29 +61,29 @@ function IsolatedRoofModel({ const list: { pos: [number, number, number]; h: number }[] = []; if (type === 'shed') { list.push( - { pos: [-halfDepth, height / 2, -halfDepth], h: height }, - { pos: [halfDepth, height / 2, -halfDepth], h: height }, - { pos: [-halfDepth, (height + h_diff) / 2, halfDepth], h: height + h_diff }, - { pos: [halfDepth, (height + h_diff) / 2, halfDepth], h: height + h_diff }, + { pos: [-halfWidth, height / 2, -halfDepth], h: height }, + { pos: [halfWidth, height / 2, -halfDepth], h: height }, + { pos: [-halfWidth, (height + h_diff) / 2, halfDepth], h: height + h_diff }, + { pos: [halfWidth, (height + h_diff) / 2, halfDepth], h: height + h_diff }, ); } else { list.push( - { pos: [-halfDepth, height / 2, -halfDepth], h: height }, - { pos: [halfDepth, height / 2, -halfDepth], h: height }, - { pos: [-halfDepth, height / 2, halfDepth], h: height }, - { pos: [halfDepth, height / 2, halfDepth], h: height }, - { pos: [-halfDepth, (height + h_half) / 2, 0], h: height + h_half }, - { pos: [halfDepth, (height + h_half) / 2, 0], h: height + h_half }, + { pos: [-halfWidth, height / 2, -halfDepth], h: height }, + { pos: [halfWidth, height / 2, -halfDepth], h: height }, + { pos: [-halfWidth, height / 2, halfDepth], h: height }, + { pos: [halfWidth, height / 2, halfDepth], h: height }, + { pos: [-halfWidth, (height + h_half) / 2, 0], h: height + h_half }, + { pos: [halfWidth, (height + h_half) / 2, 0], h: height + h_half }, ); } return list; - }, [type, depth, height, h_diff, h_half, halfDepth]); + }, [type, depth, width, height, h_diff, h_half, halfDepth, halfWidth]); return ( {/* Solo translúcido */} - + @@ -97,7 +98,7 @@ function IsolatedRoofModel({ castShadow receiveShadow > - + {/* Metade Sotavento (Z > 0) */} @@ -107,7 +108,7 @@ function IsolatedRoofModel({ castShadow receiveShadow > - + @@ -121,7 +122,7 @@ function IsolatedRoofModel({ castShadow receiveShadow > - + {/* Água Direita / Sotavento (Z > 0) */} @@ -131,7 +132,7 @@ function IsolatedRoofModel({ castShadow receiveShadow > - + @@ -145,12 +146,10 @@ function IsolatedRoofModel({ ))} - {/* Seta de força resultante (sucção para cima) */} - {/* === LINHAS DE COTA (CAD-Style) === */} @@ -208,8 +207,7 @@ function IsolatedRoofModel({ {/* Rótulo Superior */} new THREE.Vector3(start.x + direction.x * length, start.y + direction.y * length, start.z + direction.z * length), - [start, direction, length], - ); - const headLen = 0.3; - const headRadius = 0.1; - const shaftRadius = 0.04; - - const midPoint = useMemo( - () => new THREE.Vector3((start.x + end.x) / 2, (start.y + end.y) / 2, (start.z + end.z) / 2), - [start, end], - ); - const shaftLength = length - headLen; - - const rotation = useMemo(() => { - const dir = new THREE.Vector3().subVectors(end, start).normalize(); - const quat = new THREE.Quaternion(); - quat.setFromUnitVectors(new THREE.Vector3(0, 1, 0), dir); - const euler = new THREE.Euler().setFromQuaternion(quat); - return [euler.x, euler.y, euler.z] as [number, number, number]; - }, [start, end]); - - return ( - - {shaftLength > 0 && ( - - - - - )} - - - - - - ); -} - export default function IsolatedRoof3DViewer({ type, theta, height, + width, depth, cpeWindward, cpeLeeward, cpeTop, forceKN, }: IsolatedRoof3DInput) { - const cameraDistance = Math.max(depth * 1.3, height * 1.5, 8); + const cameraDistance = Math.max(Math.max(width, depth) * 1.3, height * 1.5, 8); const fallback = ( ); @@ -311,6 +257,7 @@ export default function IsolatedRoof3DViewer({ type={type} theta={theta} height={height} + width={width} depth={depth} cpeWindward={cpeWindward} cpeLeeward={cpeLeeward} diff --git a/app/src/components/three/Sign3D.tsx b/app/src/components/three/Sign3D.tsx index 32d6d5e..79bbb4c 100644 --- a/app/src/components/three/Sign3D.tsx +++ b/app/src/components/three/Sign3D.tsx @@ -3,6 +3,7 @@ import { OrbitControls, Grid, Environment, Text } from '@react-three/drei'; import * as THREE from 'three'; import SceneCanvas from '../SceneCanvas'; import FallbackDiagram from '../FallbackDiagram'; +import { WindArrow } from './WindArrow'; export interface Sign3DInput { /** Comprimento ℓ (m) */ @@ -22,10 +23,8 @@ export interface Sign3DInput { } /** - * Converte kN para um comprimento visual proporcional no eixo 3D. - * 1 kN = 0.25 m de seta (escala calibrada para visualização). + * Módulo 3D para visualização de Muros e Placas Isoladas. */ -const forceToLength = (kN: number): number => Math.min(Math.max(kN * 0.25, 0.5), 8); function SignModel({ length, @@ -33,13 +32,11 @@ function SignModel({ groundClearance, alpha, cf, - forceKN, applicationPoint, }: Sign3DInput) { const baseY = groundClearance; const topY = baseY + height; const halfL = length / 2; - const arrowLen = forceToLength(forceKN); // Direção da seta no plano XZ (α é o ângulo de incidência do vento relativo à superfície) // O ângulo em relação à normal da placa (eixo X) é 90 - α @@ -114,12 +111,10 @@ function SignModel({ Resultante (e = {applicationPoint.toFixed(2)}m) - {/* Vetor de força resultante */} - = 0 ? '#ef4444' : '#3b82f6'} /> {/* === LINHAS DE COTA (CAD-Style Dimensions) === */} @@ -154,7 +149,7 @@ function SignModel({ {/* Cota de Comprimento (l) */} - + {/* Linha horizontal longitudinal */} @@ -172,8 +167,8 @@ function SignModel({ {/* Texto do comprimento */} new THREE.Vector3(start.x + direction.x * length, start.y, start.z + direction.z * length), - [start, direction, length], - ); - const headLen = 0.3; - const headRadius = 0.1; - const shaftRadius = 0.04; - // Cilindro principal (haste) - const midPoint = useMemo( - () => new THREE.Vector3((start.x + end.x) / 2, (start.y + end.y) / 2, (start.z + end.z) / 2), - [start, end], - ); - const shaftLength = length - headLen; - - // Rotação do cilindro (apontar de start para end) - const rotation = useMemo(() => { - const dir = new THREE.Vector3().subVectors(end, start).normalize(); - const quat = new THREE.Quaternion(); - quat.setFromUnitVectors(new THREE.Vector3(0, 1, 0), dir); - const euler = new THREE.Euler().setFromQuaternion(quat); - return [euler.x, euler.y, euler.z] as [number, number, number]; - }, [start, end]); - - return ( - - {shaftLength > 0 && ( - - - - - )} - {/* Ponta da seta (cone) */} - - - - - - ); -} export default function Sign3DViewer({ length, diff --git a/app/src/components/three/Tower3D.tsx b/app/src/components/three/Tower3D.tsx index 1613e9f..d69c011 100644 --- a/app/src/components/three/Tower3D.tsx +++ b/app/src/components/three/Tower3D.tsx @@ -2,6 +2,7 @@ import { useMemo } from 'react'; import { OrbitControls, Grid, Environment, Text } from '@react-three/drei'; import * as THREE from 'three'; import SceneCanvas from '../SceneCanvas'; +import { WindArrow } from './WindArrow'; import FallbackDiagram from '../FallbackDiagram'; export interface Tower3DInput { @@ -190,31 +191,11 @@ function TowerModel({ ); })} - {/* Vetores de força distribuídos pelos tramos (meio de cada tramo) */} - {Array.from({ length: panels }).map((_, i) => { - const pHeight = height / panels; - const startY = (i + 0.5) * pHeight; // Altura no meio do tramo - const pForce = forceKN / panels; // Força por tramo - - // Escala da seta menor para ficar visualmente agradável - const pArrowLen = Math.min(Math.max(pForce * 0.1, 0.5), 2.5); - - // Calcular o ponto inicial para que a ponta da seta encoste na face (baseWidth / 2) - const endX = -forceDir.x * (baseWidth / 2); - const endZ = -forceDir.z * (baseWidth / 2); - const startX = endX - forceDir.x * pArrowLen; - const startZ = endZ - forceDir.z * pArrowLen; - - return ( - - ); - })} + {/* Seta de Vento única atingindo o meio da torre */} + {/* Labels indicativos */} @@ -234,50 +215,6 @@ function TowerModel({ ); } -function ForceArrow({ - start, - direction, - length, - color, -}: { - start: [number, number, number]; - direction: THREE.Vector3; - length: number; - color: string; -}) { - const startVec = useMemo(() => new THREE.Vector3(...start), [start]); - const end = useMemo( - () => new THREE.Vector3(startVec.x + direction.x * length, startVec.y, startVec.z + direction.z * length), - [startVec, direction, length], - ); - const mid = useMemo( - () => new THREE.Vector3((startVec.x + end.x) / 2, (startVec.y + end.y) / 2, (startVec.z + end.z) / 2), - [startVec, end], - ); - const quat = useMemo(() => { - const dir = new THREE.Vector3().subVectors(end, startVec).normalize(); - const q = new THREE.Quaternion(); - q.setFromUnitVectors(new THREE.Vector3(0, 1, 0), dir); - return new THREE.Euler().setFromQuaternion(q); - }, [startVec, end]); - const headLen = 0.3; - - return ( - - {length - headLen > 0 && ( - - - - - )} - - - - - - ); -} - export default function Tower3DViewer(input: Tower3DInput) { const { baseWidth, height } = input; const dist = Math.max(baseWidth * 3, height * 1.2); diff --git a/app/src/components/three/WindArrow.tsx b/app/src/components/three/WindArrow.tsx new file mode 100644 index 0000000..f14841b --- /dev/null +++ b/app/src/components/three/WindArrow.tsx @@ -0,0 +1,59 @@ +import { useMemo } from 'react'; +import * as THREE from 'three'; + +export interface WindArrowProps { + /** Ponto onde a ponta da seta do vento toca (ex: centro geométrico da estrutura) */ + target: THREE.Vector3 | [number, number, number]; + /** Direção de onde o vento vem (e para onde ele vai). Vetor normalizado. */ + direction: THREE.Vector3 | [number, number, number]; + /** Fator de escala da seta (default: 1) */ + scale?: number; +} + +export function WindArrow({ target, direction, scale = 1 }: WindArrowProps) { + const length = 2.5 * scale; // Tamanho visual fixo + const color = '#3b82f6'; // Azul padrão do vento + + const targetVec = target instanceof THREE.Vector3 ? target : new THREE.Vector3(...target); + const dirVec = direction instanceof THREE.Vector3 ? direction : new THREE.Vector3(...direction); + + const end = targetVec; + const base = useMemo( + () => new THREE.Vector3(end.x - dirVec.x * length, end.y - dirVec.y * length, end.z - dirVec.z * length), + [end, dirVec, length], + ); + + const headLen = 0.3 * scale; + const headRadius = 0.1 * scale; + const shaftRadius = 0.04 * scale; + + const midPoint = useMemo( + () => new THREE.Vector3((base.x + end.x) / 2, (base.y + end.y) / 2, (base.z + end.z) / 2), + [base, end], + ); + const shaftLength = length - headLen; + + const rotation = useMemo(() => { + const dir = new THREE.Vector3().subVectors(end, base).normalize(); + const quat = new THREE.Quaternion(); + quat.setFromUnitVectors(new THREE.Vector3(0, 1, 0), dir); + const euler = new THREE.Euler().setFromQuaternion(quat); + return [euler.x, euler.y, euler.z] as [number, number, number]; + }, [base, end]); + + return ( + + {shaftLength > 0 && ( + + + + + )} + {/* Ponta da seta (cone) */} + + + + + + ); +} diff --git a/app/src/components/ui/sheet.tsx b/app/src/components/ui/sheet.tsx new file mode 100644 index 0000000..b45665a --- /dev/null +++ b/app/src/components/ui/sheet.tsx @@ -0,0 +1,141 @@ +import * as React from "react" +import { XIcon } from "lucide-react" +import { Dialog as SheetPrimitive } from "radix-ui" + +import { cn } from "@/lib/utils" + +function Sheet({ ...props }: React.ComponentProps) { + return +} + +function SheetTrigger({ + ...props +}: React.ComponentProps) { + return +} + +function SheetClose({ + ...props +}: React.ComponentProps) { + return +} + +function SheetPortal({ + ...props +}: React.ComponentProps) { + return +} + +function SheetOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function SheetContent({ + className, + children, + side = "right", + showCloseButton = true, + ...props +}: React.ComponentProps & { + side?: "top" | "right" | "bottom" | "left" + showCloseButton?: boolean +}) { + return ( + + + + {children} + {showCloseButton && ( + + + Close + + )} + + + ) +} + +function SheetHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function SheetFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function SheetTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function SheetDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { + Sheet, + SheetTrigger, + SheetClose, + SheetContent, + SheetHeader, + SheetFooter, + SheetTitle, + SheetDescription, +} diff --git a/app/src/data/manuals/bar.tsx b/app/src/data/manuals/bar.tsx new file mode 100644 index 0000000..28f8e93 --- /dev/null +++ b/app/src/data/manuals/bar.tsx @@ -0,0 +1,38 @@ + + +export const barManual = { + title: 'Barras Estruturais Individuais (Seção 8.2)', + intro: 'Coeficientes de arrasto para perfis estruturais isolados (perfis I, U, L, tubulares), abordando forças por unidade de comprimento.', + sections: [ + { + title: '1. Equação do Esforço', + content: ( +
+

+ A força de arrasto exercida sobre barras individuais é dada por metro linear de elemento: +

+
+ F = q · Ca · d +
+

+ Onde d é a dimensão característica da seção transversal do perfil (altura exposta frontalmente) e Ca é o coeficiente de arrasto da seção estudada. +

+
+ ), + }, + { + title: '2. Formas Geométricas dos Perfis (Tabelas 26 a 28)', + content: ( +
+

+ O coeficiente Ca varia drasticamente com o perfil devido a cantos vivos ou aerodinâmicos: +

+
    +
  • Perfis de Faces Planas (L, U, I): Possuem cantos vivos que forçam o descolamento imediato do vento, gerando altos valores de arrasto (Ca em torno de 1.4 a 2.0).
  • +
  • Perfis Circulares/Tubulares: O vento consegue contornar a forma circular, reduzindo o arrasto consideravelmente (Ca em torno de 0.6 a 1.2, dependendo de Reynolds).
  • +
+
+ ), + }, + ], +}; diff --git a/app/src/data/manuals/bridge.tsx b/app/src/data/manuals/bridge.tsx new file mode 100644 index 0000000..b3b8c9d --- /dev/null +++ b/app/src/data/manuals/bridge.tsx @@ -0,0 +1,93 @@ + + +export const bridgeManual = { + title: 'Tabuleiro de Ponte (Seção 11.2 / 11.3)', + intro: 'Explicação detalhada sobre a ação estática do vento em pontes, cobrindo forças de arrasto (horizontais) e forças de sustentação (verticais).', + sections: [ + { + title: '1. Forças Atuantes', + content: ( +
+

+ A ação do vento em tabuleiros de pontes é dividida em duas componentes principais: +

+
+
+ Força de Arrasto (Fx): Fx = q · Cx · Heg · Lp +
+
+ Força de Sustentação (Fz): Fz = q · Cz · B · Lp +
+
+

+ Onde q é a pressão dinâmica do vento, Lp é o comprimento do vão exposto, Heg é a altura equivalente do tabuleiro (soma das áreas de projeção das vigas, guarda-corpos e barreiras) e B é a largura da ponte. +

+
+ ), + }, + { + title: '2. Coeficientes Aerodinâmicos (Cx e Cz)', + content: ( +
+

+ Os coeficientes são determinados com base na relação geométrica entre a largura e a altura do tabuleiro (B / Heg), bem como o ângulo de ataque do vento (α): +

+
    +
  • + Cx (Arrasto): Representa a resistência que a ponte oferece ao fluxo horizontal de vento. Valores maiores de Cx significam maior força empurrando a ponte lateralmente. +
  • +
  • + Cz (Sustentação/Lift): Representa a força vertical (para cima ou para baixo) induzida pela inclinação do vento (ângulo de ataque α). +
  • +
+

+ Se o vento incide com um ângulo positivo (vindo ligeiramente de baixo, α > 0), ele tende a "levantar" a ponte (Cz positivo). Se incide de cima (α < 0), empurra a ponte para baixo (Cz negativo). +

+
+ ), + }, + { + title: '3. Glossário de Parâmetros', + content: ( +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SímboloParâmetroDescrição
LpMaior Vão (m)O comprimento do vão livre da ponte sob análise.
BLargura (m)Largura total do tabuleiro da ponte (perpendicular ao fluxo principal).
HegAltura Equivalente (m)Soma da projeção vertical de todos os elementos expostos ao vento.
α (alpha)Ângulo de Ataque (º)Inclinação do vetor do vento em relação ao plano horizontal (varia de -5º a +5º).
qPressão Dinâmica (kN/m²)Pressão do vento calculada a partir da velocidade de projeto.
+
+ ), + }, + ], +}; diff --git a/app/src/data/manuals/cylinder.tsx b/app/src/data/manuals/cylinder.tsx new file mode 100644 index 0000000..c37fa39 --- /dev/null +++ b/app/src/data/manuals/cylinder.tsx @@ -0,0 +1,66 @@ + + +export const cylinderManual = { + title: 'Cilindros e Chaminés (Tabela 13 - Seção 6.1)', + intro: 'Ação do vento em cilindros circulares (chaminés, silos e reservatórios), detalhando a influência do número de Reynolds e da rugosidade superficial no arrasto.', + sections: [ + { + title: '1. Equação do Arrasto Global', + content: ( +
+

+ A força de arrasto exercida por unidade de comprimento de um cilindro circular é calculada por: +

+
+ F = q · Ca · D +
+

+ Onde D é o diâmetro externo do cilindro e Ca é o coeficiente de arrasto corrigido pela esbeltez. +

+
+ ), + }, + { + title: '2. Regimes de Fluxo e Número de Reynolds (Re)', + content: ( +
+

+ O comportamento aerodinâmico em cilindros circulares é governado pelo Número de Reynolds (Re), um parâmetro adimensional que indica a relação entre as forças de inércia e de viscosidade do fluxo: +

+
+ Re = 70.000 · Vk · D +
+

+ O fluxo é classificado em três regimes principais na NBR 6123: +

+
    +
  • + Subcrítico (Re < 1.5 · 10⁵): A camada limite é laminar. O descolamento do fluxo ocorre cedo (cerca de 80º em relação ao ponto de estagnação). A esteira de turbulência atrás do cilindro é larga, resultando em um arrasto elevado (Ca ≈ 1.2). +
  • +
  • + Crítico / Supercrítico (Re > 4 · 10⁵): A camada limite torna-se turbulenta. Ela consegue "colar" mais tempo na parede do cilindro, atrasando o descolamento para cerca de 120º. A esteira traseira estreita significativamente, derrubando o arrasto de forma abrupta (Ca pode cair para 0.4 a 0.7). +
  • +
+
+ ), + }, + { + title: '3. Influência da Rugosidade Superficial', + content: ( +
+

+ Para altos números de Reynolds, a rugosidade superficial do cilindro (rugosidade relativa d/k) impede que o arrasto caia excessivamente. +

+
    +
  • + Superfícies lisas (d/k > 10⁶): Exibem o menor arrasto possível no regime supercrítico (Ca ≈ 0.4 - 0.5). +
  • +
  • + Superfícies rugosas (d/k reduzido, ex: concreto bruto ou chapa ondulada): Aceleram a transição para fluxo turbulento, mas a rugosidade gera turbulência local que impede a esteira de estreitar tanto, mantendo o arrasto em patamares mais elevados (Ca ≈ 0.7 - 0.9). +
  • +
+
+ ), + }, + ], +}; diff --git a/app/src/data/manuals/dome.tsx b/app/src/data/manuals/dome.tsx new file mode 100644 index 0000000..761d677 --- /dev/null +++ b/app/src/data/manuals/dome.tsx @@ -0,0 +1,33 @@ + + +export const domeManual = { + title: 'Cúpulas Esféricas (Tabela 21 e 22)', + intro: 'Ação do vento em cúpulas e coberturas hemisféricas, analisando coeficientes de forças globais e pressões locais.', + sections: [ + { + title: '1. Aerodinâmica Tridimensional', + content: ( +
+

+ Cúpulas esféricas possuem comportamento aerodinâmico tridimensional muito favorável. O ar contorna a cúpula não apenas por cima, mas também pelos lados, reduzindo a turbulência geral comparado com coberturas planas ou inclinadas. +

+
+ ), + }, + { + title: '2. Coeficientes de Pressão Externa', + content: ( +
+

+ O vento acelerado sobre a cúpula cria três zonas de pressões marcantes: +

+
    +
  • Barlavento (frente): Sobrepressão sutil perto da base, que rapidamente vira sucção à medida que sobe no arco esférico.
  • +
  • Topo: Ponto de máxima velocidade do vento. Sofre uma sucção crítica gerada pelo escoamento aerodinâmico acelerado.
  • +
  • Sotavento (traseira): Zona de vácuo aerodinâmico com sucção homogênea constante e desprendimento de esteira tridimensional.
  • +
+
+ ), + }, + ], +}; diff --git a/app/src/data/manuals/dynamics.tsx b/app/src/data/manuals/dynamics.tsx new file mode 100644 index 0000000..aef88e8 --- /dev/null +++ b/app/src/data/manuals/dynamics.tsx @@ -0,0 +1,72 @@ + + +export const dynamicsManual = { + title: 'Efeitos Dinâmicos e Ressonância (Seção 9 e Anexo G)', + intro: 'Explicação detalhada dos fenômenos dinâmicos induzidos pelo vento, com foco especial no desprendimento de vórtices (Vortex Shedding).', + sections: [ + { + title: '1. Desprendimento de Vórtices (Vortex Shedding)', + content: ( +
+

+ Quando o vento sopra perpendicularmente a um obstáculo cilíndrico (como uma chaminé ou torre), o fluxo de ar se divide e cria redemoinhos alternados na traseira da estrutura. +

+

+ Estes redemoinhos, conhecidos como Vórtices de Von Kármán, geram forças de sucção alternadas nas laterais perpendiculares à direção do vento. Como consequência, o cilindro sofre forças dinâmicas oscilantes transversais. +

+
+ ), + }, + { + title: '2. Velocidade Crítica de Ressonância (Vcr)', + content: ( +
+

+ A frequência na qual os vórtices se desprendem é dada por: +

+
+ f = St · V / D +
+

+ Onde St é o número de Strouhal (adimensional, geralmente 0.2 para cilindros), V é a velocidade do vento e D é o diâmetro. +

+

+ Se a frequência de descolamento de vórtices se igualar a uma das frequências naturais da estrutura (fn), ocorre o fenômeno de **ressonância**, gerando grandes amplitudes de oscilação. A velocidade do vento que ativa a ressonância é chamada de **Velocidade Crítica (Vcr)**: +

+
+ Vcr = fn · D / St +
+
+ ), + }, + { + title: '3. Parâmetros Chave', + content: ( +
+ + + + + + + + + + + + + + + + + + + + + +
ParâmetroDescrição
St (Strouhal)Número adimensional que depende da geometria da seção. Para cilindro liso, St = 0.2.
fn (Frequência)Frequência natural de vibração da estrutura (em Hz). Estruturas mais esbeltas têm fn menor.
Vcr (Vel. Crítica)Velocidade em que os esforços alternados dos vórtices entram em ressonância com a estrutura.
+
+ ), + }, + ], +}; diff --git a/app/src/data/manuals/index.ts b/app/src/data/manuals/index.ts new file mode 100644 index 0000000..6930a92 --- /dev/null +++ b/app/src/data/manuals/index.ts @@ -0,0 +1,23 @@ +import { bridgeManual } from './bridge'; +import { towerManual } from './tower'; +import { signManual } from './sign'; +import { warehouseManual } from './warehouse'; +import { cylinderManual } from './cylinder'; +import { vaultManual } from './vault'; +import { domeManual } from './dome'; +import { barManual } from './bar'; +import { dynamicsManual } from './dynamics'; +import { isolatedRoofManual } from './isolated-roof'; + +export const manuals: Record = { + bridge: bridgeManual, + tower: towerManual, + sign: signManual, + warehouse: warehouseManual, + cylinder: cylinderManual, + vault: vaultManual, + dome: domeManual, + bar: barManual, + dynamics: dynamicsManual, + 'isolated-roof': isolatedRoofManual, +}; diff --git a/app/src/data/manuals/isolated-roof.tsx b/app/src/data/manuals/isolated-roof.tsx new file mode 100644 index 0000000..105ed2d --- /dev/null +++ b/app/src/data/manuals/isolated-roof.tsx @@ -0,0 +1,41 @@ + + +export const isolatedRoofManual = { + title: 'Coberturas Isoladas (Tabelas 24 e 25)', + intro: 'Ação do vento em coberturas sem paredes (estruturas abertas, hangares abertos, postos de gasolina), abordando coeficientes de pressão líquida e forças resultantes.', + sections: [ + { + title: '1. Coeficiente de Pressão Líquida (Cp)', + content: ( +
+

+ Como as coberturas isoladas não possuem paredes para vedação, o vento escoa livremente tanto por cima quanto por baixo do telhado. +

+

+ Por isso, a norma utiliza o coeficiente de **pressão líquida (Cp)**, que já representa a diferença líquida de pressão entre a face superior e inferior da chapa do telhado: +

+
+ F = q · Cp · A +
+

+ Dependendo da direção e do ângulo de inclinação do telhado, a chapa pode sofrer sucção (força para cima) ou sobrepressão (força para baixo). +

+
+ ), + }, + { + title: '2. Efeito da Inclinação (θ)', + content: ( +
+

+ O ângulo de inclinação do telhado (θ) define se o escoamento é suave ou gera turbulência pesada: +

+
    +
  • Ângulos pequenos (θ < 5º): O escoamento gera sucção quase uniforme em toda a chapa devido ao efeito Venturi do vento passando sob a cobertura.
  • +
  • Ângulos elevados (θ > 15º): A cobertura atua como uma asa de avião com alto ângulo de ataque. O lado de barlavento sofre forte pressão para baixo, enquanto o lado de sotavento sofre sucção severa.
  • +
+
+ ), + }, + ], +}; diff --git a/app/src/data/manuals/sign.tsx b/app/src/data/manuals/sign.tsx new file mode 100644 index 0000000..bc1738b --- /dev/null +++ b/app/src/data/manuals/sign.tsx @@ -0,0 +1,61 @@ + + +export const signManual = { + title: 'Muros e Placas Isoladas (Tabela 11 e Seção 6.1)', + intro: 'Ação do vento em muros, painéis e placas isoladas livres no solo, detalhando arrasto, excentricidades e efeitos de placas de extremidade.', + sections: [ + { + title: '1. Coeficiente de Força (Cf)', + content: ( +
+

+ Para muros e placas isoladas, a NBR 6123 define o coeficiente de força (Cf) com base na relação de aspecto e folga do solo: +

+
+ F = q · Cf · A +
+

+ Onde A é a área total exposta do muro (Comprimento · Altura). O coeficiente Cf varia conforme o alongamento do muro. Muros muito longos e rente ao solo têm Cf maior porque o fluxo de ar não consegue escapar pelas pontas, gerando maior retenção de energia do vento. +

+
+ ), + }, + { + title: '2. Excentricidade da Força (Torsão)', + content: ( +
+

+ O vento raramente atinge o muro de forma perfeitamente homogênea. Quando o vento incide com um ângulo oblíquo (ex: α = 50º), ele se concentra mais próximo a uma das bordas. +

+

+ Por este motivo, a norma NBR 6123 exige a aplicação da força resultante com uma excentricidade horizontal (e) em relação ao centro geométrico da placa: +

+
+ e = 0.25 · L (comprimento do muro) +
+

+ Esta excentricidade gera um momento torsor nas fundações e suportes estruturais do muro, sendo um fator crítico no dimensionamento de placas e outdoors. +

+
+ ), + }, + { + title: '3. Placas de Extremidade (End Plates)', + content: ( +
+

+ Placas de extremidade são abas físicas verticais instaladas nas pontas do muro para fins aerodinâmicos ou construtivos: +

+
    +
  • + Sem abas: O vento bate na frente e escoa com facilidade pelas pontas (efeito de borda tridimensional), o que reduz ligeiramente o Cf local próximo às pontas. +
  • +
  • + Com abas: As abas forçam o vento a subir apenas pelo topo, gerando um padrão bidimensional de escoamento. Isso aumenta significativamente a pressão diferencial entre a face da frente e de trás do muro, elevando o valor de Cf para patamares elevados (de 1.3 até 2.0). +
  • +
+
+ ), + }, + ], +}; diff --git a/app/src/data/manuals/tower.tsx b/app/src/data/manuals/tower.tsx new file mode 100644 index 0000000..330d50a --- /dev/null +++ b/app/src/data/manuals/tower.tsx @@ -0,0 +1,91 @@ + + +export const towerManual = { + title: 'Torre Reticulada (Seção 8.5)', + intro: 'Ação do vento em torres de seção quadrada ou triangular equilátera, cobrindo o índice de solidez, coeficientes de arrasto e fatores de incidência.', + sections: [ + { + title: '1. Força de Arrasto Total', + content: ( +
+

+ A força de arrasto exercida sobre cada tramo de uma torre reticulada é dada por: +

+
+ F = q · Ca · Ae +
+

+ Onde q é a pressão de vento de projeto, Ca é o coeficiente de arrasto e Ae é a área projetada efetiva (soma da projeção das barras de barlavento e sotavento, ajustada pelo coeficiente de proteção de tramos). +

+
+ ), + }, + { + title: '2. Índice de Solidez (φ)', + content: ( +
+

+ O índice de solidez (φ) é a relação entre a área real exposta pelas barras e a área total envolvente delimitada pelo contorno da face da torre: +

+
+ φ = Área das Barras / (Largura da Base · Altura do Tramo) +
+

+ Uma solidez baixa (φ próximo a 0.05) indica uma torre muito vazada (vento passa direto). Uma solidez alta (φ próximo a 1) indica uma torre quase sólida. O coeficiente de arrasto Ca diminui à medida que a solidez aumenta porque as barras de sotavento ficam mais protegidas do vento. +

+
+ ), + }, + { + title: '3. Ação Direcional (Kα) - Quadrada vs Triangular', + content: ( +
+

+ O vento pode atingir a torre de diferentes direções. A norma aborda isso aplicando um fator direcional : +

+
    +
  • + Torres Quadradas: O vento incidindo na diagonal (45º) causa maior arrasto global do que nas faces (0º). Portanto, Kα é maior para vento a 45º. +
  • +
  • + Torres Triangulares: Devido à simetria geométrica de um triângulo equilátero, a NBR 6123 assume que o arrasto total é constante independentemente da direção. Assim, Kα é sempre 1.0 para qualquer direção do vento. +
  • +
+
+ ), + }, + { + title: '4. Glossário de Parâmetros', + content: ( +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
SímboloParâmetroDescrição
φ (phi)Índice de SolidezRazão entre a área das barras e a área total envolvente da face.
Fator de DireçãoMultiplicador que ajusta a força com base no ângulo de incidência do vento.
CaCoeficiente de ArrastoCoeficiente adimensional que mede a resistência aerodinâmica da estrutura.
+
+ ), + }, + ], +}; diff --git a/app/src/data/manuals/vault.tsx b/app/src/data/manuals/vault.tsx new file mode 100644 index 0000000..c351db6 --- /dev/null +++ b/app/src/data/manuals/vault.tsx @@ -0,0 +1,44 @@ + + +export const vaultManual = { + title: 'Abóbadas Cilíndricas (Tabela 15 - Seção 6.2)', + intro: 'Coeficientes de pressão externa para coberturas cilíndricas convexas (abóbadas) assentadas diretamente no solo ou elevadas.', + sections: [ + { + title: '1. Geometria da Abóbada', + content: ( +
+

+ O fluxo de vento sobre abóbadas é fortemente influenciado pelas relações geométricas: +

+
    +
  • f (flecha): Altura do arco da abóbada.
  • +
  • d (vão): Largura da projeção horizontal da abóbada.
  • +
  • h (altura da parede de suporte): Se a abóbada estiver assentada diretamente no solo, h = 0. Se estiver elevada, h representa a altura da parede.
  • +
+
+ ), + }, + { + title: '2. Distribuição de Pressões (Cpe)', + content: ( +
+

+ O perfil curvo da abóbada atua como um aerofólio (perfil de asa), gerando aceleração acentuada do ar no topo: +

+
    +
  • + Barlavento (início do arco): Pode sofrer pressão positiva se a parede frontal for alta, ou sucção leve caso o vento deslize suavemente pela curvatura suave. +
  • +
  • + Topo (Zona Central): É a região de maior aceleração do fluxo de vento. Sofre uma sucção fortíssima (Cpe muito negativo), tendendo a puxar a cobertura para cima. +
  • +
  • + Sotavento (fim do arco): O fluxo descola da abóbada, gerando uma zona de recirculação com sucção constante, embora de menor magnitude que no topo. +
  • +
+
+ ), + }, + ], +}; diff --git a/app/src/data/manuals/warehouse.tsx b/app/src/data/manuals/warehouse.tsx new file mode 100644 index 0000000..e23a1fb --- /dev/null +++ b/app/src/data/manuals/warehouse.tsx @@ -0,0 +1,65 @@ + + +export const warehouseManual = { + title: 'Galpão Retangular (Tabelas 4 e 5 - Seção 6)', + intro: 'Ação do vento em galpões de base retangular e telhados a duas águas. Abrange os coeficientes de pressão externa (Cpe) e interna (Cpi).', + sections: [ + { + title: '1. Pressão Resultante (p)', + content: ( +
+

+ A pressão efetiva do vento em qualquer ponto da superfície externa do galpão é a diferença entre a pressão externa e a interna: +

+
+ p = q · (Cpe - Cpi) +
+

+ Onde: +

+
    +
  • q: Pressão dinâmica do vento na altura do topo do telhado.
  • +
  • Cpe: Coeficiente de pressão externa (depende da zona do galpão).
  • +
  • Cpi: Coeficiente de pressão interna (depende de aberturas nas paredes).
  • +
+
+ ), + }, + { + title: '2. Zonas de Pressão Externa (Cpe)', + content: ( +
+

+ Devido à complexidade aerodinâmica, a NBR 6123 divide o galpão em várias zonas de pressão localizadas: +

+
    +
  • + Paredes (Zonas A, B, C, D, E): Geralmente, a parede de barlavento sofre sobrepressão (Cpe > 0, Zona D), enquanto as paredes laterais (Zonas A, B, C) e sotavento (Zona E) sofrem sucção (Cpe < 0) devido ao fluxo descolado. +
  • +
  • + Telhado (Zonas F, G, H, I, J): O fluxo de ar descola bruscamente no beiral de barlavento, criando sucções locais fortíssimas nas bordas (Zonas F e G). No meio do telhado (Zona H) e na água de sotavento (Zonas I, J), a sucção tende a diminuir levemente. +
  • +
+
+ ), + }, + { + title: '3. Pressão Interna (Cpi)', + content: ( +
+

+ A pressão interna surge da passagem do ar através de frestas, janelas, portões e aberturas: +

+
    +
  • + Abertura dominante em barlavento (frente): O vento entra e "infla" o galpão por dentro. Isso gera uma sobrepressão interna positiva (Cpi até +0.9). Esta pressão positiva empurra as paredes e o telhado de dentro para fora, somando-se à sucção externa e aumentando o risco de colapso do telhado. +
  • +
  • + Abertura dominante em sotavento ou laterais: O ar é "sugado" de dentro do galpão, criando vácuo/sucção interna (Cpi até -0.9). Isso ajuda a estabilizar o telhado contra a sucção externa, mas sobrecarrega a parede de barlavento. +
  • +
+
+ ), + }, + ], +}; diff --git a/app/src/pages/BarSelectorModule.tsx b/app/src/pages/BarSelectorModule.tsx index 7ed5be4..cc0ebb5 100644 --- a/app/src/pages/BarSelectorModule.tsx +++ b/app/src/pages/BarSelectorModule.tsx @@ -10,6 +10,7 @@ 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 { EducationalManual } from '@/components/EducationalManual'; import { exportGenericToPDF, type GenericPDFSection } from '../lib/export-generic-pdf'; const BarSelectorModule: React.FC = () => { @@ -157,6 +158,9 @@ const BarSelectorModule: React.FC = () => {
α = {alpha}°
+
+ +
{ @@ -149,6 +150,9 @@ const BridgeModule: React.FC = () => { Classe {classification.bridgeClass}
+
+ +
{ cz={forces.cz} fxPerLength={forces.fxPerLength} fzPerLength={forces.fzPerLength} + alpha={alpha} />
diff --git a/app/src/pages/CylinderModule.tsx b/app/src/pages/CylinderModule.tsx index b2964b5..21823a3 100644 --- a/app/src/pages/CylinderModule.tsx +++ b/app/src/pages/CylinderModule.tsx @@ -9,6 +9,7 @@ import { calculateCylinder, type CylinderEndType } from '@/lib/modules/cylinder' import Cylinder3DViewer from '@/components/three/Cylinder3D'; import SceneCapturePanel from '../components/SceneCapturePanel'; import ExportMenu from '../components/ExportMenu'; +import { EducationalManual } from '@/components/EducationalManual'; import { exportGenericToPDF, type GenericPDFSection } from '../lib/export-generic-pdf'; const CylinderModule: React.FC = () => { @@ -140,6 +141,9 @@ const CylinderModule: React.FC = () => { Re = {result.re.toExponential(2)} +
+ +
{ @@ -118,6 +119,9 @@ const DomeModule: React.FC = () => { Cpi = {cpi.toFixed(2)}
+
+ +
{ @@ -256,8 +257,9 @@ const DynamicsModule: React.FC = () => {
)} - + Visualização 3D + { const { @@ -103,8 +104,9 @@ const GalpaoModule: React.FC = () => { -
+
+
diff --git a/app/src/pages/IsolatedRoofModule.tsx b/app/src/pages/IsolatedRoofModule.tsx index 8f6b404..8ac3137 100644 --- a/app/src/pages/IsolatedRoofModule.tsx +++ b/app/src/pages/IsolatedRoofModule.tsx @@ -9,6 +9,7 @@ import { calculateIsolatedShedRoof, calculateIsolatedGableRoof, frictionForceIso import IsolatedRoof3DViewer from '@/components/three/IsolatedRoof3D'; import SceneCapturePanel from '../components/SceneCapturePanel'; import ExportMenu from '../components/ExportMenu'; +import { EducationalManual } from '@/components/EducationalManual'; import { exportGenericToPDF, type GenericPDFSection } from '../lib/export-generic-pdf'; const IsolatedRoofModule: React.FC = () => { @@ -164,10 +165,14 @@ const IsolatedRoofModule: React.FC = () => { θ = {theta}° {type === 'shed' ? 'Uma água' : 'Duas águas'}
+
+ +
{ @@ -118,6 +119,9 @@ const SignModule: React.FC = () => { F = {result.forceKN.toFixed(2)} kN Cf = {result.cf.toFixed(2)}
+
+ +
{ @@ -141,6 +142,9 @@ const TowerModule: React.FC = () => { {section === 'square' ? 'Quadrada' : 'Triangular'} α = {alphaWind}° +
+ +
{ @@ -126,6 +127,9 @@ const VaultModule: React.FC = () => { Cpi = {localCpi > 0 ? `+${localCpi.toFixed(2)}` : localCpi.toFixed(2)} +
+ +