feat: unified 0 and 90 degree PDF envelope and category descriptors

This commit is contained in:
2026-07-08 19:52:34 +00:00
commit 9fece3f174
170 changed files with 27177 additions and 0 deletions
+139
View File
@@ -0,0 +1,139 @@
import { create } from 'zustand';
import {
calculateGlobalWindData,
type TerrainCategory,
type StructureClass,
} from '../lib/wind-kernel';
import { computeCpiSimplified, clampCpi, type PermeabilityCase } from '../lib/internal-pressure';
export interface GlobalWindState {
// Entradas (Inputs)
v0: number;
s1: number;
s3: number;
s3Group: 1 | 2 | 3 | 4 | 5;
terrainCategory: TerrainCategory;
largestDimension: number;
heightZ: number;
// Saídas (Calculados)
structureClass: StructureClass;
s2: number;
vk: number;
q: number;
// Pressão interna e Vento
windAngle: 0 | 90;
permeabilityCase: PermeabilityCase;
cpiRatio: number;
cpi: number;
// Ações
updateCalculations: () => void;
setV0: (val: number) => void;
setS1: (val: number) => void;
setS3: (val: number) => void;
setS3Group: (group: 1 | 2 | 3 | 4 | 5) => void;
setTerrainCategory: (cat: TerrainCategory) => void;
setDimensions: (largestDim: number, z: number) => void;
setWindAngle: (angle: 0 | 90) => void;
setPermeabilityCase: (c: PermeabilityCase) => void;
setCpiRatio: (r: number) => void;
setCpiManual: (c: number) => void;
}
function calcCpi(permeabilityCase: PermeabilityCase, ratio: number, windAngle: 0 | 90): number {
if (permeabilityCase === 'airtight') return 0; // Just as an example, computeCpiSimplified handles it
const value = computeCpiSimplified({
case: permeabilityCase,
ratio,
windAngle,
});
return clampCpi(value);
}
export const useWindStore = create<GlobalWindState>((set, get) => {
const recalc = (state: GlobalWindState): Partial<GlobalWindState> => {
const calc = calculateGlobalWindData(
state.v0,
state.s1,
state.s3,
state.terrainCategory,
state.largestDimension,
state.heightZ,
);
return {
structureClass: calc.structClass,
s2: calc.s2,
vk: calc.vk,
q: calc.q,
cpi: calcCpi(state.permeabilityCase, state.cpiRatio, state.windAngle),
};
};
return {
v0: 40,
s1: 1.0,
s3: 1.0,
s3Group: 3,
terrainCategory: 'II',
largestDimension: 30,
heightZ: 10,
structureClass: 'B',
s2: 1.06,
vk: 42.4,
q: 1.1024,
windAngle: 0,
permeabilityCase: 'four-equally-permeable',
cpiRatio: 1.0,
cpi: calcCpi('four-equally-permeable', 1.0, 0),
updateCalculations: () => set((state) => recalc(state)),
setV0: (val) => {
set({ v0: val });
get().updateCalculations();
},
setS1: (val) => {
set({ s1: val });
get().updateCalculations();
},
setS3: (val) => {
set({ s3: val });
get().updateCalculations();
},
setS3Group: (group) => {
const newS3 = group === 1 ? 1.10 : group === 2 ? 1.08 : group === 3 ? 1.0 : group === 4 ? 0.95 : 0.83;
set({ s3Group: group, s3: newS3 });
get().updateCalculations();
},
setTerrainCategory: (cat) => {
set({ terrainCategory: cat });
get().updateCalculations();
},
setDimensions: (largestDim, z) => {
set({ largestDimension: largestDim, heightZ: z });
get().updateCalculations();
},
setWindAngle: (angle) => {
set({ windAngle: angle });
get().updateCalculations();
},
setPermeabilityCase: (c) => {
set({ permeabilityCase: c });
get().updateCalculations();
},
setCpiRatio: (r) => {
set({ cpiRatio: r });
get().updateCalculations();
},
setCpiManual: (c) => {
set({ cpi: clampCpi(c) });
// Not calling updateCalculations to avoid overriding it immediately if permeabilityCase applies,
// but if the user overrides manually we might want to switch permeabilityCase to 'custom' ?
// Wait, there is no 'custom'. We just set the value.
},
};
});