Merged from jingwen_main for image icon revision

This commit is contained in:
=
2026-06-04 20:00:13 +08:00
30 changed files with 1514 additions and 98 deletions
+218
View File
@@ -0,0 +1,218 @@
# GDS and SVG Generation Logic Path
This document traces the current code path for generating saved layout YAML,
layout preview SVG, and downloadable project GDS when the user clicks
`Build Layout` or `Build GDS`.
Line numbers refer to the files as currently checked in.
## Build-Time Router Gate
`python backend/server.py` -> `app.run` starts without importing
`mxpic_router`, `mxpic_forge`, Nazca, or gdstk. Login, dashboard, canvas
editing, YAML generation, PDK browsing, and project save without preview do not
require the external build stack.
Build actions validate the external stack on demand:
- `Build GDS` -> `backend/gds_builder.py` -> `require_router_stack()`.
- `Build Layout` SVG preview -> `backend/routed_layout_preview.py` ->
`require_router_stack(require_gdstk=True)`.
Important functions:
- `ensure_router_path` (`backend/router_dependency.py` line 23) adds the sibling
`../mxpic_router` checkout to `sys.path` when present.
- `require_router_stack` (`backend/router_dependency.py` line 31) imports
`mxpic_router`, `nazca`, and the route backend used by `mxpic_router`.
The preferred route backend is `mxpic_forge.Route`; if it is absent,
`mxpic_router` falls back to Nazca `interconnects.Interconnect`.
`gdstk` is checked only when SVG preview generation requests it.
## Generated Files
- Saved cell YAML: `database/<username>/layout/<project>/<cell>.yml`
- Path helpers: `user_layout_root`, `project_root`, `cell_file_path`
(`backend/server.py` lines 124-137).
- Saved layout preview SVG: `database/<username>/layout/<project>/<cell>.svg`
- Path helper: `cell_svg_path` (`backend/server.py` lines 140-142).
- Optional route sidecar: `database/<username>/layout/<project>/<cell>.routes.yml`
- Path helper and writer: `cell_routes_path`, `write_route_points_sidecar`
(`backend/server.py` lines 145-175).
- Downloadable GDS export: `database/_exports/<uuid>/<project>.gds`
- Created by `create_export_path` (`backend/pdk_access.py` lines 53-59).
## Build Layout: Click Button -> YAML -> Router GDS -> SVG
Compact path:
`Click Build Layout` -> `handleBuildLayout` (`frontend/canvas.html` line 6209) :
`buildYamlForPage` (`frontend/canvas.html` line 6141) generates layout YAML ->
POST `/api/save-layout` (`frontend/canvas.html` line 6219) -> `save_layout`
(`backend/server.py` line 715) writes `<cell>.yml` -> `create_routed_layout_svg`
(`backend/server.py` line 734) -> `mxpic_router.build_project_gds` generates
temporary Nazca GDS -> `gdstk.read_gds` + `write_svg` writes `<cell>.svg` ->
backend returns `svg_url` -> `openLayoutPreview` (`frontend/canvas.html` line
6183) opens the SVG preview tab.
Detailed path:
1. The button is rendered only for non-preview pages:
- `<button onClick={handleBuildLayout}>` (`frontend/canvas.html` lines
6491-6498).
2. `handleBuildLayout` creates YAML for the active page:
- `handleBuildLayout` starts at `frontend/canvas.html` line 6209.
- It validates route crossings and calls `buildYamlForPage(activePage)` at
line 6215.
- `buildYamlForPage` starts at line 6141 and writes:
- `schema_version`, `kind`, `coordinate_system: gds_y_up`,
`canvas_size`, `project`, `name`, `type`, `version`.
- `pins` via `buildCanvasPinsYaml`.
- `instances` via `buildInstancesYaml`.
- `elements` via `buildElementsYaml`.
- `bundles` via `buildBundlesYaml`.
3. Frontend sends the YAML to Flask:
- `fetch('/api/save-layout', ...)` at `frontend/canvas.html` line 6219.
- Body includes `project`, `cell`, and `content` at lines 6222-6226.
4. Flask saves YAML and route sidecar:
- Route: `/api/save-layout` (`backend/server.py` line 713).
- Function: `save_layout` (`backend/server.py` line 715).
- `save_path = cell_file_path(project, cell)` (`backend/server.py` line 724).
- Writes the YAML content to disk (`backend/server.py` lines 727-728).
- Extracts route points into `<cell>.routes.yml` through
`write_route_points_sidecar` (`backend/server.py` line 729).
5. Flask always uses the router-backed SVG preview:
- Preview output path is computed with `cell_svg_path`
(`backend/server.py` line 733).
- `create_routed_layout_svg(...)` is called unconditionally for preview
generation (`backend/server.py` lines 734-742).
- There is no production branch based on `bundles.*.links`; no-link and
linked layouts both use the same router path.
6. Routed preview helper converts router GDS into SVG:
- `create_routed_layout_svg` starts at `backend/routed_layout_preview.py`
line 14.
- It loads the YAML string, gets the target cell name, and imports
`mxpic_router.build_project_gds` (`backend/routed_layout_preview.py` lines
25-32).
- It builds a temporary GDS with `target_cell_name=cell_name`
(`backend/routed_layout_preview.py` lines 37-46).
- It reads that temporary GDS with `gdstk.read_gds`
(`backend/routed_layout_preview.py` line 47).
- It writes the saved preview SVG with `top_cells[0].write_svg(output_path)`
(`backend/routed_layout_preview.py` line 51).
Inside sibling router checkout:
- `mxpic_router.build_project_gds` starts at
`../mxpic_router/mxpic_router/builder.py` line 12.
- `_load_project_specs` reads all saved `.yml` / `.yaml` files from the project
directory (`../mxpic_router/mxpic_router/builder.py` line 46).
- `load_cell_spec` reads YAML with `yaml.safe_load`
(`../mxpic_router/mxpic_router/eda_loader.py` line 80).
- `parse_cell_dict` converts YAML into `CellSpec`, including `pins`,
`elements`, `instances`, and `bundles`
(`../mxpic_router/mxpic_router/eda_loader.py` lines 85-157).
- `_build_cell` creates `with nd.Cell(name=spec.name) as top`
(`../mxpic_router/mxpic_router/builder.py` line 70).
- `_build_cell` places basic, local, or PDK GDS instances and registers pins
(`../mxpic_router/mxpic_router/builder.py` lines 73-108).
- `_route_link` draws routed geometry for each bundle link when links exist
(`../mxpic_router/mxpic_router/builder.py` line 269).
- `nd.export_gds(topcells=[built_cells[top.name]], filename=output_path)`
writes the temporary routed GDS (`../mxpic_router/mxpic_router/builder.py`
line 37).
## Build GDS: Click Button -> Saved YAML -> Router GDS
Compact path:
`Click Build GDS` -> Project Tree button calls `onBuildGds`
(`frontend/canvas.html` line 2738) -> `handleBuildGds`
(`frontend/canvas.html` line 6287) -> POST `/api/build-gds`
(`frontend/canvas.html` line 6294) -> `build_gds`
(`backend/server.py` line 775) -> `build_project_gds`
(`backend/gds_builder.py` line 26) : read saved `.yml` files for project
validation -> `_build_with_mxpic_router` (`backend/gds_builder.py` line 47) ->
`mxpic_router` creates `nd.Cell` objects and exports `.gds` -> backend returns
`download_url` -> frontend clicks a temporary download link.
Detailed path:
1. The `Build GDS` button is in the project tree header:
- `<button className="build-gds-btn" onClick={onBuildGds}>`
(`frontend/canvas.html` lines 2738-2739).
- `onBuildGds={handleBuildGds}` is passed into `LeftPanel`
(`frontend/canvas.html` line 6354).
2. `handleBuildGds` calls the backend:
- Function starts at `frontend/canvas.html` line 6287.
- It validates route crossings for all non-preview pages
(`frontend/canvas.html` lines 6289-6290).
- It POSTs only `{ project: currentProjectName }` to `/api/build-gds`
(`frontend/canvas.html` lines 6294-6298).
Important behavior:
- `Build GDS` does not serialize the current in-memory canvas first.
- It reads YAML files that already exist on disk.
- Those YAML files are produced by `Build Layout` or by the separate `Save`
button, where `handleSaveProjectLayouts` saves every non-preview page with
`preview: false` (`frontend/canvas.html` lines 6253-6284).
3. Flask creates a temporary export path and calls the GDS builder:
- Route: `/api/build-gds` (`backend/server.py` line 773).
- Function: `build_gds` (`backend/server.py` line 775).
- Old exports are cleaned (`backend/server.py` line 781).
- The project directory is resolved and validated
(`backend/server.py` lines 782-784).
- `create_export_path` creates `database/_exports/<uuid>/<project>.gds`
(`backend/server.py` line 785, `backend/pdk_access.py` lines 53-59).
- `build_project_gds(...)` is called (`backend/server.py` lines 786-792).
4. `backend/gds_builder.py` delegates to `mxpic_router`:
- `build_project_gds` starts at `backend/gds_builder.py` line 26.
- `_load_project_cells(project_dir)` reads every saved `.yml` / `.yaml`
file for validation (`backend/gds_builder.py` line 73).
- `_build_with_mxpic_router` starts at `backend/gds_builder.py` line 47.
- `_build_with_mxpic_router` calls `ensure_router_path` and imports
`mxpic_router.build_project_gds` (`backend/gds_builder.py` lines 54-55).
- There is no production fallback to local gdstk or local Nazca builders.
5. `mxpic_router` builds the final GDS:
- `mxpic_router.build_project_gds` starts at
`../mxpic_router/mxpic_router/builder.py` line 12.
- `_build_cell` creates one `nd.Cell` per `CellSpec`
(`../mxpic_router/mxpic_router/builder.py` line 70).
- `_resolve_pdk_asset` finds component YAML/GDS in the active PDK root
(`../mxpic_router/mxpic_router/builder.py` line 509).
- `_route_link` draws routed geometry when saved bundle links exist
(`../mxpic_router/mxpic_router/builder.py` line 269).
- `nd.export_gds(...)` writes the output GDS
(`../mxpic_router/mxpic_router/builder.py` line 37).
## PDK Asset Resolution
The active PDK root still comes from the EDA backend request/session state:
- `current_pdk_root` uses `pdk_root_for_session`
(`backend/server.py` lines 196-198).
- `pdk_root_for_session` chooses the root based on the logged-in user group
(`backend/pdk_access.py` lines 43-50).
- Preview and GDS generation pass that PDK root into `mxpic_router`.
- `mxpic_router._resolve_pdk_asset` resolves component YAML/GDS under that root
(`../mxpic_router/mxpic_router/builder.py` line 509).
## Download Completion
After GDS build:
`build_gds` returns `download_url` (`backend/server.py` line 803) ->
`handleBuildGds` creates a temporary `<a>` and clicks it
(`frontend/canvas.html` lines 6309-6316) -> `/api/exports/<export_id>/<filename>`
serves the GDS (`backend/server.py` lines 812-829) -> the temporary export
folder is deleted when the response closes (`backend/server.py` line 829).
+84
View File
@@ -0,0 +1,84 @@
<!--
Description: Project documentation for setup, deployment, and operational guidance.
Inside functions: N/A - documentation content.
Developer : Qin Yue @ 2026
Organization : OptiHK Limited
-->
# mxPIC EDA Intranet Deployment
## Build-Time Router Stack
The Flask server can launch for login, dashboard, canvas editing, YAML
generation, and PDK browsing without importing `mxpic_router` or `mxpic_forge`.
Build actions require `mxpic_router` and Nazca. When `mxpic_forge.Route` is not
available, routing falls back to Nazca `interconnects.Interconnect`; SVG preview
generation also requires `gdstk`.
## Start on the office LAN
1. On the host computer, open PowerShell in this repository.
2. Set a persistent secret key:
```powershell
$env:MXPIC_SECRET_KEY = "replace-with-a-long-random-secret"
```
3. Start the server:
```powershell
.\run_intranet_server.ps1
```
The app listens on `0.0.0.0:3000`, so other users can open:
```text
http://<host-computer-ip>:3000
```
Find the host IP with:
```powershell
ipconfig
```
Use the IPv4 address on the company LAN adapter.
## Windows firewall
If coworkers cannot connect, allow inbound TCP port `3000` on the host computer.
## Accounts
Default local accounts:
```text
admin / 123456
engineer / 123456
```
Change these passwords from the dashboard profile panel before regular use.
Each user stores projects under:
```text
database/<username>/layout
```
## Useful environment variables
```text
MXPIC_HOST=0.0.0.0
MXPIC_PORT=3000
MXPIC_DEBUG=0
MXPIC_SECRET_KEY=<long random string>
MXPIC_COOKIE_SECURE=0
MXPIC_PDK_PUBLIC_ROOT=<path-to-public-foundries>
MXPIC_PDK_ATLAS_ROOT=<path-to-atlas-foundries>
```
Set `MXPIC_COOKIE_SECURE=1` only when serving through HTTPS.
PDK component metadata, GDS assets, and `technology.yml` manifests are all read
from these role-scoped roots. If the variables are not set, the backend defaults
to `../opt_pdk_public/foundries` for normal users/developers and
`../opt_pdk_atlas/foundries` for managers.
+581
View File
@@ -0,0 +1,581 @@
# PDK, Technology, and Xsection Loading Logic Path
This document explains how the current code loads:
- PDK component information: component folders, component YAML, images, and GDS assets.
- Technology information: selected foundry/technology for each project.
- Xsection information: routing cross-section defaults and Nazca layer bindings from `technology.yml`.
Line numbers refer to the current codebase at the time this document was written.
## Mental Model
There are three related but separate data paths.
1. Component PDK assets
- Source: role-controlled PDK trees:
- `opt_pdk_public/foundries`
- `opt_pdk_atlas/foundries`
- Controlled by user group through `pdk_root_for_session`
(`backend/pdk_access.py` line 43).
- Used for library browsing, component metadata, component images, and final
component GDS resolution.
2. Technology registry and manifests
- Source: the same role-controlled PDK trees as component assets.
- Path: `<active_role_pdk_root>/<foundry>/<technology>/technology.yml`.
- Used to list available technologies, save the project technology choice,
load route defaults, and register Nazca layers/xsections.
3. Saved project layout YAML
- Source: `database/<username>/layout/<project>/<cell>.yml`.
- Contains placed component paths and `bundles.*.links[*].xsection`.
- Consumed by `mxpic_router` when building SVG preview GDS or downloadable GDS.
Important distinction:
- The active role PDK root is now the single source of truth.
- Normal/developer users read from `opt_pdk_public/foundries`.
- Manager users read from `opt_pdk_atlas/foundries`.
- The selected project technology scopes the library browser and selects which
`technology.yml` is loaded from that same role root.
## Active Roots
Backend startup defines the important paths:
- `REPO_ROOT` (`backend/server.py` line 36) points two levels above
`backend/`.
- There is no separate technology-manifest copy inside `mxpic_EDA`.
Role-based PDK root:
`Flask session user_group` -> `current_pdk_root`
(`backend/server.py` line 196) -> `pdk_root_for_session`
(`backend/pdk_access.py` line 43) -> `pdk_root_for_group`
(`backend/pdk_access.py` line 27) : choose:
- manager -> `MXPIC_PDK_ATLAS_ROOT` or `../opt_pdk_atlas/foundries`.
- developers/user -> `MXPIC_PDK_PUBLIC_ROOT` or `../opt_pdk_public/foundries`.
## Technology Selection Path
Compact path:
`Dashboard opens` -> `loadTechnologies` (`frontend/dashboard.html` line 1238)
-> GET `/api/technologies` -> `list_technologies`
(`backend/server.py` line 441) : scan `current_pdk_root()` ->
return `{ foundry, technology, id, label }` -> dashboard fills the technology
select.
Detailed path:
1. The dashboard calls `/api/technologies`:
- `loadTechnologies` starts at `frontend/dashboard.html` line 1238.
- It stores `data.technologies` and creates `<option>` values using
`tech.id`, for example `Silterra/EMO1_2ML_CU_Al_RDL`.
2. The backend scans the technology registry:
- Route: `/api/technologies` (`backend/server.py` line 439).
- Function: `list_technologies` (`backend/server.py` line 441).
- It scans `current_pdk_root()`.
- A technology directory is exposed only when it contains `technology.yml`.
- For each valid directory pair it returns:
- `foundry`
- `technology`
- `id: foundry/technology`
- `label: foundry / technology`
3. Creating a project stores the selected technology:
- Dashboard create click posts to `/api/projects`
(`frontend/dashboard.html` line 1303).
- Body contains `{ name, technology }`.
- Backend route: `/api/projects` (`backend/server.py` line 603).
- Function: `create_project` (`backend/server.py` line 605).
- It writes `.project.json` with:
- `name`
- `technology`
- Metadata helper path: `project_meta_path`
(`backend/server.py` line 228).
- Metadata writer: `write_project_meta`
(`backend/server.py` line 242).
Saved metadata location:
`database/<username>/layout/<project>/.project.json`
Example:
```json
{
"name": "mxpic_project_1",
"technology": "Silterra/EMO1_2ML_CU_Al_RDL"
}
```
## Technology Manifest Loading Path
Compact path:
`Canvas opens project` -> `loadProject` (`frontend/canvas.html` line 5030)
-> GET `/api/projects/<project>` -> `get_project`
(`backend/server.py` line 632) : returns saved `technology` -> frontend
`loadTechnologyManifest` (`frontend/canvas.html` line 4056) -> GET
`/api/technologies/<foundry>/<technology>/manifest` -> `read_technology_manifest`
(`backend/technology_manifest.py` line 27) -> return full `technology.yml`
manifest.
Detailed path:
1. Canvas loads project metadata:
- `loadProject` fetches `/api/projects/<project>`
(`frontend/canvas.html` line 5033).
- Backend `get_project` returns:
- project name
- saved cells
- `technology: read_project_meta(project_name).get("technology")`
(`backend/server.py` line 653).
2. Canvas loads the selected technology manifest:
- `loadTechnologyManifest` starts at `frontend/canvas.html` line 4056.
- It expects technology IDs in `foundry/technology` format.
- It fetches:
`/api/technologies/<foundry>/<technology>/manifest`.
- If missing or invalid, it logs an error and uses
`FALLBACK_TECHNOLOGY_MANIFEST`.
3. Backend validates and returns the manifest:
- Route:
`/api/technologies/<foundry>/<technology>/manifest`
(`backend/server.py` line 465).
- Function: `get_technology_manifest`
(`backend/server.py` line 467).
- It calls `read_technology_manifest`
(`backend/technology_manifest.py` line 27).
- The expected path is built by `technology_manifest_path`
(`backend/technology_manifest.py` line 16):
```text
<active_role_pdk_root>/<foundry>/<technology>/technology.yml
```
4. Backend manifest validation:
- `technology.yml` must exist.
- `manifest.xsections` must be a dictionary.
- `manifest.defaults` must be a dictionary.
- Otherwise the API returns 404 with a `TechnologyManifestError`.
## Current `technology.yml` Structure
Active example:
`opt_pdk_public/foundries/Silterra/EMO1_2ML_CU_Al_RDL/technology.yml`
For manager sessions, the equivalent file is read from
`opt_pdk_atlas/foundries`.
Important sections:
- `schema_version` (`technology.yml` line 7).
- `foundry` (`technology.yml` line 8).
- `technology` (`technology.yml` line 9).
- `layers` (`technology.yml` line 18).
- `routing_types` (`technology.yml` line 41).
- `defaults` (`technology.yml` line 44).
- `xsections` (`technology.yml` line 50).
Current xsections:
- `strip` (`technology.yml` line 51).
- `rib_low` (`technology.yml` line 58).
- `metal_1` (`technology.yml` line 67).
- `metal_2` (`technology.yml` line 74).
Meaning of the main fields:
- `layers`: maps technology layer names to GDS layer/datatype pairs.
- `routing_types`: choices shown by the route editor.
- `defaults`: default route `xsection`, `family`, `width`, `radius`, and
`routing_type`.
- `xsections`: per-cross-section defaults and layer bindings.
- `constants` and `source_class`: currently kept as manifest metadata. The
active frontend and router code do not consume them directly.
## Frontend Xsection Usage
Compact path:
`technology.yml xsections` -> backend manifest API -> `technologyManifest`
state (`frontend/canvas.html` line 3756) -> route editor dropdown and route
defaults -> saved YAML `bundles.output_bus.links[*].xsection`.
Detailed path:
1. Fallback manifest:
- `FALLBACK_TECHNOLOGY_MANIFEST` is defined in
`frontend/canvas-helpers.js` line 105.
- It includes fallback `strip`, `rib_low`, `metal_1`, and `metal_2`
xsections.
2. Canvas state:
- `projectTechnology` state is declared at `frontend/canvas.html` line 3755.
- `technologyManifest` state is declared at `frontend/canvas.html` line 3756.
3. Route defaults:
- `getXsectionInfo` (`frontend/canvas-helpers.js` line 132) looks up
`manifest.xsections[xsection]`.
- `createRouteSettings` (`frontend/canvas-helpers.js` line 138) merges:
- manifest defaults
- selected xsection defaults
- user overrides saved on the edge
- Resulting route state always has:
- `xsection`
- `family`
- `width`
- `radius`
- `routing_type`
4. Changing xsection in the route editor:
- Right panel builds available xsections from
`Object.keys(technologyManifest.xsections)`
(`frontend/canvas.html` line 2999).
- The xsection select calls `updateRouteXsection`
(`frontend/canvas.html` line 3037).
- `updateRouteXsection` refreshes family, default width, and default radius
(`frontend/canvas-helpers.js` line 167).
5. Link toolbar choices:
- `linkXsectionChoices` is built from `technologyManifest.xsections`
(`frontend/canvas.html` line 3775).
- It prefers the order `strip`, `rib_low`, `metal_1`, `metal_2`, then any
extra xsections.
## Component PDK Library Loading Path
Compact path:
`Canvas needs library` -> `fetchLibrary` (`frontend/canvas.html` line 4576)
-> GET `/api/library?project=<project>` -> `pdk_root_for_request_project`
(`backend/server.py` line 222) -> `scoped_pdk_root_for_project`
(`backend/server.py` line 201) -> `findComps`
(`backend/server.py` line 262) -> frontend library tree.
Detailed path:
1. Frontend requests the library:
- `fetchLibrary` calls `/api/library?project=<currentProjectName>`
(`frontend/canvas.html` line 4576).
2. Backend chooses the PDK root for this request:
- Route: `/api/library` (`backend/server.py` line 854).
- Function: `getLib` uses `pdk_root_for_request_project`.
- `pdk_root_for_request_project` starts at `backend/server.py` line 222.
- If a project is supplied, it calls `scoped_pdk_root_for_project`.
- Otherwise it uses `current_pdk_root`.
3. Project scoping:
- `scoped_pdk_root_for_project` starts at `backend/server.py` line 201.
- It reads the project `.project.json` technology field.
- It splits the value into `foundry/technology`.
- It scopes the role root to:
```text
<active_role_pdk_root>/<foundry>/<technology>
```
- It only returns that scoped path if it exists and stays under the active
role root.
- If not valid, it falls back to the active role root.
4. Component scanning:
- `findComps` starts at `backend/server.py` line 262.
- It walks the chosen PDK directory.
- Any folder containing a `.yml` file is treated as one component leaf.
- It stores component metadata:
- folder path
- first component YAML filename
- category
- relative component path
- `addCompsToTree` (`backend/server.py` line 290) converts this flat map
into the nested library tree returned to the canvas.
Important detail:
`getLib` scans the project-scoped directory but calls:
```python
findComps(comps_root, current_pdk_root())
```
This means the visible library is limited to the selected project technology,
but each component `__path__` is stored relative to the base role PDK root, not
relative to the scoped technology directory.
For example, if the active role PDK root is:
```text
opt_pdk_public/foundries
```
and the project technology is:
```text
Silterra/EMO1_2ML_CU_Al_RDL
```
a component path saved into layout YAML can include:
```text
Silterra/EMO1_2ML_CU_Al_RDL/<category>/<component>/<component>
```
This is important because build and preview pass the base role PDK root into
`mxpic_router`, and the saved component path can still resolve under that base
root.
## Component Metadata and Image Loading
Component YAML path:
`Canvas load/drop component` -> `loadComponentMetadata`
(`frontend/canvas.html` line 4082) -> GET
`/api/component/<component_name>?project=<project>` -> `readCompYaml`
(`backend/server.py` line 345) -> return component YAML.
Details:
- Route: `/api/component/<component_name>` (`backend/server.py` line 869).
- The backend uses the same project-scoped PDK root logic as `/api/library`.
- `readCompYaml` walks the selected PDK root and matches the component folder
basename.
- If the PDK root is under `opt_pdk_public` or `opt_pdk_atlas`, legacy
component metadata with `ports` is normalized to `pins`.
Component image path:
`Canvas thumbnail` -> `/api/component/<component_name>/image?project=<project>`
-> `find_component_dir` (`backend/server.py` line 367) -> first
`.png`, `.jpg`, `.jpeg`, or `.svg` in the component folder.
Route:
- `/api/component/<component_name>/image` (`backend/server.py` line 878).
## Saved YAML: How PDK Paths and Xsections Are Written
Compact path:
`Build Layout` or `Save` -> `buildYamlForPage`
(`frontend/canvas.html` line 6141) -> `findComponentPath`
(`frontend/canvas.html` line 3659) -> `buildInstancesYaml`
(`frontend/canvas-helpers.js` line 665) -> saved `instances.*.component`.
Component path details:
- `findComponentPath` searches the loaded library tree.
- If a component node has `__path__`, that path is used.
- `buildInstanceYaml` (`frontend/canvas-helpers.js` line 643) writes:
```yaml
instances:
<instance_name>:
component: <component path from library>
```
- Forge and basic components are special:
- Forge components save the forge component type.
- Basic components save names such as `waveguide`, `90 bend`, or `taper`.
- Normal PDK components save the PDK library path.
Xsection path details:
`Canvas edges` -> `buildBundlesYaml` (`frontend/canvas-helpers.js` line 988)
-> `createRouteSettings` (`frontend/canvas-helpers.js` line 138) -> saved
bundle links:
```yaml
bundles:
output_bus:
routing_type: euler_bend
links:
- from: <instance>:<pin>
to: <instance>:<pin>
xsection: strip
family: optical
width: 0.45
radius: 10
routing_type: euler_bend
```
The saved `xsection` field is the bridge from frontend route editing into
`mxpic_router` and Nazca.
## Build-Time Technology and PDK Loading
Both SVG preview and final GDS now use the router-backed path.
### SVG Preview
Compact path:
`POST /api/save-layout` -> `save_layout` (`backend/server.py` line 715)
-> `create_routed_layout_svg` (`backend/routed_layout_preview.py` line 14)
-> `mxpic_router.build_project_gds` -> temporary GDS -> `gdstk.write_svg`.
Arguments passed from `save_layout`:
- `pdk_root=current_pdk_root()` (`backend/server.py` line 737).
- `technology_manifest_path=technology_manifest_path_for_project(project)`
(`backend/server.py` line 739).
- `prefer_full_gds=prefer_full_gds_for_session(session)`
(`backend/server.py` line 740).
The preview helper:
- Imports `mxpic_router.build_project_gds`
(`backend/routed_layout_preview.py` line 32).
- Builds a temporary GDS with the same PDK root and technology manifest path
(`backend/routed_layout_preview.py` lines 39-45).
- Reads that GDS with `gdstk`.
- Writes the SVG preview (`backend/routed_layout_preview.py` line 51).
### Final GDS
Compact path:
`POST /api/build-gds` -> `build_gds` (`backend/server.py` line 775)
-> `backend.gds_builder.build_project_gds` (`backend/gds_builder.py` line 26)
-> `_build_with_mxpic_router` (`backend/gds_builder.py` line 47)
-> `mxpic_router.build_project_gds`.
Arguments passed from `build_gds`:
- `current_pdk_root()` (`backend/server.py` line 789).
- `technology_manifest_path=technology_manifest_path_for_project(project)`
(`backend/server.py` line 790).
- `prefer_full_gds=prefer_full_gds_for_session(session)`
(`backend/server.py` line 791).
`technology_manifest_path_for_project`:
- Starts at `backend/server.py` line 183.
- Reads the project technology from `.project.json`.
- Builds:
```text
<active_role_pdk_root>/<foundry>/<technology>/technology.yml
```
- Returns the path only if it exists and stays under `current_pdk_root()`.
## Inside `mxpic_router`
Compact path:
`mxpic_router.build_project_gds` (`../mxpic_router/mxpic_router/builder.py`
line 12) -> `load_technology_manifest`
(`../mxpic_router/mxpic_router/technology.py` line 6) ->
`apply_technology_manifest` (`../mxpic_router/mxpic_router/technology.py`
line 13) -> Nazca `nd.add_layer`, `nd.add_xsection`,
`nd.add_layer2xsection`.
Detailed path:
1. Build starts:
- `build_project_gds` starts at
`../mxpic_router/mxpic_router/builder.py` line 12.
- It imports Nazca as `nd`.
- It loads the manifest from `technology_manifest_path`
(`../mxpic_router/mxpic_router/builder.py` line 23).
- It applies the manifest to Nazca
(`../mxpic_router/mxpic_router/builder.py` line 24).
2. Technology manifest to Nazca:
- `load_technology_manifest` returns `{}` if the path is missing
(`../mxpic_router/mxpic_router/technology.py` line 6).
- `apply_technology_manifest` does nothing for an empty manifest.
- For each `manifest.layers` entry it calls:
```python
nd.add_layer(name=name, layer=(layer, datatype), overwrite=True)
```
- For each `manifest.xsections` entry it calls:
```python
nd.add_xsection(name=xsection)
nd.add_layer2xsection(...)
```
- Supported layer binding keys are:
- `growx`
- `growy`
- `leftedge`
- `rightedge`
3. Project YAML parsing:
- `_load_project_specs` reads saved `.yml` / `.yaml` files
(`../mxpic_router/mxpic_router/builder.py` line 46).
- `load_cell_spec` reads YAML
(`../mxpic_router/mxpic_router/eda_loader.py` line 80).
- `parse_cell_dict` converts dictionaries into `CellSpec`
(`../mxpic_router/mxpic_router/eda_loader.py` line 85).
4. Link xsections:
- `LinkSpec.xsection` defaults to `strip`
(`../mxpic_router/mxpic_router/eda_loader.py` line 51).
- `BundleSpec.xsection` defaults to `strip`
(`../mxpic_router/mxpic_router/eda_loader.py` line 66).
- Bundle-level `xsection` is read at
`../mxpic_router/mxpic_router/eda_loader.py` line 137.
- Link-level `xsection` overrides bundle-level xsection at
`../mxpic_router/mxpic_router/eda_loader.py` line 142.
- If `family` is missing, `_family_from_xsection` maps metal-like xsections
to `electrical`, otherwise `optical`
(`../mxpic_router/mxpic_router/eda_loader.py` line 171).
5. PDK asset resolution:
- `_resolve_pdk_asset` starts at
`../mxpic_router/mxpic_router/builder.py` line 509.
- It first tries the saved component path directly under `pdk_root`.
- If no direct directory exists, it scans the whole PDK root by component
folder basename.
- It reads the component YAML if present.
- It chooses GDS through `_find_gds`
(`../mxpic_router/mxpic_router/builder.py` line 533).
- Normal users prefer `<component>_BB.gds`; manager sessions can prefer
`<component>.gds`.
6. Route generation:
- `_route_link` starts at
`../mxpic_router/mxpic_router/builder.py` line 269.
- It creates the route object with:
```python
Route(radius=link.radius or 10, width=link.width, xs=link.xsection, PCB=...)
```
- Optical and electrical routes both use `link.xsection`; metal routes also
enable PCB-style routing for `metal_1` and `metal_2`.
- Nazca can generate geometry on the correct layers because
`apply_technology_manifest` already registered the xsection layer bindings.
## Current Fallbacks and Observations
- If a project has no saved technology, the frontend uses
`FALLBACK_TECHNOLOGY_MANIFEST`.
- If `/api/technologies/<foundry>/<technology>/manifest` fails, the frontend
also uses `FALLBACK_TECHNOLOGY_MANIFEST`.
- If build-time `technology_manifest_path_for_project` cannot find
`technology.yml`, `mxpic_router` receives no manifest path and Nazca
registration is skipped.
- The library browser is project-scoped by selected technology, but preview and
GDS build pass the base role PDK root to `mxpic_router`.
- This works because normal saved component paths include
`foundry/technology/...` relative to the base role PDK root.
- Manually edited YAML with only a component folder name can still resolve, but
the router will scan the whole active PDK root and use the first matching
folder basename.
- The legacy local GDS/SVG preview builder and local PDK GDS registry have been
removed from `mxpic_EDA`; active build and preview actions call the
router-backed API wrappers instead.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 14 KiB

@@ -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
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="6516.95" height="2377.45" viewBox="14124.775 14328.775 6516.95 2377.45">
<defs>
<style type="text/css">
.l1111d0 {stroke: #B3446C; fill: #B3446C; fill-opacity: 0.5;}
</style>
</defs>
<rect x="14124.775" y="14328.775" width="6516.95" height="2377.45" fill="#222222" stroke="none"/>
<g id="canvas_2" transform="scale(1 -1)">
<polygon id="00000215A6721690" class="l1111d0" points="20243,-14702.5 14421,-14702.5 14421,-14697.5 20243,-14697.5"/>
<polygon id="00000215A67222D0" class="l1111d0" points="20340.5,-14800 20345.5,-14800 20345.5,-14798.61 20345.42,-14795.84 20345.27,-14793.06 20345.05,-14790.3 20344.75,-14787.54 20344.37,-14784.79 20343.92,-14782.05 20343.4,-14779.32 20342.8,-14776.61 20342.13,-14773.92 20341.39,-14771.24 20340.57,-14768.59 20339.69,-14765.96 20338.73,-14763.35 20337.7,-14760.77 20336.61,-14758.22 20335.44,-14755.7 20334.21,-14753.22 20332.91,-14750.76 20331.54,-14748.35 20330.11,-14745.97 20328.61,-14743.63 20327.06,-14741.33 20325.44,-14739.08 20323.76,-14736.87 20322.02,-14734.7 20320.22,-14732.59 20318.37,-14730.52 20316.46,-14728.51 20314.49,-14726.54 20312.48,-14724.63 20310.41,-14722.78 20308.3,-14720.98 20306.13,-14719.24 20303.92,-14717.56 20301.67,-14715.94 20299.37,-14714.39 20297.03,-14712.89 20294.65,-14711.46 20292.24,-14710.09 20289.78,-14708.79 20287.3,-14707.56 20284.78,-14706.39 20282.23,-14705.3 20279.65,-14704.27 20277.04,-14703.31 20274.41,-14702.43 20271.76,-14701.61 20269.08,-14700.87 20266.39,-14700.2 20263.68,-14699.6 20260.95,-14699.08 20258.21,-14698.63 20255.46,-14698.25 20252.7,-14697.95 20249.94,-14697.73 20247.16,-14697.58 20244.39,-14697.5 20243,-14697.5 20243,-14702.5 20244.37,-14702.5 20247.1,-14702.58 20249.83,-14702.73 20252.56,-14702.96 20255.28,-14703.27 20257.98,-14703.65 20260.68,-14704.11 20263.36,-14704.64 20266.03,-14705.25 20268.68,-14705.94 20271.3,-14706.69 20273.91,-14707.52 20276.49,-14708.43 20279.05,-14709.4 20281.57,-14710.45 20284.07,-14711.57 20286.53,-14712.75 20288.96,-14714.01 20291.36,-14715.33 20293.71,-14716.72 20296.03,-14718.17 20298.3,-14719.69 20300.53,-14721.28 20302.72,-14722.92 20304.86,-14724.63 20306.95,-14726.39 20308.99,-14728.21 20310.97,-14730.09 20312.91,-14732.03 20314.79,-14734.01 20316.61,-14736.05 20318.37,-14738.14 20320.08,-14740.28 20321.72,-14742.47 20323.31,-14744.7 20324.83,-14746.97 20326.28,-14749.29 20327.67,-14751.64 20328.99,-14754.04 20330.25,-14756.47 20331.43,-14758.93 20332.55,-14761.43 20333.6,-14763.95 20334.57,-14766.51 20335.48,-14769.09 20336.31,-14771.7 20337.06,-14774.32 20337.75,-14776.97 20338.36,-14779.64 20338.89,-14782.32 20339.35,-14785.02 20339.73,-14787.72 20340.04,-14790.44 20340.27,-14793.17 20340.42,-14795.9 20340.5,-14798.63"/>
<polygon id="00000215A67218C0" class="l1111d0" points="20340.5,-15990 20340.5,-14800 20345.5,-14800 20345.5,-15990"/>
<polygon id="00000215A6722030" class="l1111d0" points="20243,-16087.5 20243,-16092.5 20244.39,-16092.5 20247.16,-16092.42 20249.94,-16092.27 20252.7,-16092.05 20255.46,-16091.75 20258.21,-16091.37 20260.95,-16090.92 20263.68,-16090.4 20266.39,-16089.8 20269.08,-16089.13 20271.76,-16088.39 20274.41,-16087.57 20277.04,-16086.69 20279.65,-16085.73 20282.23,-16084.7 20284.78,-16083.61 20287.3,-16082.44 20289.78,-16081.21 20292.24,-16079.91 20294.65,-16078.54 20297.03,-16077.11 20299.37,-16075.61 20301.67,-16074.06 20303.92,-16072.44 20306.13,-16070.76 20308.3,-16069.02 20310.41,-16067.22 20312.48,-16065.37 20314.49,-16063.46 20316.46,-16061.49 20318.37,-16059.48 20320.22,-16057.41 20322.02,-16055.3 20323.76,-16053.13 20325.44,-16050.92 20327.06,-16048.67 20328.61,-16046.37 20330.11,-16044.03 20331.54,-16041.65 20332.91,-16039.24 20334.21,-16036.78 20335.44,-16034.3 20336.61,-16031.78 20337.7,-16029.23 20338.73,-16026.65 20339.69,-16024.04 20340.57,-16021.41 20341.39,-16018.76 20342.13,-16016.08 20342.8,-16013.39 20343.4,-16010.68 20343.92,-16007.95 20344.37,-16005.21 20344.75,-16002.46 20345.05,-15999.7 20345.27,-15996.94 20345.42,-15994.16 20345.5,-15991.39 20345.5,-15990 20340.5,-15990 20340.5,-15991.37 20340.42,-15994.1 20340.27,-15996.83 20340.04,-15999.56 20339.73,-16002.28 20339.35,-16004.98 20338.89,-16007.68 20338.36,-16010.36 20337.75,-16013.03 20337.06,-16015.68 20336.31,-16018.3 20335.48,-16020.91 20334.57,-16023.49 20333.6,-16026.05 20332.55,-16028.57 20331.43,-16031.07 20330.25,-16033.53 20328.99,-16035.96 20327.67,-16038.36 20326.28,-16040.71 20324.83,-16043.03 20323.31,-16045.3 20321.72,-16047.53 20320.08,-16049.72 20318.37,-16051.86 20316.61,-16053.95 20314.79,-16055.99 20312.91,-16057.97 20310.97,-16059.91 20308.99,-16061.79 20306.95,-16063.61 20304.86,-16065.37 20302.72,-16067.08 20300.53,-16068.72 20298.3,-16070.31 20296.03,-16071.83 20293.71,-16073.28 20291.36,-16074.67 20288.96,-16075.99 20286.53,-16077.25 20284.07,-16078.43 20281.57,-16079.55 20279.05,-16080.6 20276.49,-16081.57 20273.91,-16082.48 20271.3,-16083.31 20268.68,-16084.06 20266.03,-16084.75 20263.36,-16085.36 20260.68,-16085.89 20257.98,-16086.35 20255.28,-16086.73 20252.56,-16087.04 20249.83,-16087.27 20247.1,-16087.42 20244.37,-16087.5"/>
<polygon id="00000215A6721A80" class="l1111d0" points="16810,-14775 14421,-14775 14421,-14625 16810,-14625"/>
<polygon id="00000215A6720F90" class="l1111d0" points="16835,-14800 16985,-14800 16985,-14798.17 16984.92,-14794.5 16984.77,-14790.84 16984.54,-14787.18 16984.23,-14783.53 16983.85,-14779.89 16983.39,-14776.25 16982.85,-14772.62 16982.24,-14769.01 16981.55,-14765.41 16980.79,-14761.82 16979.95,-14758.26 16979.04,-14754.71 16978.06,-14751.17 16977,-14747.67 16975.87,-14744.18 16974.66,-14740.72 16973.38,-14737.28 16972.03,-14733.88 16970.61,-14730.5 16969.12,-14727.15 16967.56,-14723.83 16965.93,-14720.55 16964.23,-14717.3 16962.47,-14714.09 16960.64,-14710.91 16958.74,-14707.78 16956.77,-14704.68 16954.74,-14701.63 16952.65,-14698.62 16950.5,-14695.66 16948.28,-14692.74 16946.01,-14689.86 16943.67,-14687.04 16941.27,-14684.27 16938.82,-14681.54 16936.31,-14678.87 16933.75,-14676.25 16931.13,-14673.69 16928.46,-14671.18 16925.73,-14668.73 16922.96,-14666.33 16920.14,-14663.99 16917.26,-14661.72 16914.34,-14659.5 16911.38,-14657.35 16908.37,-14655.26 16905.32,-14653.23 16902.22,-14651.26 16899.09,-14649.36 16895.91,-14647.53 16892.7,-14645.77 16889.45,-14644.07 16886.17,-14642.44 16882.85,-14640.88 16879.5,-14639.39 16876.12,-14637.97 16872.72,-14636.62 16869.28,-14635.34 16865.82,-14634.13 16862.33,-14633 16858.83,-14631.94 16855.29,-14630.96 16851.74,-14630.05 16848.18,-14629.21 16844.59,-14628.45 16840.99,-14627.76 16837.38,-14627.15 16833.75,-14626.61 16830.11,-14626.15 16826.47,-14625.77 16822.82,-14625.46 16819.16,-14625.23 16815.5,-14625.08 16811.83,-14625 16810,-14625 16810,-14775 16810.68,-14775 16812.03,-14775.08 16813.38,-14775.22 16814.71,-14775.44 16816.03,-14775.73 16817.34,-14776.1 16818.62,-14776.53 16819.88,-14777.03 16821.11,-14777.6 16822.31,-14778.23 16823.47,-14778.93 16824.59,-14779.69 16825.67,-14780.51 16826.7,-14781.39 16827.68,-14782.32 16828.61,-14783.3 16829.49,-14784.33 16830.31,-14785.41 16831.07,-14786.53 16831.77,-14787.69 16832.4,-14788.89 16832.97,-14790.12 16833.47,-14791.38 16833.9,-14792.66 16834.27,-14793.97 16834.56,-14795.29 16834.78,-14796.62 16834.92,-14797.97 16835,-14799.32"/>
<polygon id="00000215A6721930" class="l1111d0" points="16835,-16235 16835,-14800 16985,-14800 16985,-16235"/>
<polygon id="00000215A67214D0" class="l1111d0" points="16810,-16260 16810,-16410 16811.83,-16410 16815.5,-16409.92 16819.16,-16409.77 16822.82,-16409.54 16826.47,-16409.23 16830.11,-16408.85 16833.75,-16408.39 16837.38,-16407.85 16840.99,-16407.24 16844.59,-16406.55 16848.18,-16405.79 16851.74,-16404.95 16855.29,-16404.04 16858.83,-16403.06 16862.33,-16402 16865.82,-16400.87 16869.28,-16399.66 16872.72,-16398.38 16876.12,-16397.03 16879.5,-16395.61 16882.85,-16394.12 16886.17,-16392.56 16889.45,-16390.93 16892.7,-16389.23 16895.91,-16387.47 16899.09,-16385.64 16902.22,-16383.74 16905.32,-16381.77 16908.37,-16379.74 16911.38,-16377.65 16914.34,-16375.5 16917.26,-16373.28 16920.14,-16371.01 16922.96,-16368.67 16925.73,-16366.27 16928.46,-16363.82 16931.13,-16361.31 16933.75,-16358.75 16936.31,-16356.13 16938.82,-16353.46 16941.27,-16350.73 16943.67,-16347.96 16946.01,-16345.14 16948.28,-16342.26 16950.5,-16339.34 16952.65,-16336.38 16954.74,-16333.37 16956.77,-16330.32 16958.74,-16327.22 16960.64,-16324.09 16962.47,-16320.91 16964.23,-16317.7 16965.93,-16314.45 16967.56,-16311.17 16969.12,-16307.85 16970.61,-16304.5 16972.03,-16301.12 16973.38,-16297.72 16974.66,-16294.28 16975.87,-16290.82 16977,-16287.33 16978.06,-16283.83 16979.04,-16280.29 16979.95,-16276.74 16980.79,-16273.18 16981.55,-16269.59 16982.24,-16265.99 16982.85,-16262.38 16983.39,-16258.75 16983.85,-16255.11 16984.23,-16251.47 16984.54,-16247.82 16984.77,-16244.16 16984.92,-16240.5 16985,-16236.83 16985,-16235 16835,-16235 16835,-16235.68 16834.92,-16237.03 16834.78,-16238.38 16834.56,-16239.71 16834.27,-16241.03 16833.9,-16242.34 16833.47,-16243.62 16832.97,-16244.88 16832.4,-16246.11 16831.77,-16247.31 16831.07,-16248.47 16830.31,-16249.59 16829.49,-16250.67 16828.61,-16251.7 16827.68,-16252.68 16826.7,-16253.61 16825.67,-16254.49 16824.59,-16255.31 16823.47,-16256.07 16822.31,-16256.77 16821.11,-16257.4 16819.88,-16257.97 16818.62,-16258.47 16817.34,-16258.9 16816.03,-16259.27 16814.71,-16259.56 16813.38,-16259.78 16812.03,-16259.92 16810.68,-16260"/>
<polygon id="00000215A67220A0" class="l1111d0" points="15610,-14775 14421,-14775 14421,-14625 15610,-14625"/>
<polygon id="00000215A6722650" class="l1111d0" points="15635,-14800 15785,-14800 15785,-14798.17 15784.92,-14794.5 15784.77,-14790.84 15784.54,-14787.18 15784.23,-14783.53 15783.85,-14779.89 15783.39,-14776.25 15782.85,-14772.62 15782.24,-14769.01 15781.55,-14765.41 15780.79,-14761.82 15779.95,-14758.26 15779.04,-14754.71 15778.06,-14751.17 15777,-14747.67 15775.87,-14744.18 15774.66,-14740.72 15773.38,-14737.28 15772.03,-14733.88 15770.61,-14730.5 15769.12,-14727.15 15767.56,-14723.83 15765.93,-14720.55 15764.23,-14717.3 15762.47,-14714.09 15760.64,-14710.91 15758.74,-14707.78 15756.77,-14704.68 15754.74,-14701.63 15752.65,-14698.62 15750.5,-14695.66 15748.28,-14692.74 15746.01,-14689.86 15743.67,-14687.04 15741.27,-14684.27 15738.82,-14681.54 15736.31,-14678.87 15733.75,-14676.25 15731.13,-14673.69 15728.46,-14671.18 15725.73,-14668.73 15722.96,-14666.33 15720.14,-14663.99 15717.26,-14661.72 15714.34,-14659.5 15711.38,-14657.35 15708.37,-14655.26 15705.32,-14653.23 15702.22,-14651.26 15699.09,-14649.36 15695.91,-14647.53 15692.7,-14645.77 15689.45,-14644.07 15686.17,-14642.44 15682.85,-14640.88 15679.5,-14639.39 15676.12,-14637.97 15672.72,-14636.62 15669.28,-14635.34 15665.82,-14634.13 15662.33,-14633 15658.83,-14631.94 15655.29,-14630.96 15651.74,-14630.05 15648.18,-14629.21 15644.59,-14628.45 15640.99,-14627.76 15637.38,-14627.15 15633.75,-14626.61 15630.11,-14626.15 15626.47,-14625.77 15622.82,-14625.46 15619.16,-14625.23 15615.5,-14625.08 15611.83,-14625 15610,-14625 15610,-14775 15610.68,-14775 15612.03,-14775.08 15613.38,-14775.22 15614.71,-14775.44 15616.03,-14775.73 15617.34,-14776.1 15618.62,-14776.53 15619.88,-14777.03 15621.11,-14777.6 15622.31,-14778.23 15623.47,-14778.93 15624.59,-14779.69 15625.67,-14780.51 15626.7,-14781.39 15627.68,-14782.32 15628.61,-14783.3 15629.49,-14784.33 15630.31,-14785.41 15631.07,-14786.53 15631.77,-14787.69 15632.4,-14788.89 15632.97,-14790.12 15633.47,-14791.38 15633.9,-14792.66 15634.27,-14793.97 15634.56,-14795.29 15634.78,-14796.62 15634.92,-14797.97 15635,-14799.32"/>
<polygon id="00000215A67226C0" class="l1111d0" points="15635,-16235 15635,-14800 15785,-14800 15785,-16235"/>
<polygon id="00000215A6721FC0" class="l1111d0" points="15810,-16260 15810,-16410 15808.17,-16410 15804.5,-16409.92 15800.84,-16409.77 15797.18,-16409.54 15793.53,-16409.23 15789.89,-16408.85 15786.25,-16408.39 15782.62,-16407.85 15779.01,-16407.24 15775.41,-16406.55 15771.82,-16405.79 15768.26,-16404.95 15764.71,-16404.04 15761.17,-16403.06 15757.67,-16402 15754.18,-16400.87 15750.72,-16399.66 15747.28,-16398.38 15743.88,-16397.03 15740.5,-16395.61 15737.15,-16394.12 15733.83,-16392.56 15730.55,-16390.93 15727.3,-16389.23 15724.09,-16387.47 15720.91,-16385.64 15717.78,-16383.74 15714.68,-16381.77 15711.63,-16379.74 15708.62,-16377.65 15705.66,-16375.5 15702.74,-16373.28 15699.86,-16371.01 15697.04,-16368.67 15694.27,-16366.27 15691.54,-16363.82 15688.87,-16361.31 15686.25,-16358.75 15683.69,-16356.13 15681.18,-16353.46 15678.73,-16350.73 15676.33,-16347.96 15673.99,-16345.14 15671.72,-16342.26 15669.5,-16339.34 15667.35,-16336.38 15665.26,-16333.37 15663.23,-16330.32 15661.26,-16327.22 15659.36,-16324.09 15657.53,-16320.91 15655.77,-16317.7 15654.07,-16314.45 15652.44,-16311.17 15650.88,-16307.85 15649.39,-16304.5 15647.97,-16301.12 15646.62,-16297.72 15645.34,-16294.28 15644.13,-16290.82 15643,-16287.33 15641.94,-16283.83 15640.96,-16280.29 15640.05,-16276.74 15639.21,-16273.18 15638.45,-16269.59 15637.76,-16265.99 15637.15,-16262.38 15636.61,-16258.75 15636.15,-16255.11 15635.77,-16251.47 15635.46,-16247.82 15635.23,-16244.16 15635.08,-16240.5 15635,-16236.83 15635,-16235 15785,-16235 15785,-16235.68 15785.08,-16237.03 15785.22,-16238.38 15785.44,-16239.71 15785.73,-16241.03 15786.1,-16242.34 15786.53,-16243.62 15787.03,-16244.88 15787.6,-16246.11 15788.23,-16247.31 15788.93,-16248.47 15789.69,-16249.59 15790.51,-16250.67 15791.39,-16251.7 15792.32,-16252.68 15793.3,-16253.61 15794.33,-16254.49 15795.41,-16255.31 15796.53,-16256.07 15797.69,-16256.77 15798.89,-16257.4 15800.12,-16257.97 15801.38,-16258.47 15802.66,-16258.9 15803.97,-16259.27 15805.29,-16259.56 15806.62,-16259.78 15807.97,-16259.92 15809.32,-16260"/>
<polygon id="00000215A6722110" class="l1111d0" points="15810,-16260 16810,-16260 16810,-16410 15810,-16410"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

@@ -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
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 79 KiB

@@ -13,6 +13,7 @@ type: project
version: "1.0.0" version: "1.0.0"
# 1. External Ports (How this cell connects to the outside world) # 1. External Ports (How this cell connects to the outside world)
<<<<<<< HEAD
ports: [] ports: []
# 2. Instances (The sub-components dropped onto this canvas) # 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 component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/multimode_interferometers/1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2
x: 1511.5 x: 1511.5
y: -2531.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 rotation: 0.0
flip: 0 flip: 0
flop: 0 flop: 0
@@ -28,10 +70,51 @@ instances:
settings: settings:
length: 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: MMI_2:
component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/multimode_interferometers/1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2 component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/multimode_interferometers/1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2
x: 1716.4 x: 1716.4
y: -2293.8 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 rotation: 0.0
flip: 0 flip: 0
flop: 0 flop: 0
@@ -39,13 +122,42 @@ instances:
settings: settings:
length: length:
<<<<<<< HEAD
elements: {} 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) # 3. Bundles (Grouped links for multi-bus/parallel routing)
bundles: bundles:
output_bus: output_bus:
routing_type: euler_bend routing_type: euler_bend
links: links:
<<<<<<< HEAD
- from: MMI_2:a1 - from: MMI_2:a1
to: MMI_1:b2 to: MMI_1:b2
xsection: strip xsection: strip
@@ -53,3 +165,5 @@ bundles:
width: 0.45 width: 0.45
radius: 10 radius: 10
routing_type: euler_bend routing_type: euler_bend
=======
>>>>>>> jingwen_main
Binary file not shown.
+31 -14
View File
@@ -42,11 +42,14 @@
name: 'Anchor', name: 'Anchor',
elementType: 'anchor', elementType: 'anchor',
ports: { ports: {
a1: { x: 0, y: -PORT_NODE_SIZE / 2, a: 180, width: 0.5 }, a1: { x: 0, y: 0, a: 180, width: 0.5 },
b1: { x: 0, y: -PORT_NODE_SIZE / 2, a: 0, width: 0.5 } b1: { x: 0, y: 0, a: 0, width: 0.5 }
} }
} }
}; };
// Defines local primitive components that do not require PDK lookup. // Defines local primitive components that do not require PDK lookup.
const BASIC_COMPONENTS = { const BASIC_COMPONENTS = {
waveguide: { waveguide: {
@@ -804,16 +807,13 @@
} }
}; };
} }
if (portNumber > 1) { const entries = [];
const entries = []; Array.from({ length: portNumber }, (_, index) => {
Array.from({ length: portNumber }, (_, index) => { const y = elementPortOffset(index, portNumber, pitch);
const y = elementPortOffset(index, portNumber, pitch); entries.push([`a${index + 1}`, { x: 0, y, a: 180, width }]);
entries.push([`a${index + 1}`, { x: 0, y, a: 180, width }]); entries.push([`b${index + 1}`, { x: 0, y, a: 0, width }]);
entries.push([`b${index + 1}`, { x: 0, y, a: 0, width }]); });
}); return Object.fromEntries(entries);
return Object.fromEntries(entries);
}
return JSON.parse(JSON.stringify(element.ports));
}; };
// Generate port metadata for built-in primitive components. // Generate port metadata for built-in primitive components.
@@ -984,6 +984,20 @@ ${pinLines}`;
return `elements:\n${lines.join('\n')}`; 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. // Serialize canvas links into routed bundle YAML including route settings and bend points.
const buildBundlesYaml = (page, manifest) => { const buildBundlesYaml = (page, manifest) => {
const { nodes = [], edges = [] } = page || {}; const { nodes = [], edges = [] } = page || {};
@@ -1004,6 +1018,9 @@ ${pinLines}`;
? getElementPinName(targetNode, edge.targetHandle) ? getElementPinName(targetNode, edge.targetHandle)
: edge.targetHandle || 'unknown'; : edge.targetHandle || 'unknown';
const route = createRouteSettings(manifest, edge.data && edge.data.route); 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 storedPoints = Array.isArray(edge.data && edge.data.points) ? edge.data.points : [];
const points = storedPoints.length >= 2 ? getEdgeRoutePoints(edge, nodeMap) : []; const points = storedPoints.length >= 2 ? getEdgeRoutePoints(edge, nodeMap) : [];
const pointsYaml = points.length > 0 const pointsYaml = points.length > 0
@@ -1014,7 +1031,7 @@ ${pinLines}`;
return ` - id: ${toYamlScalar(edge.id)} return ` - id: ${toYamlScalar(edge.id)}
xsection: ${route.xsection} xsection: ${route.xsection}
family: ${route.family} family: ${route.family}
width: ${Number(route.width)} width: ${Number(routeWidth)}
radius: ${Number(route.radius)} radius: ${Number(route.radius)}
routing_type: ${route.routing_type}${pointsYaml}`; routing_type: ${route.routing_type}${pointsYaml}`;
} }
@@ -1022,7 +1039,7 @@ ${pinLines}`;
to: ${targetName}:${toPort} to: ${targetName}:${toPort}
xsection: ${route.xsection} xsection: ${route.xsection}
family: ${route.family} family: ${route.family}
width: ${Number(route.width)} width: ${Number(routeWidth)}
radius: ${Number(route.radius)} radius: ${Number(route.radius)}
routing_type: ${route.routing_type}${pointsYaml}`; routing_type: ${route.routing_type}${pointsYaml}`;
}); });
+116 -83
View File
@@ -1622,7 +1622,7 @@ Organization : OptiHK Limited
// Displays a category icon with cached loading and graceful failure behavior. // 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(() => { const [src, setSrc] = useState(() => {
if (!category) return undefined; if (!category) return undefined;
const cache = fetchIcon(category); const cache = fetchIcon(category);
@@ -1671,7 +1671,7 @@ Organization : OptiHK Limited
style={{ style={{
width: '100%', width: '100%',
height: '100%', height: '100%',
objectFit: 'fill', objectFit: imgObjectFit || 'fill',
pointerEvents: 'none', pointerEvents: 'none',
}} }}
onError={(e) => { onError={(e) => {
@@ -1764,42 +1764,46 @@ Organization : OptiHK Limited
width: componentSize.width, width: componentSize.width,
height: visualSize.height, height: visualSize.height,
minHeight: visualSize.height, minHeight: visualSize.height,
overflow: 'hidden',
...(visualSize.height < 50 && !isAnchorElement ? { padding: '2px 4px' } : {}),
border: selected ? '2px solid var(--accent)' : '1px solid var(--border)', 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)', boxShadow: selected ? '0 0 15px rgba(56, 189, 248, 0.2)' : '0 4px 6px rgba(0,0,0,0.3)',
...(isBasicCompactComponent ? { transform: componentVisualTransform,
padding: 0, transformOrigin: 'center center',
display: 'flex', ...(isBasicCompactComponent ? {
alignItems: 'center', padding: 0,
justifyContent: 'center' display: 'flex',
} : {}), alignItems: 'center',
...(isAnchorElement ? { justifyContent: 'center'
width: PORT_NODE_SIZE, } : {}),
minHeight: PORT_NODE_SIZE, ...(isAnchorElement ? {
padding: 0, width: PORT_NODE_SIZE,
borderRadius: '50%', minHeight: PORT_NODE_SIZE,
display: 'flex', padding: 0,
alignItems: 'center', borderRadius: '50%',
justifyContent: 'center' display: 'flex',
} : {}), alignItems: 'center',
}} justifyContent: 'center'
> } : {}),
{isAnchorElement ? ( }}
<span style={{ fontSize: 8, fontWeight: 800, color: selected ? 'var(--accent)' : 'var(--text-main)' }}>A</span> >
) : ( {isAnchorElement ? (
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: '8px', minHeight: '100%' }}> <span style={{ fontSize: 8, fontWeight: 800, color: selected ? 'var(--accent)' : 'var(--text-main)' }}>A</span>
{!data.hideIcon && data.category && ( ) : (
<div style={{ width: iconSize.width, height: iconSize.height }}> <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: '8px', minHeight: '100%' }}>
<IconImg category={data.category} /> {!data.hideIcon && data.category && (
<div style={{ maxWidth: iconSize.width, maxHeight: iconSize.height, width: '100%', aspectRatio: `${iconSize.width}/${iconSize.height}`, overflow: 'hidden' }}>
<IconImg category={data.category} objectFit="contain" />
</div>
)}
{!data.category && <div style={{ width: iconSize.width, height: iconSize.height, borderRadius: 4, border: '1px solid var(--border-strong)', background: 'rgba(148, 163, 184, 0.08)' }} />}
</div> </div>
)} )}
{!data.category && <div style={{ width: iconSize.width, height: iconSize.height, borderRadius: 4, border: '1px solid var(--border-strong)', background: 'rgba(148, 163, 184, 0.08)' }} />} </div>
</div>
)}
</div>
<div style={{ <div style={{
position: 'absolute', inset: 0, position: 'absolute',
top: 0, left: 0,
width: componentSize.width, width: componentSize.width,
height: visualSize.height, height: visualSize.height,
pointerEvents: 'none' pointerEvents: 'none'
@@ -1985,29 +1989,22 @@ Organization : OptiHK Limited
const name = String(portName || ''); const name = String(portName || '');
return name.startsWith('a') || name.startsWith('left') ? 'left' : 'right'; return name.startsWith('a') || name.startsWith('left') ? 'left' : 'right';
}; };
const anchorHandleVisualStyle = (portHandle, zIndex) => { const anchorHandleVisualStyle = (portHandle, zIndex) => ({
const visualSide = anchorPortVisualSide(portHandle.name); ...baseHandleStyle,
const localLeft = visualSide === 'left' ? 0 : elementSize.width; zIndex,
const localTop = portHandle.style?.top || '50%'; left: portHandle.style?.left,
return { top: portHandle.style?.top || '50%',
...baseHandleStyle, right: portHandle.style?.right || 'auto',
zIndex, bottom: portHandle.style?.bottom || 'auto',
left: localLeft, transform: portHandle.style?.transform || 'translate(-50%, -50%)'
top: localTop, });
right: 'auto',
bottom: 'auto',
transform: 'translate(-50%, -50%)'
};
};
const pinLabelStyle = (portHandle) => { const pinLabelStyle = (portHandle) => {
const visualSide = anchorPortVisualSide(portHandle.name); const visualSide = anchorPortVisualSide(portHandle.name);
const localLeft = visualSide === 'left' ? 0 : elementSize.width;
const localTop = portHandle.style?.top || '50%';
return { return {
left: localLeft, left: portHandle.style?.left,
top: localTop, top: portHandle.style?.top || '50%',
right: 'auto', right: portHandle.style?.right || 'auto',
bottom: 'auto', bottom: portHandle.style?.bottom || 'auto',
transform: visualSide === 'left' ? 'translate(calc(-100% - 5px), -50%)' : 'translate(5px, -50%)' transform: visualSide === 'left' ? 'translate(calc(-100% - 5px), -50%)' : 'translate(5px, -50%)'
}; };
}; };
@@ -3359,6 +3356,7 @@ Organization : OptiHK Limited
const forge = isForgeComponent(componentName); const forge = isForgeComponent(componentName);
onUpdateNode(selectedNode.id, { onUpdateNode(selectedNode.id, {
data: { data: {
...selectedNode.data,
componentName, componentName,
label: componentName, label: componentName,
ports: forge ? {} : undefined, ports: forge ? {} : undefined,
@@ -3760,8 +3758,6 @@ 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]);
@@ -3807,6 +3803,14 @@ Organization : OptiHK Limited
} }
: null : null
), [mouseCanvasPoint, canvasOrigin]); ), [mouseCanvasPoint, canvasOrigin]);
const handleCanvasViewportMoveEnd = useCallback((event, viewport) => {
if (!activePageId || !viewport) return;
setPages(prev => prev.map(page => (
page.id === activePageId
? { ...page, viewport: { x: viewport.x, y: viewport.y, zoom: viewport.zoom } }
: page
)));
}, [activePageId]);
// Normalizes free-route control points and removes adjacent duplicates before storage. // Normalizes free-route control points and removes adjacent duplicates before storage.
const compactRoutePoints = useCallback((points) => { const compactRoutePoints = useCallback((points) => {
return (points || []) return (points || [])
@@ -5058,6 +5062,35 @@ Organization : OptiHK Limited
return boxSize ? { ...node, data: { ...node.data, boxSize } } : node; return boxSize ? { ...node, data: { ...node.data, boxSize } } : node;
}) })
})); }));
// Pre-fetch PDK component metadata so nodes render with correct boxSize immediately.
const allNodes = cellPages.flatMap(page => page.nodes);
const pdkNames = [...new Set(allNodes
.filter(n => n.data?.componentName && !n.data?.elementType
&& !isForgeComponent(n.data.componentName)
&& !isBasicComponent(n.data.componentName))
.map(n => n.data.componentName))];
if (pdkNames.length > 0) {
const metaResults = await Promise.all(
pdkNames.map(name => loadComponentMetadata(name).catch(() => null))
);
const metaMap = new Map(
pdkNames.filter((_, i) => metaResults[i]).map((name, i) => [name, metaResults[i]])
);
for (const page of cellPages) {
page.nodes = page.nodes.map(node => {
const metadata = metaMap.get(node.data?.componentName);
if (!metadata) return node;
const sz = normalizeBoxSize(metadata);
return {
...node,
position: clampPositionToCanvas(node.position, page.canvasSize || DEFAULT_CANVAS_SIZE, sz),
data: { ...node.data, boxSize: sz, ports: metadata.pins || metadata.ports || {}, foundry: metadata.foundry || '', process: metadata.process || '' }
};
});
}
}
const loadedProjectPage = cellPages.find(page => page.type === 'project' && page.name === currentProjectName); const loadedProjectPage = cellPages.find(page => page.type === 'project' && page.name === currentProjectName);
const nonProjectPages = cellPages.filter(page => page !== loadedProjectPage); const nonProjectPages = cellPages.filter(page => page !== loadedProjectPage);
const resolvedProjectPage = loadedProjectPage || projectPage; const resolvedProjectPage = loadedProjectPage || projectPage;
@@ -5084,12 +5117,18 @@ Organization : OptiHK Limited
useEffect(() => { useEffect(() => {
if (activePage && activePage.type !== 'layoutPreview' && reactFlowInstance) { if (activePage && activePage.type !== 'layoutPreview' && reactFlowInstance) {
reactFlowInstance.fitBounds( if (activePage.viewport) {
{ x: 0, y: 0, width: activeCanvasSize.width, height: activeCanvasSize.height }, window.requestAnimationFrame(() => {
{ padding: 0.12, duration: 0 } reactFlowInstance.setViewport(activePage.viewport, { duration: 0 });
); });
} else {
reactFlowInstance.fitBounds(
{ x: 0, y: 0, width: activeCanvasSize.width, height: activeCanvasSize.height },
{ padding: 0.12, duration: 0 }
);
}
} }
}, [activePage?.id, activeCanvasSize.width, activeCanvasSize.height, reactFlowInstance]); }, [activePage?.id, activePage?.viewport, activeCanvasSize.width, activeCanvasSize.height, reactFlowInstance]);
useEffect(() => { useEffect(() => {
setRulerStartPoint(null); setRulerStartPoint(null);
@@ -5176,12 +5215,20 @@ Organization : OptiHK Limited
}; };
}) })
}))); })));
// Force React Flow to re-measure nodes whose boxSize / ports have changed.
requestAnimationFrame(() => {
const updatedIds = results.filter(r => r.metadata).map(r => r.nodeId);
if (updatedIds.length > 0 && reactFlowInstance.updateNodeInternals) {
reactFlowInstance.updateNodeInternals(updatedIds);
}
});
}); });
return () => { return () => {
cancelled = true; cancelled = true;
}; };
}, [pages, loadComponentMetadata]); }, [pages, loadComponentMetadata, reactFlowInstance]);
const openTabs = useMemo(() => pages.filter(page => !page.isClosed), [pages]); const openTabs = useMemo(() => pages.filter(page => !page.isClosed), [pages]);
@@ -6210,64 +6257,49 @@ ${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 (buildLayoutBusyRef.current) return; if (buildLayoutBusy) 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(buildPage); const yamlContent = buildYamlForPage(activePage);
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: buildPage.name, cell: activePage.name,
content: yamlContent, content: yamlContent,
}), }),
}); });
if (!response.ok) { if (!response.ok) {
const errData = await response.json().catch(() => ({})); const errData = await response.json();
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_ready && result.svg_url) { if (result.svg_url) {
completeBuildProgress('Layout ready'); completeBuildProgress('Layout ready');
openLayoutPreview(buildPage.name, result.svg_url, layoutBounds); openLayoutPreview(activePage.name, result.svg_url, calculateLayoutBounds(activePage));
} 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 {
if (buildRequestId === buildLayoutRequestRef.current) { setBuildLayoutBusy(false);
buildLayoutBusyRef.current = false;
setBuildLayoutBusy(false);
}
} }
}, [activePage, buildYamlForPage, currentProjectName, addLog, openLayoutPreview, validateRouteCrossings, startBuildProgress, completeBuildProgress, stopBuildProgress]); }, [activePage, buildLayoutBusy, 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 () => {
@@ -6551,6 +6583,7 @@ ${bundlesBlock}`;
minZoom={0.02} minZoom={0.02}
maxZoom={4} maxZoom={4}
defaultViewport={{ x: 80, y: 80, zoom: 0.12 }} defaultViewport={{ x: 80, y: 80, zoom: 0.12 }}
onMoveEnd={handleCanvasViewportMoveEnd}
panOnDrag={false} panOnDrag={false}
selectionOnDrag={true} selectionOnDrag={true}
selectionMode={FULL_SELECTION_MODE} selectionMode={FULL_SELECTION_MODE}
+3
View File
@@ -61,6 +61,7 @@ assert(
'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( assert(
<<<<<<< HEAD
serverPy.includes('svg_ready') && serverPy.includes('svg_ready') &&
serverPy.includes('svg_version') && serverPy.includes('svg_version') &&
serverPy.includes('file_version(svg_path)') && serverPy.includes('file_version(svg_path)') &&
@@ -73,6 +74,8 @@ assert(
'save-layout should publish generated SVG previews atomically instead of serving partially written files' 'save-layout should publish generated SVG previews atomically instead of serving partially written files'
); );
assert( assert(
=======
>>>>>>> jingwen_main
serverPy.includes('RouterStackUnavailable') && serverPy.includes('RouterStackUnavailable') &&
serverPy.includes('except RouterStackUnavailable as e') && serverPy.includes('except RouterStackUnavailable as e') &&
serverPy.includes('"preview_status": preview_status') && serverPy.includes('"preview_status": preview_status') &&
+3
View File
@@ -33,6 +33,7 @@ assert(
'Build Layout should use the backend svg_url response' 'Build Layout should use the backend svg_url response'
); );
assert( assert(
<<<<<<< HEAD
canvasHtml.includes('result.svg_ready && result.svg_url') && canvasHtml.includes('result.svg_ready && result.svg_url') &&
canvasHtml.includes('buildLayoutRequestRef') && canvasHtml.includes('buildLayoutRequestRef') &&
canvasHtml.includes('buildLayoutBusyRef') && canvasHtml.includes('buildLayoutBusyRef') &&
@@ -40,6 +41,8 @@ assert(
'Build Layout should wait for a ready, versioned SVG response and prevent stale duplicate preview updates' 'Build Layout should wait for a ready, versioned SVG response and prevent stale duplicate preview updates'
); );
assert( assert(
=======
>>>>>>> jingwen_main
canvasHtml.includes('result.preview_error') && canvasHtml.includes('result.preview_error') &&
canvasHtml.includes('Preview skipped: '), canvasHtml.includes('Preview skipped: '),
'Build Layout should log when the backend saves YAML but skips SVG preview because the router stack is unavailable' 'Build Layout should log when the backend saves YAML but skips SVG preview because the router stack is unavailable'
+11
View File
@@ -0,0 +1,11 @@
# work log
1.Fixed an issue where switching between different tabs would automatically reset the zoom level.
2.Fixed an port width mismatch in YAML bundles.
3.Fixed the issue where SVG were displaying in incorrect positions.
4.Fixed the abnormal port shift after rotation.
5.Fixed the abnormal position of individual ports.