旧版前端工程文件导入时版本对比查验、审阅与仿真时部分阻挡功能实现;前端参数输入格式统一规范
This commit is contained in:
1 parent
22579e51c9
commit
44b6ea74ab
32 files changed
+2087
-322
No files matched your search
@@ -0,0 +1,92 @@
|
||||
import unitTable from "../../schemas/parameter-units.json" with { type: "json" };
|
||||
|
||||
export const DECIMAL_LITERAL = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/;
|
||||
export const sharedUnitTable = unitTable as unknown as Record<string, Record<string, [number, number, string]>>;
|
||||
type Parameter = { unit?: string; quantity?: string };
|
||||
type Definition = { type: string; modelType: string; modelVersion: string; parameters: Record<string, Parameter> };
|
||||
type Node = { id: string; data: { label: string; componentType: string; modelType: string; modelVersion?: string;
|
||||
parameters: Record<string, number | string>; parameterUnits?: Record<string, string> } };
|
||||
type Project = { projectSchemaVersion: 1 | 2; nodes: Node[] };
|
||||
|
||||
const indices = new WeakMap<readonly Definition[], Map<string, Definition>>();
|
||||
export function componentIndex<D extends Definition>(definitions: readonly D[]): Map<string, D> {
|
||||
let index = indices.get(definitions);
|
||||
if (!index) {
|
||||
index = new Map(definitions.map(definition => [definition.type, definition]));
|
||||
indices.set(definitions, index);
|
||||
}
|
||||
return index as Map<string, D>;
|
||||
}
|
||||
|
||||
export function componentVersionNotices(nodes: readonly Node[], definitions: readonly Definition[]) {
|
||||
const index = componentIndex(definitions);
|
||||
return nodes.flatMap(node => {
|
||||
const definition = index.get(node.data.componentType);
|
||||
if (!definition || definition.modelType !== node.data.modelType ||
|
||||
definition.modelVersion === node.data.modelVersion) return [];
|
||||
return [{ componentId: node.id, label: node.data.label || node.id,
|
||||
storedVersion: node.data.modelVersion, currentVersion: definition.modelVersion }];
|
||||
});
|
||||
}
|
||||
|
||||
export function componentVersionWarning(nodes: readonly Node[], definitions: readonly Definition[], running = false) {
|
||||
const notices = componentVersionNotices(nodes, definitions);
|
||||
if (!notices.length) return null;
|
||||
return `${running ? "正在使用旧版或版本未知的工程组件,将按当前模型执行,可能出现仿真失败或仿真结果与实际不符" : "工程包含旧版或版本未知的组件"}:` +
|
||||
notices.map(n => `${n.label}(${n.storedVersion || "版本未知"} → ${n.currentVersion})`).join(";");
|
||||
}
|
||||
|
||||
export function parameterUnitConversion(definition: Parameter, unit: string) {
|
||||
const conversion = definition.unit && definition.quantity ? sharedUnitTable[definition.quantity]?.[unit] : undefined;
|
||||
if (conversion) return { scale: conversion[0], offset: conversion[1] };
|
||||
if (unit === (definition.unit ?? "")) return { scale: 1, offset: 0 };
|
||||
throw new Error(`不支持的参数单位“${unit}”(${definition.quantity ?? definition.unit})`);
|
||||
}
|
||||
|
||||
/** Editor numbers stay SI. v2 wire numbers and expressions both use selected units. */
|
||||
export function decodeProjectParameters<P extends Project>(project: P, definitions: readonly Definition[]): P {
|
||||
if (project.projectSchemaVersion === 1) return project;
|
||||
const index = componentIndex(definitions);
|
||||
return { ...project, projectSchemaVersion: 1, nodes: project.nodes.map(node => {
|
||||
const definition = index.get(node.data.componentType);
|
||||
if (!definition) throw new Error(`无法换算未知组件 ${node.data.label} 的单位`);
|
||||
const parameters = Object.fromEntries(Object.entries(node.data.parameters).map(([name, value]) => {
|
||||
const parameter = definition.parameters[name];
|
||||
if (!parameter) throw new Error(`组件 ${node.data.label} 包含未知参数 ${name}`);
|
||||
const { scale, offset } = parameterUnitConversion(parameter, node.data.parameterUnits?.[name] ?? parameter.unit ?? "");
|
||||
const isNumber = typeof value === "number" || DECIMAL_LITERAL.test(value.trim());
|
||||
const si = isNumber ? Number(value) * scale + offset : value;
|
||||
if (typeof si === "number" && !Number.isFinite(si)) throw new Error(`${node.id}.${name} 必须为有限数值`);
|
||||
return [name, si];
|
||||
}));
|
||||
return { ...node, data: { ...node.data, parameters } };
|
||||
}) } as P;
|
||||
}
|
||||
|
||||
/** Call only for editor SI data, never on an already-encoded external project. */
|
||||
export function encodeProjectParameters<P extends Project>(project: P, definitions: readonly Definition[]): P {
|
||||
if (project.projectSchemaVersion !== 1) throw new Error("工程已按显示单位编码,不能重复换算");
|
||||
const index = componentIndex(definitions);
|
||||
return { ...project, projectSchemaVersion: 2, nodes: project.nodes.map(node => {
|
||||
const definition = index.get(node.data.componentType);
|
||||
if (!definition) throw new Error(`无法导出未知组件 ${node.data.label} 的新版单位格式`);
|
||||
const parameterUnits = { ...node.data.parameterUnits };
|
||||
const parameters = Object.fromEntries(Object.entries(node.data.parameters).map(([name, value]) => {
|
||||
const parameter = definition.parameters[name];
|
||||
if (!parameter) throw new Error(`组件 ${node.data.label} 包含未知参数 ${name}`);
|
||||
const { scale, offset } = parameterUnitConversion(parameter, node.data.parameterUnits?.[name] ?? parameter.unit ?? "");
|
||||
const isNumber = typeof value === "number" || DECIMAL_LITERAL.test(value.trim());
|
||||
let display = isNumber ? (Number(value) - offset) / scale : value;
|
||||
// Some SI doubles have no exact representation after a unit round trip.
|
||||
// Preserve the numerical input by writing that parameter in its SI unit.
|
||||
// Never round the physical value or add a hidden second source of truth.
|
||||
if (typeof display === "number" && display * scale + offset !== Number(value)) {
|
||||
display = Number(value);
|
||||
parameterUnits[name] = parameter.unit ?? "";
|
||||
}
|
||||
if (typeof display === "number" && !Number.isFinite(display)) throw new Error(`${node.id}.${name} 必须为有限数值`);
|
||||
return [name, display];
|
||||
}));
|
||||
return { ...node, data: { ...node.data, parameters, parameterUnits } };
|
||||
}) } as P;
|
||||
}
|
||||
Reference in new issue
Block a user