From 11093613c5cff47e2c54923435927f9a4370a2fc Mon Sep 17 00:00:00 2001 From: ljz <425868052@qq.com> Date: Mon, 3 Aug 2026 01:39:32 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=20PNVO001=20=E4=B8=8E=20PNRP?= =?UTF-8?q?17=20=E5=89=8D=E7=AB=AF=E5=9B=BE=E6=A0=87=E5=8F=8A=E7=AB=AF?= =?UTF-8?q?=E5=8F=A3=E4=BA=A4=E4=BA=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 按 AMESim 参考样式重绘孔板与气动活塞图标,调整端口锚点和旋转后的连接方向,并补充组件图标及接触连接回归测试。 --- frontend/src/App.tsx | 20 +++- frontend/src/ComponentSymbol.tsx | 18 +-- frontend/src/componentSymbols/mechanical.tsx | 118 ++++++++++++------- frontend/src/componentSymbols/pneumatic.tsx | 59 +++++++--- frontend/tests/e2e/component-symbols.spec.ts | 59 +++++++++- 5 files changed, 202 insertions(+), 72 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index b64e028..043853d 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1546,7 +1546,7 @@ function transformedPortPlacements( return { port, - position: transformedPortPosition(side, rotation, mirrored), + position: transformedPortPosition(side, rotation, mirrored, anchor), x: renderedX, y: renderedY, }; @@ -1571,12 +1571,28 @@ function transformedPortPosition( side: PortSide, rotation: NodeRotation, mirrored: boolean, + anchor?: { x: number; y: number }, ) { let directionX = side === "left" ? -1 : 1; + let directionY = 0; + + if (anchor) { + const closestEdge = [ + { distance: anchor.x, x: -1, y: 0 }, + { distance: SYMBOL_VIEWBOX_WIDTH - anchor.x, x: 1, y: 0 }, + { distance: anchor.y, x: 0, y: -1 }, + { distance: SYMBOL_VIEWBOX_HEIGHT - anchor.y, x: 0, y: 1 }, + ].reduce((closest, candidate) => + candidate.distance < closest.distance ? candidate : closest, + ); + directionX = closestEdge.x; + directionY = closestEdge.y; + } + if (mirrored) { directionX = -directionX; } - const [x, y] = rotatePoint(directionX, 0, rotation); + const [x, y] = rotatePoint(directionX, directionY, rotation); if (x < -0.01) { return Position.Left; } diff --git a/frontend/src/ComponentSymbol.tsx b/frontend/src/ComponentSymbol.tsx index d507477..530d7ff 100644 --- a/frontend/src/ComponentSymbol.tsx +++ b/frontend/src/ComponentSymbol.tsx @@ -263,15 +263,15 @@ const symbolPortAnchors: Partial< right: [{ x: 62, y: 24 }], }, amesim_pnvo001_fixed: { - left: [{ x: 2, y: 28 }], - right: [{ x: 62, y: 28 }], + left: [{ x: 2, y: 24 }], + right: [{ x: 62, y: 24 }], }, amesim_pnvo001: { left: [ - { x: 2, y: 16 }, - { x: 2, y: 32 }, + { x: 26, y: 46 }, + { x: 2, y: 24 }, ], - right: [{ x: 62, y: 25 }], + right: [{ x: 62, y: 24 }], }, amesim_pnl00r: { left: [{ x: 2, y: 24 }], @@ -325,11 +325,11 @@ const symbolPortAnchors: Partial< }, amesim_pnrp17: { left: [ - { x: 2, y: 24 }, - { x: 2, y: 10 }, - { x: 2, y: 38 }, + { x: 25.5, y: 48 }, + { x: 2, y: 4.4 }, + { x: 2, y: 24.1 }, ], - right: [{ x: 62, y: 10 }, { x: 62, y: 38 }], + right: [{ x: 62, y: 4.4 }, { x: 62, y: 24.1 }], }, }; diff --git a/frontend/src/componentSymbols/mechanical.tsx b/frontend/src/componentSymbols/mechanical.tsx index f6167a2..7bb1ff4 100644 --- a/frontend/src/componentSymbols/mechanical.tsx +++ b/frontend/src/componentSymbols/mechanical.tsx @@ -7,6 +7,9 @@ import { } from "./primitives"; const MECHANICAL_LINE_WIDTH = 1.8; +const PNRP17_STROKE = "#8b134f"; +const PNRP17_FILL = "#cf9db6"; +const PNRP17_LIGHT_FILL = "#f5ebf0"; const mechanicalStroke = { fill: "none", @@ -220,50 +223,81 @@ export function AmesimPnrp17Symbol() { return ( - - - - - - - P - + + + + + + + + {[15.3, 33].map((centerY) => ( + + + + + + + ))} ); } diff --git a/frontend/src/componentSymbols/pneumatic.tsx b/frontend/src/componentSymbols/pneumatic.tsx index 989da51..ccf3ec7 100644 --- a/frontend/src/componentSymbols/pneumatic.tsx +++ b/frontend/src/componentSymbols/pneumatic.tsx @@ -7,6 +7,7 @@ import { } from "./primitives"; const PNEUMATIC_STROKE_WIDTH = 1.7; +const PNVO001_STROKE = "#8b134f"; function OrificeBody({ centerY = 25, @@ -49,6 +50,46 @@ export function AmesimPnor001Symbol() { ); } +function Pnvo001Body({ + signalControlled = false, +}: { + signalControlled?: boolean; +}) { + return ( + <> + + + + {signalControlled ? ( + + ) : null} + + + ); +} + /** PNVO001 convenience variant whose opening is fixed by a parameter. */ export function AmesimPnvo001FixedSymbol() { return ( @@ -56,14 +97,7 @@ export function AmesimPnvo001FixedSymbol() { className="component-symbol-amesim-pnvo001-fixed-svg" domain="pneumatic" > - - + ); } @@ -75,14 +109,7 @@ export function AmesimPnvo001SignalSymbol() { className="component-symbol-amesim-pnvo001-signal-svg" domain="pneumatic" > - - + ); } diff --git a/frontend/tests/e2e/component-symbols.spec.ts b/frontend/tests/e2e/component-symbols.spec.ts index ff47b4e..aad8850 100644 --- a/frontend/tests/e2e/component-symbols.spec.ts +++ b/frontend/tests/e2e/component-symbols.spec.ts @@ -63,6 +63,22 @@ function signalPort( }; } +function mechanicalPort( + name: string, + side: "left" | "right", + order: number, +) { + return { + name, + kind: "physical", + domain: "mechanical", + nominalRole: "bidirectional", + positiveFlowDirection: "intoComponent", + side, + order, + }; +} + function portsForSymbol(modelType: string) { if (modelType === "amesim_pnor001") { return [ @@ -80,6 +96,15 @@ function portsForSymbol(modelType: string) { if (modelType === "amesim_step0") { return [signalPort("out", "right", "output", 10)]; } + if (modelType === "amesim_pnrp17") { + return [ + physicalPort("port_1", "left", 10), + mechanicalPort("port_3", "left", 20), + mechanicalPort("port_4", "left", 30), + mechanicalPort("port_2", "right", 40), + mechanicalPort("port_5", "right", 50), + ]; + } return []; } @@ -277,6 +302,33 @@ test("全部已移植 AMESim 模型使用专属图标及正确领域配色", asy } }); +test("PNRP17 图标匹配参考结构并将五个端口放在对应接口", async ({ page }) => { + await page.goto("/"); + + const paletteSymbol = page.locator( + 'aside.palette [data-component-symbol="amesim_pnrp17"]', + ); + await expect(paletteSymbol).toHaveCount(1); + await expect( + paletteSymbol.locator('[stroke="#00af00"], [fill="#00af00"]'), + ).toHaveCount(0); + + const piston = await dragSymbolToCanvas(page, "amesim_pnrp17", { + x: 430, + y: 300, + }); + await expectSymbolAnchors(piston, [ + { portName: "port_1", x: 25.5, y: 48 }, + { portName: "port_3", x: 2, y: 4.4 }, + { portName: "port_4", x: 2, y: 24.1 }, + { portName: "port_2", x: 62, y: 4.4 }, + { portName: "port_5", x: 62, y: 24.1 }, + ]); + await expect( + piston.locator('.port-handle[data-port-name="port_1"]'), + ).toHaveClass(/react-flow__handle-bottom/); +}); + test("AMESim canvas nodes use icon anchors and highlight only compatible free ports", async ({ page, }) => { @@ -320,9 +372,9 @@ test("AMESim canvas nodes use icon anchors and highlight only compatible free po ]; await expectSymbolAnchors(firstOrifice, firstOrificeAnchors); await expectSymbolAnchors(signalOrifice, [ - { portName: "res", x: 2, y: 16 }, - { portName: "port_2", x: 2, y: 32 }, - { portName: "port_3", x: 62, y: 25 }, + { portName: "res", x: 26, y: 46 }, + { portName: "port_2", x: 2, y: 24 }, + { portName: "port_3", x: 62, y: 24 }, ]); await firstOrifice.locator(".node-symbol").click({ position: { x: 56, y: 42 } }); @@ -350,6 +402,7 @@ test("AMESim canvas nodes use icon anchors and highlight only compatible free po const signalInput = signalOrifice.locator( '.port-handle[data-port-name="res"]', ); + await expect(signalInput).toHaveClass(/react-flow__handle-bottom/); const pneumaticInput = signalOrifice.locator( '.port-handle[data-port-name="port_2"]', );