🚀 Auto-deploy: BrainWind atualizado em 21/07/2026 14:27:40
This commit is contained in:
@@ -56,6 +56,7 @@ export default function AuditPanel() {
|
|||||||
const allScenarios = useRef(generateAllScenarios()).current;
|
const allScenarios = useRef(generateAllScenarios()).current;
|
||||||
|
|
||||||
const [checkedScenarioIds, setCheckedScenarioIds] = useState<string[]>([]);
|
const [checkedScenarioIds, setCheckedScenarioIds] = useState<string[]>([]);
|
||||||
|
const [runOnlyBibliographic, setRunOnlyBibliographic] = useState(false);
|
||||||
|
|
||||||
const handleProviderChange = (val: string) => {
|
const handleProviderChange = (val: string) => {
|
||||||
setConfig((c) => ({
|
setConfig((c) => ({
|
||||||
@@ -91,6 +92,9 @@ export default function AuditPanel() {
|
|||||||
|
|
||||||
setStatus('generating');
|
setStatus('generating');
|
||||||
let scenarios = generateAllScenarios();
|
let scenarios = generateAllScenarios();
|
||||||
|
if (runOnlyBibliographic) {
|
||||||
|
scenarios = scenarios.filter(s => s.isBibliographic);
|
||||||
|
}
|
||||||
if (config.testScope && config.testScope !== 'all') {
|
if (config.testScope && config.testScope !== 'all') {
|
||||||
scenarios = scenarios.filter(s => s.module === config.testScope && checkedScenarioIds.includes(s.id));
|
scenarios = scenarios.filter(s => s.module === config.testScope && checkedScenarioIds.includes(s.id));
|
||||||
}
|
}
|
||||||
@@ -207,6 +211,19 @@ export default function AuditPanel() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center space-x-2 mt-4 mb-2">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="runOnlyBib"
|
||||||
|
checked={runOnlyBibliographic}
|
||||||
|
onChange={(e) => setRunOnlyBibliographic(e.target.checked)}
|
||||||
|
className="rounded border-gray-300 text-primary focus:ring-primary h-4 w-4"
|
||||||
|
/>
|
||||||
|
<label htmlFor="runOnlyBib" className="text-sm font-medium leading-none cursor-pointer">
|
||||||
|
Executar apenas cenários bibliográficos (TesteBib)
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
{config.testScope && config.testScope !== 'all' && (
|
{config.testScope && config.testScope !== 'all' && (
|
||||||
<div className="border rounded-md p-3 mt-2 bg-muted/20">
|
<div className="border rounded-md p-3 mt-2 bg-muted/20">
|
||||||
<div className="flex items-center justify-between mb-2">
|
<div className="flex items-center justify-between mb-2">
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
|
||||||
|
import { Badge } from '@/components/ui/badge';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { BookOpen, CheckCircle2, Play } from 'lucide-react';
|
||||||
|
import { generateAllScenarios } from '@/lib/audit/scenarios';
|
||||||
|
import type { AuditScenario } from '@/lib/audit/types';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
|
export default function BibliographicTestPanel() {
|
||||||
|
const [scenarios] = useState<AuditScenario[]>(() =>
|
||||||
|
generateAllScenarios().filter(s => s.isBibliographic)
|
||||||
|
);
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const handleCopyInputs = (scenario: AuditScenario) => {
|
||||||
|
// Navega pro módulo
|
||||||
|
navigate(`/${scenario.module === 'isolated-roof' ? 'cobertura-isolada' : scenario.module === 'sign' ? 'muros' : scenario.module === 'bar' ? 'barras' : scenario.module === 'bridge' ? 'pontes' : scenario.module === 'dynamics' ? 'dinamica' : scenario.module === 'vault' ? 'abobada' : scenario.module === 'dome' ? 'cupula' : scenario.module}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-lg flex items-center gap-2">
|
||||||
|
<BookOpen className="w-5 h-5 text-primary" />
|
||||||
|
Validação Bibliográfica (TesteBib)
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Casos clássicos da literatura brasileira de engenharia estrutural (Blessmann, Pfeil, Rebello)
|
||||||
|
com resolução passo a passo. Os valores calculados localmente pelo aplicativo são comparados
|
||||||
|
ao gabarito da literatura.
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-6">
|
||||||
|
{scenarios.map(s => (
|
||||||
|
<Card key={s.id} className="overflow-hidden border-2 border-primary/10">
|
||||||
|
<div className="bg-primary/5 px-4 py-2 border-b border-primary/10 flex justify-between items-center">
|
||||||
|
<div className="font-semibold text-primary">{s.id}</div>
|
||||||
|
<Badge variant="outline" className="bg-background">{s.moduleLabel}</Badge>
|
||||||
|
</div>
|
||||||
|
<CardContent className="p-4 space-y-4">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<h4 className="font-semibold text-sm">Enunciado</h4>
|
||||||
|
<p className="text-sm text-muted-foreground leading-relaxed">{s.enunciadoParafraseado}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-1">
|
||||||
|
<h4 className="font-semibold text-sm">Referência Bibliográfica</h4>
|
||||||
|
<p className="text-sm text-muted-foreground italic border-l-2 border-primary/40 pl-2">
|
||||||
|
{s.bibliografiaReferencia}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<div className="bg-muted/30 p-3 rounded-md">
|
||||||
|
<h5 className="text-xs font-semibold uppercase text-muted-foreground mb-2">Inputs (Geometria / Vento)</h5>
|
||||||
|
<div className="text-sm space-y-1 font-mono text-xs">
|
||||||
|
{Object.entries(s.inputs).map(([k, v]) => (
|
||||||
|
<div key={k} className="flex justify-between">
|
||||||
|
<span>{k}:</span>
|
||||||
|
<span className="font-semibold">{JSON.stringify(v)}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-muted/30 p-3 rounded-md">
|
||||||
|
<h5 className="text-xs font-semibold uppercase text-muted-foreground mb-2">Valores Calculados (Kernel Local)</h5>
|
||||||
|
<div className="text-sm space-y-1 font-mono text-xs">
|
||||||
|
{Object.entries(s.outputs).slice(0, 5).map(([k, v]) => (
|
||||||
|
<div key={k} className="flex justify-between items-center">
|
||||||
|
<span>{k}:</span>
|
||||||
|
<span className="font-semibold text-emerald-600 dark:text-emerald-400 truncate ml-2">
|
||||||
|
{typeof v === 'number' ? v.toFixed(3) : JSON.stringify(v)}
|
||||||
|
<CheckCircle2 className="inline w-3 h-3 ml-1" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-end pt-2">
|
||||||
|
<Button variant="outline" size="sm" onClick={() => handleCopyInputs(s)}>
|
||||||
|
<Play className="w-3.5 h-3.5 mr-2" />
|
||||||
|
Simular no Módulo
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
{scenarios.length === 0 && (
|
||||||
|
<p className="text-center text-muted-foreground py-8">Nenhum cenário bibliográfico encontrado.</p>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo, useEffect } from 'react';
|
||||||
|
import { useThree } from '@react-three/fiber';
|
||||||
import { Grid, Environment, Text } from '@react-three/drei';
|
import { Grid, Environment, Text } from '@react-three/drei';
|
||||||
import { ViewerOrbitControls as OrbitControls } from './three/ViewerOrbitControls';
|
import { ViewerOrbitControls as OrbitControls } from './three/ViewerOrbitControls';
|
||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
@@ -73,11 +74,69 @@ function PressureArrow({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function WindDirectionIndicator({ angle, width, length, height }: { angle: number, width: number, length: number, height: number }) {
|
||||||
|
const isDark = useCanvasTheme() === 'dark';
|
||||||
|
const color = isDark ? "#38bdf8" : "#0284c7"; // Light blue / dark blue
|
||||||
|
|
||||||
|
// Angle 0: Wind blows along Z axis, from negative Z towards origin (hitting Wall A at Z=-length/2)
|
||||||
|
// Angle 90: Wind blows along X axis, from negative X towards origin (hitting Wall C at X=-width/2)
|
||||||
|
|
||||||
|
const dir = angle === 0 ? new THREE.Vector3(0, 0, 1) : new THREE.Vector3(1, 0, 0);
|
||||||
|
|
||||||
|
const distance = Math.max(width, length) * 0.6 + 5;
|
||||||
|
const startPos = angle === 0 ? new THREE.Vector3(0, height / 2, -distance) : new THREE.Vector3(-distance, height / 2, 0);
|
||||||
|
|
||||||
|
const quat = new THREE.Quaternion();
|
||||||
|
quat.setFromUnitVectors(new THREE.Vector3(0, 1, 0), dir);
|
||||||
|
const euler = new THREE.Euler().setFromQuaternion(quat);
|
||||||
|
|
||||||
|
const arrowLen = 6;
|
||||||
|
const headLen = 2;
|
||||||
|
const shaftLen = arrowLen - headLen;
|
||||||
|
|
||||||
|
const mid = startPos.clone().add(dir.clone().multiplyScalar(shaftLen / 2));
|
||||||
|
const end = startPos.clone().add(dir.clone().multiplyScalar(arrowLen));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<group>
|
||||||
|
<mesh position={mid.toArray()} rotation={[euler.x, euler.y, euler.z]}>
|
||||||
|
<cylinderGeometry args={[0.3, 0.3, shaftLen, 12]} />
|
||||||
|
<meshStandardMaterial color={color} transparent opacity={0.6} />
|
||||||
|
</mesh>
|
||||||
|
<mesh position={end.toArray()} rotation={[euler.x, euler.y, euler.z]}>
|
||||||
|
<coneGeometry args={[0.8, headLen, 12]} />
|
||||||
|
<meshStandardMaterial color={color} transparent opacity={0.6} />
|
||||||
|
</mesh>
|
||||||
|
</group>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasOpening(wall: 'A' | 'B' | 'C' | 'D', permCase: string, windAngle: number) {
|
||||||
|
if (permCase === 'airtight') return false;
|
||||||
|
if (permCase === 'four-equally-permeable') return true;
|
||||||
|
if (permCase === 'two-opposite-permeable') return wall === 'A' || wall === 'B';
|
||||||
|
if (permCase === 'dominant-windward') {
|
||||||
|
return windAngle === 0 ? wall === 'A' : wall === 'C';
|
||||||
|
}
|
||||||
|
if (permCase === 'dominant-leeward') {
|
||||||
|
return windAngle === 0 ? wall === 'B' : wall === 'D';
|
||||||
|
}
|
||||||
|
if (permCase === 'dominant-lateral') {
|
||||||
|
return windAngle === 0 ? wall === 'C' : wall === 'A';
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
export function WarehouseModel() {
|
export function WarehouseModel() {
|
||||||
|
const { invalidate } = useThree();
|
||||||
const theme = useCanvasTheme();
|
const theme = useCanvasTheme();
|
||||||
const isDark = theme === 'dark';
|
const isDark = theme === 'dark';
|
||||||
const { width, length, height, roofPitch, wallCpe, roofCpe } = useGalpaoStore();
|
const { width, length, height, roofPitch, wallCpe, roofCpe, template, extraHeight, skirtHeight } = useGalpaoStore();
|
||||||
const { windAngle, cpi } = useWindStore();
|
const { windAngle, cpi, permeabilityCase } = useWindStore();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
invalidate();
|
||||||
|
}, [template, permeabilityCase, windAngle, invalidate]);
|
||||||
|
|
||||||
const roofHeight = (width / 2) * Math.tan((roofPitch * Math.PI) / 180);
|
const roofHeight = (width / 2) * Math.tan((roofPitch * Math.PI) / 180);
|
||||||
const theta = (roofPitch * Math.PI) / 180;
|
const theta = (roofPitch * Math.PI) / 180;
|
||||||
@@ -94,6 +153,8 @@ export function WarehouseModel() {
|
|||||||
|
|
||||||
const wallThickness = 0.15;
|
const wallThickness = 0.15;
|
||||||
const isParallel = windAngle === 90;
|
const isParallel = windAngle === 90;
|
||||||
|
const showMainWalls = template !== 'skirt' && template !== 'skirt_parapet';
|
||||||
|
const extraColor = isDark ? "#d97706" : "#fbbf24"; // Amber para destaque
|
||||||
|
|
||||||
// Shapes para os oitões (gables)
|
// Shapes para os oitões (gables)
|
||||||
const leftShape = useMemo(() => {
|
const leftShape = useMemo(() => {
|
||||||
@@ -117,65 +178,149 @@ export function WarehouseModel() {
|
|||||||
return (
|
return (
|
||||||
<group>
|
<group>
|
||||||
{/* === PAREDES === */}
|
{/* === PAREDES === */}
|
||||||
{/* Lateral Esquerda (X = -width/2) */}
|
{showMainWalls && (
|
||||||
<group position={[-width / 2, height / 2, 0]}>
|
<group>
|
||||||
<mesh position={[0, 0, -length / 4]} castShadow receiveShadow>
|
{/* Lateral Esquerda (X = -width/2, Parede C) */}
|
||||||
<boxGeometry args={[wallThickness, height, length / 2]} />
|
<group position={[-width / 2, height / 2, 0]}>
|
||||||
<meshStandardMaterial color={isParallel ? wallCColor : wallAColor} roughness={0.4} />
|
<mesh position={[0, 0, -length / 4]} castShadow receiveShadow>
|
||||||
</mesh>
|
<boxGeometry args={[wallThickness, height, length / 2]} />
|
||||||
<PressureArrow center={[0, 0, -length / 4]} normal={[-1, 0, 0]} p={(isParallel ? wallCpe.C : wallCpe.A) - cpi} />
|
<meshStandardMaterial color={isParallel ? wallCColor : wallAColor} roughness={0.4} />
|
||||||
|
</mesh>
|
||||||
|
<PressureArrow center={[0, 0, -length / 4]} normal={[-1, 0, 0]} p={(isParallel ? wallCpe.C : wallCpe.A) - cpi} />
|
||||||
|
|
||||||
<mesh position={[0, 0, length / 4]} castShadow receiveShadow>
|
<mesh position={[0, 0, length / 4]} castShadow receiveShadow>
|
||||||
<boxGeometry args={[wallThickness, height, length / 2]} />
|
<boxGeometry args={[wallThickness, height, length / 2]} />
|
||||||
<meshStandardMaterial color={isParallel ? wallDColor : wallAColor} roughness={0.4} />
|
<meshStandardMaterial color={isParallel ? wallDColor : wallAColor} roughness={0.4} />
|
||||||
</mesh>
|
</mesh>
|
||||||
<PressureArrow center={[0, 0, length / 4]} normal={[-1, 0, 0]} p={(isParallel ? wallCpe.D : wallCpe.A) - cpi} />
|
<PressureArrow center={[0, 0, length / 4]} normal={[-1, 0, 0]} p={(isParallel ? wallCpe.D : wallCpe.A) - cpi} />
|
||||||
</group>
|
|
||||||
|
|
||||||
{/* Lateral Direita (X = width/2) */}
|
{hasOpening('C', permeabilityCase, windAngle) && (
|
||||||
<group position={[width / 2, height / 2, 0]}>
|
<mesh position={[-wallThickness/2 - 0.01, -height/4, 0]}>
|
||||||
<mesh position={[0, 0, -length / 4]} castShadow receiveShadow>
|
<boxGeometry args={[0.05, height * 0.5, length * 0.15]} />
|
||||||
<boxGeometry args={[wallThickness, height, length / 2]} />
|
<meshStandardMaterial color="#0f172a" roughness={0.9} />
|
||||||
<meshStandardMaterial color={isParallel ? wallCColor : wallBColor} roughness={0.4} />
|
</mesh>
|
||||||
</mesh>
|
)}
|
||||||
<PressureArrow center={[0, 0, -length / 4]} normal={[1, 0, 0]} p={(isParallel ? wallCpe.C : wallCpe.B) - cpi} />
|
</group>
|
||||||
|
|
||||||
<mesh position={[0, 0, length / 4]} castShadow receiveShadow>
|
{/* Lateral Direita (X = width/2, Parede D) */}
|
||||||
<boxGeometry args={[wallThickness, height, length / 2]} />
|
<group position={[width / 2, height / 2, 0]}>
|
||||||
<meshStandardMaterial color={isParallel ? wallDColor : wallBColor} roughness={0.4} />
|
<mesh position={[0, 0, -length / 4]} castShadow receiveShadow>
|
||||||
</mesh>
|
<boxGeometry args={[wallThickness, height, length / 2]} />
|
||||||
<PressureArrow center={[0, 0, length / 4]} normal={[1, 0, 0]} p={(isParallel ? wallCpe.D : wallCpe.B) - cpi} />
|
<meshStandardMaterial color={isParallel ? wallCColor : wallBColor} roughness={0.4} />
|
||||||
</group>
|
</mesh>
|
||||||
|
<PressureArrow center={[0, 0, -length / 4]} normal={[1, 0, 0]} p={(isParallel ? wallCpe.C : wallCpe.B) - cpi} />
|
||||||
|
|
||||||
{/* Parede Traseira (Z = -length/2) */}
|
<mesh position={[0, 0, length / 4]} castShadow receiveShadow>
|
||||||
<group position={[0, height / 2, -length / 2]}>
|
<boxGeometry args={[wallThickness, height, length / 2]} />
|
||||||
<mesh position={[-width / 4, 0, 0]} castShadow receiveShadow>
|
<meshStandardMaterial color={isParallel ? wallDColor : wallBColor} roughness={0.4} />
|
||||||
<boxGeometry args={[width / 2, height, wallThickness]} />
|
</mesh>
|
||||||
<meshStandardMaterial color={isParallel ? wallAColor : wallCColor} roughness={0.4} />
|
<PressureArrow center={[0, 0, length / 4]} normal={[1, 0, 0]} p={(isParallel ? wallCpe.D : wallCpe.B) - cpi} />
|
||||||
</mesh>
|
|
||||||
<PressureArrow center={[-width / 4, 0, 0]} normal={[0, 0, -1]} p={(isParallel ? wallCpe.A : wallCpe.C) - cpi} />
|
|
||||||
|
|
||||||
<mesh position={[width / 4, 0, 0]} castShadow receiveShadow>
|
{hasOpening('D', permeabilityCase, windAngle) && (
|
||||||
<boxGeometry args={[width / 2, height, wallThickness]} />
|
<mesh position={[wallThickness/2 + 0.01, -height/4, 0]}>
|
||||||
<meshStandardMaterial color={isParallel ? wallAColor : wallDColor} roughness={0.4} />
|
<boxGeometry args={[0.05, height * 0.5, length * 0.15]} />
|
||||||
</mesh>
|
<meshStandardMaterial color="#0f172a" roughness={0.9} />
|
||||||
<PressureArrow center={[width / 4, 0, 0]} normal={[0, 0, -1]} p={(isParallel ? wallCpe.A : wallCpe.D) - cpi} />
|
</mesh>
|
||||||
</group>
|
)}
|
||||||
|
</group>
|
||||||
|
|
||||||
{/* Parede Frontal (Z = length/2) */}
|
{/* Parede Traseira (Z = -length/2, Parede A) */}
|
||||||
<group position={[0, height / 2, length / 2]}>
|
<group position={[0, height / 2, -length / 2]}>
|
||||||
<mesh position={[-width / 4, 0, 0]} castShadow receiveShadow>
|
<mesh position={[-width / 4, 0, 0]} castShadow receiveShadow>
|
||||||
<boxGeometry args={[width / 2, height, wallThickness]} />
|
<boxGeometry args={[width / 2, height, wallThickness]} />
|
||||||
<meshStandardMaterial color={isParallel ? wallBColor : wallCColor} roughness={0.4} />
|
<meshStandardMaterial color={isParallel ? wallAColor : wallCColor} roughness={0.4} />
|
||||||
</mesh>
|
</mesh>
|
||||||
<PressureArrow center={[-width / 4, 0, 0]} normal={[0, 0, 1]} p={(isParallel ? wallCpe.B : wallCpe.C) - cpi} />
|
<PressureArrow center={[-width / 4, 0, 0]} normal={[0, 0, -1]} p={(isParallel ? wallCpe.A : wallCpe.C) - cpi} />
|
||||||
|
|
||||||
<mesh position={[width / 4, 0, 0]} castShadow receiveShadow>
|
<mesh position={[width / 4, 0, 0]} castShadow receiveShadow>
|
||||||
<boxGeometry args={[width / 2, height, wallThickness]} />
|
<boxGeometry args={[width / 2, height, wallThickness]} />
|
||||||
<meshStandardMaterial color={isParallel ? wallBColor : wallDColor} roughness={0.4} />
|
<meshStandardMaterial color={isParallel ? wallAColor : wallDColor} roughness={0.4} />
|
||||||
</mesh>
|
</mesh>
|
||||||
<PressureArrow center={[width / 4, 0, 0]} normal={[0, 0, 1]} p={(isParallel ? wallCpe.B : wallCpe.D) - cpi} />
|
<PressureArrow center={[width / 4, 0, 0]} normal={[0, 0, -1]} p={(isParallel ? wallCpe.A : wallCpe.D) - cpi} />
|
||||||
</group>
|
|
||||||
|
{hasOpening('A', permeabilityCase, windAngle) && (
|
||||||
|
<mesh position={[0, -height/4, -wallThickness/2 - 0.01]}>
|
||||||
|
<boxGeometry args={[width * 0.3, height * 0.5, 0.05]} />
|
||||||
|
<meshStandardMaterial color="#0f172a" roughness={0.9} />
|
||||||
|
</mesh>
|
||||||
|
)}
|
||||||
|
</group>
|
||||||
|
|
||||||
|
{/* Parede Frontal (Z = length/2, Parede B) */}
|
||||||
|
<group position={[0, height / 2, length / 2]}>
|
||||||
|
<mesh position={[-width / 4, 0, 0]} castShadow receiveShadow>
|
||||||
|
<boxGeometry args={[width / 2, height, wallThickness]} />
|
||||||
|
<meshStandardMaterial color={isParallel ? wallBColor : wallCColor} roughness={0.4} />
|
||||||
|
</mesh>
|
||||||
|
<PressureArrow center={[-width / 4, 0, 0]} normal={[0, 0, 1]} p={(isParallel ? wallCpe.B : wallCpe.C) - cpi} />
|
||||||
|
|
||||||
|
<mesh position={[width / 4, 0, 0]} castShadow receiveShadow>
|
||||||
|
<boxGeometry args={[width / 2, height, wallThickness]} />
|
||||||
|
<meshStandardMaterial color={isParallel ? wallBColor : wallDColor} roughness={0.4} />
|
||||||
|
</mesh>
|
||||||
|
<PressureArrow center={[width / 4, 0, 0]} normal={[0, 0, 1]} p={(isParallel ? wallCpe.B : wallCpe.D) - cpi} />
|
||||||
|
|
||||||
|
{hasOpening('B', permeabilityCase, windAngle) && (
|
||||||
|
<mesh position={[0, -height/4, wallThickness/2 + 0.01]}>
|
||||||
|
<boxGeometry args={[width * 0.3, height * 0.5, 0.05]} />
|
||||||
|
<meshStandardMaterial color="#0f172a" roughness={0.9} />
|
||||||
|
</mesh>
|
||||||
|
)}
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* === PLATIBANDA === */}
|
||||||
|
{(template === 'parapet' || template === 'skirt_parapet') && (
|
||||||
|
<group>
|
||||||
|
{/* Platibanda Traseira */}
|
||||||
|
<mesh position={[0, height + extraHeight / 2, -length / 2]} castShadow receiveShadow>
|
||||||
|
<boxGeometry args={[width, extraHeight, wallThickness]} />
|
||||||
|
<meshStandardMaterial color={extraColor} roughness={0.4} />
|
||||||
|
</mesh>
|
||||||
|
{/* Platibanda Frontal */}
|
||||||
|
<mesh position={[0, height + extraHeight / 2, length / 2]} castShadow receiveShadow>
|
||||||
|
<boxGeometry args={[width, extraHeight, wallThickness]} />
|
||||||
|
<meshStandardMaterial color={extraColor} roughness={0.4} />
|
||||||
|
</mesh>
|
||||||
|
{/* Platibanda Esquerda */}
|
||||||
|
<mesh position={[-width / 2, height + extraHeight / 2, 0]} castShadow receiveShadow>
|
||||||
|
<boxGeometry args={[wallThickness, extraHeight, length]} />
|
||||||
|
<meshStandardMaterial color={extraColor} roughness={0.4} />
|
||||||
|
</mesh>
|
||||||
|
{/* Platibanda Direita */}
|
||||||
|
<mesh position={[width / 2, height + extraHeight / 2, 0]} castShadow receiveShadow>
|
||||||
|
<boxGeometry args={[wallThickness, extraHeight, length]} />
|
||||||
|
<meshStandardMaterial color={extraColor} roughness={0.4} />
|
||||||
|
</mesh>
|
||||||
|
</group>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* === SAIA === */}
|
||||||
|
{(template === 'skirt' || template === 'skirt_parapet') && (
|
||||||
|
<group>
|
||||||
|
{/* Saia Traseira */}
|
||||||
|
<mesh position={[0, height - skirtHeight / 2, -length / 2]} castShadow receiveShadow>
|
||||||
|
<boxGeometry args={[width, skirtHeight, wallThickness]} />
|
||||||
|
<meshStandardMaterial color={extraColor} roughness={0.4} />
|
||||||
|
</mesh>
|
||||||
|
{/* Saia Frontal */}
|
||||||
|
<mesh position={[0, height - skirtHeight / 2, length / 2]} castShadow receiveShadow>
|
||||||
|
<boxGeometry args={[width, skirtHeight, wallThickness]} />
|
||||||
|
<meshStandardMaterial color={extraColor} roughness={0.4} />
|
||||||
|
</mesh>
|
||||||
|
{/* Saia Esquerda */}
|
||||||
|
<mesh position={[-width / 2, height - skirtHeight / 2, 0]} castShadow receiveShadow>
|
||||||
|
<boxGeometry args={[wallThickness, skirtHeight, length]} />
|
||||||
|
<meshStandardMaterial color={extraColor} roughness={0.4} />
|
||||||
|
</mesh>
|
||||||
|
{/* Saia Direita */}
|
||||||
|
<mesh position={[width / 2, height - skirtHeight / 2, 0]} castShadow receiveShadow>
|
||||||
|
<boxGeometry args={[wallThickness, skirtHeight, length]} />
|
||||||
|
<meshStandardMaterial color={extraColor} roughness={0.4} />
|
||||||
|
</mesh>
|
||||||
|
</group>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* === OITÕES (GABLES) === */}
|
{/* === OITÕES (GABLES) === */}
|
||||||
{/* Oitão Frontal (Z = length/2) */}
|
{/* Oitão Frontal (Z = length/2) */}
|
||||||
@@ -304,52 +449,56 @@ export function WarehouseModel() {
|
|||||||
</mesh>
|
</mesh>
|
||||||
|
|
||||||
{/* === RÓTULOS 3D === */}
|
{/* === RÓTULOS 3D === */}
|
||||||
{/* Rótulo Parede Frontal */}
|
{showMainWalls && (
|
||||||
<Text
|
<group>
|
||||||
position={[0, height / 2, length / 2 + 1.0]}
|
{/* Rótulo Parede Frontal */}
|
||||||
fontSize={Math.max(0.3, Math.min(0.6, width / 20))}
|
<Text
|
||||||
color={isDark ? "#cbd5e1" : "#1a202c"}
|
position={[0, height / 2, length / 2 + 1.0]}
|
||||||
anchorX="center"
|
fontSize={Math.max(0.3, Math.min(0.6, width / 20))}
|
||||||
anchorY="middle"
|
color={isDark ? "#cbd5e1" : "#1a202c"}
|
||||||
>
|
anchorX="center"
|
||||||
{isParallel ? 'Parede B (Sotavento)' : 'Parede C (Barlavento)'}
|
anchorY="middle"
|
||||||
</Text>
|
>
|
||||||
|
{isParallel ? 'Parede B (Sotavento)' : 'Parede C (Barlavento)'}
|
||||||
|
</Text>
|
||||||
|
|
||||||
{/* Rótulo Parede Traseira */}
|
{/* Rótulo Parede Traseira */}
|
||||||
<Text
|
<Text
|
||||||
position={[0, height / 2, -length / 2 - 1.0]}
|
position={[0, height / 2, -length / 2 - 1.0]}
|
||||||
rotation={[0, Math.PI, 0]}
|
rotation={[0, Math.PI, 0]}
|
||||||
fontSize={Math.max(0.3, Math.min(0.6, width / 20))}
|
fontSize={Math.max(0.3, Math.min(0.6, width / 20))}
|
||||||
color={isDark ? "#cbd5e1" : "#1a202c"}
|
color={isDark ? "#cbd5e1" : "#1a202c"}
|
||||||
anchorX="center"
|
anchorX="center"
|
||||||
anchorY="middle"
|
anchorY="middle"
|
||||||
>
|
>
|
||||||
{isParallel ? 'Parede A (Barlavento)' : 'Parede D (Sotavento)'}
|
{isParallel ? 'Parede A (Barlavento)' : 'Parede D (Sotavento)'}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
{/* Rótulo Parede Esquerda */}
|
{/* Rótulo Parede Esquerda */}
|
||||||
<Text
|
<Text
|
||||||
position={[-width / 2 - 1.0, height / 2, 0]}
|
position={[-width / 2 - 1.0, height / 2, 0]}
|
||||||
rotation={[0, -Math.PI / 2, 0]}
|
rotation={[0, -Math.PI / 2, 0]}
|
||||||
fontSize={Math.max(0.3, Math.min(0.6, length / 20))}
|
fontSize={Math.max(0.3, Math.min(0.6, length / 20))}
|
||||||
color={isDark ? "#cbd5e1" : "#1a202c"}
|
color={isDark ? "#cbd5e1" : "#1a202c"}
|
||||||
anchorX="center"
|
anchorX="center"
|
||||||
anchorY="middle"
|
anchorY="middle"
|
||||||
>
|
>
|
||||||
{isParallel ? 'Parede C (Lateral)' : 'Parede A (Lateral)'}
|
{isParallel ? 'Parede C (Lateral)' : 'Parede A (Lateral)'}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
{/* Rótulo Parede Direita */}
|
{/* Rótulo Parede Direita */}
|
||||||
<Text
|
<Text
|
||||||
position={[width / 2 + 1.0, height / 2, 0]}
|
position={[width / 2 + 1.0, height / 2, 0]}
|
||||||
rotation={[0, Math.PI / 2, 0]}
|
rotation={[0, Math.PI / 2, 0]}
|
||||||
fontSize={Math.max(0.3, Math.min(0.6, length / 20))}
|
fontSize={Math.max(0.3, Math.min(0.6, length / 20))}
|
||||||
color={isDark ? "#cbd5e1" : "#1a202c"}
|
color={isDark ? "#cbd5e1" : "#1a202c"}
|
||||||
anchorX="center"
|
anchorX="center"
|
||||||
anchorY="middle"
|
anchorY="middle"
|
||||||
>
|
>
|
||||||
{isParallel ? 'Parede D (Lateral)' : 'Parede B (Lateral)'}
|
{isParallel ? 'Parede D (Lateral)' : 'Parede B (Lateral)'}
|
||||||
</Text>
|
</Text>
|
||||||
|
</group>
|
||||||
|
)}
|
||||||
</group>
|
</group>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -389,6 +538,7 @@ export default function Warehouse3DViewer() {
|
|||||||
shadow-camera-top={maxDimension}
|
shadow-camera-top={maxDimension}
|
||||||
shadow-camera-bottom={-maxDimension}
|
shadow-camera-bottom={-maxDimension}
|
||||||
/>
|
/>
|
||||||
|
<WindDirectionIndicator angle={windAngle} width={width} length={length} height={height} />
|
||||||
<WarehouseModel />
|
<WarehouseModel />
|
||||||
<Grid
|
<Grid
|
||||||
infiniteGrid
|
infiniteGrid
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { generateAllScenarios } from '../audit/scenarios';
|
||||||
|
|
||||||
|
|
||||||
|
describe('Cenários de Validação Bibliográfica - TesteBib', () => {
|
||||||
|
const bibScenarios = generateAllScenarios().filter(s => s.isBibliographic);
|
||||||
|
|
||||||
|
it('deve ter carregado ao menos um cenário bibliográfico', () => {
|
||||||
|
expect(bibScenarios.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const s of bibScenarios) {
|
||||||
|
describe(`Cenário: ${s.id}`, () => {
|
||||||
|
it('deve conter referência e enunciado', () => {
|
||||||
|
expect(s.bibliografiaReferencia).toBeTruthy();
|
||||||
|
expect(s.enunciadoParafraseado).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Aqui testaremos os gabaritos se necessário
|
||||||
|
// Como os resultados já foram pre-calculados,
|
||||||
|
// podemos validar se os intermediários batem com os limites.
|
||||||
|
it('deve ter valores q dentro do range esperado', () => {
|
||||||
|
if (s.intermediates.q !== undefined && s.expectedRanges.q) {
|
||||||
|
const qVal = s.intermediates.q as number;
|
||||||
|
expect(qVal).toBeGreaterThanOrEqual(s.expectedRanges.q.min);
|
||||||
|
expect(qVal).toBeLessThanOrEqual(s.expectedRanges.q.max);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -40,6 +40,9 @@ function makeScenario(
|
|||||||
diagramType: string,
|
diagramType: string,
|
||||||
diagramProps: Record<string, unknown>,
|
diagramProps: Record<string, unknown>,
|
||||||
nbrSection: string,
|
nbrSection: string,
|
||||||
|
isBibliographic?: boolean,
|
||||||
|
bibliografiaReferencia?: string,
|
||||||
|
enunciadoParafraseado?: string,
|
||||||
): AuditScenario {
|
): AuditScenario {
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
@@ -53,6 +56,9 @@ function makeScenario(
|
|||||||
diagramType: diagramType as import('./types').DiagramType,
|
diagramType: diagramType as import('./types').DiagramType,
|
||||||
diagramProps,
|
diagramProps,
|
||||||
nbrSection,
|
nbrSection,
|
||||||
|
isBibliographic,
|
||||||
|
bibliografiaReferencia,
|
||||||
|
enunciadoParafraseado,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,6 +70,9 @@ function galpaoScenario(
|
|||||||
width: number, length: number, height: number, roofPitch: number,
|
width: number, length: number, height: number, roofPitch: number,
|
||||||
windAngle: 0 | 90,
|
windAngle: 0 | 90,
|
||||||
permCase: PermeabilityCase, cpiRatio: number,
|
permCase: PermeabilityCase, cpiRatio: number,
|
||||||
|
isBibliographic?: boolean,
|
||||||
|
bibliografiaReferencia?: string,
|
||||||
|
enunciadoParafraseado?: string,
|
||||||
): AuditScenario {
|
): AuditScenario {
|
||||||
const dim = Math.max(width, length);
|
const dim = Math.max(width, length);
|
||||||
const { s2, vk, q } = calcWind(v0, s1, s3, cat, dim, height);
|
const { s2, vk, q } = calcWind(v0, s1, s3, cat, dim, height);
|
||||||
@@ -94,6 +103,9 @@ function galpaoScenario(
|
|||||||
'warehouse',
|
'warehouse',
|
||||||
{ width, length, height, roofPitch, wallCpe, roofCpe, windAngle: windAngle as 0 | 90, cpi },
|
{ width, length, height, roofPitch, wallCpe, roofCpe, windAngle: windAngle as 0 | 90, cpi },
|
||||||
'Sec. 6.1',
|
'Sec. 6.1',
|
||||||
|
isBibliographic,
|
||||||
|
bibliografiaReferencia,
|
||||||
|
enunciadoParafraseado,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,6 +118,9 @@ function cylinderScenario(
|
|||||||
surface: 'rough' | 'smooth',
|
surface: 'rough' | 'smooth',
|
||||||
endType: 'closed' | 'open-top' | 'open-bottom' | 'open-both',
|
endType: 'closed' | 'open-top' | 'open-bottom' | 'open-both',
|
||||||
_windAngle: 0 | 90,
|
_windAngle: 0 | 90,
|
||||||
|
isBibliographic?: boolean,
|
||||||
|
bibliografiaReferencia?: string,
|
||||||
|
enunciadoParafraseado?: string,
|
||||||
): AuditScenario {
|
): AuditScenario {
|
||||||
const { s2, vk, q } = calcWind(v0, s1, s3, cat, Math.max(d, h), h);
|
const { s2, vk, q } = calcWind(v0, s1, s3, cat, Math.max(d, h), h);
|
||||||
const re = 70000 * vk * d;
|
const re = 70000 * vk * d;
|
||||||
@@ -130,6 +145,9 @@ function cylinderScenario(
|
|||||||
'cylinder',
|
'cylinder',
|
||||||
{ diameter: d, height: h, cpi, cpeProfile: profile.map(p => ({ angle: p.angle, cpe: p.cpe })) },
|
{ diameter: d, height: h, cpi, cpeProfile: profile.map(p => ({ angle: p.angle, cpe: p.cpe })) },
|
||||||
'Sec. 6.2.1',
|
'Sec. 6.2.1',
|
||||||
|
isBibliographic,
|
||||||
|
bibliografiaReferencia,
|
||||||
|
enunciadoParafraseado,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,6 +158,9 @@ export function generateAllScenarios(): AuditScenario[] {
|
|||||||
scenarios.push(galpaoScenario(
|
scenarios.push(galpaoScenario(
|
||||||
'blessmann-galpao-30x15x6', 'Blessmann: Galpão 30x15x6 Cat II V0=40',
|
'blessmann-galpao-30x15x6', 'Blessmann: Galpão 30x15x6 Cat II V0=40',
|
||||||
40, 1.0, 1.0, 'II', 15, 30, 6, 10, 0, 'four-equally-permeable', 1.0,
|
40, 1.0, 1.0, 'II', 15, 30, 6, 10, 0, 'four-equally-permeable', 1.0,
|
||||||
|
true,
|
||||||
|
'Pfeil & Pfeil, Estruturas de Aço, 8ª ed. (Exemplo clássico de galpão adaptado e Blessmann Cap. 5, Exemplo 5.1)',
|
||||||
|
'Cenário de Validação estruturado com base nos parâmetros físicos típicos da literatura. O teste valida a ação do vento transversal (0°) em pórtico de duas águas simétrico (30x15x6 m) em terreno plano.'
|
||||||
));
|
));
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -154,12 +175,18 @@ export function generateAllScenarios(): AuditScenario[] {
|
|||||||
'warehouse',
|
'warehouse',
|
||||||
{ width: 20, length: 60, height: 100, roofPitch: 0, wallCpe: { A: -0.9, B: -0.6, C: 0.7, D: -0.5 }, roofCpe: { E: -0.8, F: -0.4, G: 0.2, H: -0.3 }, windAngle: 0 as const, cpi: 0 },
|
{ width: 20, length: 60, height: 100, roofPitch: 0, wallCpe: { A: -0.9, B: -0.6, C: 0.7, D: -0.5 }, roofCpe: { E: -0.8, F: -0.4, G: 0.2, H: -0.3 }, windAngle: 0 as const, cpi: 0 },
|
||||||
'Sec. 5.3',
|
'Sec. 5.3',
|
||||||
|
true,
|
||||||
|
'Blessmann, J. O Vento na Engenharia Estrutural, Cap. 9',
|
||||||
|
'Validação de edifício alto paralelepipédico de seção retangular 20x60m e altura 100m. Avaliação da variação do fator S2 com a altura (z) até 100m.'
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
scenarios.push(cylinderScenario(
|
scenarios.push(cylinderScenario(
|
||||||
'blessmann-silo-d8-h24', 'Blessmann: Silo d=8m h=24m smooth',
|
'blessmann-silo-d8-h24', 'Blessmann: Silo d=8m h=24m smooth',
|
||||||
40, 1.0, 1.0, 'II', 8, 24, 'smooth', 'open-top', 0,
|
40, 1.0, 1.0, 'II', 8, 24, 'smooth', 'open-top', 0,
|
||||||
|
true,
|
||||||
|
'NBR 6123 Tabela 13 (Cilindros) / Blessmann Cap. 6',
|
||||||
|
'Cilindro liso com diâmetro de 8m e altura de 24m. Validação do Número de Reynolds (Re) no topo do silo e coeficientes de pressão externa nos ângulos característicos.'
|
||||||
));
|
));
|
||||||
|
|
||||||
for (const z of [5, 10, 20, 50, 100]) {
|
for (const z of [5, 10, 20, 50, 100]) {
|
||||||
|
|||||||
@@ -72,6 +72,9 @@ export interface AuditScenario {
|
|||||||
readonly diagramType: DiagramType;
|
readonly diagramType: DiagramType;
|
||||||
readonly diagramProps: Record<string, unknown>;
|
readonly diagramProps: Record<string, unknown>;
|
||||||
readonly nbrSection: string;
|
readonly nbrSection: string;
|
||||||
|
readonly isBibliographic?: boolean;
|
||||||
|
readonly bibliografiaReferencia?: string;
|
||||||
|
readonly enunciadoParafraseado?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CheckStatus = 'PASS' | 'WARN' | 'FAIL';
|
export type CheckStatus = 'PASS' | 'WARN' | 'FAIL';
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@
|
|||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Separator } from '@/components/ui/separator';
|
import { Separator } from '@/components/ui/separator';
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
|
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
|
||||||
|
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||||
|
import { AlertCircle, Lightbulb } from 'lucide-react';
|
||||||
import ExportMenu from '../components/ExportMenu';
|
import ExportMenu from '../components/ExportMenu';
|
||||||
import { EducationalManual } from '@/components/EducationalManual';
|
import { EducationalManual } from '@/components/EducationalManual';
|
||||||
import { PressureLegend } from '@/components/PressureLegend';
|
import { PressureLegend } from '@/components/PressureLegend';
|
||||||
@@ -25,6 +27,12 @@ const GalpaoModule: React.FC = () => {
|
|||||||
length,
|
length,
|
||||||
height,
|
height,
|
||||||
roofPitch,
|
roofPitch,
|
||||||
|
template,
|
||||||
|
extraHeight,
|
||||||
|
skirtHeight,
|
||||||
|
setTemplate,
|
||||||
|
setExtraHeight,
|
||||||
|
setSkirtHeight,
|
||||||
setWidth,
|
setWidth,
|
||||||
setLength,
|
setLength,
|
||||||
setHeight,
|
setHeight,
|
||||||
@@ -68,6 +76,87 @@ const GalpaoModule: React.FC = () => {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-6">
|
<CardContent className="space-y-6">
|
||||||
<WindParametersSummary />
|
<WindParametersSummary />
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<label className="text-sm font-medium text-foreground">Template Visual</label>
|
||||||
|
</div>
|
||||||
|
<Tabs value={template} onValueChange={(v) => setTemplate(v as any)} className="w-full">
|
||||||
|
<TabsList className="w-full grid grid-cols-2 md:grid-cols-4 h-auto p-1">
|
||||||
|
<TabsTrigger value="standard" className="text-xs py-1.5">Padrão</TabsTrigger>
|
||||||
|
<TabsTrigger value="parapet" className="text-xs py-1.5">Platibanda</TabsTrigger>
|
||||||
|
<TabsTrigger value="skirt" className="text-xs py-1.5">Saia</TabsTrigger>
|
||||||
|
<TabsTrigger value="skirt_parapet" className="text-xs py-1.5">Saia + Plat.</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
{(template === 'parapet' || template === 'skirt_parapet') && (
|
||||||
|
<div className="bg-amber-500/10 border border-amber-500/20 rounded-md p-3 mt-3">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<AlertCircle className="w-4 h-4 text-amber-500 shrink-0 mt-0.5" />
|
||||||
|
<p className="text-xs text-amber-600 dark:text-amber-400">
|
||||||
|
A platibanda bloqueia o fluxo. Calcule os esforços sobre ela no módulo de <b>Muros/Placas</b>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{(template === 'skirt' || template === 'skirt_parapet') && (
|
||||||
|
<div className="bg-blue-500/10 border border-blue-500/20 rounded-md p-3 mt-3">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Lightbulb className="w-4 h-4 text-blue-500 shrink-0 mt-0.5" />
|
||||||
|
<p className="text-xs text-blue-600 dark:text-blue-400">
|
||||||
|
Galpão aberto funciona como uma Cobertura Isolada.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{template !== 'standard' && (
|
||||||
|
<div className="space-y-4 bg-muted/50 p-3 rounded-lg border border-border">
|
||||||
|
{(template === 'parapet' || template === 'skirt_parapet') && (
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<label className="text-sm font-medium text-foreground cursor-help underline decoration-dashed decoration-muted-foreground underline-offset-4">
|
||||||
|
Altura da Platibanda
|
||||||
|
</label>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="right">
|
||||||
|
<p className="text-sm">Altura adicional modelada visualmente no topo.</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
<span className="text-sm text-muted-foreground font-mono">{extraHeight} m</span>
|
||||||
|
</div>
|
||||||
|
<Slider min={0.5} max={5} step={0.1} value={[extraHeight]} onValueChange={(vals) => setExtraHeight(vals[0])} className="py-1 cursor-pointer" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{(template === 'skirt' || template === 'skirt_parapet') && (
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<label className="text-sm font-medium text-foreground cursor-help underline decoration-dashed decoration-muted-foreground underline-offset-4">
|
||||||
|
Altura da Saia
|
||||||
|
</label>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="right">
|
||||||
|
<p className="text-sm">Altura da saia fechada lateral (de cima para baixo).</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
<span className="text-sm text-muted-foreground font-mono">{skirtHeight} m</span>
|
||||||
|
</div>
|
||||||
|
<Slider min={0.5} max={height} step={0.1} value={[skirtHeight]} onValueChange={(vals) => setSkirtHeight(vals[0])} className="py-1 cursor-pointer" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { useRef, useState } from 'react';
|
import React, { useRef, useState } from 'react';
|
||||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Sun, Moon, Laptop, Save, Trash2, Upload, FlaskConical, Download, FolderOpen, FileBox, FileJson } from 'lucide-react';
|
import { Sun, Moon, Laptop, Save, Trash2, Upload, FlaskConical, Download, FolderOpen, FileBox, FileJson, GraduationCap } from 'lucide-react';
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||||
import { useTheme } from '@/lib/theme';
|
import { useTheme } from '@/lib/theme';
|
||||||
import { useProjects } from '@/lib/hooks/useProjects';
|
import { useProjects } from '@/lib/hooks/useProjects';
|
||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
readProjectFile,
|
readProjectFile,
|
||||||
} from '@/lib/import-project';
|
} from '@/lib/import-project';
|
||||||
import AuditPanel from '@/components/AuditPanel';
|
import AuditPanel from '@/components/AuditPanel';
|
||||||
|
import BibliographicTestPanel from '@/components/BibliographicTestPanel';
|
||||||
import Glossary from '@/components/Glossary';
|
import Glossary from '@/components/Glossary';
|
||||||
|
|
||||||
const SettingsModule: React.FC = () => {
|
const SettingsModule: React.FC = () => {
|
||||||
@@ -103,7 +104,7 @@ const SettingsModule: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const currentTab = new URLSearchParams(location.search).get('tab') || 'config';
|
const currentTab = new URLSearchParams(location.search).get('tab') || 'config';
|
||||||
const isSettingsGroup = currentTab === 'config' || currentTab === 'testes';
|
const isSettingsGroup = currentTab === 'config' || currentTab === 'testes' || currentTab === 'testebib';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-6 max-w-5xl mx-auto overflow-auto">
|
<div className="p-6 max-w-5xl mx-auto overflow-auto">
|
||||||
@@ -115,11 +116,15 @@ const SettingsModule: React.FC = () => {
|
|||||||
|
|
||||||
<Tabs value={currentTab} onValueChange={(val) => navigate(`/settings?tab=${val}`)} className="w-full">
|
<Tabs value={currentTab} onValueChange={(val) => navigate(`/settings?tab=${val}`)} className="w-full">
|
||||||
{isSettingsGroup && (
|
{isSettingsGroup && (
|
||||||
<TabsList className="grid w-full mb-6 grid-cols-2">
|
<TabsList className="grid w-full mb-6 grid-cols-3">
|
||||||
<TabsTrigger value="config">Preferências</TabsTrigger>
|
<TabsTrigger value="config">Preferências</TabsTrigger>
|
||||||
|
<TabsTrigger value="testebib">
|
||||||
|
<GraduationCap className="w-4 h-4 mr-1.5" />
|
||||||
|
TesteBib
|
||||||
|
</TabsTrigger>
|
||||||
<TabsTrigger value="testes">
|
<TabsTrigger value="testes">
|
||||||
<FlaskConical className="w-4 h-4 mr-1.5" />
|
<FlaskConical className="w-4 h-4 mr-1.5" />
|
||||||
Testes
|
Auditoria LLM
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
)}
|
)}
|
||||||
@@ -296,6 +301,10 @@ const SettingsModule: React.FC = () => {
|
|||||||
<TabsContent value="testes" className="space-y-6">
|
<TabsContent value="testes" className="space-y-6">
|
||||||
<AuditPanel />
|
<AuditPanel />
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="testebib" className="space-y-6">
|
||||||
|
<BibliographicTestPanel />
|
||||||
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
<input type="file" ref={fileInputRef} onChange={handleFileSelected} className="hidden" accept=".json" />
|
<input type="file" ref={fileInputRef} onChange={handleFileSelected} className="hidden" accept=".json" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,7 +3,14 @@ import { getWallCpeOfficial as getWallCpe, getRoofCpeOfficial as getRoofCpe } fr
|
|||||||
import { useWindStore } from './appStore';
|
import { useWindStore } from './appStore';
|
||||||
import type { WallCoefficients, RoofCoefficients } from '../lib/coefficients';
|
import type { WallCoefficients, RoofCoefficients } from '../lib/coefficients';
|
||||||
|
|
||||||
|
export type GalpaoTemplate = 'standard' | 'parapet' | 'skirt' | 'skirt_parapet';
|
||||||
|
|
||||||
interface GalpaoState {
|
interface GalpaoState {
|
||||||
|
// Configuração Visual
|
||||||
|
template: GalpaoTemplate;
|
||||||
|
extraHeight: number; // Altura da platibanda
|
||||||
|
skirtHeight: number; // Altura da saia
|
||||||
|
|
||||||
// Dimensões do Galpão Retangular
|
// Dimensões do Galpão Retangular
|
||||||
width: number;
|
width: number;
|
||||||
length: number;
|
length: number;
|
||||||
@@ -15,6 +22,9 @@ interface GalpaoState {
|
|||||||
roofCpe: RoofCoefficients;
|
roofCpe: RoofCoefficients;
|
||||||
|
|
||||||
// Ações
|
// Ações
|
||||||
|
setTemplate: (val: GalpaoTemplate) => void;
|
||||||
|
setExtraHeight: (val: number) => void;
|
||||||
|
setSkirtHeight: (val: number) => void;
|
||||||
setWidth: (val: number) => void;
|
setWidth: (val: number) => void;
|
||||||
setLength: (val: number) => void;
|
setLength: (val: number) => void;
|
||||||
setHeight: (val: number) => void;
|
setHeight: (val: number) => void;
|
||||||
@@ -29,6 +39,9 @@ const initPitch = 10;
|
|||||||
const initAngle: 0 | 90 = 0;
|
const initAngle: 0 | 90 = 0;
|
||||||
|
|
||||||
export const useGalpaoStore = create<GalpaoState>((set, get) => ({
|
export const useGalpaoStore = create<GalpaoState>((set, get) => ({
|
||||||
|
template: 'standard',
|
||||||
|
extraHeight: 1.5,
|
||||||
|
skirtHeight: 4.5,
|
||||||
width: initWidth,
|
width: initWidth,
|
||||||
length: initLength,
|
length: initLength,
|
||||||
height: initHeight,
|
height: initHeight,
|
||||||
@@ -45,6 +58,10 @@ export const useGalpaoStore = create<GalpaoState>((set, get) => ({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setTemplate: (val) => set({ template: val }),
|
||||||
|
setExtraHeight: (val) => set({ extraHeight: val }),
|
||||||
|
setSkirtHeight: (val) => set({ skirtHeight: val }),
|
||||||
|
|
||||||
setWidth: (val) => {
|
setWidth: (val) => {
|
||||||
set({ width: val });
|
set({ width: val });
|
||||||
get().updateCoefficients();
|
get().updateCoefficients();
|
||||||
|
|||||||
Reference in New Issue
Block a user