From e3f708a1a7e562a22bd88d2b7b0f39ca16a3eff0 Mon Sep 17 00:00:00 2001 From: PotatoMaxwell Date: Sat, 30 May 2026 16:37:37 +0800 Subject: [PATCH] routing for canvase level object is finished. --- .../__pycache__/layout_preview.cpython-39.pyc | Bin 5244 -> 5244 bytes .../admin/layout/mxpic_project_1/canvas_1.svg | 344 +++++ .../admin/layout/mxpic_project_1/canvas_1.yml | 243 ++++ .../mxpic_project_1/mxpic_project_1.svg | 1247 ++++++++++++----- .../mxpic_project_1/mxpic_project_1.yml | 173 +-- database/mxpic_data.db | Bin 86016 -> 98304 bytes frontend/canvas-helpers.js | 45 +- frontend/canvas.html | 72 +- tests/canvas-helpers.test.js | 53 +- tests/layout-ui-wiring.test.js | 37 + 10 files changed, 1647 insertions(+), 567 deletions(-) create mode 100644 database/admin/layout/mxpic_project_1/canvas_1.svg create mode 100644 database/admin/layout/mxpic_project_1/canvas_1.yml diff --git a/backend/__pycache__/layout_preview.cpython-39.pyc b/backend/__pycache__/layout_preview.cpython-39.pyc index 17964fa6557495352923baa0a473d6f2a89a3398..c36fde71ab89ab15e625e026783d709419db07b2 100644 GIT binary patch delta 22 ccmeyP@kfI + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/database/admin/layout/mxpic_project_1/canvas_1.yml b/database/admin/layout/mxpic_project_1/canvas_1.yml new file mode 100644 index 0000000..7e71e68 --- /dev/null +++ b/database/admin/layout/mxpic_project_1/canvas_1.yml @@ -0,0 +1,243 @@ +# ============================================= +# mxPIC Cell/Project Definition File +# ============================================= +schema_version: "2.0.0" +kind: cell +coordinate_system: gds_y_up +canvas_size: + width: 1000 + height: 1000 +project: mxpic_project_1 +name: canvas_1 +type: composite +version: "1.0.0" + +# 1. External Ports (How this cell connects to the outside world) +ports: +- name: port + layer: WG_CORE + x: 200.0 + y: -370.0 + angle: 180.0 + width: 0.5 +- name: port_2 + layer: WG_CORE + x: 200.0 + y: -370.0 + angle: 180.0 + width: 0.5 +- name: port_1 + layer: WG_CORE + x: 200.0 + y: -420.0 + angle: 180.0 + width: 0.5 +- name: port_3 + layer: WG_CORE + x: 691.9 + y: -267.5 + angle: 0.0 + width: 0.5 + +# 2. Instances (The sub-components dropped onto this canvas) +instances: + MMI_7: + component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/multimode_interferometers/1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2 + x: 310.0 + y: -370.0 + rotation: 0.0 + flip: 0 + flop: 0 + mirror: false + settings: + length: + + MMI_8: + component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/multimode_interferometers/1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2 + x: 560.0 + y: -130.0 + rotation: 0.0 + flip: 0 + flop: 0 + mirror: false + settings: + length: + + MMI_9: + component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/multimode_interferometers/1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2 + x: 560.0 + y: -180.0 + rotation: 0.0 + flip: 0 + flop: 0 + mirror: false + settings: + length: + + MMI_10: + component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/multimode_interferometers/1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2 + x: 560.0 + y: -230.0 + rotation: 0.0 + flip: 0 + flop: 0 + mirror: false + settings: + length: + + MMI_11: + component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/multimode_interferometers/1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2 + x: 560.0 + y: -280.0 + rotation: 0.0 + flip: 0 + flop: 0 + mirror: false + settings: + length: + + MMI_12: + component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/multimode_interferometers/1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2 + x: 310.0 + y: -420.0 + rotation: 0.0 + flip: 0 + flop: 0 + mirror: false + settings: + length: + +elements: + port: + type: port + x: 200.0 + y: -370.0 + angle: 0.0 + port_number: 1 + pitch: 10 + layer: WG_CORE + width: 0.5 + description: "" + Anchor_1: + type: anchor + x: 460.0 + y: -300.0 + angle: 90.0 + port_number: 4 + pitch: 10 + layer: WG_CORE + width: 0.5 + description: "" + port_2: + type: port + x: 200.0 + y: -370.0 + angle: 0.0 + port_number: 1 + pitch: 10 + layer: WG_CORE + width: 0.5 + description: "" + port_1: + type: port + x: 200.0 + y: -420.0 + angle: 0.0 + port_number: 1 + pitch: 10 + layer: WG_CORE + width: 0.5 + description: "" + port_3: + type: port + x: 691.9 + y: -267.5 + angle: 180.0 + port_number: 1 + pitch: 10 + layer: WG_CORE + width: 0.5 + description: "" + +# 3. Bundles (Grouped links for multi-bus/parallel routing) +bundles: + output_bus: + routing_type: euler_bend + links: + - from: MMI_7:a1 + to: port_2:port + xsection: strip + family: optical + width: 0.45 + radius: 10 + routing_type: euler_bend + - from: MMI_12:a1 + to: port_1:port + xsection: strip + family: optical + width: 0.45 + radius: 10 + routing_type: euler_bend + - from: MMI_7:b1 + to: Anchor_1:a1 + xsection: strip + family: optical + width: 0.45 + radius: 10 + routing_type: euler_bend + - from: MMI_7:b2 + to: Anchor_1:a2 + xsection: strip + family: optical + width: 0.45 + radius: 10 + routing_type: euler_bend + - from: MMI_12:b1 + to: Anchor_1:a3 + xsection: strip + family: optical + width: 0.45 + radius: 10 + routing_type: euler_bend + - from: MMI_12:b2 + to: Anchor_1:a4 + xsection: strip + family: optical + width: 0.45 + radius: 10 + routing_type: euler_bend + - from: Anchor_1:b4 + to: MMI_11:a1 + xsection: strip + family: optical + width: 0.45 + radius: 10 + routing_type: euler_bend + - from: Anchor_1:b3 + to: MMI_10:a1 + xsection: strip + family: optical + width: 0.45 + radius: 10 + routing_type: euler_bend + - from: Anchor_1:b2 + to: MMI_9:a1 + xsection: strip + family: optical + width: 0.45 + radius: 10 + routing_type: euler_bend + - from: Anchor_1:b1 + to: MMI_8:a1 + xsection: strip + family: optical + width: 0.45 + radius: 10 + routing_type: euler_bend + - from: MMI_11:b1 + to: port_3:port + xsection: strip + family: optical + width: 0.45 + radius: 10 + routing_type: euler_bend \ No newline at end of file diff --git a/database/admin/layout/mxpic_project_1/mxpic_project_1.svg b/database/admin/layout/mxpic_project_1/mxpic_project_1.svg index 710dfc1..1461f6e 100644 --- a/database/admin/layout/mxpic_project_1/mxpic_project_1.svg +++ b/database/admin/layout/mxpic_project_1/mxpic_project_1.svg @@ -1,393 +1,906 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Parameters: +x = 0.0 +y = 4.475 +a = 180.0 +width = 0.7 +name = a1 +Parameters: +x = 0.0 +y = -4.475 +a = 180.0 +width = 0.7 +name = a2 +Parameters: +x = 1660.0 +y = 4.475 +a = 360.0 +width = 0.7 +name = b1 +Parameters: +x = 1660.0 +y = -4.475 +a = 360.0 +width = 0.7 +name = b2 +Parameters: +x = 1660.0 +y = 25.0 +a = 0.0 +width = 10 +name = pin_p +Parameters: +x = 1660.0 +y = 55.0 +a = 0.0 +width = 10 +name = pin_n +Parameters: +x = 1660.0 +y = -25.0 +a = 0.0 +width = 10 +name = ht_s +Parameters: +x = 1660.0 +y = -55.0 +a = 0.0 +width = 10 +name = ht_g + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/database/admin/layout/mxpic_project_1/mxpic_project_1.yml b/database/admin/layout/mxpic_project_1/mxpic_project_1.yml index c0b6f47..54abceb 100644 --- a/database/admin/layout/mxpic_project_1/mxpic_project_1.yml +++ b/database/admin/layout/mxpic_project_1/mxpic_project_1.yml @@ -23,21 +23,10 @@ ports: # 2. Instances (The sub-components dropped onto this canvas) instances: - EC_1: - component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/edge_couplers/EC_SiN400_1310_1p0dB_L635_A0_QY_202604 - x: 0.0 - y: -2660.0 - rotation: 180.0 - flip: 0 - flop: 0 - mirror: false - settings: - length: - - MMI_1: - component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/multimode_interferometers/1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2 - x: 936.8 - y: -2358.5 + MZM_1: + component: Silterra/EMO1_2ML_CU_Al_RDL/composites/Mach_Zender_modulators/MZI_SiN400_Si220_PIN_mod_1310_L1300_QY_202603 + x: 1740.0 + y: -2350.0 rotation: 0.0 flip: 0 flop: 0 @@ -45,87 +34,10 @@ instances: settings: length: - MMI_2: - component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/multimode_interferometers/1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2 - x: 1089.2 - y: -2247.3 - rotation: 0.0 - flip: 0 - flop: 0 - mirror: false - settings: - length: - - MMI_3: - component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/multimode_interferometers/1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2 - x: 1096.8 - y: -2598.0 - rotation: 0.0 - flip: 0 - flop: 0 - mirror: false - settings: - length: - - MMI_4: - component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/multimode_interferometers/1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2 - x: 735.0 - y: -2541.1 - rotation: 90.0 - flip: 0 - flop: 0 - mirror: false - settings: - length: - - MMI_5: - component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/multimode_interferometers/1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2 - x: 1086.5 - y: -2097.1 - rotation: 0.0 - flip: 0 - flop: 0 - mirror: false - settings: - length: - - EC_2: - component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/edge_couplers/EC_SiN400_1310_1p0dB_L635_A0_QY_202604 - x: 0.0 - y: -2825.7 - rotation: 180.0 - flip: 0 - flop: 0 - mirror: false - settings: - length: - - MMI_6: - component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/multimode_interferometers/1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2 - x: 913.7 - y: -2537.3 - rotation: 90.0 - flip: 0 - flop: 0 - mirror: false - settings: - length: - - MMI_7: - component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/multimode_interferometers/1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2 - x: 1027.2 - y: -2736.7 - rotation: 0.0 - flip: 0 - flop: 0 - mirror: false - settings: - length: - - MMI_8: - component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/multimode_interferometers/1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2 - x: 842.6 - y: -2941.6 + canvas_1: + component: canvas_1 + x: 903.5 + y: -2681.6 rotation: 0.0 flip: 0 flop: 0 @@ -138,86 +50,27 @@ elements: type: port x: 50.0 y: -150.0 - angle: 0.0 + angle: 180.0 port_number: 1 pitch: 10 layer: WG_CORE width: 0.5 description: "" - anchor_1: - type: anchor - x: 732.7 - y: -2824.7 - angle: 0.0 - port_number: 5 - pitch: 10 - layer: WG_CORE - width: 0.5 - description: "" # 3. Bundles (Grouped links for multi-bus/parallel routing) bundles: output_bus: routing_type: euler_bend links: - - from: MMI_1:b1 - to: MMI_2:a1 + - from: canvas_1:port_1 + to: MZM_1:a1 xsection: strip family: optical width: 0.45 radius: 10 routing_type: euler_bend - - from: MMI_1:b2 - to: MMI_3:a1 - xsection: strip - family: optical - width: 0.45 - radius: 10 - routing_type: euler_bend - - from: EC_1:a1 - to: anchor_1:a1 - xsection: strip - family: optical - width: 0.45 - radius: 10 - routing_type: euler_bend - - from: anchor_1:b1 - to: MMI_4:a1 - xsection: strip - family: optical - width: 0.45 - radius: 10 - routing_type: euler_bend - - from: MMI_4:b2 - to: MMI_1:a1 - xsection: strip - family: optical - width: 0.45 - radius: 10 - routing_type: euler_bend - - from: MMI_5:a1 - to: MMI_4:b1 - xsection: strip - family: optical - width: 0.45 - radius: 10 - routing_type: euler_bend - - from: EC_2:a1 - to: anchor_1:a2 - xsection: strip - family: optical - width: 0.45 - radius: 10 - routing_type: euler_bend - - from: anchor_1:b2 - to: MMI_6:a1 - xsection: strip - family: optical - width: 0.45 - radius: 10 - routing_type: euler_bend - - from: MMI_8:b1 - to: MMI_7:a1 + - from: canvas_1:port_3 + to: MZM_1:a2 xsection: strip family: optical width: 0.45 diff --git a/database/mxpic_data.db b/database/mxpic_data.db index 44917c522807fb5086c06f3b01bc7adb23f79f70..6da1a61fd4f6188bcddcde554ee2f9c1893a67ec 100644 GIT binary patch delta 3124 zcmcguOKenS6u$q=o%c+KQp)uGw1BqYea_?ZDAV3Ti*2nT1alJ{YMIai>2unKmoWW9 zWMKm3C%F&;L4!fXNE#B^V6+ighzKr7Sm1+=L>DR>9}9`+|L@GSX=#lcHBgel9%K)p(UIivh|K}2rNJ}s%M}s-#tLWf zQt(3X%r#+SZ*VCHLpTb*%W*aQ7Gn3c4_-EYiDie4G#1du^ZE%Fw&qQ|{Pgy3Ia@-Q?Sw+sspIP_aL(396=q zjdU$`k-J~?Bav*lwaOe^j(DG?Q;igMnnU%d-PzMO+_NufPOd>Eg_cq_(v{fZ<2<3& z_4VmOb7utUX0QZp$vBpbbRFi+?Gbc7^FlDv)!3eWQOsVD&u>-E3oOfqh;1Z4UdOVr zo=Z{s9R2HaSra8=408nS*^?_q)`Y0hHgSY>{JowDX_6p>>h*~?Nc=)GuZCrjOrIaJ zLaLgoYBCKuad`2NmZfZrO&prR))E2)wG#*DVC%PZ07l)!0S1qoNkqlC{-_mIy0iea zyO`_dg2(e7=M4q^2-Nxy_^x_?^6H+i+ zk!7jw!Q^mFLsd;*|46F4zb9FvRn;(IVuo0S%mF1`H7?6KcqS7H!kQ#O6O2(@>IIV3 z*Vng@C)agXOh~+*?BA88q8QVJ71!ruZ->pZVHCAY=n!qj!K}${YEs(;3Da6S z2KxI_1H*}}eUc<3HYaIOon4)YS6e&DlLw&+YDYQM)16B09vI$FIW^cjkQ&&POeK5B z)R|DWWJp`4GllakO0z;lU`7`X%z!`_TTBzf#yIwq>TB)hstBqDWMC6QXGX9ugG`)K zji>B2^EFgTD*+1_nR4vS!t!5up9w+1_bdqh1{Ot$7<2)IxN}^vA@6eF`#{KV`tJG; zc|Y+y^_+4)a4W8T&P&eKjwJt%{i%JWErCqb!=0N1*@=3-tIrmq%iy-6;&hMh^4H9?dFghZE1rZcXYHRB-(8HA=3}oBE3Kk z#sDf*vRXC!5vCu51H&oH2)@YIqZv*>G=(k@C`P0k-mb`D3lyf2CAe+^yKdymy=A$) z6-|JRr{fpt8(vYxSPlW$ld7C?xn?0XOc+$XI#;#z2u@l$j2p<{o9iGCl{|WDFN;{f zTuQRhhwCk3l&=Nu)sd6qUUK9>9#4xS-;6HHAqe*k8nhO_U?ErzLpRIbyy!%HXC#YH zH2V>wAMrVOib7T`<}emn3mBxV2NcrjI~Jt0foz39_~-xs@IDqEro0QUBKbXfu4TYk zfLF&W=e1d)C;l*AL8g*B%wJ1U5$y(CFkq&R>!`U6nlb&+NY0l?rv;SHJzGgRjc~0Q zFGBmNFF}*pj;Wd1F=4oIF%8Cxj&T9H&jq7-gMp9zzxY@9Uh_WoHhT`br`;}BqjTJG zm4D1v+PB+Ip!-wl%ZLY#ykv1C#s;{Z+&fljDU+{T+*GZe5T{}5BSan3|r8V17#>W7HGXPONfxW{bP x*FQr;Z-wIzgIMnt_2qbfSViqx8mvDfSYToQ%xkjHSh?Ma4|4Kq-FyRX~zKbTW^F z+-9wS2L8>f{G&cdadSLm;BV$z&3lLEC(i+%9_}OD0bCuND>)u+Y&^`d{h { - const percent = fallbackPercent(index, ports.length); + const explicitPercent = Number(port.info && port.info.handlePercent); + const percent = Number.isFinite(explicitPercent) ? explicitPercent : fallbackPercent(index, ports.length); const percentValue = `${percent}%`; const style = vertical ? { top: percentValue, transform: side === 'left' ? 'translate(-50%, -50%)' : 'translate(50%, -50%)' } @@ -587,6 +588,24 @@ // Calculate the centered offset for one repeated port index. const elementPortOffset = (index, count, pitch) => ((count - 1) / 2 - index) * pitch; + // Keep Basic line-like components at twice the 10 px canvas port-circle size. + const BASIC_LINE_COMPONENT_HEIGHT = 20; + + // Keep Basic line-like components visually slim with a stable canvas hit area. + const basicLineComponentHeight = () => BASIC_LINE_COMPONENT_HEIGHT; + + // Keep bend components readable by using the radius-25 footprint as the + // minimum canvas size, while still allowing larger radii to grow. + const BASIC_BEND_MIN_CANVAS_RADIUS = 25; + + // Normalize bend radius into a positive canvas footprint dimension. + const basicBendRadiusSize = (radius) => { + const numericRadius = Number(radius || 0); + return Number.isFinite(numericRadius) && numericRadius > 0 + ? Math.max(BASIC_BEND_MIN_CANVAS_RADIUS, numericRadius) + : BASIC_BEND_MIN_CANVAS_RADIUS; + }; + // Grow port and anchor visual bodies so repeated port circles do not overlap. const buildElementBoxSize = (data) => { const portNumber = normalizePortNumber(data && data.portNumber); @@ -643,6 +662,7 @@ const values = createBasicSettings(componentName, settings); const length = Number(values.length || 0); const radius = Number(values.radius || 10); + const bendSize = basicBendRadiusSize(radius); const width = Number(values.width ?? values.width1 ?? 0.5); const xsection = values.xsection || values.xs || 'strip'; if (componentName === 'waveguide') { @@ -653,14 +673,14 @@ } if (componentName === '90 bend') { return { - a1: { x: 0, y: 0, a: 180, width, xsection, description: 'Optical power input' }, - b1: { x: radius, y: radius, a: 90, width, xsection, description: 'Optical power output' } + a1: { x: 0, y: bendSize / 2, a: 180, width, xsection, description: 'Optical power input' }, + b1: { x: bendSize / 2, y: 0, a: 90, width, xsection, description: 'Optical power output' } }; } if (componentName === '180 bend') { return { a1: { x: 0, y: 0, a: 180, width, xsection, description: 'Optical power input' }, - b1: { x: 0, y: 2 * radius, a: 180, width, xsection, description: 'Optical power output' } + b1: { x: 0, y: 2 * bendSize, a: 180, width, xsection, description: 'Optical power output' } }; } if (componentName === 'cricle' || componentName === 'circle') { @@ -686,13 +706,14 @@ const radius = Number(values.radius || 10); const width = Number(values.width ?? values.width1 ?? 0.5); const width2 = Number(values.width2 ?? width); + const bendSize = basicBendRadiusSize(radius); const boxSize = componentName === 'waveguide' - ? [Math.max(length, 10), Math.max(width * 4, 4)] + ? [Math.max(length, 10), basicLineComponentHeight(width)] : componentName === 'taper' - ? [Math.max(length, 10), Math.max(width, width2) * 10 + 18] + ? [Math.max(length, 10), basicLineComponentHeight(width, width2)] : componentName === '180 bend' - ? [radius, radius * 2] - : [radius, radius]; + ? [bendSize, bendSize * 2] + : [bendSize, bendSize]; return { name: componentName, foundry: 'mxpic', @@ -703,6 +724,10 @@ }; }; + // Flip an internal standalone Port angle into the outward-facing cell port + // angle used when this canvas is placed as a component elsewhere. + const externalPortAngle = (angle) => normalizeAngle(Number(angle ?? 0) + 180); + // Convert standalone port nodes into page-level layout ports. const buildPageComponentPorts = (port, nodes) => { const portNodes = (nodes || []).filter(isPortElementNode); @@ -723,7 +748,7 @@ ports[exportName] = { x: Number(point.x || 0), y: Number(point.y || 0), - a: Number(portInfo.a ?? data.angle ?? data.a ?? 0), + a: externalPortAngle(portInfo.a ?? data.angle ?? data.a ?? 0), width: Number(portInfo.width || data.width || 0.5) }; }); @@ -735,7 +760,7 @@ port: { x: Number(port.x || 0), y: Number(port.y || 0), - a: Number(port.a || 0), + a: externalPortAngle(port.a || 0), width: Number(port.width || 0.5) } }; diff --git a/frontend/canvas.html b/frontend/canvas.html index 886928d..c7074ca 100644 --- a/frontend/canvas.html +++ b/frontend/canvas.html @@ -1186,7 +1186,7 @@ Organization : OptiHK Limited border: 1px solid var(--floating-label-border); color: var(--port-label-text); box-shadow: 0 5px 12px rgba(0, 0, 0, 0.18); - font-size: 0.42rem; + font-size: 0.3rem; line-height: 1.2; font-family: 'IBM Plex Mono', Consolas, Monaco, monospace; white-space: nowrap; @@ -1278,6 +1278,10 @@ Organization : OptiHK Limited box-shadow: var(--floating-label-shadow); } + .canvas-text-hidden .component-floating-label { + display: none; + } + body.light-mode .port-name-label { background: var(--port-label-bg); border-color: var(--floating-label-border); @@ -1312,14 +1316,14 @@ Organization : OptiHK Limited } .component-floating-label strong { - font-size: 0.52rem; + font-size: 0.4rem; font-weight: 650; } .component-floating-label span { margin-top: 1px; color: var(--text-muted); - font-size: 0.44rem; + font-size: 0.32rem; } .canvas-size-panel { @@ -1379,7 +1383,7 @@ Organization : OptiHK Limited background: rgba(9, 18, 28, 0.94); color: #e2f7f3; box-shadow: 0 10px 24px rgba(0, 0, 0, 0.3); - font: 600 0.62rem/1.35 'IBM Plex Mono', Consolas, Monaco, monospace; + font: 600 0.5rem/1.35 'IBM Plex Mono', Consolas, Monaco, monospace; text-align: center; transform: translate(-50%, -50%); pointer-events: none; @@ -1594,7 +1598,7 @@ Organization : OptiHK Limited }, [id, data.ports, data.componentName, data.boxSize]); const baseHandleStyle = { - width: 10, height: 10, + width: 8, height: 8, background: 'var(--bg-main)', border: '2px solid var(--accent)', borderRadius: '50%', @@ -1611,6 +1615,7 @@ Organization : OptiHK Limited ); const componentSize = normalizeBoxSize({ box_size: data.boxSize }, DEFAULT_COMPONENT_BOX_SIZE); const isAnchorElement = data.elementType === 'anchor'; + const isBasicCompactComponent = isBasicComponent(data.componentName) && ['waveguide', 'taper', '90 bend'].includes(data.componentName); const visualSize = isAnchorElement ? { width: PORT_NODE_SIZE, height: PORT_NODE_SIZE } : componentSize; const iconSize = createComponentSymbolMetrics(componentSize); const portLabelStyle = (portHandle) => { @@ -1643,9 +1648,16 @@ Organization : OptiHK Limited style={{ width: componentSize.width, height: visualSize.height, + minHeight: visualSize.height, border: selected ? '2px solid var(--accent)' : '1px solid var(--border)', transform: `rotate(${data.rotation || 0}deg) scaleX(${data.flop ? -1 : 1}) scaleY(${data.flip ? -1 : 1})`, boxShadow: selected ? '0 0 15px rgba(56, 189, 248, 0.2)' : '0 4px 6px rgba(0,0,0,0.3)', + ...(isBasicCompactComponent ? { + padding: 0, + display: 'flex', + alignItems: 'center', + justifyContent: 'center' + } : {}), ...(isAnchorElement ? { width: PORT_NODE_SIZE, minHeight: PORT_NODE_SIZE, @@ -1658,7 +1670,7 @@ Organization : OptiHK Limited }} > {isAnchorElement ? ( - A + A ) : (
{!data.hideIcon && data.category && ( @@ -1733,8 +1745,8 @@ Organization : OptiHK Limited }; const baseHandleStyle = { background: 'var(--accent)', - width: 8, - height: 8 + width: 6, + height: 6 }; return (
@@ -1783,8 +1795,8 @@ Organization : OptiHK Limited bottom: Position.Bottom }; const baseHandleStyle = { - width: 8, - height: 8, + width: 6, + height: 6, background: 'var(--accent)', border: '1px solid var(--bg-main)', borderRadius: '50%' @@ -2729,6 +2741,7 @@ Organization : OptiHK Limited const selectedNodeBoxSize = selectedNode?.data?.componentName && !selectedNode?.data?.elementType ? normalizeBoxSize({ box_size: selectedNode.data?.boxSize }, DEFAULT_COMPONENT_BOX_SIZE) : null; + const xsections = Object.keys((technologyManifest || FALLBACK_TECHNOLOGY_MANIFEST).xsections || {}); const selectedRouteEdges = selectedEdges.length > 0 ? selectedEdges : (selectedEdge ? [selectedEdge] : []); if (selectedRouteEdges.length > 0) { @@ -2744,7 +2757,6 @@ Organization : OptiHK Limited radius: mixedValue('radius'), routing_type: mixedValue('routing_type') }; - const xsections = Object.keys((technologyManifest || FALLBACK_TECHNOLOGY_MANIFEST).xsections || {}); const routingTypes = (technologyManifest || FALLBACK_TECHNOLOGY_MANIFEST).routing_types || ['euler_bend', 'standard_bend']; return (
@@ -3461,6 +3487,7 @@ Organization : OptiHK Limited const [dragging, setDragging] = useState(null); const [gridSnap, setGridSnap] = useState(false); + const [canvasTextVisible, setCanvasTextVisible] = useState(true); const [themeMode, setThemeMode] = useState(() => localStorage.getItem('mxpic-theme') || 'dark'); const [logs, setLogs] = useState([{ time: new Date().toLocaleTimeString(), message: 'Editor ready.' }]); const [buildLayoutBusy, setBuildLayoutBusy] = useState(false); @@ -5463,6 +5490,11 @@ Organization : OptiHK Limited setGridSnap(prev => !prev); }, []); + // Toggle the instance name/PDK labels shown above canvas components. + const toggleCanvasText = useCallback(() => { + setCanvasTextVisible(prev => !prev); + }, []); + // Toggle the measurement ruler and clear partial measurements. const toggleRulerMode = useCallback(() => { setRulerMode(prev => { @@ -5999,6 +6031,9 @@ ${bundlesBlock}`; transition: 'transform 0.2s', }} />
+
Link @@ -6069,6 +6104,7 @@ ${bundlesBlock}`; ) : ( handle.name === 'a1').position, 'left'); +assert.strictEqual(ninetyBendHandles.find(handle => handle.name === 'a1').style.top, '50%'); +assert.strictEqual(ninetyBendHandles.find(handle => handle.name === 'b1').position, 'top'); +assert.strictEqual(ninetyBendHandles.find(handle => handle.name === 'b1').style.left, '50%'); assert.deepStrictEqual( helpers.getBasicComponentMetadata('180 bend', { radius: 15 }).box_size, - [15, 30], - '180 bend symbol should be one radius wide and two radii tall' + [25, 50], + '180 bend symbol should not shrink below the radius-25 canvas size' +); +assert.deepStrictEqual( + helpers.getBasicComponentMetadata('180 bend', { radius: 30 }).box_size, + [30, 60], + '180 bend symbol should still scale above radius 25' +); +assert.deepStrictEqual( + helpers.getBasicComponentMetadata('taper', { length: 80, width1: 0.4, width2: 1.2 }).box_size, + [80, 20], + 'basic taper symbol should use a height that is two port-circle diameters' ); assert.deepStrictEqual( helpers.getBasicComponentMetadata('taper', { length: 80, width1: 0.4, width2: 1.2 }).ports.a1.description, @@ -259,11 +287,11 @@ const pagePortsYaml = helpers.buildPortsYaml({ x: 50, y: 150, a: 90 }); assert(pagePortsYaml.includes('- name: port')); assert(pagePortsYaml.includes('x: 50.0')); assert(pagePortsYaml.includes('y: -150.0')); -assert(pagePortsYaml.includes('angle: 90.0')); +assert(pagePortsYaml.includes('angle: -90.0')); const componentPorts = helpers.buildPageComponentPorts({ x: 12, y: -6, a: 180 }); assert.deepStrictEqual(componentPorts, { - port: { x: 12, y: -6, a: 180, width: 0.5 } + port: { x: 12, y: -6, a: 0, width: 0.5 } }); const elementNodes = [ @@ -341,9 +369,9 @@ assert.deepStrictEqual( data: { componentDisplayName: 'array', elementType: 'port', portNumber: 3, pitch: 10, width: 0.6 } }]), { - array_1: { x: 100, y: 190, a: 0, width: 0.6 }, - array_2: { x: 100, y: 200, a: 0, width: 0.6 }, - array_3: { x: 100, y: 210, a: 0, width: 0.6 } + array_1: { x: 100, y: 190, a: 180, width: 0.6 }, + array_2: { x: 100, y: 200, a: 180, width: 0.6 }, + array_3: { x: 100, y: 210, a: 180, width: 0.6 } } ); @@ -352,6 +380,7 @@ assert(canvasPortsYaml.includes('name: in0')); assert(canvasPortsYaml.includes('description: "input port"')); assert(canvasPortsYaml.includes('width: 0.7')); assert(canvasPortsYaml.includes('y: -20.0')); +assert(canvasPortsYaml.includes('angle: 0.0')); const elementsYaml = helpers.buildElementsYaml(elementNodes); assert(elementsYaml.includes('in0:')); @@ -372,7 +401,7 @@ assert(instancesWithoutElements.includes('component_1:')); assert(instancesWithoutElements.includes('y: -60.0')); const multiPortComponentPorts = helpers.buildPageComponentPorts(null, elementNodes); -assert.deepStrictEqual(multiPortComponentPorts.in0, { x: 10, y: 20, a: 180, width: 0.7 }); +assert.deepStrictEqual(multiPortComponentPorts.in0, { x: 10, y: 20, a: 0, width: 0.7 }); const technologyManifest = { defaults: { xsection: 'strip', width: 0.45, radius: 10, routing_type: 'euler_bend' }, diff --git a/tests/layout-ui-wiring.test.js b/tests/layout-ui-wiring.test.js index cb4d799..d72a066 100644 --- a/tests/layout-ui-wiring.test.js +++ b/tests/layout-ui-wiring.test.js @@ -282,6 +282,15 @@ assert( canvasHtml.includes('component-floating-label') && canvasHtml.includes('component-visual-body'), 'component labels should float outside the rotated body' ); +assert( + canvasHtml.includes('canvasTextVisible') && + canvasHtml.includes('toggleCanvasText') && + canvasHtml.includes('Text On') && + canvasHtml.includes('Text Off') && + canvasHtml.includes('canvas-text-hidden') && + canvasHtml.includes('.canvas-text-hidden .component-floating-label'), + 'canvas toolbar should toggle instance name and PDK text above components' +); assert( canvasHtml.includes('--floating-label-bg') && canvasHtml.includes('--port-label-bg') && canvasHtml.includes('--mini-button-bg'), 'theme variables should keep labels, port chips, and header buttons readable in light and dark modes' @@ -400,6 +409,34 @@ assert( canvasHtml.includes("isUserCell ? 'compact-tree-card' : ''"), 'Basic, Port, and Anchor entries should render as consistent 2D cards instead of compact list rows' ); +assert( + canvasHtml.includes("key === 'xsection'") && + canvasHtml.includes('') && + canvasHtml.includes('updateBasicArgument(key, event.target.value)'), + 'Basic component xsection should be selected from technology manifest xsections instead of free text' +); +assert( + canvasHtml.indexOf('const xsections = Object.keys') < + canvasHtml.indexOf('if (selectedRouteEdges.length > 0)'), + 'Basic and route property panels should share the same xsection list from RightPanel scope' +); +assert( + canvasHtml.includes("['waveguide', 'taper', '90 bend'].includes(data.componentName)") && + canvasHtml.includes('minHeight: visualSize.height') && + canvasHtml.includes('isBasicCompactComponent ?'), + 'waveguide, taper, and 90 bend nodes should override the default component min-height and padding on the canvas' +); +assert( + canvasHtml.includes('font-size: 0.3rem;') && + canvasHtml.includes('font-size: 0.4rem;') && + canvasHtml.includes('font-size: 0.32rem;') && + canvasHtml.includes("font: 600 0.5rem/1.35") && + canvasHtml.includes('width: 8, height: 8') && + canvasHtml.includes('width: 6,') && + canvasHtml.includes('fontSize: 8'), + 'canvas labels and port circles should render smaller than the previous sizing' +); assert( canvasHtml.includes('ParallelRouteEdge') && canvasHtml.includes('parallelOffset') &&