🚀 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 [checkedScenarioIds, setCheckedScenarioIds] = useState<string[]>([]);
|
||||
const [runOnlyBibliographic, setRunOnlyBibliographic] = useState(false);
|
||||
|
||||
const handleProviderChange = (val: string) => {
|
||||
setConfig((c) => ({
|
||||
@@ -91,6 +92,9 @@ export default function AuditPanel() {
|
||||
|
||||
setStatus('generating');
|
||||
let scenarios = generateAllScenarios();
|
||||
if (runOnlyBibliographic) {
|
||||
scenarios = scenarios.filter(s => s.isBibliographic);
|
||||
}
|
||||
if (config.testScope && config.testScope !== 'all') {
|
||||
scenarios = scenarios.filter(s => s.module === config.testScope && checkedScenarioIds.includes(s.id));
|
||||
}
|
||||
@@ -207,6 +211,19 @@ export default function AuditPanel() {
|
||||
</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' && (
|
||||
<div className="border rounded-md p-3 mt-2 bg-muted/20">
|
||||
<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 { ViewerOrbitControls as OrbitControls } from './three/ViewerOrbitControls';
|
||||
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() {
|
||||
const { invalidate } = useThree();
|
||||
const theme = useCanvasTheme();
|
||||
const isDark = theme === 'dark';
|
||||
const { width, length, height, roofPitch, wallCpe, roofCpe } = useGalpaoStore();
|
||||
const { windAngle, cpi } = useWindStore();
|
||||
const { width, length, height, roofPitch, wallCpe, roofCpe, template, extraHeight, skirtHeight } = useGalpaoStore();
|
||||
const { windAngle, cpi, permeabilityCase } = useWindStore();
|
||||
|
||||
useEffect(() => {
|
||||
invalidate();
|
||||
}, [template, permeabilityCase, windAngle, invalidate]);
|
||||
|
||||
const roofHeight = (width / 2) * Math.tan((roofPitch * Math.PI) / 180);
|
||||
const theta = (roofPitch * Math.PI) / 180;
|
||||
@@ -94,6 +153,8 @@ export function WarehouseModel() {
|
||||
|
||||
const wallThickness = 0.15;
|
||||
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)
|
||||
const leftShape = useMemo(() => {
|
||||
@@ -117,65 +178,149 @@ export function WarehouseModel() {
|
||||
return (
|
||||
<group>
|
||||
{/* === PAREDES === */}
|
||||
{/* Lateral Esquerda (X = -width/2) */}
|
||||
<group position={[-width / 2, height / 2, 0]}>
|
||||
<mesh position={[0, 0, -length / 4]} castShadow receiveShadow>
|
||||
<boxGeometry args={[wallThickness, height, length / 2]} />
|
||||
<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} />
|
||||
{showMainWalls && (
|
||||
<group>
|
||||
{/* Lateral Esquerda (X = -width/2, Parede C) */}
|
||||
<group position={[-width / 2, height / 2, 0]}>
|
||||
<mesh position={[0, 0, -length / 4]} castShadow receiveShadow>
|
||||
<boxGeometry args={[wallThickness, height, length / 2]} />
|
||||
<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>
|
||||
<boxGeometry args={[wallThickness, height, length / 2]} />
|
||||
<meshStandardMaterial color={isParallel ? wallDColor : wallAColor} roughness={0.4} />
|
||||
</mesh>
|
||||
<PressureArrow center={[0, 0, length / 4]} normal={[-1, 0, 0]} p={(isParallel ? wallCpe.D : wallCpe.A) - cpi} />
|
||||
</group>
|
||||
<mesh position={[0, 0, length / 4]} castShadow receiveShadow>
|
||||
<boxGeometry args={[wallThickness, height, length / 2]} />
|
||||
<meshStandardMaterial color={isParallel ? wallDColor : wallAColor} roughness={0.4} />
|
||||
</mesh>
|
||||
<PressureArrow center={[0, 0, length / 4]} normal={[-1, 0, 0]} p={(isParallel ? wallCpe.D : wallCpe.A) - cpi} />
|
||||
|
||||
{/* Lateral Direita (X = width/2) */}
|
||||
<group position={[width / 2, height / 2, 0]}>
|
||||
<mesh position={[0, 0, -length / 4]} castShadow receiveShadow>
|
||||
<boxGeometry args={[wallThickness, height, length / 2]} />
|
||||
<meshStandardMaterial color={isParallel ? wallCColor : wallBColor} roughness={0.4} />
|
||||
</mesh>
|
||||
<PressureArrow center={[0, 0, -length / 4]} normal={[1, 0, 0]} p={(isParallel ? wallCpe.C : wallCpe.B) - cpi} />
|
||||
{hasOpening('C', permeabilityCase, windAngle) && (
|
||||
<mesh position={[-wallThickness/2 - 0.01, -height/4, 0]}>
|
||||
<boxGeometry args={[0.05, height * 0.5, length * 0.15]} />
|
||||
<meshStandardMaterial color="#0f172a" roughness={0.9} />
|
||||
</mesh>
|
||||
)}
|
||||
</group>
|
||||
|
||||
<mesh position={[0, 0, length / 4]} castShadow receiveShadow>
|
||||
<boxGeometry args={[wallThickness, height, length / 2]} />
|
||||
<meshStandardMaterial color={isParallel ? wallDColor : wallBColor} roughness={0.4} />
|
||||
</mesh>
|
||||
<PressureArrow center={[0, 0, length / 4]} normal={[1, 0, 0]} p={(isParallel ? wallCpe.D : wallCpe.B) - cpi} />
|
||||
</group>
|
||||
{/* Lateral Direita (X = width/2, Parede D) */}
|
||||
<group position={[width / 2, height / 2, 0]}>
|
||||
<mesh position={[0, 0, -length / 4]} castShadow receiveShadow>
|
||||
<boxGeometry args={[wallThickness, height, length / 2]} />
|
||||
<meshStandardMaterial color={isParallel ? wallCColor : wallBColor} roughness={0.4} />
|
||||
</mesh>
|
||||
<PressureArrow center={[0, 0, -length / 4]} normal={[1, 0, 0]} p={(isParallel ? wallCpe.C : wallCpe.B) - cpi} />
|
||||
|
||||
{/* Parede Traseira (Z = -length/2) */}
|
||||
<group position={[0, height / 2, -length / 2]}>
|
||||
<mesh position={[-width / 4, 0, 0]} castShadow receiveShadow>
|
||||
<boxGeometry args={[width / 2, height, wallThickness]} />
|
||||
<meshStandardMaterial color={isParallel ? wallAColor : wallCColor} roughness={0.4} />
|
||||
</mesh>
|
||||
<PressureArrow center={[-width / 4, 0, 0]} normal={[0, 0, -1]} p={(isParallel ? wallCpe.A : wallCpe.C) - cpi} />
|
||||
<mesh position={[0, 0, length / 4]} castShadow receiveShadow>
|
||||
<boxGeometry args={[wallThickness, height, length / 2]} />
|
||||
<meshStandardMaterial color={isParallel ? wallDColor : wallBColor} roughness={0.4} />
|
||||
</mesh>
|
||||
<PressureArrow center={[0, 0, length / 4]} normal={[1, 0, 0]} p={(isParallel ? wallCpe.D : wallCpe.B) - cpi} />
|
||||
|
||||
<mesh position={[width / 4, 0, 0]} castShadow receiveShadow>
|
||||
<boxGeometry args={[width / 2, height, wallThickness]} />
|
||||
<meshStandardMaterial color={isParallel ? wallAColor : wallDColor} roughness={0.4} />
|
||||
</mesh>
|
||||
<PressureArrow center={[width / 4, 0, 0]} normal={[0, 0, -1]} p={(isParallel ? wallCpe.A : wallCpe.D) - cpi} />
|
||||
</group>
|
||||
{hasOpening('D', permeabilityCase, windAngle) && (
|
||||
<mesh position={[wallThickness/2 + 0.01, -height/4, 0]}>
|
||||
<boxGeometry args={[0.05, height * 0.5, length * 0.15]} />
|
||||
<meshStandardMaterial color="#0f172a" roughness={0.9} />
|
||||
</mesh>
|
||||
)}
|
||||
</group>
|
||||
|
||||
{/* Parede Frontal (Z = length/2) */}
|
||||
<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} />
|
||||
{/* Parede Traseira (Z = -length/2, Parede A) */}
|
||||
<group position={[0, height / 2, -length / 2]}>
|
||||
<mesh position={[-width / 4, 0, 0]} castShadow receiveShadow>
|
||||
<boxGeometry args={[width / 2, height, wallThickness]} />
|
||||
<meshStandardMaterial color={isParallel ? wallAColor : wallCColor} roughness={0.4} />
|
||||
</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>
|
||||
<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} />
|
||||
</group>
|
||||
<mesh position={[width / 4, 0, 0]} castShadow receiveShadow>
|
||||
<boxGeometry args={[width / 2, height, wallThickness]} />
|
||||
<meshStandardMaterial color={isParallel ? wallAColor : wallDColor} roughness={0.4} />
|
||||
</mesh>
|
||||
<PressureArrow center={[width / 4, 0, 0]} normal={[0, 0, -1]} p={(isParallel ? wallCpe.A : wallCpe.D) - cpi} />
|
||||
|
||||
{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ão Frontal (Z = length/2) */}
|
||||
@@ -304,52 +449,56 @@ export function WarehouseModel() {
|
||||
</mesh>
|
||||
|
||||
{/* === RÓTULOS 3D === */}
|
||||
{/* Rótulo Parede Frontal */}
|
||||
<Text
|
||||
position={[0, height / 2, length / 2 + 1.0]}
|
||||
fontSize={Math.max(0.3, Math.min(0.6, width / 20))}
|
||||
color={isDark ? "#cbd5e1" : "#1a202c"}
|
||||
anchorX="center"
|
||||
anchorY="middle"
|
||||
>
|
||||
{isParallel ? 'Parede B (Sotavento)' : 'Parede C (Barlavento)'}
|
||||
</Text>
|
||||
{showMainWalls && (
|
||||
<group>
|
||||
{/* Rótulo Parede Frontal */}
|
||||
<Text
|
||||
position={[0, height / 2, length / 2 + 1.0]}
|
||||
fontSize={Math.max(0.3, Math.min(0.6, width / 20))}
|
||||
color={isDark ? "#cbd5e1" : "#1a202c"}
|
||||
anchorX="center"
|
||||
anchorY="middle"
|
||||
>
|
||||
{isParallel ? 'Parede B (Sotavento)' : 'Parede C (Barlavento)'}
|
||||
</Text>
|
||||
|
||||
{/* Rótulo Parede Traseira */}
|
||||
<Text
|
||||
position={[0, height / 2, -length / 2 - 1.0]}
|
||||
rotation={[0, Math.PI, 0]}
|
||||
fontSize={Math.max(0.3, Math.min(0.6, width / 20))}
|
||||
color={isDark ? "#cbd5e1" : "#1a202c"}
|
||||
anchorX="center"
|
||||
anchorY="middle"
|
||||
>
|
||||
{isParallel ? 'Parede A (Barlavento)' : 'Parede D (Sotavento)'}
|
||||
</Text>
|
||||
{/* Rótulo Parede Traseira */}
|
||||
<Text
|
||||
position={[0, height / 2, -length / 2 - 1.0]}
|
||||
rotation={[0, Math.PI, 0]}
|
||||
fontSize={Math.max(0.3, Math.min(0.6, width / 20))}
|
||||
color={isDark ? "#cbd5e1" : "#1a202c"}
|
||||
anchorX="center"
|
||||
anchorY="middle"
|
||||
>
|
||||
{isParallel ? 'Parede A (Barlavento)' : 'Parede D (Sotavento)'}
|
||||
</Text>
|
||||
|
||||
{/* Rótulo Parede Esquerda */}
|
||||
<Text
|
||||
position={[-width / 2 - 1.0, height / 2, 0]}
|
||||
rotation={[0, -Math.PI / 2, 0]}
|
||||
fontSize={Math.max(0.3, Math.min(0.6, length / 20))}
|
||||
color={isDark ? "#cbd5e1" : "#1a202c"}
|
||||
anchorX="center"
|
||||
anchorY="middle"
|
||||
>
|
||||
{isParallel ? 'Parede C (Lateral)' : 'Parede A (Lateral)'}
|
||||
</Text>
|
||||
{/* Rótulo Parede Esquerda */}
|
||||
<Text
|
||||
position={[-width / 2 - 1.0, height / 2, 0]}
|
||||
rotation={[0, -Math.PI / 2, 0]}
|
||||
fontSize={Math.max(0.3, Math.min(0.6, length / 20))}
|
||||
color={isDark ? "#cbd5e1" : "#1a202c"}
|
||||
anchorX="center"
|
||||
anchorY="middle"
|
||||
>
|
||||
{isParallel ? 'Parede C (Lateral)' : 'Parede A (Lateral)'}
|
||||
</Text>
|
||||
|
||||
{/* Rótulo Parede Direita */}
|
||||
<Text
|
||||
position={[width / 2 + 1.0, height / 2, 0]}
|
||||
rotation={[0, Math.PI / 2, 0]}
|
||||
fontSize={Math.max(0.3, Math.min(0.6, length / 20))}
|
||||
color={isDark ? "#cbd5e1" : "#1a202c"}
|
||||
anchorX="center"
|
||||
anchorY="middle"
|
||||
>
|
||||
{isParallel ? 'Parede D (Lateral)' : 'Parede B (Lateral)'}
|
||||
</Text>
|
||||
{/* Rótulo Parede Direita */}
|
||||
<Text
|
||||
position={[width / 2 + 1.0, height / 2, 0]}
|
||||
rotation={[0, Math.PI / 2, 0]}
|
||||
fontSize={Math.max(0.3, Math.min(0.6, length / 20))}
|
||||
color={isDark ? "#cbd5e1" : "#1a202c"}
|
||||
anchorX="center"
|
||||
anchorY="middle"
|
||||
>
|
||||
{isParallel ? 'Parede D (Lateral)' : 'Parede B (Lateral)'}
|
||||
</Text>
|
||||
</group>
|
||||
)}
|
||||
</group>
|
||||
);
|
||||
}
|
||||
@@ -389,6 +538,7 @@ export default function Warehouse3DViewer() {
|
||||
shadow-camera-top={maxDimension}
|
||||
shadow-camera-bottom={-maxDimension}
|
||||
/>
|
||||
<WindDirectionIndicator angle={windAngle} width={width} length={length} height={height} />
|
||||
<WarehouseModel />
|
||||
<Grid
|
||||
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,
|
||||
diagramProps: Record<string, unknown>,
|
||||
nbrSection: string,
|
||||
isBibliographic?: boolean,
|
||||
bibliografiaReferencia?: string,
|
||||
enunciadoParafraseado?: string,
|
||||
): AuditScenario {
|
||||
return {
|
||||
id,
|
||||
@@ -53,6 +56,9 @@ function makeScenario(
|
||||
diagramType: diagramType as import('./types').DiagramType,
|
||||
diagramProps,
|
||||
nbrSection,
|
||||
isBibliographic,
|
||||
bibliografiaReferencia,
|
||||
enunciadoParafraseado,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -64,6 +70,9 @@ function galpaoScenario(
|
||||
width: number, length: number, height: number, roofPitch: number,
|
||||
windAngle: 0 | 90,
|
||||
permCase: PermeabilityCase, cpiRatio: number,
|
||||
isBibliographic?: boolean,
|
||||
bibliografiaReferencia?: string,
|
||||
enunciadoParafraseado?: string,
|
||||
): AuditScenario {
|
||||
const dim = Math.max(width, length);
|
||||
const { s2, vk, q } = calcWind(v0, s1, s3, cat, dim, height);
|
||||
@@ -94,6 +103,9 @@ function galpaoScenario(
|
||||
'warehouse',
|
||||
{ width, length, height, roofPitch, wallCpe, roofCpe, windAngle: windAngle as 0 | 90, cpi },
|
||||
'Sec. 6.1',
|
||||
isBibliographic,
|
||||
bibliografiaReferencia,
|
||||
enunciadoParafraseado,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -106,6 +118,9 @@ function cylinderScenario(
|
||||
surface: 'rough' | 'smooth',
|
||||
endType: 'closed' | 'open-top' | 'open-bottom' | 'open-both',
|
||||
_windAngle: 0 | 90,
|
||||
isBibliographic?: boolean,
|
||||
bibliografiaReferencia?: string,
|
||||
enunciadoParafraseado?: string,
|
||||
): AuditScenario {
|
||||
const { s2, vk, q } = calcWind(v0, s1, s3, cat, Math.max(d, h), h);
|
||||
const re = 70000 * vk * d;
|
||||
@@ -130,6 +145,9 @@ function cylinderScenario(
|
||||
'cylinder',
|
||||
{ diameter: d, height: h, cpi, cpeProfile: profile.map(p => ({ angle: p.angle, cpe: p.cpe })) },
|
||||
'Sec. 6.2.1',
|
||||
isBibliographic,
|
||||
bibliografiaReferencia,
|
||||
enunciadoParafraseado,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -140,6 +158,9 @@ export function generateAllScenarios(): AuditScenario[] {
|
||||
scenarios.push(galpaoScenario(
|
||||
'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,
|
||||
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',
|
||||
{ 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',
|
||||
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(
|
||||
'blessmann-silo-d8-h24', 'Blessmann: Silo d=8m h=24m smooth',
|
||||
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]) {
|
||||
|
||||
@@ -72,6 +72,9 @@ export interface AuditScenario {
|
||||
readonly diagramType: DiagramType;
|
||||
readonly diagramProps: Record<string, unknown>;
|
||||
readonly nbrSection: string;
|
||||
readonly isBibliographic?: boolean;
|
||||
readonly bibliografiaReferencia?: string;
|
||||
readonly enunciadoParafraseado?: string;
|
||||
}
|
||||
|
||||
export type CheckStatus = 'PASS' | 'WARN' | 'FAIL';
|
||||
|
||||
@@ -12,6 +12,8 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
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 { EducationalManual } from '@/components/EducationalManual';
|
||||
import { PressureLegend } from '@/components/PressureLegend';
|
||||
@@ -25,6 +27,12 @@ const GalpaoModule: React.FC = () => {
|
||||
length,
|
||||
height,
|
||||
roofPitch,
|
||||
template,
|
||||
extraHeight,
|
||||
skirtHeight,
|
||||
setTemplate,
|
||||
setExtraHeight,
|
||||
setSkirtHeight,
|
||||
setWidth,
|
||||
setLength,
|
||||
setHeight,
|
||||
@@ -68,6 +76,87 @@ const GalpaoModule: React.FC = () => {
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<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="flex justify-between items-center">
|
||||
<Tooltip>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
|
||||
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 { useTheme } from '@/lib/theme';
|
||||
import { useProjects } from '@/lib/hooks/useProjects';
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
readProjectFile,
|
||||
} from '@/lib/import-project';
|
||||
import AuditPanel from '@/components/AuditPanel';
|
||||
import BibliographicTestPanel from '@/components/BibliographicTestPanel';
|
||||
import Glossary from '@/components/Glossary';
|
||||
|
||||
const SettingsModule: React.FC = () => {
|
||||
@@ -103,7 +104,7 @@ const SettingsModule: React.FC = () => {
|
||||
};
|
||||
|
||||
const currentTab = new URLSearchParams(location.search).get('tab') || 'config';
|
||||
const isSettingsGroup = currentTab === 'config' || currentTab === 'testes';
|
||||
const isSettingsGroup = currentTab === 'config' || currentTab === 'testes' || currentTab === 'testebib';
|
||||
|
||||
return (
|
||||
<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">
|
||||
{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="testebib">
|
||||
<GraduationCap className="w-4 h-4 mr-1.5" />
|
||||
TesteBib
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="testes">
|
||||
<FlaskConical className="w-4 h-4 mr-1.5" />
|
||||
Testes
|
||||
Auditoria LLM
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
)}
|
||||
@@ -296,6 +301,10 @@ const SettingsModule: React.FC = () => {
|
||||
<TabsContent value="testes" className="space-y-6">
|
||||
<AuditPanel />
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="testebib" className="space-y-6">
|
||||
<BibliographicTestPanel />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
<input type="file" ref={fileInputRef} onChange={handleFileSelected} className="hidden" accept=".json" />
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,14 @@ import { getWallCpeOfficial as getWallCpe, getRoofCpeOfficial as getRoofCpe } fr
|
||||
import { useWindStore } from './appStore';
|
||||
import type { WallCoefficients, RoofCoefficients } from '../lib/coefficients';
|
||||
|
||||
export type GalpaoTemplate = 'standard' | 'parapet' | 'skirt' | 'skirt_parapet';
|
||||
|
||||
interface GalpaoState {
|
||||
// Configuração Visual
|
||||
template: GalpaoTemplate;
|
||||
extraHeight: number; // Altura da platibanda
|
||||
skirtHeight: number; // Altura da saia
|
||||
|
||||
// Dimensões do Galpão Retangular
|
||||
width: number;
|
||||
length: number;
|
||||
@@ -15,6 +22,9 @@ interface GalpaoState {
|
||||
roofCpe: RoofCoefficients;
|
||||
|
||||
// Ações
|
||||
setTemplate: (val: GalpaoTemplate) => void;
|
||||
setExtraHeight: (val: number) => void;
|
||||
setSkirtHeight: (val: number) => void;
|
||||
setWidth: (val: number) => void;
|
||||
setLength: (val: number) => void;
|
||||
setHeight: (val: number) => void;
|
||||
@@ -29,6 +39,9 @@ const initPitch = 10;
|
||||
const initAngle: 0 | 90 = 0;
|
||||
|
||||
export const useGalpaoStore = create<GalpaoState>((set, get) => ({
|
||||
template: 'standard',
|
||||
extraHeight: 1.5,
|
||||
skirtHeight: 4.5,
|
||||
width: initWidth,
|
||||
length: initLength,
|
||||
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) => {
|
||||
set({ width: val });
|
||||
get().updateCoefficients();
|
||||
|
||||
Reference in New Issue
Block a user