修复机械事件力曲线并对齐气动孔口端口显示
This commit is contained in:
1 parent
e18399c022
commit
408b4ecb22
11 files changed
+697
-43
No files matched your search
@@ -107,15 +107,15 @@ function portsForSymbol(modelType: string) {
|
||||
}
|
||||
if (modelType === "amesim_pnvo001_fixed") {
|
||||
return [
|
||||
physicalPort("port_2", "left", 10),
|
||||
physicalPort("port_3", "right", 20),
|
||||
physicalPort("port_2", "right", 10),
|
||||
physicalPort("port_3", "left", 20),
|
||||
];
|
||||
}
|
||||
if (modelType === "amesim_pnvo001") {
|
||||
return [
|
||||
signalPort("res", "left", "input", 5),
|
||||
physicalPort("port_2", "left", 10),
|
||||
physicalPort("port_3", "right", 20),
|
||||
physicalPort("port_2", "right", 10),
|
||||
physicalPort("port_3", "left", 20),
|
||||
];
|
||||
}
|
||||
if (
|
||||
@@ -784,8 +784,8 @@ test("PNOR001 与两种 PNVO001 使用统一孔板风格和 small 画布", async
|
||||
},
|
||||
{
|
||||
anchors: [
|
||||
{ portName: "port_2", x: 12.8, y: 24 },
|
||||
{ portName: "port_3", x: 51.2, y: 24 },
|
||||
{ portName: "port_3", x: 12.8, y: 24 },
|
||||
{ portName: "port_2", x: 51.2, y: 24 },
|
||||
],
|
||||
control: "fixed",
|
||||
modelType: "amesim_pnvo001_fixed",
|
||||
@@ -794,8 +794,8 @@ test("PNOR001 与两种 PNVO001 使用统一孔板风格和 small 画布", async
|
||||
{
|
||||
anchors: [
|
||||
{ portName: "res", x: 27.35, y: 43.2 },
|
||||
{ portName: "port_2", x: 12.8, y: 24 },
|
||||
{ portName: "port_3", x: 51.2, y: 24 },
|
||||
{ portName: "port_3", x: 12.8, y: 24 },
|
||||
{ portName: "port_2", x: 51.2, y: 24 },
|
||||
],
|
||||
control: "signal",
|
||||
modelType: "amesim_pnvo001",
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
import { migratedPnvo001Mirrored } from "../../src/pnvoPortDisplayMigration";
|
||||
|
||||
const legacyPorts = [
|
||||
{ name: "res", side: "left" },
|
||||
{ name: "port_2", side: "left" },
|
||||
{ name: "port_3", side: "right" },
|
||||
];
|
||||
const amesimPorts = [
|
||||
{ name: "res", side: "left" },
|
||||
{ name: "port_2", side: "right" },
|
||||
{ name: "port_3", side: "left" },
|
||||
];
|
||||
|
||||
test("旧 PNVO001 端口快照只迁移一次镜像方向", () => {
|
||||
for (const modelType of [
|
||||
"amesim_pnvo001_fixed",
|
||||
"amesim_pnvo001",
|
||||
]) {
|
||||
const migrated = migratedPnvo001Mirrored(
|
||||
modelType,
|
||||
legacyPorts,
|
||||
amesimPorts,
|
||||
true,
|
||||
);
|
||||
expect(migrated).toBe(false);
|
||||
expect(
|
||||
migratedPnvo001Mirrored(
|
||||
modelType,
|
||||
amesimPorts,
|
||||
amesimPorts,
|
||||
migrated,
|
||||
),
|
||||
).toBe(false);
|
||||
expect(
|
||||
migratedPnvo001Mirrored(
|
||||
modelType,
|
||||
legacyPorts,
|
||||
amesimPorts,
|
||||
false,
|
||||
),
|
||||
).toBe(true);
|
||||
}
|
||||
|
||||
expect(
|
||||
migratedPnvo001Mirrored(
|
||||
"amesim_pnor001",
|
||||
legacyPorts,
|
||||
amesimPorts,
|
||||
true,
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
@@ -0,0 +1,131 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
import {
|
||||
continuousMechanicalForceChartValues,
|
||||
resultStateTransitionSampleIndices,
|
||||
segmentedChartPath,
|
||||
} from "../../src/resultEventSeries";
|
||||
|
||||
const mechanicalComponent = {
|
||||
data: {
|
||||
ports: [{ name: "port_2", domain: "mechanical" }],
|
||||
},
|
||||
};
|
||||
|
||||
const mechanicalPortForce = {
|
||||
scope: "port" as const,
|
||||
portName: "port_2",
|
||||
quantity: "force",
|
||||
category: "flow",
|
||||
};
|
||||
|
||||
test("只分离机械事件中的孤立力尖峰,同时保留连续曲线和原始值", () => {
|
||||
const time = [0, 0.5, 0.75, 1, 1.5];
|
||||
const eventIndices = resultStateTransitionSampleIndices(time, {
|
||||
integration: {
|
||||
segments: [
|
||||
{ stateTransitionTimes: [0.75] },
|
||||
{ stateTransitionTimes: [0.7500000000000001] },
|
||||
],
|
||||
},
|
||||
});
|
||||
expect([...eventIndices]).toEqual([2]);
|
||||
|
||||
const rawForce = [1, 2, 1e12, 3, 4];
|
||||
const displayedForce = continuousMechanicalForceChartValues(
|
||||
mechanicalPortForce,
|
||||
mechanicalComponent,
|
||||
rawForce,
|
||||
eventIndices,
|
||||
);
|
||||
expect(displayedForce.separatedEventSampleCount).toBe(1);
|
||||
expect(Number.isNaN(displayedForce.values[2])).toBe(true);
|
||||
expect(Math.max(...displayedForce.values.filter(Number.isFinite))).toBe(4);
|
||||
expect(rawForce[2]).toBe(1e12);
|
||||
|
||||
const path = segmentedChartPath(
|
||||
time,
|
||||
displayedForce.values,
|
||||
(value) => value,
|
||||
(value) => value,
|
||||
);
|
||||
expect(path.match(/\bM /g)).toHaveLength(1);
|
||||
|
||||
const smoothForce = [1, 2, 2.5, 3, 4];
|
||||
const displayedSmoothForce = continuousMechanicalForceChartValues(
|
||||
mechanicalPortForce,
|
||||
mechanicalComponent,
|
||||
smoothForce,
|
||||
eventIndices,
|
||||
);
|
||||
expect(displayedSmoothForce.values).toBe(smoothForce);
|
||||
expect(displayedSmoothForce.separatedEventSampleCount).toBe(0);
|
||||
|
||||
const componentForce = continuousMechanicalForceChartValues(
|
||||
{
|
||||
scope: "component",
|
||||
portName: null,
|
||||
quantity: "force",
|
||||
category: "derived",
|
||||
},
|
||||
mechanicalComponent,
|
||||
[1, 2, -1e12, 3, 4],
|
||||
eventIndices,
|
||||
);
|
||||
expect(componentForce.separatedEventSampleCount).toBe(1);
|
||||
expect(Number.isNaN(componentForce.values[2])).toBe(true);
|
||||
|
||||
const signalForce = continuousMechanicalForceChartValues(
|
||||
{
|
||||
scope: "component",
|
||||
portName: null,
|
||||
quantity: "force",
|
||||
category: "signal",
|
||||
},
|
||||
mechanicalComponent,
|
||||
[1, 2, -1e12, 3, 4],
|
||||
eventIndices,
|
||||
);
|
||||
expect(signalForce.values[2]).toBe(-1e12);
|
||||
expect(signalForce.separatedEventSampleCount).toBe(0);
|
||||
|
||||
const stateValues = [0, 0.5, 1e9, 1, 1.5];
|
||||
const displayedState = continuousMechanicalForceChartValues(
|
||||
{ ...mechanicalPortForce, quantity: "velocity" },
|
||||
mechanicalComponent,
|
||||
stateValues,
|
||||
eventIndices,
|
||||
);
|
||||
expect(displayedState.values).toBe(stateValues);
|
||||
expect(displayedState.values[2]).toBe(1e9);
|
||||
|
||||
const pneumaticForce = continuousMechanicalForceChartValues(
|
||||
mechanicalPortForce,
|
||||
{
|
||||
data: {
|
||||
ports: [{ name: "port_2", domain: "pneumatic" }],
|
||||
},
|
||||
},
|
||||
rawForce,
|
||||
eventIndices,
|
||||
);
|
||||
expect(pneumaticForce.values).toBe(rawForce);
|
||||
|
||||
const persistentContactForce = [0, 0, 1e6, 1e6, 1e6];
|
||||
const displayedPersistentContactForce =
|
||||
continuousMechanicalForceChartValues(
|
||||
mechanicalPortForce,
|
||||
mechanicalComponent,
|
||||
persistentContactForce,
|
||||
eventIndices,
|
||||
);
|
||||
expect(displayedPersistentContactForce.values).toBe(
|
||||
persistentContactForce,
|
||||
);
|
||||
expect(
|
||||
displayedPersistentContactForce.separatedEventSampleCount,
|
||||
).toBe(0);
|
||||
expect(
|
||||
resultStateTransitionSampleIndices(time, {}),
|
||||
).toEqual(new Set<number>());
|
||||
});
|
||||
@@ -34,6 +34,88 @@ const chartWindowSnapshot = {
|
||||
},
|
||||
};
|
||||
|
||||
const eventForceSnapshot = {
|
||||
...chartWindowSnapshot,
|
||||
id: "e2e-mechanical-event-force-chart",
|
||||
project: {
|
||||
...chartWindowSnapshot.project,
|
||||
nodes: chartWindowSnapshot.project.nodes.map((node, nodeIndex) =>
|
||||
nodeIndex === 0
|
||||
? {
|
||||
...node,
|
||||
data: {
|
||||
...node.data,
|
||||
ports: node.data.ports.map((port, portIndex) =>
|
||||
portIndex === 0
|
||||
? { ...port, domain: "mechanical" }
|
||||
: port,
|
||||
),
|
||||
},
|
||||
}
|
||||
: node,
|
||||
),
|
||||
simulation: {
|
||||
...chartWindowSnapshot.project.simulation,
|
||||
t_stop: 2,
|
||||
step: 0.5,
|
||||
},
|
||||
},
|
||||
result: {
|
||||
...chartWindowSnapshot.result,
|
||||
simulatedUntil: 2,
|
||||
requestedStopTime: 2,
|
||||
variables: [
|
||||
...chartWindowSnapshot.result.variables,
|
||||
{
|
||||
key: "generic_sensor_1.port_a.f",
|
||||
componentId: "generic_sensor_1",
|
||||
componentType: "generic_sensor",
|
||||
scope: "port",
|
||||
portName: "port_a",
|
||||
name: "f",
|
||||
label: "力",
|
||||
quantity: "force",
|
||||
unit: "N",
|
||||
category: "flow",
|
||||
order: 30,
|
||||
},
|
||||
{
|
||||
key: "generic_sensor_1.force",
|
||||
componentId: "generic_sensor_1",
|
||||
componentType: "generic_sensor",
|
||||
scope: "component",
|
||||
portName: null,
|
||||
name: "force",
|
||||
label: "接触力",
|
||||
quantity: "force",
|
||||
unit: "N",
|
||||
category: "derived",
|
||||
order: 31,
|
||||
},
|
||||
],
|
||||
final: {
|
||||
...chartWindowSnapshot.result.final,
|
||||
"generic_sensor_1.value": 2,
|
||||
"generic_sensor_1.port_a.f": 5,
|
||||
"generic_sensor_1.force": 5,
|
||||
},
|
||||
series: {
|
||||
...chartWindowSnapshot.result.series,
|
||||
time: [0, 0.5, 0.75, 1, 1.5, 2],
|
||||
"generic_sensor_1.value": [0, 0.5, 1e9, 1, 1.5, 2],
|
||||
"generic_sensor_1.port_a.f": [1, 2, 1e12, 3, 4, 5],
|
||||
"generic_sensor_1.force": [1, 2, -1e12, 3, 4, 5],
|
||||
},
|
||||
diagnostics: {
|
||||
...chartWindowSnapshot.result.diagnostics,
|
||||
integration: {
|
||||
segments: [{ stateTransitionTimes: [0.75] }],
|
||||
},
|
||||
sampleCount: 6,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
async function openResults(page: Page) {
|
||||
await page.getByRole("tab", { name: "结果", exact: true }).click();
|
||||
await expect(page.locator(".results-chart-workspace")).toBeVisible();
|
||||
@@ -129,6 +211,95 @@ test.beforeEach(async ({ page }) => {
|
||||
}, chartWindowSnapshot);
|
||||
});
|
||||
|
||||
test("机械状态事件力点不参与连续曲线纵轴,但原始数据与非力状态保持不变", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.addInitScript((snapshot) => {
|
||||
window.sessionStorage.setItem(
|
||||
"system-simulation-flow:latest-result",
|
||||
JSON.stringify(snapshot),
|
||||
);
|
||||
}, eventForceSnapshot);
|
||||
await page.goto("/");
|
||||
await openResults(page);
|
||||
|
||||
await page
|
||||
.locator(".results-variable-list button")
|
||||
.filter({ hasText: "port_a · 力" })
|
||||
.first()
|
||||
.click();
|
||||
|
||||
const forceWindow = page
|
||||
.locator('.result-chart-window[data-chart-kind="single"]')
|
||||
.filter({ hasText: "port_a.f" });
|
||||
await expect(forceWindow).toHaveAttribute(
|
||||
"data-separated-event-samples",
|
||||
"1",
|
||||
);
|
||||
await expect(forceWindow).toContainText("已隐藏 1 个孤立事件力尖峰");
|
||||
|
||||
const forceChart = forceWindow.locator('svg[data-result-chart="true"]');
|
||||
const forceDomain = await readChartDomain(forceChart);
|
||||
expect(forceDomain.yMax).toBeLessThan(10);
|
||||
const forcePath = await forceChart
|
||||
.locator('path[data-chart-clipped-series="true"]')
|
||||
.getAttribute("d");
|
||||
expect(forcePath?.match(/\bM /g)).toHaveLength(1);
|
||||
|
||||
const storedEventForce = await page.evaluate(() => {
|
||||
const raw = sessionStorage.getItem(
|
||||
"system-simulation-flow:latest-result",
|
||||
);
|
||||
return raw
|
||||
? JSON.parse(raw).result.series["generic_sensor_1.port_a.f"][2]
|
||||
: null;
|
||||
});
|
||||
expect(storedEventForce).toBe(1e12);
|
||||
|
||||
await page
|
||||
.locator(".results-variable-list button")
|
||||
.filter({ hasText: "接触力" })
|
||||
.click();
|
||||
const componentForceWindow = page
|
||||
.locator('.result-chart-window[data-chart-kind="single"]')
|
||||
.filter({ hasText: "generic_sensor_1.force" });
|
||||
await expect(componentForceWindow).toHaveAttribute(
|
||||
"data-separated-event-samples",
|
||||
"1",
|
||||
);
|
||||
const componentForceDomain = await readChartDomain(
|
||||
componentForceWindow.locator('svg[data-result-chart="true"]'),
|
||||
);
|
||||
expect(componentForceDomain.yMin).toBeGreaterThan(-10);
|
||||
|
||||
const storedComponentForce = await page.evaluate(() => {
|
||||
const raw = sessionStorage.getItem(
|
||||
"system-simulation-flow:latest-result",
|
||||
);
|
||||
return raw
|
||||
? JSON.parse(raw).result.series["generic_sensor_1.force"][2]
|
||||
: null;
|
||||
});
|
||||
expect(storedComponentForce).toBe(-1e12);
|
||||
|
||||
await page
|
||||
.locator(".results-variable-list button")
|
||||
.filter({ hasText: "数值" })
|
||||
.first()
|
||||
.click();
|
||||
const stateWindow = page
|
||||
.locator('.result-chart-window[data-chart-kind="single"]')
|
||||
.filter({ hasText: "数值" });
|
||||
await expect(stateWindow).toHaveAttribute(
|
||||
"data-separated-event-samples",
|
||||
"0",
|
||||
);
|
||||
const stateDomain = await readChartDomain(
|
||||
stateWindow.locator('svg[data-result-chart="true"]'),
|
||||
);
|
||||
expect(stateDomain.yMax).toBeGreaterThan(1e8);
|
||||
});
|
||||
|
||||
test("组合曲线窗口先创建为空窗,并在删除最后一条曲线后恢复待定", async ({
|
||||
page,
|
||||
}) => {
|
||||
|
||||
Reference in new issue
Block a user