Merge pull request 'requriesments of .yml for bridge written' (#2) from qinyue_main into develope #7

Merged
PotatoMaxwell merged 14 commits from qinyue_main into develope 2026-06-04 12:02:34 +00:00
7 changed files with 567 additions and 11 deletions
Showing only changes of commit 7ac76aaee9 - Show all commits
+19 -3
View File
@@ -1,6 +1,6 @@
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# Description: Flask backend API server for authentication, project management, PDK library access, layout preview, and GDS build endpoints. # 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 # Developer : Qin Yue @ 2026
# Organization : OptiHK Limited # Organization : OptiHK Limited
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@@ -9,6 +9,7 @@ import os
import re import re
import shutil import shutil
import json import json
import uuid
import yaml import yaml
from collections import OrderedDict from collections import OrderedDict
from functools import wraps 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") 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): def cell_routes_path(project_name, cell_name):
"""Return the route sidecar JSON path for a project cell.""" """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") 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)) write_route_points_sidecar(content, cell_routes_path(project, cell))
svg_path = None svg_path = None
svg_version = None
preview_status = "not_requested" preview_status = "not_requested"
preview_error = None preview_error = None
if create_preview: if create_preview:
svg_path = cell_svg_path(project, cell) svg_path = cell_svg_path(project, cell)
temp_svg_path = f"{svg_path}.building-{os.getpid()}-{uuid.uuid4().hex}.svg"
try: try:
create_routed_layout_svg( create_routed_layout_svg(
content, content,
svg_path, temp_svg_path,
pdk_root=current_pdk_root(), pdk_root=current_pdk_root(),
project_dir=project_root(project), project_dir=project_root(project),
technology_manifest_path=technology_manifest_path_for_project(project), technology_manifest_path=technology_manifest_path_for_project(project),
prefer_full_gds=prefer_full_gds_for_session(session), 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" preview_status = "generated"
except RouterStackUnavailable as e: except RouterStackUnavailable as e:
preview_status = "skipped" preview_status = "skipped"
preview_error = str(e) preview_error = str(e)
svg_path = None 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}) record_action('layout.save', project=project, cell=cell, detail={"bytes": len(content), "svg": svg_path})
return jsonify({ return jsonify({
@@ -765,7 +779,9 @@ def save_layout():
"cell": cell, "cell": cell,
"path": save_path, "path": save_path,
"svg_path": svg_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_status": preview_status,
"preview_error": preview_error "preview_error": preview_error
}), 200 }), 200
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 319 KiB

@@ -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
Binary file not shown.
+25 -8
View File
@@ -3760,6 +3760,8 @@ Organization : OptiHK Limited
const initializedRef = useRef(false); const initializedRef = useRef(false);
const canvasViewportRef = useRef(null); const canvasViewportRef = useRef(null);
const buildLayoutRequestRef = useRef(0);
const buildLayoutBusyRef = useRef(false);
const edgeTypes = useMemo(() => ({ parallelRoute: ParallelRouteEdge }), []); const edgeTypes = useMemo(() => ({ parallelRoute: ParallelRouteEdge }), []);
const activePage = useMemo(() => pages.find(p => p.id === activePageId) || null, [pages, activePageId]); const activePage = useMemo(() => pages.find(p => p.id === activePageId) || null, [pages, activePageId]);
@@ -6208,49 +6210,64 @@ ${bundlesBlock}`;
// Save the active page, generate layout preview assets, and show the preview tab. // Save the active page, generate layout preview assets, and show the preview tab.
const handleBuildLayout = useCallback(async () => { const handleBuildLayout = useCallback(async () => {
if (!activePage) return; if (!activePage) return;
if (buildLayoutBusy) return; if (buildLayoutBusyRef.current) return;
if (!validateRouteCrossings(activePage)) return; if (!validateRouteCrossings(activePage)) return;
const buildPage = activePage;
const buildRequestId = buildLayoutRequestRef.current + 1;
buildLayoutRequestRef.current = buildRequestId;
buildLayoutBusyRef.current = true;
setBuildLayoutBusy(true); setBuildLayoutBusy(true);
startBuildProgress('Building layout'); startBuildProgress('Building layout');
const yamlContent = buildYamlForPage(activePage); const yamlContent = buildYamlForPage(buildPage);
const layoutBounds = calculateLayoutBounds(buildPage);
// send to backend // send to backend
try { try {
const response = await fetch('/api/save-layout', { const response = await fetch('/api/save-layout', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
cache: 'no-store',
body: JSON.stringify({ body: JSON.stringify({
project: currentProjectName, project: currentProjectName,
cell: activePage.name, cell: buildPage.name,
content: yamlContent, content: yamlContent,
}), }),
}); });
if (!response.ok) { if (!response.ok) {
const errData = await response.json(); const errData = await response.json().catch(() => ({}));
if (buildRequestId !== buildLayoutRequestRef.current) return;
addLog(errData.error || 'Save failed, unknown error'); addLog(errData.error || 'Save failed, unknown error');
stopBuildProgress(); stopBuildProgress();
return; return;
} }
const result = await response.json(); const result = await response.json();
if (buildRequestId !== buildLayoutRequestRef.current) return;
addLog('Successfully saved: ' + result.path); addLog('Successfully saved: ' + result.path);
if (result.preview_error) { if (result.preview_error) {
addLog('Preview skipped: ' + result.preview_error); addLog('Preview skipped: ' + result.preview_error);
} }
if (result.svg_url) { if (result.svg_ready && result.svg_url) {
completeBuildProgress('Layout ready'); completeBuildProgress('Layout ready');
openLayoutPreview(activePage.name, result.svg_url, calculateLayoutBounds(activePage)); openLayoutPreview(buildPage.name, result.svg_url, layoutBounds);
} else { } else {
if (result.preview_status === 'generated') {
addLog('Layout SVG was not marked ready by the backend.');
}
completeBuildProgress('Layout saved'); completeBuildProgress('Layout saved');
} }
} catch (err) { } catch (err) {
if (buildRequestId !== buildLayoutRequestRef.current) return;
addLog('Save error: ' + err.message); addLog('Save error: ' + err.message);
stopBuildProgress(); stopBuildProgress();
} finally { } finally {
setBuildLayoutBusy(false); if (buildRequestId === buildLayoutRequestRef.current) {
buildLayoutBusyRef.current = false;
setBuildLayoutBusy(false);
}
} }
}, [activePage, buildLayoutBusy, buildYamlForPage, currentProjectName, addLog, openLayoutPreview, validateRouteCrossings, startBuildProgress, completeBuildProgress, stopBuildProgress]); }, [activePage, buildYamlForPage, currentProjectName, addLog, openLayoutPreview, validateRouteCrossings, startBuildProgress, completeBuildProgress, stopBuildProgress]);
// Save YAML for every editable project/composite page without opening previews. // Save YAML for every editable project/composite page without opening previews.
const handleSaveProjectLayouts = useCallback(async () => { const handleSaveProjectLayouts = useCallback(async () => {
+12
View File
@@ -60,6 +60,18 @@ assert(
serverPy.includes('svg_url'), serverPy.includes('svg_url'),
'save-layout response should include an svg_url for the new layout tab' 'save-layout response should include an svg_url for the new layout tab'
); );
assert(
serverPy.includes('svg_ready') &&
serverPy.includes('svg_version') &&
serverPy.includes('file_version(svg_path)') &&
serverPy.includes("url_for('get_layout_svg', project_name=project, cell_name=cell, v=svg_version)"),
'save-layout response should only expose a versioned SVG URL after the preview file is ready'
);
assert(
serverPy.includes('temp_svg_path') &&
serverPy.includes('os.replace(temp_svg_path, svg_path)'),
'save-layout should publish generated SVG previews atomically instead of serving partially written files'
);
assert( assert(
serverPy.includes('RouterStackUnavailable') && serverPy.includes('RouterStackUnavailable') &&
serverPy.includes('except RouterStackUnavailable as e') && serverPy.includes('except RouterStackUnavailable as e') &&
+7
View File
@@ -32,6 +32,13 @@ assert(
canvasHtml.includes('svg_url'), canvasHtml.includes('svg_url'),
'Build Layout should use the backend svg_url response' 'Build Layout should use the backend svg_url response'
); );
assert(
canvasHtml.includes('result.svg_ready && result.svg_url') &&
canvasHtml.includes('buildLayoutRequestRef') &&
canvasHtml.includes('buildLayoutBusyRef') &&
canvasHtml.includes("cache: 'no-store'"),
'Build Layout should wait for a ready, versioned SVG response and prevent stale duplicate preview updates'
);
assert( assert(
canvasHtml.includes('result.preview_error') && canvasHtml.includes('result.preview_error') &&
canvasHtml.includes('Preview skipped: '), canvasHtml.includes('Preview skipped: '),