diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a99879d --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*/__pycache__/ +__pycache__/ +*.pyc diff --git a/backend/__pycache__/database.cpython-311.pyc b/backend/__pycache__/database.cpython-311.pyc new file mode 100644 index 0000000..7e1cc63 Binary files /dev/null and b/backend/__pycache__/database.cpython-311.pyc differ diff --git a/backend/__pycache__/database.cpython-39.pyc b/backend/__pycache__/database.cpython-39.pyc index 0b75b5e..8379e13 100644 Binary files a/backend/__pycache__/database.cpython-39.pyc and b/backend/__pycache__/database.cpython-39.pyc differ diff --git a/backend/__pycache__/gds_builder.cpython-311.pyc b/backend/__pycache__/gds_builder.cpython-311.pyc new file mode 100644 index 0000000..0debd9a Binary files /dev/null and b/backend/__pycache__/gds_builder.cpython-311.pyc differ diff --git a/backend/__pycache__/gds_builder.cpython-39.pyc b/backend/__pycache__/gds_builder.cpython-39.pyc index 5258156..88a94ad 100644 Binary files a/backend/__pycache__/gds_builder.cpython-39.pyc and b/backend/__pycache__/gds_builder.cpython-39.pyc differ diff --git a/backend/__pycache__/pdk_access.cpython-311.pyc b/backend/__pycache__/pdk_access.cpython-311.pyc new file mode 100644 index 0000000..989f490 Binary files /dev/null and b/backend/__pycache__/pdk_access.cpython-311.pyc differ diff --git a/backend/__pycache__/pdk_access.cpython-39.pyc b/backend/__pycache__/pdk_access.cpython-39.pyc index 9c788b4..4e34003 100644 Binary files a/backend/__pycache__/pdk_access.cpython-39.pyc and b/backend/__pycache__/pdk_access.cpython-39.pyc differ diff --git a/backend/__pycache__/routed_layout_preview.cpython-311.pyc b/backend/__pycache__/routed_layout_preview.cpython-311.pyc new file mode 100644 index 0000000..cf5e986 Binary files /dev/null and b/backend/__pycache__/routed_layout_preview.cpython-311.pyc differ diff --git a/backend/__pycache__/routed_layout_preview.cpython-39.pyc b/backend/__pycache__/routed_layout_preview.cpython-39.pyc index 041666a..53d3582 100644 Binary files a/backend/__pycache__/routed_layout_preview.cpython-39.pyc and b/backend/__pycache__/routed_layout_preview.cpython-39.pyc differ diff --git a/backend/__pycache__/router_dependency.cpython-311.pyc b/backend/__pycache__/router_dependency.cpython-311.pyc new file mode 100644 index 0000000..801b363 Binary files /dev/null and b/backend/__pycache__/router_dependency.cpython-311.pyc differ diff --git a/backend/__pycache__/router_dependency.cpython-39.pyc b/backend/__pycache__/router_dependency.cpython-39.pyc index c8af590..348bbbf 100644 Binary files a/backend/__pycache__/router_dependency.cpython-39.pyc and b/backend/__pycache__/router_dependency.cpython-39.pyc differ diff --git a/backend/__pycache__/technology_manifest.cpython-311.pyc b/backend/__pycache__/technology_manifest.cpython-311.pyc new file mode 100644 index 0000000..7179b5e Binary files /dev/null and b/backend/__pycache__/technology_manifest.cpython-311.pyc differ diff --git a/backend/__pycache__/technology_manifest.cpython-39.pyc b/backend/__pycache__/technology_manifest.cpython-39.pyc index a833997..e3d2424 100644 Binary files a/backend/__pycache__/technology_manifest.cpython-39.pyc and b/backend/__pycache__/technology_manifest.cpython-39.pyc differ diff --git a/backend/server.py b/backend/server.py index 1d422d7..7b3d2f9 100644 --- a/backend/server.py +++ b/backend/server.py @@ -1,6 +1,6 @@ # ----------------------------------------------------------------------------- # Description: Flask backend API server for authentication, project management, PDK library access, layout preview, and GDS build endpoints. -# Inside functions: no_cache_response, login_required_json, wrapper, request_ip, record_action, safe_name, user_layout_root, project_root, cell_file_path, cell_svg_path, cell_routes_path, write_route_points_sidecar, project_gds_path, technology_manifest_path_for_project, current_pdk_root, scoped_pdk_root_for_project, pdk_root_for_request_project, project_meta_path, read_project_meta +# Inside functions: no_cache_response, login_required_json, wrapper, request_ip, record_action, safe_name, user_layout_root, project_root, cell_file_path, cell_svg_path, file_version, cell_routes_path, write_route_points_sidecar, project_gds_path, technology_manifest_path_for_project, current_pdk_root, scoped_pdk_root_for_project, pdk_root_for_request_project, project_meta_path, read_project_meta # Developer : Qin Yue @ 2026 # Organization : OptiHK Limited # ----------------------------------------------------------------------------- @@ -9,6 +9,7 @@ import os import re import shutil import json +import uuid import yaml from collections import OrderedDict from functools import wraps @@ -135,6 +136,12 @@ def cell_svg_path(project_name, cell_name): return os.path.join(project_root(project_name), f"{safe_name(cell_name, 'canvas_1')}.svg") +def file_version(path): + """Return a cache-busting version token for a completed file.""" + stat = os.stat(path) + return f"{stat.st_mtime_ns}-{stat.st_size}" + + def cell_routes_path(project_name, cell_name): """Return the route sidecar JSON path for a project cell.""" return os.path.join(project_root(project_name), f"{safe_name(cell_name, 'canvas_1')}.routes.yml") @@ -739,24 +746,31 @@ def save_layout(): write_route_points_sidecar(content, cell_routes_path(project, cell)) svg_path = None + svg_version = None preview_status = "not_requested" preview_error = None if create_preview: svg_path = cell_svg_path(project, cell) + temp_svg_path = f"{svg_path}.building-{os.getpid()}-{uuid.uuid4().hex}.svg" try: create_routed_layout_svg( content, - svg_path, + temp_svg_path, pdk_root=current_pdk_root(), project_dir=project_root(project), technology_manifest_path=technology_manifest_path_for_project(project), prefer_full_gds=prefer_full_gds_for_session(session), ) + os.replace(temp_svg_path, svg_path) + svg_version = file_version(svg_path) preview_status = "generated" except RouterStackUnavailable as e: preview_status = "skipped" preview_error = str(e) svg_path = None + finally: + if os.path.exists(temp_svg_path): + os.remove(temp_svg_path) record_action('layout.save', project=project, cell=cell, detail={"bytes": len(content), "svg": svg_path}) return jsonify({ @@ -765,7 +779,9 @@ def save_layout(): "cell": cell, "path": save_path, "svg_path": svg_path, - "svg_url": url_for('get_layout_svg', project_name=project, cell_name=cell) if svg_path else None, + "svg_url": url_for('get_layout_svg', project_name=project, cell_name=cell, v=svg_version) if svg_path else None, + "svg_ready": bool(svg_path and svg_version), + "svg_version": svg_version, "preview_status": preview_status, "preview_error": preview_error }), 200 diff --git a/database/_exports/3ff6e2b43f0d43dab8e169a0b8d6c3d0/mxpic_project_1.gds b/database/_exports/3ff6e2b43f0d43dab8e169a0b8d6c3d0/mxpic_project_1.gds new file mode 100644 index 0000000..ad6500b Binary files /dev/null and b/database/_exports/3ff6e2b43f0d43dab8e169a0b8d6c3d0/mxpic_project_1.gds differ diff --git a/database/admin/layout/mxpic_project_1/canvas_1.svg b/database/admin/layout/mxpic_project_1/canvas_1.svg new file mode 100644 index 0000000..a416194 --- /dev/null +++ b/database/admin/layout/mxpic_project_1/canvas_1.svg @@ -0,0 +1,293 @@ + + \ 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..17d83e6 --- /dev/null +++ b/database/admin/layout/mxpic_project_1/canvas_1.yml @@ -0,0 +1,211 @@ +# ============================================= +# mxPIC Cell/Project Definition File +# ============================================= +schema_version: "2.0.0" +kind: cell +coordinate_system: gds_y_up +canvas_size: + width: 500 + height: 600 +project: mxpic_project_1 +name: canvas_1 +type: composite +version: "1.0.0" + +# 1. External Ports (How this cell connects to the outside world) +pins: +- name: port_io1 + layer: WG_CORE + element: port + pin: io1 + x: 40.0 + y: -90.0 + angle: 180.0 + width: 0.5 +- name: port_1_io1 + layer: WG_CORE + element: port_1 + pin: io1 + x: 410.0 + y: -35.0 + angle: 0.0 + width: 0.5 +- name: port_1_io2 + layer: WG_CORE + element: port_1 + pin: io2 + x: 410.0 + y: -25.0 + angle: 0.0 + width: 0.5 +- name: port_2_io1 + layer: WG_CORE + element: port_2 + pin: io1 + x: 390.0 + y: -215.0 + angle: 0.0 + width: 0.5 +- name: port_2_io2 + layer: WG_CORE + element: port_2 + pin: io2 + x: 390.0 + y: -205.0 + angle: 0.0 + width: 0.5 + +# 2. Instances (The sub-components dropped onto this canvas) +instances: + MMI_1: + component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/multimode_interferometers/1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2 + x: 130.0 + y: -90.0 + rotation: 0.0 + flip: 0 + flop: 0 + mirror: false + settings: + length: + + MMI_2: + component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/multimode_interferometers/1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2 + x: 280.0 + y: -30.0 + 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: 320.1 + y: -144.7 + rotation: 0.0 + flip: 0 + flop: 0 + mirror: false + settings: + length: + +elements: + port: + type: port + x: 40.0 + y: -90.0 + angle: 180.0 + pin_number: 1 + pitch: 10 + layer: WG_CORE + width: 0.5 + description: "" + pins: + - name: port_io1 + role: io1 + port: + type: port + x: 40.0 + y: -90.0 + angle: 0.0 + pin_number: 1 + pitch: 10 + layer: WG_CORE + width: 0.5 + description: "" + pins: + - name: port_io1 + role: io1 + port_1: + type: port + x: 410.0 + y: -30.0 + angle: 180.0 + pin_number: 2 + pitch: 10 + layer: WG_CORE + width: 0.5 + description: "" + pins: + - name: port_1_io1 + role: io1 + - name: port_1_io2 + role: io2 + port_2: + type: port + x: 390.0 + y: -210.0 + angle: 180.0 + pin_number: 2 + pitch: 10 + layer: WG_CORE + width: 0.5 + description: "" + pins: + - name: port_2_io1 + role: io1 + - name: port_2_io2 + role: io2 + +# 3. Bundles (Grouped links for multi-bus/parallel routing) +bundles: + output_bus: + routing_type: euler_bend + links: + - from: MMI_1:a1 + to: port:port_io1 + xsection: strip + family: optical + width: 0.45 + radius: 10 + routing_type: euler_bend + - from: MMI_2:a1 + to: MMI_1:b1 + xsection: strip + family: optical + width: 0.45 + radius: 10 + routing_type: euler_bend + - from: MMI_3:a1 + to: MMI_1:b2 + xsection: strip + family: optical + width: 0.45 + radius: 10 + routing_type: euler_bend + - from: MMI_2:b1 + to: port_1:port_1_io1 + xsection: strip + family: optical + width: 0.45 + radius: 10 + routing_type: euler_bend + - from: MMI_2:b1 + to: port_1:port_1_io2 + xsection: strip + family: optical + width: 0.45 + radius: 10 + routing_type: euler_bend + - from: MMI_2:b2 + to: port_1:port_1_io1 + xsection: strip + family: optical + width: 0.45 + radius: 10 + routing_type: euler_bend + - from: MMI_3:b1 + to: port_2:port_2_io2 + xsection: strip + family: optical + width: 0.45 + radius: 10 + routing_type: euler_bend + - from: MMI_3:b2 + to: port_2:port_2_io1 + xsection: strip + family: optical + width: 0.45 + radius: 10 + routing_type: euler_bend \ No newline at end of file diff --git a/database/engineer/layout/mxpic_project_1/canvas_1.svg b/database/engineer/layout/mxpic_project_1/canvas_1.svg new file mode 100644 index 0000000..da409e3 --- /dev/null +++ b/database/engineer/layout/mxpic_project_1/canvas_1.svg @@ -0,0 +1,21 @@ + + \ No newline at end of file diff --git a/database/engineer/layout/mxpic_project_1/canvas_1.yml b/database/engineer/layout/mxpic_project_1/canvas_1.yml new file mode 100644 index 0000000..f890271 --- /dev/null +++ b/database/engineer/layout/mxpic_project_1/canvas_1.yml @@ -0,0 +1,100 @@ +# ============================================= +# mxPIC Cell/Project Definition File +# ============================================= +schema_version: "2.0.0" +kind: cell +coordinate_system: gds_y_up +canvas_size: + width: 5000 + height: 5000 +project: mxpic_project_1 +name: canvas_1 +type: composite +version: "1.0.0" + +# 1. External Ports (How this cell connects to the outside world) +pins: +- name: port_io1 + layer: WG_CORE + element: port + pin: io1 + x: 50.0 + y: -150.0 + angle: 180.0 + width: 0.5 + +# 2. Instances (The sub-components dropped onto this canvas) +instances: + waveguide_1: + component: waveguide + x: 686.5 + y: -1027.9 + rotation: 0.0 + flip: 0 + flop: 0 + mirror: false + settings: + length: 100 + width: 0.5 + xsection: "strip" + + circle_1: + component: circle + x: 877.2 + y: -1093.7 + rotation: 0.0 + flip: 0 + flop: 0 + mirror: false + settings: + radius: 10 + width: 0.5 + xsection: "strip" + + waveguide_2: + component: waveguide + x: 858.0 + y: -1029.6 + rotation: 0.0 + flip: 0 + flop: 0 + mirror: false + settings: + length: 100 + width: 0.5 + xsection: "strip" + +elements: + port: + type: port + x: 50.0 + y: -150.0 + angle: 0.0 + pin_number: 1 + pitch: 10 + layer: WG_CORE + width: 0.5 + description: "" + pins: + - name: port_io1 + role: io1 + +# 3. Bundles (Grouped links for multi-bus/parallel routing) +bundles: + output_bus: + routing_type: euler_bend + links: + - from: waveguide_1:b1 + to: waveguide_2:a1 + xsection: strip + family: optical + width: 0.5 + radius: 10 + routing_type: euler_bend + - from: waveguide_2:b1 + to: circle_1:a1 + xsection: strip + family: optical + width: 0.5 + radius: 10 + routing_type: euler_bend \ No newline at end of file diff --git a/database/engineer/layout/mxpic_project_1/canvas_2.svg b/database/engineer/layout/mxpic_project_1/canvas_2.svg new file mode 100644 index 0000000..30ca134 --- /dev/null +++ b/database/engineer/layout/mxpic_project_1/canvas_2.svg @@ -0,0 +1,24 @@ + + \ No newline at end of file diff --git a/database/engineer/layout/mxpic_project_1/canvas_2.yml b/database/engineer/layout/mxpic_project_1/canvas_2.yml new file mode 100644 index 0000000..6d415b5 --- /dev/null +++ b/database/engineer/layout/mxpic_project_1/canvas_2.yml @@ -0,0 +1,136 @@ +# ============================================= +# mxPIC Cell/Project Definition File +# ============================================= +schema_version: "2.0.0" +kind: cell +coordinate_system: gds_y_up +canvas_size: + width: 5000 + height: 5000 +project: mxpic_project_1 +name: canvas_2 +type: composite +version: "1.0.0" + +# 1. External Ports (How this cell connects to the outside world) +pins: +- name: port_io1 + layer: WG_CORE + element: port + pin: io1 + x: 50.0 + y: -150.0 + angle: 0.0 + width: 0.5 +- name: port_2_io1 + layer: WG_CORE + element: port_2 + pin: io1 + x: 1442.1 + y: -1470.0 + angle: 180.0 + width: 10 +- name: port_3_io1 + layer: WG_CORE + element: port_3 + pin: io1 + x: 2024.3 + y: -1609.0 + angle: 180.0 + width: 0.5 + +# 2. Instances (The sub-components dropped onto this canvas) +instances: + waveguide_3: + component: waveguide + x: 1581.0 + y: -1633.5 + rotation: 0.0 + flip: 0 + flop: 0 + mirror: false + settings: + length: 100 + width: 15 + xsection: "strip" + +elements: + port: + type: port + x: 50.0 + y: -150.0 + angle: 0.0 + pin_number: 1 + pitch: 10 + layer: WG_CORE + width: 0.5 + description: "" + pins: + - name: port_io1 + role: io1 + port: + type: port + x: 50.0 + y: -150.0 + angle: 180.0 + pin_number: 1 + pitch: 10 + layer: WG_CORE + width: 0.5 + description: "" + pins: + - name: port_io1 + role: io1 + port_2: + type: port + x: 1442.1 + y: -1470.0 + angle: 0.0 + pin_number: 1 + pitch: 10 + layer: WG_CORE + width: 10 + description: "" + pins: + - name: port_2_io1 + role: io1 + port_3: + type: port + x: 2024.3 + y: -1609.0 + angle: 0.0 + pin_number: 1 + pitch: 10 + layer: WG_CORE + width: 0.5 + description: "" + pins: + - name: port_3_io1 + role: io1 + +# 3. Bundles (Grouped links for multi-bus/parallel routing) +bundles: + output_bus: + routing_type: euler_bend + links: + - from: waveguide_3:a1 + to: port_2:port_2_io1 + xsection: strip + family: optical + width: 15 + radius: 10 + routing_type: euler_bend + - from: waveguide_3:b1 + to: port_2:port_2_io1 + xsection: strip + family: optical + width: 15 + radius: 10 + routing_type: euler_bend + - from: port_3:port_3_io1 + to: port_2:port_2_io1 + xsection: strip + family: optical + width: 0.5 + radius: 10 + routing_type: euler_bend \ No newline at end of file diff --git a/database/engineer/layout/mxpic_project_1/mxpic_project_1.svg b/database/engineer/layout/mxpic_project_1/mxpic_project_1.svg index ba8ac45..ddcf788 100644 --- a/database/engineer/layout/mxpic_project_1/mxpic_project_1.svg +++ b/database/engineer/layout/mxpic_project_1/mxpic_project_1.svg @@ -1,4 +1,5 @@ +<<<<<<< HEAD \ No newline at end of file diff --git a/database/engineer/layout/mxpic_project_1/mxpic_project_1.yml b/database/engineer/layout/mxpic_project_1/mxpic_project_1.yml index a4bc821..6b6986a 100644 --- a/database/engineer/layout/mxpic_project_1/mxpic_project_1.yml +++ b/database/engineer/layout/mxpic_project_1/mxpic_project_1.yml @@ -13,6 +13,7 @@ type: project version: "1.0.0" # 1. External Ports (How this cell connects to the outside world) +<<<<<<< HEAD ports: [] # 2. Instances (The sub-components dropped onto this canvas) @@ -21,6 +22,47 @@ instances: component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/multimode_interferometers/1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2 x: 1511.5 y: -2531.5 +======= +pins: +- name: port_1_io1 + layer: WG_CORE + element: port_1 + pin: io1 + x: 1699.6 + y: -1844.2 + angle: 180.0 + width: 0.5 + +# 2. Instances (The sub-components dropped onto this canvas) +instances: + circle_1: + component: circle + x: 1877.6 + y: -1816.7 + rotation: 90.0 + flip: 0 + flop: 0 + mirror: false + settings: + radius: 10 + width: 0.5 + xsection: "strip" + + BD_1: + component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/bendings/SiN_EUB_1310_H400_w2500_L45_QY_202604 + x: 1926.2 + y: -1813.9 + rotation: 90.0 + flip: 0 + flop: 0 + mirror: false + settings: + length: + + DC_1: + component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/directional_couplers/DC_SiN400_99_1_1310_jyh_quantex_202603 + x: 1766.5 + y: -1945.3 rotation: 0.0 flip: 0 flop: 0 @@ -28,10 +70,51 @@ instances: settings: length: + MZM_1: + component: Silterra/EMO1_2ML_CU_Al_RDL/composites/Mach_Zender_modulators/MZI_SiN400_Si220_PIN_mod_1310_L1300_QY_202603 + x: 1341.6 + y: -2103.8 + rotation: 0.0 + flip: 0 + flop: 0 + mirror: false + settings: + length: + + phase_shifter_1: + component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/phase_shifters/HT_150R_SiPPP_L500_100OHM_DUMMY_QY_202604 + x: 2695.0 + y: -2275.0 + rotation: 90.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: 2849.0 + y: -1988.6 +>>>>>>> jingwen_main + rotation: 0.0 + flip: 0 + flop: 0 + mirror: false + settings: + length: + +<<<<<<< HEAD MMI_2: component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/multimode_interferometers/1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2 x: 1716.4 y: -2293.8 +======= + DC_2: + component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/directional_couplers/DC_SiN400_99_1_1310_jyh_quantex_202603 + x: 2656.9 + y: -1992.8 +>>>>>>> jingwen_main rotation: 0.0 flip: 0 flop: 0 @@ -39,17 +122,48 @@ instances: settings: length: +<<<<<<< HEAD elements: {} +======= + PD_1: + component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/photodetectors/PD_1310_Monitor_Si220_Ge500_NPN_XHN_202604 + x: 3151.7 + y: -2032.1 + rotation: 0.0 + flip: 0 + flop: 0 + mirror: false + settings: + length: + +elements: + port_1: + type: port + x: 1699.6 + y: -1844.2 + angle: 0.0 + pin_number: 1 + pitch: 10 + layer: WG_CORE + width: 0.5 + description: "" + pins: + - name: port_1_io1 + role: io1 +>>>>>>> jingwen_main # 3. Bundles (Grouped links for multi-bus/parallel routing) bundles: output_bus: routing_type: euler_bend links: +<<<<<<< HEAD - from: MMI_2:a1 to: MMI_1:b2 xsection: strip family: optical width: 0.45 radius: 10 - routing_type: euler_bend \ No newline at end of file + routing_type: euler_bend +======= +>>>>>>> jingwen_main diff --git a/database/mxpic_data.db b/database/mxpic_data.db index 817af67..d2b26c6 100644 Binary files a/database/mxpic_data.db and b/database/mxpic_data.db differ diff --git a/GDS_SVG_GENERATION_LOGIC.md b/docs/GDS_SVG_GENERATION_LOGIC.md similarity index 100% rename from GDS_SVG_GENERATION_LOGIC.md rename to docs/GDS_SVG_GENERATION_LOGIC.md diff --git a/INTRANET_DEPLOYMENT.md b/docs/INTRANET_DEPLOYMENT.md similarity index 100% rename from INTRANET_DEPLOYMENT.md rename to docs/INTRANET_DEPLOYMENT.md diff --git a/PDK_TECHNOLOGY_XSECTION_LOADING.md b/docs/PDK_TECHNOLOGY_XSECTION_LOADING.md similarity index 100% rename from PDK_TECHNOLOGY_XSECTION_LOADING.md rename to docs/PDK_TECHNOLOGY_XSECTION_LOADING.md diff --git a/frontend/canvas-helpers.js b/frontend/canvas-helpers.js index 3b09dcb..16cdde1 100644 --- a/frontend/canvas-helpers.js +++ b/frontend/canvas-helpers.js @@ -42,11 +42,14 @@ name: 'Anchor', elementType: 'anchor', ports: { - a1: { x: 0, y: -PORT_NODE_SIZE / 2, a: 180, width: 0.5 }, - b1: { x: 0, y: -PORT_NODE_SIZE / 2, a: 0, width: 0.5 } + a1: { x: 0, y: 0, a: 180, width: 0.5 }, + b1: { x: 0, y: 0, a: 0, width: 0.5 } } } }; + + + // Defines local primitive components that do not require PDK lookup. const BASIC_COMPONENTS = { waveguide: { @@ -804,16 +807,13 @@ } }; } - if (portNumber > 1) { - const entries = []; - Array.from({ length: portNumber }, (_, index) => { - const y = elementPortOffset(index, portNumber, pitch); - entries.push([`a${index + 1}`, { x: 0, y, a: 180, width }]); - entries.push([`b${index + 1}`, { x: 0, y, a: 0, width }]); - }); - return Object.fromEntries(entries); - } - return JSON.parse(JSON.stringify(element.ports)); + const entries = []; + Array.from({ length: portNumber }, (_, index) => { + const y = elementPortOffset(index, portNumber, pitch); + entries.push([`a${index + 1}`, { x: 0, y, a: 180, width }]); + entries.push([`b${index + 1}`, { x: 0, y, a: 0, width }]); + }); + return Object.fromEntries(entries); }; // Generate port metadata for built-in primitive components. @@ -984,6 +984,20 @@ ${pinLines}`; return `elements:\n${lines.join('\n')}`; }; + const finiteNumberOrNull = (value) => { + const number = Number(value); + return Number.isFinite(number) ? number : null; + }; + + const getRouteEndpointWidth = (node, handleId) => { + if (!node || !node.data) return null; + const dataWidth = finiteNumberOrNull(node.data.width); + if (dataWidth !== null) return dataWidth; + const ports = node.data.ports || {}; + const portWidth = ports[handleId] ? finiteNumberOrNull(ports[handleId].width) : null; + return portWidth; + }; + // Serialize canvas links into routed bundle YAML including route settings and bend points. const buildBundlesYaml = (page, manifest) => { const { nodes = [], edges = [] } = page || {}; @@ -1004,6 +1018,9 @@ ${pinLines}`; ? getElementPinName(targetNode, edge.targetHandle) : edge.targetHandle || 'unknown'; const route = createRouteSettings(manifest, edge.data && edge.data.route); + const routeWidth = getRouteEndpointWidth(sourceNode, edge.sourceHandle) + ?? getRouteEndpointWidth(targetNode, edge.targetHandle) + ?? route.width; const storedPoints = Array.isArray(edge.data && edge.data.points) ? edge.data.points : []; const points = storedPoints.length >= 2 ? getEdgeRoutePoints(edge, nodeMap) : []; const pointsYaml = points.length > 0 @@ -1014,7 +1031,7 @@ ${pinLines}`; return ` - id: ${toYamlScalar(edge.id)} xsection: ${route.xsection} family: ${route.family} - width: ${Number(route.width)} + width: ${Number(routeWidth)} radius: ${Number(route.radius)} routing_type: ${route.routing_type}${pointsYaml}`; } @@ -1022,7 +1039,7 @@ ${pinLines}`; to: ${targetName}:${toPort} xsection: ${route.xsection} family: ${route.family} - width: ${Number(route.width)} + width: ${Number(routeWidth)} radius: ${Number(route.radius)} routing_type: ${route.routing_type}${pointsYaml}`; }); diff --git a/frontend/canvas.html b/frontend/canvas.html index 9acde81..e3e3b48 100644 --- a/frontend/canvas.html +++ b/frontend/canvas.html @@ -1622,7 +1622,7 @@ Organization : OptiHK Limited // Displays a category icon with cached loading and graceful failure behavior. - const IconImg = memo(({ category, containerStyle }) => { + const IconImg = memo(({ category, containerStyle, objectFit: imgObjectFit }) => { const [src, setSrc] = useState(() => { if (!category) return undefined; const cache = fetchIcon(category); @@ -1671,7 +1671,7 @@ Organization : OptiHK Limited style={{ width: '100%', height: '100%', - objectFit: 'fill', + objectFit: imgObjectFit || 'fill', pointerEvents: 'none', }} onError={(e) => { @@ -1764,42 +1764,46 @@ Organization : OptiHK Limited width: componentSize.width, height: visualSize.height, minHeight: visualSize.height, + overflow: 'hidden', + ...(visualSize.height < 50 && !isAnchorElement ? { padding: '2px 4px' } : {}), border: selected ? '2px solid var(--accent)' : '1px solid var(--border)', - transform: componentVisualTransform, 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, - padding: 0, - borderRadius: '50%', - display: 'flex', - alignItems: 'center', - justifyContent: 'center' - } : {}), - }} - > - {isAnchorElement ? ( - A - ) : ( -