Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d577edf348 | |||
| 7195dea7cd | |||
| fa0ebe899c | |||
| 2846899097 | |||
| 7953c8b624 | |||
| 75dd78aa33 | |||
| bf8e72f5b6 | |||
| 2ddd30e7bb | |||
| 1b7357e419 | |||
| b67c647995 | |||
| 7ac76aaee9 | |||
| 866bc1de18 | |||
| 23d631c4f0 | |||
| af5134dcee | |||
| feed2e0576 | |||
| f9c3f12aea | |||
| 84d43d1ef4 | |||
| 960066735c | |||
| 9b4f43f0b1 | |||
| cf28676756 | |||
| 09cadc7430 | |||
| 13d42af90d |
+11
@@ -0,0 +1,11 @@
|
||||
*/__pycache__/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Runtime data belongs outside this repository.
|
||||
/database/
|
||||
/backend/*.db
|
||||
*.db-journal
|
||||
*.db-shm
|
||||
*.db-wal
|
||||
/mxpic_EDA_database/
|
||||
@@ -23,3 +23,25 @@ PDK component assets and `technology.yml` manifests are loaded from the same
|
||||
role-scoped PDK roots. Normal users and developers use
|
||||
`MXPIC_PDK_PUBLIC_ROOT` or `../opt_pdk_public/foundries`; managers use
|
||||
`MXPIC_PDK_ATLAS_ROOT` or `../opt_pdk_atlas/foundries`.
|
||||
|
||||
## Runtime Database Root
|
||||
|
||||
Runtime data is not stored inside this repository. Move the existing `database`
|
||||
folder beside `mxpic_EDA` and rename it:
|
||||
|
||||
```text
|
||||
mxpic_EDA_database
|
||||
```
|
||||
|
||||
Default runtime data path:
|
||||
|
||||
```text
|
||||
../mxpic_EDA_database
|
||||
```
|
||||
|
||||
The app fails fast if this folder is missing so debugging cannot silently create
|
||||
or modify tracked database files. For nonstandard deployments, set:
|
||||
|
||||
```text
|
||||
MXPIC_DATABASE_ROOT=<path-to-mxpic_EDA_database>
|
||||
```
|
||||
|
||||
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.
+1
-3
@@ -10,9 +10,7 @@ import os
|
||||
from werkzeug.security import generate_password_hash
|
||||
from datetime import datetime
|
||||
|
||||
# Store application data in the shared database folder so all backend modules
|
||||
# use the same SQLite file regardless of their import path.
|
||||
DB_FILE = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "database", "mxpic_data.db"))
|
||||
from storage_paths import DB_FILE
|
||||
|
||||
def connect_db():
|
||||
"""Open a SQLite connection with row-style access for application data queries."""
|
||||
|
||||
+21
-22
@@ -4,12 +4,11 @@
|
||||
# Developer : Qin Yue @ 2026
|
||||
# Organization : OptiHK Limited
|
||||
# -----------------------------------------------------------------------------
|
||||
import os
|
||||
import tempfile
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Dict, List
|
||||
|
||||
import yaml
|
||||
from typing import List
|
||||
|
||||
from layout_files import load_layout_cell_files, write_layout_cells_to_directory
|
||||
from router_dependency import require_router_stack
|
||||
|
||||
|
||||
@@ -31,16 +30,25 @@ def build_project_gds(
|
||||
prefer_full_gds: bool = False,
|
||||
) -> BuildResult:
|
||||
"""Build a hierarchical project GDS from saved cell YAML files with mxpic_router."""
|
||||
cells = _load_project_cells(project_dir)
|
||||
cells, warnings = _load_project_cells(project_dir)
|
||||
if not cells:
|
||||
raise ValueError("No saved cell YAML files found for this project")
|
||||
|
||||
return _build_with_mxpic_router(
|
||||
project_dir,
|
||||
output_path,
|
||||
pdk_public_root,
|
||||
technology_manifest_path,
|
||||
prefer_full_gds,
|
||||
with tempfile.TemporaryDirectory(prefix="mxpic_gds_project_") as staged_project_dir:
|
||||
write_layout_cells_to_directory(cells, staged_project_dir)
|
||||
result = _build_with_mxpic_router(
|
||||
staged_project_dir,
|
||||
output_path,
|
||||
pdk_public_root,
|
||||
technology_manifest_path,
|
||||
prefer_full_gds,
|
||||
)
|
||||
|
||||
return BuildResult(
|
||||
output_path=result.output_path,
|
||||
engine=result.engine,
|
||||
cells_built=result.cells_built,
|
||||
warnings=warnings + result.warnings,
|
||||
)
|
||||
|
||||
|
||||
@@ -70,15 +78,6 @@ def _build_with_mxpic_router(
|
||||
)
|
||||
|
||||
|
||||
def _load_project_cells(project_dir: str) -> Dict[str, dict]:
|
||||
def _load_project_cells(project_dir: str):
|
||||
"""Load saved cell YAML documents from a project directory."""
|
||||
cells = {}
|
||||
for filename in sorted(os.listdir(project_dir)):
|
||||
if not filename.lower().endswith((".yml", ".yaml")):
|
||||
continue
|
||||
path = os.path.join(project_dir, filename)
|
||||
with open(path, "r", encoding="utf-8") as file:
|
||||
data = yaml.safe_load(file) or {}
|
||||
cell_name = str(data.get("name") or os.path.splitext(filename)[0])
|
||||
cells[cell_name] = data
|
||||
return cells
|
||||
return load_layout_cell_files(project_dir)
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
# -----------------------------------------------------------------------------
|
||||
# Description: Layout YAML file filtering, parsing, and staging helpers.
|
||||
# Inside functions: is_layout_cell_filename, parse_layout_cell_content, read_layout_cell_file, load_layout_cell_files, layout_cell_filename, write_layout_cells_to_directory
|
||||
# Developer : Qin Yue @ 2026
|
||||
# Organization : OptiHK Limited
|
||||
# -----------------------------------------------------------------------------
|
||||
import os
|
||||
import re
|
||||
|
||||
import yaml
|
||||
|
||||
|
||||
LAYOUT_YAML_EXTENSIONS = (".yml", ".yaml")
|
||||
ROUTE_SIDECAR_SUFFIXES = (".routes.yml", ".routes.yaml")
|
||||
|
||||
|
||||
class LayoutFileError(ValueError):
|
||||
"""Raised when a saved layout YAML file cannot be used as a cell."""
|
||||
|
||||
|
||||
def is_layout_cell_filename(filename):
|
||||
"""Return True for user layout cell YAML files, excluding sidecars/manifests."""
|
||||
lower = (filename or "").lower()
|
||||
if lower == "technology.yml":
|
||||
return False
|
||||
if lower.endswith(ROUTE_SIDECAR_SUFFIXES):
|
||||
return False
|
||||
return lower.endswith(LAYOUT_YAML_EXTENSIONS)
|
||||
|
||||
|
||||
def is_layout_cell_document(data):
|
||||
"""Return True when parsed YAML looks like an mxPIC saved cell/project."""
|
||||
if not isinstance(data, dict):
|
||||
return False
|
||||
|
||||
kind = data.get("kind")
|
||||
if kind and str(kind).strip().lower() != "cell":
|
||||
return False
|
||||
|
||||
layout_keys = {
|
||||
"schema_version",
|
||||
"canvas_size",
|
||||
"canvasSize",
|
||||
"instances",
|
||||
"elements",
|
||||
"pins",
|
||||
"ports",
|
||||
"bundles",
|
||||
}
|
||||
return any(key in data for key in layout_keys)
|
||||
|
||||
|
||||
def parse_layout_cell_content(content, source="layout YAML"):
|
||||
"""Parse and validate saved layout YAML content."""
|
||||
try:
|
||||
data = yaml.safe_load(content or "") or {}
|
||||
except yaml.YAMLError as exc:
|
||||
raise LayoutFileError(f"{source} is not valid YAML: {exc}") from exc
|
||||
|
||||
if not is_layout_cell_document(data):
|
||||
raise LayoutFileError(f"{source} is not a saved mxPIC layout cell")
|
||||
return data
|
||||
|
||||
|
||||
def read_layout_cell_file(path):
|
||||
"""Read a saved layout cell file and return parsed data plus raw content."""
|
||||
try:
|
||||
with open(path, "r", encoding="utf-8") as file:
|
||||
content = file.read()
|
||||
except OSError as exc:
|
||||
raise LayoutFileError(f"{os.path.basename(path)} could not be read: {exc}") from exc
|
||||
return parse_layout_cell_content(content, os.path.basename(path)), content
|
||||
|
||||
|
||||
def load_layout_cell_files(project_dir):
|
||||
"""Load valid layout cell files from a project directory and collect warnings."""
|
||||
cells = []
|
||||
warnings = []
|
||||
if not os.path.isdir(project_dir):
|
||||
return cells, warnings
|
||||
|
||||
for filename in sorted(os.listdir(project_dir)):
|
||||
if not is_layout_cell_filename(filename):
|
||||
continue
|
||||
path = os.path.join(project_dir, filename)
|
||||
if not os.path.isfile(path):
|
||||
continue
|
||||
try:
|
||||
data, content = read_layout_cell_file(path)
|
||||
except LayoutFileError as exc:
|
||||
warnings.append(f"Skipped {filename}: {exc}")
|
||||
continue
|
||||
|
||||
cells.append({
|
||||
"filename": filename,
|
||||
"name": str(data.get("name") or os.path.splitext(filename)[0]),
|
||||
"data": data,
|
||||
"content": content,
|
||||
})
|
||||
return cells, warnings
|
||||
|
||||
|
||||
def layout_cell_filename(cell_name, fallback="canvas_1.yml"):
|
||||
"""Build a safe filename for staging a parsed layout cell."""
|
||||
name = re.sub(r"[^A-Za-z0-9_.-]+", "_", str(cell_name or "")).strip("._")
|
||||
if not name:
|
||||
return fallback
|
||||
return f"{name}.yml"
|
||||
|
||||
|
||||
def write_layout_cells_to_directory(cells, output_dir):
|
||||
"""Write valid layout cell contents into a clean staging directory."""
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
used_names = set()
|
||||
for index, cell in enumerate(cells, start=1):
|
||||
filename = layout_cell_filename(cell.get("name"), cell.get("filename") or f"cell_{index}.yml")
|
||||
base, ext = os.path.splitext(filename)
|
||||
unique_filename = filename
|
||||
counter = 1
|
||||
while unique_filename.lower() in used_names:
|
||||
counter += 1
|
||||
unique_filename = f"{base}_{counter}{ext}"
|
||||
used_names.add(unique_filename.lower())
|
||||
|
||||
with open(os.path.join(output_dir, unique_filename), "w", encoding="utf-8") as file:
|
||||
file.write(cell["content"])
|
||||
Binary file not shown.
@@ -9,6 +9,7 @@ import tempfile
|
||||
|
||||
import yaml
|
||||
|
||||
from layout_files import load_layout_cell_files, write_layout_cells_to_directory
|
||||
from router_dependency import require_router_stack
|
||||
|
||||
|
||||
@@ -32,9 +33,20 @@ def create_routed_layout_svg(
|
||||
# Build into a temporary GDS first, then convert the generated top cell into
|
||||
# the SVG preview consumed by the canvas.
|
||||
with tempfile.TemporaryDirectory(prefix="mxpic_routed_preview_") as temp_dir:
|
||||
staged_project_dir = os.path.join(temp_dir, "project")
|
||||
saved_cells, _warnings = load_layout_cell_files(project_dir)
|
||||
staged_cells = [cell for cell in saved_cells if cell["name"] != cell_name]
|
||||
staged_cells.append({
|
||||
"filename": f"{cell_name}.yml",
|
||||
"name": cell_name,
|
||||
"data": layout,
|
||||
"content": yaml_content,
|
||||
})
|
||||
write_layout_cells_to_directory(staged_cells, staged_project_dir)
|
||||
|
||||
temp_gds = os.path.join(temp_dir, f"{cell_name}.gds")
|
||||
build_project_gds(
|
||||
project_dir=project_dir,
|
||||
project_dir=staged_project_dir,
|
||||
output_path=temp_gds,
|
||||
pdk_root=pdk_root,
|
||||
technology_manifest_path=technology_manifest_path,
|
||||
|
||||
+58
-34
@@ -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
|
||||
@@ -17,6 +18,13 @@ from werkzeug.security import check_password_hash
|
||||
import database
|
||||
from flask import Response
|
||||
from gds_builder import build_project_gds
|
||||
from layout_files import (
|
||||
LayoutFileError,
|
||||
is_layout_cell_filename,
|
||||
load_layout_cell_files,
|
||||
parse_layout_cell_content,
|
||||
read_layout_cell_file,
|
||||
)
|
||||
from pdk_access import (
|
||||
cleanup_expired_exports,
|
||||
create_export_path,
|
||||
@@ -25,6 +33,7 @@ from pdk_access import (
|
||||
)
|
||||
from router_dependency import RouterStackUnavailable
|
||||
from routed_layout_preview import create_routed_layout_svg
|
||||
from storage_paths import DATABASE_ROOT, EXPORT_ROOT
|
||||
from technology_manifest import TechnologyManifestError, read_technology_manifest
|
||||
|
||||
# --- Path Configurations ---
|
||||
@@ -37,11 +46,6 @@ REPO_ROOT = os.path.abspath(os.path.join(BASE_DIR, '..', '..'))
|
||||
# Component/category icons are served from backend/icons for the library panel.
|
||||
ICONS_DIR = os.path.join(BASE_DIR, 'icons')
|
||||
|
||||
# Saved project YAML, generated previews, and temporary exports live under the
|
||||
# database folder so each user can have isolated project storage.
|
||||
DATABASE_ROOT = os.path.abspath(os.path.join(BASE_DIR, '..', 'database'))
|
||||
EXPORT_ROOT = os.path.abspath(os.path.join(DATABASE_ROOT, '_exports'))
|
||||
|
||||
|
||||
# Flask serves the HTML/CSS/JS frontend and exposes JSON APIs for persistence,
|
||||
# PDK lookup, preview generation, and GDS export.
|
||||
@@ -135,6 +139,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")
|
||||
@@ -142,13 +152,17 @@ def cell_routes_path(project_name, cell_name):
|
||||
|
||||
def write_route_points_sidecar(yaml_content, output_path):
|
||||
"""Extract route points from layout YAML and save them beside the cell."""
|
||||
layout = yaml.safe_load(yaml_content) or {}
|
||||
layout = yaml_content if isinstance(yaml_content, dict) else parse_layout_cell_content(yaml_content)
|
||||
routes = {}
|
||||
# The sidecar preserves manually edited route control points separately from
|
||||
# the main YAML file for tooling that wants route-only metadata.
|
||||
for bundle_name, bundle in (layout.get("bundles") or {}).items():
|
||||
if not isinstance(bundle, dict):
|
||||
continue
|
||||
saved_links = []
|
||||
for link in bundle.get("links") or []:
|
||||
if not isinstance(link, dict):
|
||||
continue
|
||||
points = link.get("points") or []
|
||||
if not points:
|
||||
continue
|
||||
@@ -579,27 +593,29 @@ def user_logs():
|
||||
@app.route('/api/projects', methods=['GET'])
|
||||
@login_required_json
|
||||
def list_projects():
|
||||
"""List projects stored under database/<username>/layout."""
|
||||
"""List projects stored under the external runtime database root."""
|
||||
root = user_layout_root()
|
||||
os.makedirs(root, exist_ok=True)
|
||||
|
||||
projects = []
|
||||
# Each project is a folder and each YAML file inside that folder is treated
|
||||
# as one saved cell/canvas.
|
||||
# Each project is a folder and each valid layout YAML file inside that
|
||||
# folder is treated as one saved cell/canvas. Route sidecars and malformed
|
||||
# files are ignored so reopen stays resilient to stale runtime artifacts.
|
||||
for name in sorted(os.listdir(root)):
|
||||
path = os.path.join(root, name)
|
||||
if not os.path.isdir(path):
|
||||
continue
|
||||
cells = []
|
||||
for filename in sorted(os.listdir(path)):
|
||||
if not filename.lower().endswith(('.yml', '.yaml')):
|
||||
if not is_layout_cell_filename(filename):
|
||||
continue
|
||||
cell_name = os.path.splitext(filename)[0]
|
||||
yml_path = os.path.join(path, filename)
|
||||
cells.append({
|
||||
"name": cell_name,
|
||||
"has_layout": os.path.exists(yml_path)
|
||||
})
|
||||
try:
|
||||
read_layout_cell_file(yml_path)
|
||||
except LayoutFileError:
|
||||
continue
|
||||
cells.append({"name": cell_name, "has_layout": True})
|
||||
meta = read_project_meta(name)
|
||||
projects.append({
|
||||
"name": name,
|
||||
@@ -645,31 +661,27 @@ def get_project(project_name):
|
||||
if not os.path.isdir(root):
|
||||
return jsonify({"error": "Project not found"}), 404
|
||||
|
||||
cells = []
|
||||
for filename in sorted(os.listdir(root)):
|
||||
if not filename.lower().endswith(('.yml', '.yaml')):
|
||||
continue
|
||||
cell_name = os.path.splitext(filename)[0]
|
||||
yml_path = os.path.join(root, filename)
|
||||
if not os.path.exists(yml_path):
|
||||
continue
|
||||
with open(yml_path, 'r', encoding='utf-8') as f:
|
||||
cells.append({
|
||||
"name": cell_name,
|
||||
"content": f.read()
|
||||
})
|
||||
loaded_cells, warnings = load_layout_cell_files(root)
|
||||
cells = [
|
||||
{
|
||||
"name": os.path.splitext(cell["filename"])[0],
|
||||
"content": cell["content"]
|
||||
}
|
||||
for cell in loaded_cells
|
||||
]
|
||||
|
||||
return jsonify({
|
||||
"name": safe_name(project_name, 'project_1'),
|
||||
"cells": cells,
|
||||
"technology": read_project_meta(project_name).get("technology")
|
||||
"technology": read_project_meta(project_name).get("technology"),
|
||||
"warnings": warnings
|
||||
})
|
||||
|
||||
|
||||
@app.route('/api/projects/<project_name>', methods=['DELETE'])
|
||||
@login_required_json
|
||||
def delete_project(project_name):
|
||||
"""Delete a user's project folder under database/<username>/layout."""
|
||||
"""Delete a user's project folder under the external runtime database root."""
|
||||
root = project_root(project_name)
|
||||
layout_root = os.path.abspath(user_layout_root())
|
||||
target = os.path.abspath(root)
|
||||
@@ -725,38 +737,46 @@ def rename_cell(project_name, cell_name):
|
||||
def save_layout():
|
||||
"""Persist a canvas layout YAML document and refresh its preview assets."""
|
||||
try:
|
||||
data = request.get_json()
|
||||
data = request.get_json(silent=True) or {}
|
||||
project = safe_name(data.get('project'), 'project_1')
|
||||
cell = safe_name(data.get('cell'), 'canvas_1')
|
||||
content = data.get('content', '')
|
||||
create_preview = bool(data.get('preview', True))
|
||||
layout_doc = parse_layout_cell_content(content, f"{project}/{cell}.yml")
|
||||
|
||||
save_path = cell_file_path(project, cell)
|
||||
os.makedirs(os.path.dirname(save_path), exist_ok=True)
|
||||
|
||||
with open(save_path, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
write_route_points_sidecar(content, cell_routes_path(project, cell))
|
||||
write_route_points_sidecar(layout_doc, 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,11 +785,15 @@ 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
|
||||
|
||||
except LayoutFileError as e:
|
||||
return jsonify({"error": str(e)}), 400
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# -----------------------------------------------------------------------------
|
||||
# Description: Runtime storage path resolution for SQLite data, saved layouts, and temporary exports.
|
||||
# Inside functions: resolve_database_root
|
||||
# Developer : Qin Yue @ 2026
|
||||
# Organization : OptiHK Limited
|
||||
# -----------------------------------------------------------------------------
|
||||
import os
|
||||
|
||||
|
||||
BACKEND_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
REPO_ROOT = os.path.abspath(os.path.join(BACKEND_DIR, ".."))
|
||||
DEFAULT_DATABASE_ROOT = os.path.abspath(os.path.join(REPO_ROOT, "..", "mxpic_EDA_database"))
|
||||
|
||||
|
||||
def _is_inside_repo(path: str) -> bool:
|
||||
"""Return True when a candidate runtime data folder lives inside this repo."""
|
||||
repo_root = os.path.normcase(REPO_ROOT)
|
||||
candidate = os.path.normcase(os.path.abspath(path))
|
||||
try:
|
||||
return os.path.commonpath([repo_root, candidate]) == repo_root
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
|
||||
def resolve_database_root() -> str:
|
||||
"""Resolve and validate the external runtime database root."""
|
||||
configured_root = os.environ.get("MXPIC_DATABASE_ROOT", "").strip()
|
||||
database_root = os.path.abspath(configured_root or DEFAULT_DATABASE_ROOT)
|
||||
|
||||
if _is_inside_repo(database_root):
|
||||
raise RuntimeError(
|
||||
"Runtime database root must live outside the mxpic_EDA repository. "
|
||||
f"Resolved path: {database_root}. Move the database beside the repo as "
|
||||
"mxpic_EDA_database or set MXPIC_DATABASE_ROOT to an external path."
|
||||
)
|
||||
|
||||
if not os.path.isdir(database_root):
|
||||
raise RuntimeError(
|
||||
"Runtime database root is missing. Move the existing database folder "
|
||||
f"beside mxpic_EDA and rename it to mxpic_EDA_database, or set "
|
||||
f"MXPIC_DATABASE_ROOT to the moved folder. Expected path: {database_root}"
|
||||
)
|
||||
|
||||
return database_root
|
||||
|
||||
|
||||
DATABASE_ROOT = resolve_database_root()
|
||||
DB_FILE = os.path.join(DATABASE_ROOT, "mxpic_data.db")
|
||||
EXPORT_ROOT = os.path.join(DATABASE_ROOT, "_exports")
|
||||
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.
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.
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.
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"name": "mxpic_project_1",
|
||||
"technology": "Silterra/EMO1_2ML_CU_Al_RDL"
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 165 KiB |
@@ -1,58 +0,0 @@
|
||||
# =============================================
|
||||
# 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: mxpic_project_1
|
||||
type: project
|
||||
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:
|
||||
MZM_1:
|
||||
component: Silterra/EMO1_2ML_CU_Al_RDL/composites/Mach_Zender_modulators/MZI_SiN400_Si220_PIN_mod_1310_L1300_QY_202603
|
||||
x: 222.2
|
||||
y: -473.8
|
||||
rotation: 0.0
|
||||
flip: 0
|
||||
flop: 0
|
||||
mirror: false
|
||||
settings:
|
||||
length:
|
||||
|
||||
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:
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"name": "mxpic_project_1",
|
||||
"technology": "Silterra/EMO1_2ML_CU_Al_RDL"
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="3218.6" height="2963.6" viewBox="14680.2 22644.7 3218.6 2963.6">
|
||||
<defs>
|
||||
<style type="text/css">
|
||||
.l275d0 {stroke: #654522; fill: #654522; fill-opacity: 0.5;}
|
||||
.l1200d0 {stroke: #F38400; fill: #F38400; fill-opacity: 0.5;}
|
||||
.l101d251 {stroke: #848482; fill: #848482; fill-opacity: 0.5;}
|
||||
.l1205d0 {stroke: #008856; fill: #008856; fill-opacity: 0.5;}
|
||||
.l1001t0 {stroke: none; fill: #A1CAF1;}
|
||||
</style>
|
||||
<g id="1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2">
|
||||
<polygon id="000002B6365DCE60" class="l1200d0" points="-285,-147 585,-147 585,147 -285,147"/>
|
||||
<polygon id="000002B6365DCB50" class="l1205d0" points="-281.5,3.5 -288.5,3.5 -288.5,-3.5 -281.5,-3.5"/>
|
||||
<polygon id="000002B6365DD020" class="l1205d0" points="581.5,40 588.5,40 588.5,47 581.5,47"/>
|
||||
<polygon id="000002B6365DCCA0" class="l1205d0" points="581.5,-47 588.5,-47 588.5,-40 581.5,-40"/>
|
||||
<text id="000002B605621870" class="l1001t0" text-anchor="start" dominant-baseline="text-before-edge" transform="translate(-285 0) scale(1 -1)">a1</text>
|
||||
<text id="000002B605621240" class="l1001t0" text-anchor="start" dominant-baseline="text-before-edge" transform="translate(585 43.5) scale(1 -1)">b1</text>
|
||||
<text id="000002B605622830" class="l1001t0" text-anchor="start" dominant-baseline="text-before-edge" transform="translate(585 -43.5) scale(1 -1)">b2</text>
|
||||
<text id="000002B605621D80" class="l1001t0" text-anchor="start" dominant-baseline="text-before-edge" transform="translate(0 0) scale(1 -1)">a0</text>
|
||||
<text id="000002B605621750" class="l1001t0" text-anchor="start" dominant-baseline="text-before-edge" transform="translate(0 0) scale(1 -1)">b0</text>
|
||||
</g>
|
||||
<g id="1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2_405">
|
||||
<polygon id="000002B63618CA50" class="l1200d0" points="-285,-147 585,-147 585,147 -285,147"/>
|
||||
<polygon id="000002B605652430" class="l1205d0" points="-281.5,3.5 -288.5,3.5 -288.5,-3.5 -281.5,-3.5"/>
|
||||
<polygon id="000002B605651940" class="l1205d0" points="581.5,40 588.5,40 588.5,47 581.5,47"/>
|
||||
<polygon id="000002B605651BE0" class="l1205d0" points="581.5,-47 588.5,-47 588.5,-40 581.5,-40"/>
|
||||
<text id="000002B605620E50" class="l1001t0" text-anchor="start" dominant-baseline="text-before-edge" transform="translate(-285 0) scale(1 -1)">a1</text>
|
||||
<text id="000002B6056216C0" class="l1001t0" text-anchor="start" dominant-baseline="text-before-edge" transform="translate(585 43.5) scale(1 -1)">b1</text>
|
||||
<text id="000002B605622680" class="l1001t0" text-anchor="start" dominant-baseline="text-before-edge" transform="translate(585 -43.5) scale(1 -1)">b2</text>
|
||||
<text id="000002B605622B00" class="l1001t0" text-anchor="start" dominant-baseline="text-before-edge" transform="translate(0 0) scale(1 -1)">a0</text>
|
||||
<text id="000002B605621E10" class="l1001t0" text-anchor="start" dominant-baseline="text-before-edge" transform="translate(0 0) scale(1 -1)">b0</text>
|
||||
</g>
|
||||
</defs>
|
||||
<rect x="14680.2" y="22644.7" width="3218.6" height="2963.6" fill="#222222" stroke="none"/>
|
||||
<g id="mxpic_project_1" transform="scale(1 -1)">
|
||||
<polygon id="000002B6365DC8B0" class="l275d0" points="16679,-25360.75 15700,-25360.75 15700,-25356.25 16679,-25356.25"/>
|
||||
<polygon id="000002B6365DC610" class="l275d0" points="16679,-25360.75 15700,-25360.75 15700,-25356.25 16679,-25356.25"/>
|
||||
<polygon id="000002B6365DC290" class="l275d0" points="16679,-25360.75 15700,-25360.75 15700,-25356.25 16679,-25356.25"/>
|
||||
<polygon id="000002B6365DC370" class="l275d0" points="16679,-25360.75 15700,-25360.75 15700,-25356.25 16679,-25356.25"/>
|
||||
<polygon id="000002B6365DC300" class="l275d0" points="16679,-25360.75 15700,-25360.75 15700,-25356.25 16679,-25356.25"/>
|
||||
<polygon id="000002B6365DCAE0" class="l275d0" points="16679,-25360.75 15700,-25360.75 15700,-25356.25 16679,-25356.25"/>
|
||||
<polygon id="000002B6365DC680" class="l101d251" points="16719,-25400.75 15660,-25400.75 15660,-25316.25 16719,-25316.25"/>
|
||||
<polygon id="000002B6365DCF40" class="l101d251" points="16719,-25400.75 15660,-25400.75 15660,-25316.25 16719,-25316.25"/>
|
||||
<polygon id="000002B6365DC6F0" class="l101d251" points="16719,-25400.75 15660,-25400.75 15660,-25316.25 16719,-25316.25"/>
|
||||
<polygon id="000002B6365DCFB0" class="l101d251" points="16719,-25400.75 15660,-25400.75 15660,-25316.25 16719,-25316.25"/>
|
||||
<polygon id="000002B6365DC760" class="l101d251" points="16719,-25400.75 15660,-25400.75 15660,-25316.25 16719,-25316.25"/>
|
||||
<polygon id="000002B6365DC140" class="l101d251" points="16719,-25400.75 15660,-25400.75 15660,-25316.25 16719,-25316.25"/>
|
||||
<polygon id="000002B6365DC1B0" class="l275d0" points="16776.75,-25258.5 16781.25,-25258.5 16781.24,-25259.91 16781.17,-25262.73 16781.01,-25265.54 16780.78,-25268.35 16780.47,-25271.15 16780.08,-25273.94 16779.62,-25276.72 16779.08,-25279.48 16778.46,-25282.23 16777.77,-25284.97 16777,-25287.68 16776.16,-25290.37 16775.25,-25293.03 16774.26,-25295.67 16773.2,-25298.28 16772.07,-25300.86 16770.86,-25303.41 16769.59,-25305.92 16768.25,-25308.4 16766.84,-25310.84 16765.37,-25313.24 16763.82,-25315.6 16762.22,-25317.92 16760.55,-25320.19 16758.82,-25322.41 16757.03,-25324.59 16755.18,-25326.71 16753.27,-25328.78 16751.3,-25330.8 16749.28,-25332.77 16747.21,-25334.68 16745.09,-25336.53 16742.91,-25338.32 16740.69,-25340.05 16738.42,-25341.72 16736.1,-25343.32 16733.74,-25344.87 16731.34,-25346.34 16728.9,-25347.75 16726.42,-25349.09 16723.91,-25350.36 16721.36,-25351.57 16718.78,-25352.7 16716.17,-25353.76 16713.53,-25354.75 16710.87,-25355.66 16708.18,-25356.5 16705.47,-25357.27 16702.73,-25357.96 16699.98,-25358.58 16697.22,-25359.12 16694.44,-25359.58 16691.65,-25359.97 16688.85,-25360.28 16686.04,-25360.51 16683.23,-25360.67 16680.41,-25360.74 16679,-25360.75 16679,-25356.25 16680.35,-25356.24 16683.04,-25356.17 16685.73,-25356.02 16688.41,-25355.8 16691.09,-25355.5 16693.76,-25355.13 16696.42,-25354.69 16699.06,-25354.17 16701.69,-25353.58 16704.3,-25352.92 16706.89,-25352.19 16709.46,-25351.39 16712.01,-25350.51 16714.53,-25349.57 16717.03,-25348.55 16719.5,-25347.47 16721.93,-25346.32 16724.34,-25345.1 16726.71,-25343.82 16729.04,-25342.48 16731.33,-25341.06 16733.59,-25339.59 16735.8,-25338.06 16737.97,-25336.46 16740.1,-25334.81 16742.18,-25333.09 16744.21,-25331.32 16746.19,-25329.5 16748.12,-25327.62 16750,-25325.69 16751.82,-25323.71 16753.59,-25321.68 16755.31,-25319.6 16756.96,-25317.47 16758.56,-25315.3 16760.09,-25313.09 16761.56,-25310.83 16762.98,-25308.54 16764.32,-25306.21 16765.6,-25303.84 16766.82,-25301.43 16767.97,-25299 16769.05,-25296.53 16770.07,-25294.03 16771.01,-25291.51 16771.89,-25288.96 16772.69,-25286.39 16773.42,-25283.8 16774.08,-25281.19 16774.67,-25278.56 16775.19,-25275.92 16775.63,-25273.26 16776,-25270.59 16776.3,-25267.91 16776.52,-25265.23 16776.67,-25262.54 16776.74,-25259.85"/>
|
||||
<polygon id="000002B6365DC3E0" class="l275d0" points="16776.75,-25258.5 16781.25,-25258.5 16781.24,-25259.91 16781.17,-25262.73 16781.01,-25265.54 16780.78,-25268.35 16780.47,-25271.15 16780.08,-25273.94 16779.62,-25276.72 16779.08,-25279.48 16778.46,-25282.23 16777.77,-25284.97 16777,-25287.68 16776.16,-25290.37 16775.25,-25293.03 16774.26,-25295.67 16773.2,-25298.28 16772.07,-25300.86 16770.86,-25303.41 16769.59,-25305.92 16768.25,-25308.4 16766.84,-25310.84 16765.37,-25313.24 16763.82,-25315.6 16762.22,-25317.92 16760.55,-25320.19 16758.82,-25322.41 16757.03,-25324.59 16755.18,-25326.71 16753.27,-25328.78 16751.3,-25330.8 16749.28,-25332.77 16747.21,-25334.68 16745.09,-25336.53 16742.91,-25338.32 16740.69,-25340.05 16738.42,-25341.72 16736.1,-25343.32 16733.74,-25344.87 16731.34,-25346.34 16728.9,-25347.75 16726.42,-25349.09 16723.91,-25350.36 16721.36,-25351.57 16718.78,-25352.7 16716.17,-25353.76 16713.53,-25354.75 16710.87,-25355.66 16708.18,-25356.5 16705.47,-25357.27 16702.73,-25357.96 16699.98,-25358.58 16697.22,-25359.12 16694.44,-25359.58 16691.65,-25359.97 16688.85,-25360.28 16686.04,-25360.51 16683.23,-25360.67 16680.41,-25360.74 16679,-25360.75 16679,-25356.25 16680.35,-25356.24 16683.04,-25356.17 16685.73,-25356.02 16688.41,-25355.8 16691.09,-25355.5 16693.76,-25355.13 16696.42,-25354.69 16699.06,-25354.17 16701.69,-25353.58 16704.3,-25352.92 16706.89,-25352.19 16709.46,-25351.39 16712.01,-25350.51 16714.53,-25349.57 16717.03,-25348.55 16719.5,-25347.47 16721.93,-25346.32 16724.34,-25345.1 16726.71,-25343.82 16729.04,-25342.48 16731.33,-25341.06 16733.59,-25339.59 16735.8,-25338.06 16737.97,-25336.46 16740.1,-25334.81 16742.18,-25333.09 16744.21,-25331.32 16746.19,-25329.5 16748.12,-25327.62 16750,-25325.69 16751.82,-25323.71 16753.59,-25321.68 16755.31,-25319.6 16756.96,-25317.47 16758.56,-25315.3 16760.09,-25313.09 16761.56,-25310.83 16762.98,-25308.54 16764.32,-25306.21 16765.6,-25303.84 16766.82,-25301.43 16767.97,-25299 16769.05,-25296.53 16770.07,-25294.03 16771.01,-25291.51 16771.89,-25288.96 16772.69,-25286.39 16773.42,-25283.8 16774.08,-25281.19 16774.67,-25278.56 16775.19,-25275.92 16775.63,-25273.26 16776,-25270.59 16776.3,-25267.91 16776.52,-25265.23 16776.67,-25262.54 16776.74,-25259.85"/>
|
||||
<polygon id="000002B6365DC450" class="l275d0" points="16776.75,-25258.5 16781.25,-25258.5 16781.24,-25259.91 16781.17,-25262.73 16781.01,-25265.54 16780.78,-25268.35 16780.47,-25271.15 16780.08,-25273.94 16779.62,-25276.72 16779.08,-25279.48 16778.46,-25282.23 16777.77,-25284.97 16777,-25287.68 16776.16,-25290.37 16775.25,-25293.03 16774.26,-25295.67 16773.2,-25298.28 16772.07,-25300.86 16770.86,-25303.41 16769.59,-25305.92 16768.25,-25308.4 16766.84,-25310.84 16765.37,-25313.24 16763.82,-25315.6 16762.22,-25317.92 16760.55,-25320.19 16758.82,-25322.41 16757.03,-25324.59 16755.18,-25326.71 16753.27,-25328.78 16751.3,-25330.8 16749.28,-25332.77 16747.21,-25334.68 16745.09,-25336.53 16742.91,-25338.32 16740.69,-25340.05 16738.42,-25341.72 16736.1,-25343.32 16733.74,-25344.87 16731.34,-25346.34 16728.9,-25347.75 16726.42,-25349.09 16723.91,-25350.36 16721.36,-25351.57 16718.78,-25352.7 16716.17,-25353.76 16713.53,-25354.75 16710.87,-25355.66 16708.18,-25356.5 16705.47,-25357.27 16702.73,-25357.96 16699.98,-25358.58 16697.22,-25359.12 16694.44,-25359.58 16691.65,-25359.97 16688.85,-25360.28 16686.04,-25360.51 16683.23,-25360.67 16680.41,-25360.74 16679,-25360.75 16679,-25356.25 16680.35,-25356.24 16683.04,-25356.17 16685.73,-25356.02 16688.41,-25355.8 16691.09,-25355.5 16693.76,-25355.13 16696.42,-25354.69 16699.06,-25354.17 16701.69,-25353.58 16704.3,-25352.92 16706.89,-25352.19 16709.46,-25351.39 16712.01,-25350.51 16714.53,-25349.57 16717.03,-25348.55 16719.5,-25347.47 16721.93,-25346.32 16724.34,-25345.1 16726.71,-25343.82 16729.04,-25342.48 16731.33,-25341.06 16733.59,-25339.59 16735.8,-25338.06 16737.97,-25336.46 16740.1,-25334.81 16742.18,-25333.09 16744.21,-25331.32 16746.19,-25329.5 16748.12,-25327.62 16750,-25325.69 16751.82,-25323.71 16753.59,-25321.68 16755.31,-25319.6 16756.96,-25317.47 16758.56,-25315.3 16760.09,-25313.09 16761.56,-25310.83 16762.98,-25308.54 16764.32,-25306.21 16765.6,-25303.84 16766.82,-25301.43 16767.97,-25299 16769.05,-25296.53 16770.07,-25294.03 16771.01,-25291.51 16771.89,-25288.96 16772.69,-25286.39 16773.42,-25283.8 16774.08,-25281.19 16774.67,-25278.56 16775.19,-25275.92 16775.63,-25273.26 16776,-25270.59 16776.3,-25267.91 16776.52,-25265.23 16776.67,-25262.54 16776.74,-25259.85"/>
|
||||
<polygon id="000002B6365DC220" class="l275d0" points="16776.75,-25258.5 16781.25,-25258.5 16781.24,-25259.91 16781.17,-25262.73 16781.01,-25265.54 16780.78,-25268.35 16780.47,-25271.15 16780.08,-25273.94 16779.62,-25276.72 16779.08,-25279.48 16778.46,-25282.23 16777.77,-25284.97 16777,-25287.68 16776.16,-25290.37 16775.25,-25293.03 16774.26,-25295.67 16773.2,-25298.28 16772.07,-25300.86 16770.86,-25303.41 16769.59,-25305.92 16768.25,-25308.4 16766.84,-25310.84 16765.37,-25313.24 16763.82,-25315.6 16762.22,-25317.92 16760.55,-25320.19 16758.82,-25322.41 16757.03,-25324.59 16755.18,-25326.71 16753.27,-25328.78 16751.3,-25330.8 16749.28,-25332.77 16747.21,-25334.68 16745.09,-25336.53 16742.91,-25338.32 16740.69,-25340.05 16738.42,-25341.72 16736.1,-25343.32 16733.74,-25344.87 16731.34,-25346.34 16728.9,-25347.75 16726.42,-25349.09 16723.91,-25350.36 16721.36,-25351.57 16718.78,-25352.7 16716.17,-25353.76 16713.53,-25354.75 16710.87,-25355.66 16708.18,-25356.5 16705.47,-25357.27 16702.73,-25357.96 16699.98,-25358.58 16697.22,-25359.12 16694.44,-25359.58 16691.65,-25359.97 16688.85,-25360.28 16686.04,-25360.51 16683.23,-25360.67 16680.41,-25360.74 16679,-25360.75 16679,-25356.25 16680.35,-25356.24 16683.04,-25356.17 16685.73,-25356.02 16688.41,-25355.8 16691.09,-25355.5 16693.76,-25355.13 16696.42,-25354.69 16699.06,-25354.17 16701.69,-25353.58 16704.3,-25352.92 16706.89,-25352.19 16709.46,-25351.39 16712.01,-25350.51 16714.53,-25349.57 16717.03,-25348.55 16719.5,-25347.47 16721.93,-25346.32 16724.34,-25345.1 16726.71,-25343.82 16729.04,-25342.48 16731.33,-25341.06 16733.59,-25339.59 16735.8,-25338.06 16737.97,-25336.46 16740.1,-25334.81 16742.18,-25333.09 16744.21,-25331.32 16746.19,-25329.5 16748.12,-25327.62 16750,-25325.69 16751.82,-25323.71 16753.59,-25321.68 16755.31,-25319.6 16756.96,-25317.47 16758.56,-25315.3 16760.09,-25313.09 16761.56,-25310.83 16762.98,-25308.54 16764.32,-25306.21 16765.6,-25303.84 16766.82,-25301.43 16767.97,-25299 16769.05,-25296.53 16770.07,-25294.03 16771.01,-25291.51 16771.89,-25288.96 16772.69,-25286.39 16773.42,-25283.8 16774.08,-25281.19 16774.67,-25278.56 16775.19,-25275.92 16775.63,-25273.26 16776,-25270.59 16776.3,-25267.91 16776.52,-25265.23 16776.67,-25262.54 16776.74,-25259.85"/>
|
||||
<polygon id="000002B6365DC4C0" class="l275d0" points="16776.75,-25258.5 16781.25,-25258.5 16781.24,-25259.91 16781.17,-25262.73 16781.01,-25265.54 16780.78,-25268.35 16780.47,-25271.15 16780.08,-25273.94 16779.62,-25276.72 16779.08,-25279.48 16778.46,-25282.23 16777.77,-25284.97 16777,-25287.68 16776.16,-25290.37 16775.25,-25293.03 16774.26,-25295.67 16773.2,-25298.28 16772.07,-25300.86 16770.86,-25303.41 16769.59,-25305.92 16768.25,-25308.4 16766.84,-25310.84 16765.37,-25313.24 16763.82,-25315.6 16762.22,-25317.92 16760.55,-25320.19 16758.82,-25322.41 16757.03,-25324.59 16755.18,-25326.71 16753.27,-25328.78 16751.3,-25330.8 16749.28,-25332.77 16747.21,-25334.68 16745.09,-25336.53 16742.91,-25338.32 16740.69,-25340.05 16738.42,-25341.72 16736.1,-25343.32 16733.74,-25344.87 16731.34,-25346.34 16728.9,-25347.75 16726.42,-25349.09 16723.91,-25350.36 16721.36,-25351.57 16718.78,-25352.7 16716.17,-25353.76 16713.53,-25354.75 16710.87,-25355.66 16708.18,-25356.5 16705.47,-25357.27 16702.73,-25357.96 16699.98,-25358.58 16697.22,-25359.12 16694.44,-25359.58 16691.65,-25359.97 16688.85,-25360.28 16686.04,-25360.51 16683.23,-25360.67 16680.41,-25360.74 16679,-25360.75 16679,-25356.25 16680.35,-25356.24 16683.04,-25356.17 16685.73,-25356.02 16688.41,-25355.8 16691.09,-25355.5 16693.76,-25355.13 16696.42,-25354.69 16699.06,-25354.17 16701.69,-25353.58 16704.3,-25352.92 16706.89,-25352.19 16709.46,-25351.39 16712.01,-25350.51 16714.53,-25349.57 16717.03,-25348.55 16719.5,-25347.47 16721.93,-25346.32 16724.34,-25345.1 16726.71,-25343.82 16729.04,-25342.48 16731.33,-25341.06 16733.59,-25339.59 16735.8,-25338.06 16737.97,-25336.46 16740.1,-25334.81 16742.18,-25333.09 16744.21,-25331.32 16746.19,-25329.5 16748.12,-25327.62 16750,-25325.69 16751.82,-25323.71 16753.59,-25321.68 16755.31,-25319.6 16756.96,-25317.47 16758.56,-25315.3 16760.09,-25313.09 16761.56,-25310.83 16762.98,-25308.54 16764.32,-25306.21 16765.6,-25303.84 16766.82,-25301.43 16767.97,-25299 16769.05,-25296.53 16770.07,-25294.03 16771.01,-25291.51 16771.89,-25288.96 16772.69,-25286.39 16773.42,-25283.8 16774.08,-25281.19 16774.67,-25278.56 16775.19,-25275.92 16775.63,-25273.26 16776,-25270.59 16776.3,-25267.91 16776.52,-25265.23 16776.67,-25262.54 16776.74,-25259.85"/>
|
||||
<polygon id="000002B6365DCBC0" class="l275d0" points="16776.75,-25258.5 16781.25,-25258.5 16781.24,-25259.91 16781.17,-25262.73 16781.01,-25265.54 16780.78,-25268.35 16780.47,-25271.15 16780.08,-25273.94 16779.62,-25276.72 16779.08,-25279.48 16778.46,-25282.23 16777.77,-25284.97 16777,-25287.68 16776.16,-25290.37 16775.25,-25293.03 16774.26,-25295.67 16773.2,-25298.28 16772.07,-25300.86 16770.86,-25303.41 16769.59,-25305.92 16768.25,-25308.4 16766.84,-25310.84 16765.37,-25313.24 16763.82,-25315.6 16762.22,-25317.92 16760.55,-25320.19 16758.82,-25322.41 16757.03,-25324.59 16755.18,-25326.71 16753.27,-25328.78 16751.3,-25330.8 16749.28,-25332.77 16747.21,-25334.68 16745.09,-25336.53 16742.91,-25338.32 16740.69,-25340.05 16738.42,-25341.72 16736.1,-25343.32 16733.74,-25344.87 16731.34,-25346.34 16728.9,-25347.75 16726.42,-25349.09 16723.91,-25350.36 16721.36,-25351.57 16718.78,-25352.7 16716.17,-25353.76 16713.53,-25354.75 16710.87,-25355.66 16708.18,-25356.5 16705.47,-25357.27 16702.73,-25357.96 16699.98,-25358.58 16697.22,-25359.12 16694.44,-25359.58 16691.65,-25359.97 16688.85,-25360.28 16686.04,-25360.51 16683.23,-25360.67 16680.41,-25360.74 16679,-25360.75 16679,-25356.25 16680.35,-25356.24 16683.04,-25356.17 16685.73,-25356.02 16688.41,-25355.8 16691.09,-25355.5 16693.76,-25355.13 16696.42,-25354.69 16699.06,-25354.17 16701.69,-25353.58 16704.3,-25352.92 16706.89,-25352.19 16709.46,-25351.39 16712.01,-25350.51 16714.53,-25349.57 16717.03,-25348.55 16719.5,-25347.47 16721.93,-25346.32 16724.34,-25345.1 16726.71,-25343.82 16729.04,-25342.48 16731.33,-25341.06 16733.59,-25339.59 16735.8,-25338.06 16737.97,-25336.46 16740.1,-25334.81 16742.18,-25333.09 16744.21,-25331.32 16746.19,-25329.5 16748.12,-25327.62 16750,-25325.69 16751.82,-25323.71 16753.59,-25321.68 16755.31,-25319.6 16756.96,-25317.47 16758.56,-25315.3 16760.09,-25313.09 16761.56,-25310.83 16762.98,-25308.54 16764.32,-25306.21 16765.6,-25303.84 16766.82,-25301.43 16767.97,-25299 16769.05,-25296.53 16770.07,-25294.03 16771.01,-25291.51 16771.89,-25288.96 16772.69,-25286.39 16773.42,-25283.8 16774.08,-25281.19 16774.67,-25278.56 16775.19,-25275.92 16775.63,-25273.26 16776,-25270.59 16776.3,-25267.91 16776.52,-25265.23 16776.67,-25262.54 16776.74,-25259.85"/>
|
||||
<polygon id="000002B6365DCED0" class="l101d251" points="16736.75,-25258.5 16821.25,-25258.5 16821.24,-25260.17 16821.17,-25263.5 16821.01,-25266.83 16820.77,-25270.16 16820.46,-25273.48 16820.07,-25276.79 16819.6,-25280.09 16819.06,-25283.38 16818.44,-25286.66 16817.74,-25289.92 16816.96,-25293.17 16816.11,-25296.39 16815.19,-25299.59 16814.19,-25302.78 16813.11,-25305.93 16811.96,-25309.06 16810.74,-25312.17 16809.45,-25315.24 16808.08,-25318.28 16806.64,-25321.29 16805.14,-25324.27 16803.56,-25327.21 16801.92,-25330.11 16800.2,-25332.97 16798.42,-25335.79 16796.58,-25338.57 16794.67,-25341.3 16792.7,-25343.99 16790.66,-25346.63 16788.57,-25349.23 16786.41,-25351.77 16784.19,-25354.26 16781.92,-25356.7 16779.59,-25359.09 16777.2,-25361.42 16774.76,-25363.69 16772.27,-25365.91 16769.73,-25368.07 16767.13,-25370.16 16764.49,-25372.2 16761.8,-25374.17 16759.07,-25376.08 16756.29,-25377.92 16753.47,-25379.7 16750.61,-25381.42 16747.71,-25383.06 16744.77,-25384.64 16741.79,-25386.14 16738.78,-25387.58 16735.74,-25388.95 16732.67,-25390.24 16729.56,-25391.46 16726.43,-25392.61 16723.28,-25393.69 16720.09,-25394.69 16716.89,-25395.61 16713.67,-25396.46 16710.42,-25397.24 16707.16,-25397.94 16703.88,-25398.56 16700.59,-25399.1 16697.29,-25399.57 16693.98,-25399.96 16690.66,-25400.27 16687.33,-25400.51 16684,-25400.67 16680.67,-25400.74 16679,-25400.75 16679,-25316.25 16679.68,-25316.25 16681.03,-25316.22 16682.38,-25316.15 16683.73,-25316.06 16685.08,-25315.93 16686.43,-25315.77 16687.77,-25315.58 16689.1,-25315.36 16690.43,-25315.11 16691.76,-25314.82 16693.07,-25314.51 16694.38,-25314.16 16695.68,-25313.79 16696.97,-25313.38 16698.26,-25312.95 16699.53,-25312.48 16700.79,-25311.98 16702.04,-25311.46 16703.27,-25310.9 16704.49,-25310.32 16705.7,-25309.71 16706.89,-25309.07 16708.07,-25308.4 16709.23,-25307.71 16710.38,-25306.98 16711.51,-25306.23 16712.62,-25305.46 16713.71,-25304.66 16714.78,-25303.83 16715.83,-25302.98 16716.87,-25302.11 16717.88,-25301.21 16718.87,-25300.28 16719.84,-25299.34 16720.78,-25298.37 16721.71,-25297.38 16722.61,-25296.37 16723.48,-25295.33 16724.33,-25294.28 16725.16,-25293.21 16725.96,-25292.12 16726.73,-25291.01 16727.48,-25289.88 16728.21,-25288.73 16728.9,-25287.57 16729.57,-25286.39 16730.21,-25285.2 16730.82,-25283.99 16731.4,-25282.77 16731.96,-25281.54 16732.48,-25280.29 16732.98,-25279.03 16733.45,-25277.76 16733.88,-25276.47 16734.29,-25275.18 16734.66,-25273.88 16735.01,-25272.57 16735.32,-25271.26 16735.61,-25269.93 16735.86,-25268.6 16736.08,-25267.27 16736.27,-25265.93 16736.43,-25264.58 16736.56,-25263.23 16736.65,-25261.88 16736.72,-25260.53 16736.75,-25259.18"/>
|
||||
<polygon id="000002B6365DC920" class="l101d251" points="16736.75,-25258.5 16821.25,-25258.5 16821.24,-25260.17 16821.17,-25263.5 16821.01,-25266.83 16820.77,-25270.16 16820.46,-25273.48 16820.07,-25276.79 16819.6,-25280.09 16819.06,-25283.38 16818.44,-25286.66 16817.74,-25289.92 16816.96,-25293.17 16816.11,-25296.39 16815.19,-25299.59 16814.19,-25302.78 16813.11,-25305.93 16811.96,-25309.06 16810.74,-25312.17 16809.45,-25315.24 16808.08,-25318.28 16806.64,-25321.29 16805.14,-25324.27 16803.56,-25327.21 16801.92,-25330.11 16800.2,-25332.97 16798.42,-25335.79 16796.58,-25338.57 16794.67,-25341.3 16792.7,-25343.99 16790.66,-25346.63 16788.57,-25349.23 16786.41,-25351.77 16784.19,-25354.26 16781.92,-25356.7 16779.59,-25359.09 16777.2,-25361.42 16774.76,-25363.69 16772.27,-25365.91 16769.73,-25368.07 16767.13,-25370.16 16764.49,-25372.2 16761.8,-25374.17 16759.07,-25376.08 16756.29,-25377.92 16753.47,-25379.7 16750.61,-25381.42 16747.71,-25383.06 16744.77,-25384.64 16741.79,-25386.14 16738.78,-25387.58 16735.74,-25388.95 16732.67,-25390.24 16729.56,-25391.46 16726.43,-25392.61 16723.28,-25393.69 16720.09,-25394.69 16716.89,-25395.61 16713.67,-25396.46 16710.42,-25397.24 16707.16,-25397.94 16703.88,-25398.56 16700.59,-25399.1 16697.29,-25399.57 16693.98,-25399.96 16690.66,-25400.27 16687.33,-25400.51 16684,-25400.67 16680.67,-25400.74 16679,-25400.75 16679,-25316.25 16679.68,-25316.25 16681.03,-25316.22 16682.38,-25316.15 16683.73,-25316.06 16685.08,-25315.93 16686.43,-25315.77 16687.77,-25315.58 16689.1,-25315.36 16690.43,-25315.11 16691.76,-25314.82 16693.07,-25314.51 16694.38,-25314.16 16695.68,-25313.79 16696.97,-25313.38 16698.26,-25312.95 16699.53,-25312.48 16700.79,-25311.98 16702.04,-25311.46 16703.27,-25310.9 16704.49,-25310.32 16705.7,-25309.71 16706.89,-25309.07 16708.07,-25308.4 16709.23,-25307.71 16710.38,-25306.98 16711.51,-25306.23 16712.62,-25305.46 16713.71,-25304.66 16714.78,-25303.83 16715.83,-25302.98 16716.87,-25302.11 16717.88,-25301.21 16718.87,-25300.28 16719.84,-25299.34 16720.78,-25298.37 16721.71,-25297.38 16722.61,-25296.37 16723.48,-25295.33 16724.33,-25294.28 16725.16,-25293.21 16725.96,-25292.12 16726.73,-25291.01 16727.48,-25289.88 16728.21,-25288.73 16728.9,-25287.57 16729.57,-25286.39 16730.21,-25285.2 16730.82,-25283.99 16731.4,-25282.77 16731.96,-25281.54 16732.48,-25280.29 16732.98,-25279.03 16733.45,-25277.76 16733.88,-25276.47 16734.29,-25275.18 16734.66,-25273.88 16735.01,-25272.57 16735.32,-25271.26 16735.61,-25269.93 16735.86,-25268.6 16736.08,-25267.27 16736.27,-25265.93 16736.43,-25264.58 16736.56,-25263.23 16736.65,-25261.88 16736.72,-25260.53 16736.75,-25259.18"/>
|
||||
<polygon id="000002B6365DC840" class="l101d251" points="16736.75,-25258.5 16821.25,-25258.5 16821.24,-25260.17 16821.17,-25263.5 16821.01,-25266.83 16820.77,-25270.16 16820.46,-25273.48 16820.07,-25276.79 16819.6,-25280.09 16819.06,-25283.38 16818.44,-25286.66 16817.74,-25289.92 16816.96,-25293.17 16816.11,-25296.39 16815.19,-25299.59 16814.19,-25302.78 16813.11,-25305.93 16811.96,-25309.06 16810.74,-25312.17 16809.45,-25315.24 16808.08,-25318.28 16806.64,-25321.29 16805.14,-25324.27 16803.56,-25327.21 16801.92,-25330.11 16800.2,-25332.97 16798.42,-25335.79 16796.58,-25338.57 16794.67,-25341.3 16792.7,-25343.99 16790.66,-25346.63 16788.57,-25349.23 16786.41,-25351.77 16784.19,-25354.26 16781.92,-25356.7 16779.59,-25359.09 16777.2,-25361.42 16774.76,-25363.69 16772.27,-25365.91 16769.73,-25368.07 16767.13,-25370.16 16764.49,-25372.2 16761.8,-25374.17 16759.07,-25376.08 16756.29,-25377.92 16753.47,-25379.7 16750.61,-25381.42 16747.71,-25383.06 16744.77,-25384.64 16741.79,-25386.14 16738.78,-25387.58 16735.74,-25388.95 16732.67,-25390.24 16729.56,-25391.46 16726.43,-25392.61 16723.28,-25393.69 16720.09,-25394.69 16716.89,-25395.61 16713.67,-25396.46 16710.42,-25397.24 16707.16,-25397.94 16703.88,-25398.56 16700.59,-25399.1 16697.29,-25399.57 16693.98,-25399.96 16690.66,-25400.27 16687.33,-25400.51 16684,-25400.67 16680.67,-25400.74 16679,-25400.75 16679,-25316.25 16679.68,-25316.25 16681.03,-25316.22 16682.38,-25316.15 16683.73,-25316.06 16685.08,-25315.93 16686.43,-25315.77 16687.77,-25315.58 16689.1,-25315.36 16690.43,-25315.11 16691.76,-25314.82 16693.07,-25314.51 16694.38,-25314.16 16695.68,-25313.79 16696.97,-25313.38 16698.26,-25312.95 16699.53,-25312.48 16700.79,-25311.98 16702.04,-25311.46 16703.27,-25310.9 16704.49,-25310.32 16705.7,-25309.71 16706.89,-25309.07 16708.07,-25308.4 16709.23,-25307.71 16710.38,-25306.98 16711.51,-25306.23 16712.62,-25305.46 16713.71,-25304.66 16714.78,-25303.83 16715.83,-25302.98 16716.87,-25302.11 16717.88,-25301.21 16718.87,-25300.28 16719.84,-25299.34 16720.78,-25298.37 16721.71,-25297.38 16722.61,-25296.37 16723.48,-25295.33 16724.33,-25294.28 16725.16,-25293.21 16725.96,-25292.12 16726.73,-25291.01 16727.48,-25289.88 16728.21,-25288.73 16728.9,-25287.57 16729.57,-25286.39 16730.21,-25285.2 16730.82,-25283.99 16731.4,-25282.77 16731.96,-25281.54 16732.48,-25280.29 16732.98,-25279.03 16733.45,-25277.76 16733.88,-25276.47 16734.29,-25275.18 16734.66,-25273.88 16735.01,-25272.57 16735.32,-25271.26 16735.61,-25269.93 16735.86,-25268.6 16736.08,-25267.27 16736.27,-25265.93 16736.43,-25264.58 16736.56,-25263.23 16736.65,-25261.88 16736.72,-25260.53 16736.75,-25259.18"/>
|
||||
<polygon id="000002B6365DCC30" class="l101d251" points="16736.75,-25258.5 16821.25,-25258.5 16821.24,-25260.17 16821.17,-25263.5 16821.01,-25266.83 16820.77,-25270.16 16820.46,-25273.48 16820.07,-25276.79 16819.6,-25280.09 16819.06,-25283.38 16818.44,-25286.66 16817.74,-25289.92 16816.96,-25293.17 16816.11,-25296.39 16815.19,-25299.59 16814.19,-25302.78 16813.11,-25305.93 16811.96,-25309.06 16810.74,-25312.17 16809.45,-25315.24 16808.08,-25318.28 16806.64,-25321.29 16805.14,-25324.27 16803.56,-25327.21 16801.92,-25330.11 16800.2,-25332.97 16798.42,-25335.79 16796.58,-25338.57 16794.67,-25341.3 16792.7,-25343.99 16790.66,-25346.63 16788.57,-25349.23 16786.41,-25351.77 16784.19,-25354.26 16781.92,-25356.7 16779.59,-25359.09 16777.2,-25361.42 16774.76,-25363.69 16772.27,-25365.91 16769.73,-25368.07 16767.13,-25370.16 16764.49,-25372.2 16761.8,-25374.17 16759.07,-25376.08 16756.29,-25377.92 16753.47,-25379.7 16750.61,-25381.42 16747.71,-25383.06 16744.77,-25384.64 16741.79,-25386.14 16738.78,-25387.58 16735.74,-25388.95 16732.67,-25390.24 16729.56,-25391.46 16726.43,-25392.61 16723.28,-25393.69 16720.09,-25394.69 16716.89,-25395.61 16713.67,-25396.46 16710.42,-25397.24 16707.16,-25397.94 16703.88,-25398.56 16700.59,-25399.1 16697.29,-25399.57 16693.98,-25399.96 16690.66,-25400.27 16687.33,-25400.51 16684,-25400.67 16680.67,-25400.74 16679,-25400.75 16679,-25316.25 16679.68,-25316.25 16681.03,-25316.22 16682.38,-25316.15 16683.73,-25316.06 16685.08,-25315.93 16686.43,-25315.77 16687.77,-25315.58 16689.1,-25315.36 16690.43,-25315.11 16691.76,-25314.82 16693.07,-25314.51 16694.38,-25314.16 16695.68,-25313.79 16696.97,-25313.38 16698.26,-25312.95 16699.53,-25312.48 16700.79,-25311.98 16702.04,-25311.46 16703.27,-25310.9 16704.49,-25310.32 16705.7,-25309.71 16706.89,-25309.07 16708.07,-25308.4 16709.23,-25307.71 16710.38,-25306.98 16711.51,-25306.23 16712.62,-25305.46 16713.71,-25304.66 16714.78,-25303.83 16715.83,-25302.98 16716.87,-25302.11 16717.88,-25301.21 16718.87,-25300.28 16719.84,-25299.34 16720.78,-25298.37 16721.71,-25297.38 16722.61,-25296.37 16723.48,-25295.33 16724.33,-25294.28 16725.16,-25293.21 16725.96,-25292.12 16726.73,-25291.01 16727.48,-25289.88 16728.21,-25288.73 16728.9,-25287.57 16729.57,-25286.39 16730.21,-25285.2 16730.82,-25283.99 16731.4,-25282.77 16731.96,-25281.54 16732.48,-25280.29 16732.98,-25279.03 16733.45,-25277.76 16733.88,-25276.47 16734.29,-25275.18 16734.66,-25273.88 16735.01,-25272.57 16735.32,-25271.26 16735.61,-25269.93 16735.86,-25268.6 16736.08,-25267.27 16736.27,-25265.93 16736.43,-25264.58 16736.56,-25263.23 16736.65,-25261.88 16736.72,-25260.53 16736.75,-25259.18"/>
|
||||
<polygon id="000002B6365DCD10" class="l101d251" points="16736.75,-25258.5 16821.25,-25258.5 16821.24,-25260.17 16821.17,-25263.5 16821.01,-25266.83 16820.77,-25270.16 16820.46,-25273.48 16820.07,-25276.79 16819.6,-25280.09 16819.06,-25283.38 16818.44,-25286.66 16817.74,-25289.92 16816.96,-25293.17 16816.11,-25296.39 16815.19,-25299.59 16814.19,-25302.78 16813.11,-25305.93 16811.96,-25309.06 16810.74,-25312.17 16809.45,-25315.24 16808.08,-25318.28 16806.64,-25321.29 16805.14,-25324.27 16803.56,-25327.21 16801.92,-25330.11 16800.2,-25332.97 16798.42,-25335.79 16796.58,-25338.57 16794.67,-25341.3 16792.7,-25343.99 16790.66,-25346.63 16788.57,-25349.23 16786.41,-25351.77 16784.19,-25354.26 16781.92,-25356.7 16779.59,-25359.09 16777.2,-25361.42 16774.76,-25363.69 16772.27,-25365.91 16769.73,-25368.07 16767.13,-25370.16 16764.49,-25372.2 16761.8,-25374.17 16759.07,-25376.08 16756.29,-25377.92 16753.47,-25379.7 16750.61,-25381.42 16747.71,-25383.06 16744.77,-25384.64 16741.79,-25386.14 16738.78,-25387.58 16735.74,-25388.95 16732.67,-25390.24 16729.56,-25391.46 16726.43,-25392.61 16723.28,-25393.69 16720.09,-25394.69 16716.89,-25395.61 16713.67,-25396.46 16710.42,-25397.24 16707.16,-25397.94 16703.88,-25398.56 16700.59,-25399.1 16697.29,-25399.57 16693.98,-25399.96 16690.66,-25400.27 16687.33,-25400.51 16684,-25400.67 16680.67,-25400.74 16679,-25400.75 16679,-25316.25 16679.68,-25316.25 16681.03,-25316.22 16682.38,-25316.15 16683.73,-25316.06 16685.08,-25315.93 16686.43,-25315.77 16687.77,-25315.58 16689.1,-25315.36 16690.43,-25315.11 16691.76,-25314.82 16693.07,-25314.51 16694.38,-25314.16 16695.68,-25313.79 16696.97,-25313.38 16698.26,-25312.95 16699.53,-25312.48 16700.79,-25311.98 16702.04,-25311.46 16703.27,-25310.9 16704.49,-25310.32 16705.7,-25309.71 16706.89,-25309.07 16708.07,-25308.4 16709.23,-25307.71 16710.38,-25306.98 16711.51,-25306.23 16712.62,-25305.46 16713.71,-25304.66 16714.78,-25303.83 16715.83,-25302.98 16716.87,-25302.11 16717.88,-25301.21 16718.87,-25300.28 16719.84,-25299.34 16720.78,-25298.37 16721.71,-25297.38 16722.61,-25296.37 16723.48,-25295.33 16724.33,-25294.28 16725.16,-25293.21 16725.96,-25292.12 16726.73,-25291.01 16727.48,-25289.88 16728.21,-25288.73 16728.9,-25287.57 16729.57,-25286.39 16730.21,-25285.2 16730.82,-25283.99 16731.4,-25282.77 16731.96,-25281.54 16732.48,-25280.29 16732.98,-25279.03 16733.45,-25277.76 16733.88,-25276.47 16734.29,-25275.18 16734.66,-25273.88 16735.01,-25272.57 16735.32,-25271.26 16735.61,-25269.93 16735.86,-25268.6 16736.08,-25267.27 16736.27,-25265.93 16736.43,-25264.58 16736.56,-25263.23 16736.65,-25261.88 16736.72,-25260.53 16736.75,-25259.18"/>
|
||||
<polygon id="000002B6365DC530" class="l101d251" points="16736.75,-25258.5 16821.25,-25258.5 16821.24,-25260.17 16821.17,-25263.5 16821.01,-25266.83 16820.77,-25270.16 16820.46,-25273.48 16820.07,-25276.79 16819.6,-25280.09 16819.06,-25283.38 16818.44,-25286.66 16817.74,-25289.92 16816.96,-25293.17 16816.11,-25296.39 16815.19,-25299.59 16814.19,-25302.78 16813.11,-25305.93 16811.96,-25309.06 16810.74,-25312.17 16809.45,-25315.24 16808.08,-25318.28 16806.64,-25321.29 16805.14,-25324.27 16803.56,-25327.21 16801.92,-25330.11 16800.2,-25332.97 16798.42,-25335.79 16796.58,-25338.57 16794.67,-25341.3 16792.7,-25343.99 16790.66,-25346.63 16788.57,-25349.23 16786.41,-25351.77 16784.19,-25354.26 16781.92,-25356.7 16779.59,-25359.09 16777.2,-25361.42 16774.76,-25363.69 16772.27,-25365.91 16769.73,-25368.07 16767.13,-25370.16 16764.49,-25372.2 16761.8,-25374.17 16759.07,-25376.08 16756.29,-25377.92 16753.47,-25379.7 16750.61,-25381.42 16747.71,-25383.06 16744.77,-25384.64 16741.79,-25386.14 16738.78,-25387.58 16735.74,-25388.95 16732.67,-25390.24 16729.56,-25391.46 16726.43,-25392.61 16723.28,-25393.69 16720.09,-25394.69 16716.89,-25395.61 16713.67,-25396.46 16710.42,-25397.24 16707.16,-25397.94 16703.88,-25398.56 16700.59,-25399.1 16697.29,-25399.57 16693.98,-25399.96 16690.66,-25400.27 16687.33,-25400.51 16684,-25400.67 16680.67,-25400.74 16679,-25400.75 16679,-25316.25 16679.68,-25316.25 16681.03,-25316.22 16682.38,-25316.15 16683.73,-25316.06 16685.08,-25315.93 16686.43,-25315.77 16687.77,-25315.58 16689.1,-25315.36 16690.43,-25315.11 16691.76,-25314.82 16693.07,-25314.51 16694.38,-25314.16 16695.68,-25313.79 16696.97,-25313.38 16698.26,-25312.95 16699.53,-25312.48 16700.79,-25311.98 16702.04,-25311.46 16703.27,-25310.9 16704.49,-25310.32 16705.7,-25309.71 16706.89,-25309.07 16708.07,-25308.4 16709.23,-25307.71 16710.38,-25306.98 16711.51,-25306.23 16712.62,-25305.46 16713.71,-25304.66 16714.78,-25303.83 16715.83,-25302.98 16716.87,-25302.11 16717.88,-25301.21 16718.87,-25300.28 16719.84,-25299.34 16720.78,-25298.37 16721.71,-25297.38 16722.61,-25296.37 16723.48,-25295.33 16724.33,-25294.28 16725.16,-25293.21 16725.96,-25292.12 16726.73,-25291.01 16727.48,-25289.88 16728.21,-25288.73 16728.9,-25287.57 16729.57,-25286.39 16730.21,-25285.2 16730.82,-25283.99 16731.4,-25282.77 16731.96,-25281.54 16732.48,-25280.29 16732.98,-25279.03 16733.45,-25277.76 16733.88,-25276.47 16734.29,-25275.18 16734.66,-25273.88 16735.01,-25272.57 16735.32,-25271.26 16735.61,-25269.93 16735.86,-25268.6 16736.08,-25267.27 16736.27,-25265.93 16736.43,-25264.58 16736.56,-25263.23 16736.65,-25261.88 16736.72,-25260.53 16736.75,-25259.18"/>
|
||||
<polygon id="000002B6365DC5A0" class="l275d0" points="16781.25,-23038 16781.25,-25258.5 16776.75,-25258.5 16776.75,-23038"/>
|
||||
<polygon id="000002B6365DC990" class="l275d0" points="16781.25,-23038 16781.25,-25258.5 16776.75,-25258.5 16776.75,-23038"/>
|
||||
<polygon id="000002B6365DC7D0" class="l275d0" points="16781.25,-23038 16781.25,-25258.5 16776.75,-25258.5 16776.75,-23038"/>
|
||||
<polygon id="000002B6365DCA00" class="l275d0" points="16781.25,-23038 16781.25,-25258.5 16776.75,-25258.5 16776.75,-23038"/>
|
||||
<polygon id="000002B6365DCA70" class="l275d0" points="16781.25,-23038 16781.25,-25258.5 16776.75,-25258.5 16776.75,-23038"/>
|
||||
<polygon id="000002B6365DCD80" class="l275d0" points="16781.25,-23038 16781.25,-25258.5 16776.75,-25258.5 16776.75,-23038"/>
|
||||
<polygon id="000002B6365DCDF0" class="l101d251" points="16821.25,-22998 16821.25,-25298.5 16736.75,-25298.5 16736.75,-22998"/>
|
||||
<polygon id="000002B6361D9040" class="l101d251" points="16821.25,-22998 16821.25,-25298.5 16736.75,-25298.5 16736.75,-22998"/>
|
||||
<polygon id="000002B6361D92E0" class="l101d251" points="16821.25,-22998 16821.25,-25298.5 16736.75,-25298.5 16736.75,-22998"/>
|
||||
<polygon id="000002B6361D9740" class="l101d251" points="16821.25,-22998 16821.25,-25298.5 16736.75,-25298.5 16736.75,-22998"/>
|
||||
<polygon id="000002B6361D9190" class="l101d251" points="16821.25,-22998 16821.25,-25298.5 16736.75,-25298.5 16736.75,-22998"/>
|
||||
<polygon id="000002B6361D8D30" class="l101d251" points="16821.25,-22998 16821.25,-25298.5 16736.75,-25298.5 16736.75,-22998"/>
|
||||
<polygon id="000002B6361D8B00" class="l275d0" points="16879,-22935.75 16879,-22940.25 16877.65,-22940.26 16874.96,-22940.33 16872.27,-22940.48 16869.59,-22940.7 16866.91,-22941 16864.24,-22941.37 16861.58,-22941.81 16858.94,-22942.33 16856.31,-22942.92 16853.7,-22943.58 16851.11,-22944.31 16848.54,-22945.11 16845.99,-22945.99 16843.47,-22946.93 16840.97,-22947.95 16838.5,-22949.03 16836.07,-22950.18 16833.66,-22951.4 16831.29,-22952.68 16828.96,-22954.02 16826.67,-22955.44 16824.41,-22956.91 16822.2,-22958.44 16820.03,-22960.04 16817.9,-22961.69 16815.82,-22963.41 16813.79,-22965.18 16811.81,-22967 16809.88,-22968.88 16808,-22970.81 16806.18,-22972.79 16804.41,-22974.82 16802.69,-22976.9 16801.04,-22979.03 16799.44,-22981.2 16797.91,-22983.41 16796.44,-22985.67 16795.02,-22987.96 16793.68,-22990.29 16792.4,-22992.66 16791.18,-22995.07 16790.03,-22997.5 16788.95,-22999.97 16787.93,-23002.47 16786.99,-23004.99 16786.11,-23007.54 16785.31,-23010.11 16784.58,-23012.7 16783.92,-23015.31 16783.33,-23017.94 16782.81,-23020.58 16782.37,-23023.24 16782,-23025.91 16781.7,-23028.59 16781.48,-23031.27 16781.33,-23033.96 16781.26,-23036.65 16781.25,-23038 16776.75,-23038 16776.76,-23036.59 16776.83,-23033.77 16776.99,-23030.96 16777.22,-23028.15 16777.53,-23025.35 16777.92,-23022.56 16778.38,-23019.78 16778.92,-23017.02 16779.54,-23014.27 16780.23,-23011.53 16781,-23008.82 16781.84,-23006.13 16782.75,-23003.47 16783.74,-23000.83 16784.8,-22998.22 16785.93,-22995.64 16787.14,-22993.09 16788.41,-22990.58 16789.75,-22988.1 16791.16,-22985.66 16792.63,-22983.26 16794.18,-22980.9 16795.78,-22978.58 16797.45,-22976.31 16799.18,-22974.09 16800.97,-22971.91 16802.82,-22969.79 16804.73,-22967.72 16806.7,-22965.7 16808.72,-22963.73 16810.79,-22961.82 16812.91,-22959.97 16815.09,-22958.18 16817.31,-22956.45 16819.58,-22954.78 16821.9,-22953.18 16824.26,-22951.63 16826.66,-22950.16 16829.1,-22948.75 16831.58,-22947.41 16834.09,-22946.14 16836.64,-22944.93 16839.22,-22943.8 16841.83,-22942.74 16844.47,-22941.75 16847.13,-22940.84 16849.82,-22940 16852.53,-22939.23 16855.27,-22938.54 16858.02,-22937.92 16860.78,-22937.38 16863.56,-22936.92 16866.35,-22936.53 16869.15,-22936.22 16871.96,-22935.99 16874.77,-22935.83 16877.59,-22935.76"/>
|
||||
<polygon id="000002B6361D8B70" class="l275d0" points="16879,-22935.75 16879,-22940.25 16877.65,-22940.26 16874.96,-22940.33 16872.27,-22940.48 16869.59,-22940.7 16866.91,-22941 16864.24,-22941.37 16861.58,-22941.81 16858.94,-22942.33 16856.31,-22942.92 16853.7,-22943.58 16851.11,-22944.31 16848.54,-22945.11 16845.99,-22945.99 16843.47,-22946.93 16840.97,-22947.95 16838.5,-22949.03 16836.07,-22950.18 16833.66,-22951.4 16831.29,-22952.68 16828.96,-22954.02 16826.67,-22955.44 16824.41,-22956.91 16822.2,-22958.44 16820.03,-22960.04 16817.9,-22961.69 16815.82,-22963.41 16813.79,-22965.18 16811.81,-22967 16809.88,-22968.88 16808,-22970.81 16806.18,-22972.79 16804.41,-22974.82 16802.69,-22976.9 16801.04,-22979.03 16799.44,-22981.2 16797.91,-22983.41 16796.44,-22985.67 16795.02,-22987.96 16793.68,-22990.29 16792.4,-22992.66 16791.18,-22995.07 16790.03,-22997.5 16788.95,-22999.97 16787.93,-23002.47 16786.99,-23004.99 16786.11,-23007.54 16785.31,-23010.11 16784.58,-23012.7 16783.92,-23015.31 16783.33,-23017.94 16782.81,-23020.58 16782.37,-23023.24 16782,-23025.91 16781.7,-23028.59 16781.48,-23031.27 16781.33,-23033.96 16781.26,-23036.65 16781.25,-23038 16776.75,-23038 16776.76,-23036.59 16776.83,-23033.77 16776.99,-23030.96 16777.22,-23028.15 16777.53,-23025.35 16777.92,-23022.56 16778.38,-23019.78 16778.92,-23017.02 16779.54,-23014.27 16780.23,-23011.53 16781,-23008.82 16781.84,-23006.13 16782.75,-23003.47 16783.74,-23000.83 16784.8,-22998.22 16785.93,-22995.64 16787.14,-22993.09 16788.41,-22990.58 16789.75,-22988.1 16791.16,-22985.66 16792.63,-22983.26 16794.18,-22980.9 16795.78,-22978.58 16797.45,-22976.31 16799.18,-22974.09 16800.97,-22971.91 16802.82,-22969.79 16804.73,-22967.72 16806.7,-22965.7 16808.72,-22963.73 16810.79,-22961.82 16812.91,-22959.97 16815.09,-22958.18 16817.31,-22956.45 16819.58,-22954.78 16821.9,-22953.18 16824.26,-22951.63 16826.66,-22950.16 16829.1,-22948.75 16831.58,-22947.41 16834.09,-22946.14 16836.64,-22944.93 16839.22,-22943.8 16841.83,-22942.74 16844.47,-22941.75 16847.13,-22940.84 16849.82,-22940 16852.53,-22939.23 16855.27,-22938.54 16858.02,-22937.92 16860.78,-22937.38 16863.56,-22936.92 16866.35,-22936.53 16869.15,-22936.22 16871.96,-22935.99 16874.77,-22935.83 16877.59,-22935.76"/>
|
||||
<polygon id="000002B6361D97B0" class="l275d0" points="16879,-22935.75 16879,-22940.25 16877.65,-22940.26 16874.96,-22940.33 16872.27,-22940.48 16869.59,-22940.7 16866.91,-22941 16864.24,-22941.37 16861.58,-22941.81 16858.94,-22942.33 16856.31,-22942.92 16853.7,-22943.58 16851.11,-22944.31 16848.54,-22945.11 16845.99,-22945.99 16843.47,-22946.93 16840.97,-22947.95 16838.5,-22949.03 16836.07,-22950.18 16833.66,-22951.4 16831.29,-22952.68 16828.96,-22954.02 16826.67,-22955.44 16824.41,-22956.91 16822.2,-22958.44 16820.03,-22960.04 16817.9,-22961.69 16815.82,-22963.41 16813.79,-22965.18 16811.81,-22967 16809.88,-22968.88 16808,-22970.81 16806.18,-22972.79 16804.41,-22974.82 16802.69,-22976.9 16801.04,-22979.03 16799.44,-22981.2 16797.91,-22983.41 16796.44,-22985.67 16795.02,-22987.96 16793.68,-22990.29 16792.4,-22992.66 16791.18,-22995.07 16790.03,-22997.5 16788.95,-22999.97 16787.93,-23002.47 16786.99,-23004.99 16786.11,-23007.54 16785.31,-23010.11 16784.58,-23012.7 16783.92,-23015.31 16783.33,-23017.94 16782.81,-23020.58 16782.37,-23023.24 16782,-23025.91 16781.7,-23028.59 16781.48,-23031.27 16781.33,-23033.96 16781.26,-23036.65 16781.25,-23038 16776.75,-23038 16776.76,-23036.59 16776.83,-23033.77 16776.99,-23030.96 16777.22,-23028.15 16777.53,-23025.35 16777.92,-23022.56 16778.38,-23019.78 16778.92,-23017.02 16779.54,-23014.27 16780.23,-23011.53 16781,-23008.82 16781.84,-23006.13 16782.75,-23003.47 16783.74,-23000.83 16784.8,-22998.22 16785.93,-22995.64 16787.14,-22993.09 16788.41,-22990.58 16789.75,-22988.1 16791.16,-22985.66 16792.63,-22983.26 16794.18,-22980.9 16795.78,-22978.58 16797.45,-22976.31 16799.18,-22974.09 16800.97,-22971.91 16802.82,-22969.79 16804.73,-22967.72 16806.7,-22965.7 16808.72,-22963.73 16810.79,-22961.82 16812.91,-22959.97 16815.09,-22958.18 16817.31,-22956.45 16819.58,-22954.78 16821.9,-22953.18 16824.26,-22951.63 16826.66,-22950.16 16829.1,-22948.75 16831.58,-22947.41 16834.09,-22946.14 16836.64,-22944.93 16839.22,-22943.8 16841.83,-22942.74 16844.47,-22941.75 16847.13,-22940.84 16849.82,-22940 16852.53,-22939.23 16855.27,-22938.54 16858.02,-22937.92 16860.78,-22937.38 16863.56,-22936.92 16866.35,-22936.53 16869.15,-22936.22 16871.96,-22935.99 16874.77,-22935.83 16877.59,-22935.76"/>
|
||||
<polygon id="000002B6361D8BE0" class="l275d0" points="16879,-22935.75 16879,-22940.25 16877.65,-22940.26 16874.96,-22940.33 16872.27,-22940.48 16869.59,-22940.7 16866.91,-22941 16864.24,-22941.37 16861.58,-22941.81 16858.94,-22942.33 16856.31,-22942.92 16853.7,-22943.58 16851.11,-22944.31 16848.54,-22945.11 16845.99,-22945.99 16843.47,-22946.93 16840.97,-22947.95 16838.5,-22949.03 16836.07,-22950.18 16833.66,-22951.4 16831.29,-22952.68 16828.96,-22954.02 16826.67,-22955.44 16824.41,-22956.91 16822.2,-22958.44 16820.03,-22960.04 16817.9,-22961.69 16815.82,-22963.41 16813.79,-22965.18 16811.81,-22967 16809.88,-22968.88 16808,-22970.81 16806.18,-22972.79 16804.41,-22974.82 16802.69,-22976.9 16801.04,-22979.03 16799.44,-22981.2 16797.91,-22983.41 16796.44,-22985.67 16795.02,-22987.96 16793.68,-22990.29 16792.4,-22992.66 16791.18,-22995.07 16790.03,-22997.5 16788.95,-22999.97 16787.93,-23002.47 16786.99,-23004.99 16786.11,-23007.54 16785.31,-23010.11 16784.58,-23012.7 16783.92,-23015.31 16783.33,-23017.94 16782.81,-23020.58 16782.37,-23023.24 16782,-23025.91 16781.7,-23028.59 16781.48,-23031.27 16781.33,-23033.96 16781.26,-23036.65 16781.25,-23038 16776.75,-23038 16776.76,-23036.59 16776.83,-23033.77 16776.99,-23030.96 16777.22,-23028.15 16777.53,-23025.35 16777.92,-23022.56 16778.38,-23019.78 16778.92,-23017.02 16779.54,-23014.27 16780.23,-23011.53 16781,-23008.82 16781.84,-23006.13 16782.75,-23003.47 16783.74,-23000.83 16784.8,-22998.22 16785.93,-22995.64 16787.14,-22993.09 16788.41,-22990.58 16789.75,-22988.1 16791.16,-22985.66 16792.63,-22983.26 16794.18,-22980.9 16795.78,-22978.58 16797.45,-22976.31 16799.18,-22974.09 16800.97,-22971.91 16802.82,-22969.79 16804.73,-22967.72 16806.7,-22965.7 16808.72,-22963.73 16810.79,-22961.82 16812.91,-22959.97 16815.09,-22958.18 16817.31,-22956.45 16819.58,-22954.78 16821.9,-22953.18 16824.26,-22951.63 16826.66,-22950.16 16829.1,-22948.75 16831.58,-22947.41 16834.09,-22946.14 16836.64,-22944.93 16839.22,-22943.8 16841.83,-22942.74 16844.47,-22941.75 16847.13,-22940.84 16849.82,-22940 16852.53,-22939.23 16855.27,-22938.54 16858.02,-22937.92 16860.78,-22937.38 16863.56,-22936.92 16866.35,-22936.53 16869.15,-22936.22 16871.96,-22935.99 16874.77,-22935.83 16877.59,-22935.76"/>
|
||||
<polygon id="000002B6361D9820" class="l275d0" points="16879,-22935.75 16879,-22940.25 16877.65,-22940.26 16874.96,-22940.33 16872.27,-22940.48 16869.59,-22940.7 16866.91,-22941 16864.24,-22941.37 16861.58,-22941.81 16858.94,-22942.33 16856.31,-22942.92 16853.7,-22943.58 16851.11,-22944.31 16848.54,-22945.11 16845.99,-22945.99 16843.47,-22946.93 16840.97,-22947.95 16838.5,-22949.03 16836.07,-22950.18 16833.66,-22951.4 16831.29,-22952.68 16828.96,-22954.02 16826.67,-22955.44 16824.41,-22956.91 16822.2,-22958.44 16820.03,-22960.04 16817.9,-22961.69 16815.82,-22963.41 16813.79,-22965.18 16811.81,-22967 16809.88,-22968.88 16808,-22970.81 16806.18,-22972.79 16804.41,-22974.82 16802.69,-22976.9 16801.04,-22979.03 16799.44,-22981.2 16797.91,-22983.41 16796.44,-22985.67 16795.02,-22987.96 16793.68,-22990.29 16792.4,-22992.66 16791.18,-22995.07 16790.03,-22997.5 16788.95,-22999.97 16787.93,-23002.47 16786.99,-23004.99 16786.11,-23007.54 16785.31,-23010.11 16784.58,-23012.7 16783.92,-23015.31 16783.33,-23017.94 16782.81,-23020.58 16782.37,-23023.24 16782,-23025.91 16781.7,-23028.59 16781.48,-23031.27 16781.33,-23033.96 16781.26,-23036.65 16781.25,-23038 16776.75,-23038 16776.76,-23036.59 16776.83,-23033.77 16776.99,-23030.96 16777.22,-23028.15 16777.53,-23025.35 16777.92,-23022.56 16778.38,-23019.78 16778.92,-23017.02 16779.54,-23014.27 16780.23,-23011.53 16781,-23008.82 16781.84,-23006.13 16782.75,-23003.47 16783.74,-23000.83 16784.8,-22998.22 16785.93,-22995.64 16787.14,-22993.09 16788.41,-22990.58 16789.75,-22988.1 16791.16,-22985.66 16792.63,-22983.26 16794.18,-22980.9 16795.78,-22978.58 16797.45,-22976.31 16799.18,-22974.09 16800.97,-22971.91 16802.82,-22969.79 16804.73,-22967.72 16806.7,-22965.7 16808.72,-22963.73 16810.79,-22961.82 16812.91,-22959.97 16815.09,-22958.18 16817.31,-22956.45 16819.58,-22954.78 16821.9,-22953.18 16824.26,-22951.63 16826.66,-22950.16 16829.1,-22948.75 16831.58,-22947.41 16834.09,-22946.14 16836.64,-22944.93 16839.22,-22943.8 16841.83,-22942.74 16844.47,-22941.75 16847.13,-22940.84 16849.82,-22940 16852.53,-22939.23 16855.27,-22938.54 16858.02,-22937.92 16860.78,-22937.38 16863.56,-22936.92 16866.35,-22936.53 16869.15,-22936.22 16871.96,-22935.99 16874.77,-22935.83 16877.59,-22935.76"/>
|
||||
<polygon id="000002B6361D8C50" class="l275d0" points="16879,-22935.75 16879,-22940.25 16877.65,-22940.26 16874.96,-22940.33 16872.27,-22940.48 16869.59,-22940.7 16866.91,-22941 16864.24,-22941.37 16861.58,-22941.81 16858.94,-22942.33 16856.31,-22942.92 16853.7,-22943.58 16851.11,-22944.31 16848.54,-22945.11 16845.99,-22945.99 16843.47,-22946.93 16840.97,-22947.95 16838.5,-22949.03 16836.07,-22950.18 16833.66,-22951.4 16831.29,-22952.68 16828.96,-22954.02 16826.67,-22955.44 16824.41,-22956.91 16822.2,-22958.44 16820.03,-22960.04 16817.9,-22961.69 16815.82,-22963.41 16813.79,-22965.18 16811.81,-22967 16809.88,-22968.88 16808,-22970.81 16806.18,-22972.79 16804.41,-22974.82 16802.69,-22976.9 16801.04,-22979.03 16799.44,-22981.2 16797.91,-22983.41 16796.44,-22985.67 16795.02,-22987.96 16793.68,-22990.29 16792.4,-22992.66 16791.18,-22995.07 16790.03,-22997.5 16788.95,-22999.97 16787.93,-23002.47 16786.99,-23004.99 16786.11,-23007.54 16785.31,-23010.11 16784.58,-23012.7 16783.92,-23015.31 16783.33,-23017.94 16782.81,-23020.58 16782.37,-23023.24 16782,-23025.91 16781.7,-23028.59 16781.48,-23031.27 16781.33,-23033.96 16781.26,-23036.65 16781.25,-23038 16776.75,-23038 16776.76,-23036.59 16776.83,-23033.77 16776.99,-23030.96 16777.22,-23028.15 16777.53,-23025.35 16777.92,-23022.56 16778.38,-23019.78 16778.92,-23017.02 16779.54,-23014.27 16780.23,-23011.53 16781,-23008.82 16781.84,-23006.13 16782.75,-23003.47 16783.74,-23000.83 16784.8,-22998.22 16785.93,-22995.64 16787.14,-22993.09 16788.41,-22990.58 16789.75,-22988.1 16791.16,-22985.66 16792.63,-22983.26 16794.18,-22980.9 16795.78,-22978.58 16797.45,-22976.31 16799.18,-22974.09 16800.97,-22971.91 16802.82,-22969.79 16804.73,-22967.72 16806.7,-22965.7 16808.72,-22963.73 16810.79,-22961.82 16812.91,-22959.97 16815.09,-22958.18 16817.31,-22956.45 16819.58,-22954.78 16821.9,-22953.18 16824.26,-22951.63 16826.66,-22950.16 16829.1,-22948.75 16831.58,-22947.41 16834.09,-22946.14 16836.64,-22944.93 16839.22,-22943.8 16841.83,-22942.74 16844.47,-22941.75 16847.13,-22940.84 16849.82,-22940 16852.53,-22939.23 16855.27,-22938.54 16858.02,-22937.92 16860.78,-22937.38 16863.56,-22936.92 16866.35,-22936.53 16869.15,-22936.22 16871.96,-22935.99 16874.77,-22935.83 16877.59,-22935.76"/>
|
||||
<polygon id="000002B6361D9350" class="l101d251" points="16879,-22895.75 16879,-22980.25 16878.32,-22980.25 16876.97,-22980.28 16875.62,-22980.35 16874.27,-22980.44 16872.92,-22980.57 16871.57,-22980.73 16870.23,-22980.92 16868.9,-22981.14 16867.57,-22981.39 16866.24,-22981.68 16864.93,-22981.99 16863.62,-22982.34 16862.32,-22982.71 16861.03,-22983.12 16859.74,-22983.55 16858.47,-22984.02 16857.21,-22984.52 16855.96,-22985.04 16854.73,-22985.6 16853.51,-22986.18 16852.3,-22986.79 16851.11,-22987.43 16849.93,-22988.1 16848.77,-22988.79 16847.62,-22989.52 16846.49,-22990.27 16845.38,-22991.04 16844.29,-22991.84 16843.22,-22992.67 16842.17,-22993.52 16841.13,-22994.39 16840.12,-22995.29 16839.13,-22996.22 16838.16,-22997.16 16837.22,-22998.13 16836.29,-22999.12 16835.39,-23000.13 16834.52,-23001.17 16833.67,-23002.22 16832.84,-23003.29 16832.04,-23004.38 16831.27,-23005.49 16830.52,-23006.62 16829.79,-23007.77 16829.1,-23008.93 16828.43,-23010.11 16827.79,-23011.3 16827.18,-23012.51 16826.6,-23013.73 16826.04,-23014.96 16825.52,-23016.21 16825.02,-23017.47 16824.55,-23018.74 16824.12,-23020.03 16823.71,-23021.32 16823.34,-23022.62 16822.99,-23023.93 16822.68,-23025.24 16822.39,-23026.57 16822.14,-23027.9 16821.92,-23029.23 16821.73,-23030.57 16821.57,-23031.92 16821.44,-23033.27 16821.35,-23034.62 16821.28,-23035.97 16821.25,-23037.32 16821.25,-23038 16736.75,-23038 16736.76,-23036.33 16736.83,-23033 16736.99,-23029.67 16737.23,-23026.34 16737.54,-23023.02 16737.93,-23019.71 16738.4,-23016.41 16738.94,-23013.12 16739.56,-23009.84 16740.26,-23006.58 16741.04,-23003.33 16741.89,-23000.11 16742.81,-22996.91 16743.81,-22993.72 16744.89,-22990.57 16746.04,-22987.44 16747.26,-22984.33 16748.55,-22981.26 16749.92,-22978.22 16751.36,-22975.21 16752.86,-22972.23 16754.44,-22969.29 16756.08,-22966.39 16757.8,-22963.53 16759.58,-22960.71 16761.42,-22957.93 16763.33,-22955.2 16765.3,-22952.51 16767.34,-22949.87 16769.43,-22947.27 16771.59,-22944.73 16773.81,-22942.24 16776.08,-22939.8 16778.41,-22937.41 16780.8,-22935.08 16783.24,-22932.81 16785.73,-22930.59 16788.27,-22928.43 16790.87,-22926.34 16793.51,-22924.3 16796.2,-22922.33 16798.93,-22920.42 16801.71,-22918.58 16804.53,-22916.8 16807.39,-22915.08 16810.29,-22913.44 16813.23,-22911.86 16816.21,-22910.36 16819.22,-22908.92 16822.26,-22907.55 16825.33,-22906.26 16828.44,-22905.04 16831.57,-22903.89 16834.72,-22902.81 16837.91,-22901.81 16841.11,-22900.89 16844.33,-22900.04 16847.58,-22899.26 16850.84,-22898.56 16854.12,-22897.94 16857.41,-22897.4 16860.71,-22896.93 16864.02,-22896.54 16867.34,-22896.23 16870.67,-22895.99 16874,-22895.83 16877.33,-22895.76"/>
|
||||
<polygon id="000002B6361D8A20" class="l101d251" points="16879,-22895.75 16879,-22980.25 16878.32,-22980.25 16876.97,-22980.28 16875.62,-22980.35 16874.27,-22980.44 16872.92,-22980.57 16871.57,-22980.73 16870.23,-22980.92 16868.9,-22981.14 16867.57,-22981.39 16866.24,-22981.68 16864.93,-22981.99 16863.62,-22982.34 16862.32,-22982.71 16861.03,-22983.12 16859.74,-22983.55 16858.47,-22984.02 16857.21,-22984.52 16855.96,-22985.04 16854.73,-22985.6 16853.51,-22986.18 16852.3,-22986.79 16851.11,-22987.43 16849.93,-22988.1 16848.77,-22988.79 16847.62,-22989.52 16846.49,-22990.27 16845.38,-22991.04 16844.29,-22991.84 16843.22,-22992.67 16842.17,-22993.52 16841.13,-22994.39 16840.12,-22995.29 16839.13,-22996.22 16838.16,-22997.16 16837.22,-22998.13 16836.29,-22999.12 16835.39,-23000.13 16834.52,-23001.17 16833.67,-23002.22 16832.84,-23003.29 16832.04,-23004.38 16831.27,-23005.49 16830.52,-23006.62 16829.79,-23007.77 16829.1,-23008.93 16828.43,-23010.11 16827.79,-23011.3 16827.18,-23012.51 16826.6,-23013.73 16826.04,-23014.96 16825.52,-23016.21 16825.02,-23017.47 16824.55,-23018.74 16824.12,-23020.03 16823.71,-23021.32 16823.34,-23022.62 16822.99,-23023.93 16822.68,-23025.24 16822.39,-23026.57 16822.14,-23027.9 16821.92,-23029.23 16821.73,-23030.57 16821.57,-23031.92 16821.44,-23033.27 16821.35,-23034.62 16821.28,-23035.97 16821.25,-23037.32 16821.25,-23038 16736.75,-23038 16736.76,-23036.33 16736.83,-23033 16736.99,-23029.67 16737.23,-23026.34 16737.54,-23023.02 16737.93,-23019.71 16738.4,-23016.41 16738.94,-23013.12 16739.56,-23009.84 16740.26,-23006.58 16741.04,-23003.33 16741.89,-23000.11 16742.81,-22996.91 16743.81,-22993.72 16744.89,-22990.57 16746.04,-22987.44 16747.26,-22984.33 16748.55,-22981.26 16749.92,-22978.22 16751.36,-22975.21 16752.86,-22972.23 16754.44,-22969.29 16756.08,-22966.39 16757.8,-22963.53 16759.58,-22960.71 16761.42,-22957.93 16763.33,-22955.2 16765.3,-22952.51 16767.34,-22949.87 16769.43,-22947.27 16771.59,-22944.73 16773.81,-22942.24 16776.08,-22939.8 16778.41,-22937.41 16780.8,-22935.08 16783.24,-22932.81 16785.73,-22930.59 16788.27,-22928.43 16790.87,-22926.34 16793.51,-22924.3 16796.2,-22922.33 16798.93,-22920.42 16801.71,-22918.58 16804.53,-22916.8 16807.39,-22915.08 16810.29,-22913.44 16813.23,-22911.86 16816.21,-22910.36 16819.22,-22908.92 16822.26,-22907.55 16825.33,-22906.26 16828.44,-22905.04 16831.57,-22903.89 16834.72,-22902.81 16837.91,-22901.81 16841.11,-22900.89 16844.33,-22900.04 16847.58,-22899.26 16850.84,-22898.56 16854.12,-22897.94 16857.41,-22897.4 16860.71,-22896.93 16864.02,-22896.54 16867.34,-22896.23 16870.67,-22895.99 16874,-22895.83 16877.33,-22895.76"/>
|
||||
<polygon id="000002B6361D9660" class="l101d251" points="16879,-22895.75 16879,-22980.25 16878.32,-22980.25 16876.97,-22980.28 16875.62,-22980.35 16874.27,-22980.44 16872.92,-22980.57 16871.57,-22980.73 16870.23,-22980.92 16868.9,-22981.14 16867.57,-22981.39 16866.24,-22981.68 16864.93,-22981.99 16863.62,-22982.34 16862.32,-22982.71 16861.03,-22983.12 16859.74,-22983.55 16858.47,-22984.02 16857.21,-22984.52 16855.96,-22985.04 16854.73,-22985.6 16853.51,-22986.18 16852.3,-22986.79 16851.11,-22987.43 16849.93,-22988.1 16848.77,-22988.79 16847.62,-22989.52 16846.49,-22990.27 16845.38,-22991.04 16844.29,-22991.84 16843.22,-22992.67 16842.17,-22993.52 16841.13,-22994.39 16840.12,-22995.29 16839.13,-22996.22 16838.16,-22997.16 16837.22,-22998.13 16836.29,-22999.12 16835.39,-23000.13 16834.52,-23001.17 16833.67,-23002.22 16832.84,-23003.29 16832.04,-23004.38 16831.27,-23005.49 16830.52,-23006.62 16829.79,-23007.77 16829.1,-23008.93 16828.43,-23010.11 16827.79,-23011.3 16827.18,-23012.51 16826.6,-23013.73 16826.04,-23014.96 16825.52,-23016.21 16825.02,-23017.47 16824.55,-23018.74 16824.12,-23020.03 16823.71,-23021.32 16823.34,-23022.62 16822.99,-23023.93 16822.68,-23025.24 16822.39,-23026.57 16822.14,-23027.9 16821.92,-23029.23 16821.73,-23030.57 16821.57,-23031.92 16821.44,-23033.27 16821.35,-23034.62 16821.28,-23035.97 16821.25,-23037.32 16821.25,-23038 16736.75,-23038 16736.76,-23036.33 16736.83,-23033 16736.99,-23029.67 16737.23,-23026.34 16737.54,-23023.02 16737.93,-23019.71 16738.4,-23016.41 16738.94,-23013.12 16739.56,-23009.84 16740.26,-23006.58 16741.04,-23003.33 16741.89,-23000.11 16742.81,-22996.91 16743.81,-22993.72 16744.89,-22990.57 16746.04,-22987.44 16747.26,-22984.33 16748.55,-22981.26 16749.92,-22978.22 16751.36,-22975.21 16752.86,-22972.23 16754.44,-22969.29 16756.08,-22966.39 16757.8,-22963.53 16759.58,-22960.71 16761.42,-22957.93 16763.33,-22955.2 16765.3,-22952.51 16767.34,-22949.87 16769.43,-22947.27 16771.59,-22944.73 16773.81,-22942.24 16776.08,-22939.8 16778.41,-22937.41 16780.8,-22935.08 16783.24,-22932.81 16785.73,-22930.59 16788.27,-22928.43 16790.87,-22926.34 16793.51,-22924.3 16796.2,-22922.33 16798.93,-22920.42 16801.71,-22918.58 16804.53,-22916.8 16807.39,-22915.08 16810.29,-22913.44 16813.23,-22911.86 16816.21,-22910.36 16819.22,-22908.92 16822.26,-22907.55 16825.33,-22906.26 16828.44,-22905.04 16831.57,-22903.89 16834.72,-22902.81 16837.91,-22901.81 16841.11,-22900.89 16844.33,-22900.04 16847.58,-22899.26 16850.84,-22898.56 16854.12,-22897.94 16857.41,-22897.4 16860.71,-22896.93 16864.02,-22896.54 16867.34,-22896.23 16870.67,-22895.99 16874,-22895.83 16877.33,-22895.76"/>
|
||||
<polygon id="000002B6361D90B0" class="l101d251" points="16879,-22895.75 16879,-22980.25 16878.32,-22980.25 16876.97,-22980.28 16875.62,-22980.35 16874.27,-22980.44 16872.92,-22980.57 16871.57,-22980.73 16870.23,-22980.92 16868.9,-22981.14 16867.57,-22981.39 16866.24,-22981.68 16864.93,-22981.99 16863.62,-22982.34 16862.32,-22982.71 16861.03,-22983.12 16859.74,-22983.55 16858.47,-22984.02 16857.21,-22984.52 16855.96,-22985.04 16854.73,-22985.6 16853.51,-22986.18 16852.3,-22986.79 16851.11,-22987.43 16849.93,-22988.1 16848.77,-22988.79 16847.62,-22989.52 16846.49,-22990.27 16845.38,-22991.04 16844.29,-22991.84 16843.22,-22992.67 16842.17,-22993.52 16841.13,-22994.39 16840.12,-22995.29 16839.13,-22996.22 16838.16,-22997.16 16837.22,-22998.13 16836.29,-22999.12 16835.39,-23000.13 16834.52,-23001.17 16833.67,-23002.22 16832.84,-23003.29 16832.04,-23004.38 16831.27,-23005.49 16830.52,-23006.62 16829.79,-23007.77 16829.1,-23008.93 16828.43,-23010.11 16827.79,-23011.3 16827.18,-23012.51 16826.6,-23013.73 16826.04,-23014.96 16825.52,-23016.21 16825.02,-23017.47 16824.55,-23018.74 16824.12,-23020.03 16823.71,-23021.32 16823.34,-23022.62 16822.99,-23023.93 16822.68,-23025.24 16822.39,-23026.57 16822.14,-23027.9 16821.92,-23029.23 16821.73,-23030.57 16821.57,-23031.92 16821.44,-23033.27 16821.35,-23034.62 16821.28,-23035.97 16821.25,-23037.32 16821.25,-23038 16736.75,-23038 16736.76,-23036.33 16736.83,-23033 16736.99,-23029.67 16737.23,-23026.34 16737.54,-23023.02 16737.93,-23019.71 16738.4,-23016.41 16738.94,-23013.12 16739.56,-23009.84 16740.26,-23006.58 16741.04,-23003.33 16741.89,-23000.11 16742.81,-22996.91 16743.81,-22993.72 16744.89,-22990.57 16746.04,-22987.44 16747.26,-22984.33 16748.55,-22981.26 16749.92,-22978.22 16751.36,-22975.21 16752.86,-22972.23 16754.44,-22969.29 16756.08,-22966.39 16757.8,-22963.53 16759.58,-22960.71 16761.42,-22957.93 16763.33,-22955.2 16765.3,-22952.51 16767.34,-22949.87 16769.43,-22947.27 16771.59,-22944.73 16773.81,-22942.24 16776.08,-22939.8 16778.41,-22937.41 16780.8,-22935.08 16783.24,-22932.81 16785.73,-22930.59 16788.27,-22928.43 16790.87,-22926.34 16793.51,-22924.3 16796.2,-22922.33 16798.93,-22920.42 16801.71,-22918.58 16804.53,-22916.8 16807.39,-22915.08 16810.29,-22913.44 16813.23,-22911.86 16816.21,-22910.36 16819.22,-22908.92 16822.26,-22907.55 16825.33,-22906.26 16828.44,-22905.04 16831.57,-22903.89 16834.72,-22902.81 16837.91,-22901.81 16841.11,-22900.89 16844.33,-22900.04 16847.58,-22899.26 16850.84,-22898.56 16854.12,-22897.94 16857.41,-22897.4 16860.71,-22896.93 16864.02,-22896.54 16867.34,-22896.23 16870.67,-22895.99 16874,-22895.83 16877.33,-22895.76"/>
|
||||
<polygon id="000002B6361D9890" class="l101d251" points="16879,-22895.75 16879,-22980.25 16878.32,-22980.25 16876.97,-22980.28 16875.62,-22980.35 16874.27,-22980.44 16872.92,-22980.57 16871.57,-22980.73 16870.23,-22980.92 16868.9,-22981.14 16867.57,-22981.39 16866.24,-22981.68 16864.93,-22981.99 16863.62,-22982.34 16862.32,-22982.71 16861.03,-22983.12 16859.74,-22983.55 16858.47,-22984.02 16857.21,-22984.52 16855.96,-22985.04 16854.73,-22985.6 16853.51,-22986.18 16852.3,-22986.79 16851.11,-22987.43 16849.93,-22988.1 16848.77,-22988.79 16847.62,-22989.52 16846.49,-22990.27 16845.38,-22991.04 16844.29,-22991.84 16843.22,-22992.67 16842.17,-22993.52 16841.13,-22994.39 16840.12,-22995.29 16839.13,-22996.22 16838.16,-22997.16 16837.22,-22998.13 16836.29,-22999.12 16835.39,-23000.13 16834.52,-23001.17 16833.67,-23002.22 16832.84,-23003.29 16832.04,-23004.38 16831.27,-23005.49 16830.52,-23006.62 16829.79,-23007.77 16829.1,-23008.93 16828.43,-23010.11 16827.79,-23011.3 16827.18,-23012.51 16826.6,-23013.73 16826.04,-23014.96 16825.52,-23016.21 16825.02,-23017.47 16824.55,-23018.74 16824.12,-23020.03 16823.71,-23021.32 16823.34,-23022.62 16822.99,-23023.93 16822.68,-23025.24 16822.39,-23026.57 16822.14,-23027.9 16821.92,-23029.23 16821.73,-23030.57 16821.57,-23031.92 16821.44,-23033.27 16821.35,-23034.62 16821.28,-23035.97 16821.25,-23037.32 16821.25,-23038 16736.75,-23038 16736.76,-23036.33 16736.83,-23033 16736.99,-23029.67 16737.23,-23026.34 16737.54,-23023.02 16737.93,-23019.71 16738.4,-23016.41 16738.94,-23013.12 16739.56,-23009.84 16740.26,-23006.58 16741.04,-23003.33 16741.89,-23000.11 16742.81,-22996.91 16743.81,-22993.72 16744.89,-22990.57 16746.04,-22987.44 16747.26,-22984.33 16748.55,-22981.26 16749.92,-22978.22 16751.36,-22975.21 16752.86,-22972.23 16754.44,-22969.29 16756.08,-22966.39 16757.8,-22963.53 16759.58,-22960.71 16761.42,-22957.93 16763.33,-22955.2 16765.3,-22952.51 16767.34,-22949.87 16769.43,-22947.27 16771.59,-22944.73 16773.81,-22942.24 16776.08,-22939.8 16778.41,-22937.41 16780.8,-22935.08 16783.24,-22932.81 16785.73,-22930.59 16788.27,-22928.43 16790.87,-22926.34 16793.51,-22924.3 16796.2,-22922.33 16798.93,-22920.42 16801.71,-22918.58 16804.53,-22916.8 16807.39,-22915.08 16810.29,-22913.44 16813.23,-22911.86 16816.21,-22910.36 16819.22,-22908.92 16822.26,-22907.55 16825.33,-22906.26 16828.44,-22905.04 16831.57,-22903.89 16834.72,-22902.81 16837.91,-22901.81 16841.11,-22900.89 16844.33,-22900.04 16847.58,-22899.26 16850.84,-22898.56 16854.12,-22897.94 16857.41,-22897.4 16860.71,-22896.93 16864.02,-22896.54 16867.34,-22896.23 16870.67,-22895.99 16874,-22895.83 16877.33,-22895.76"/>
|
||||
<polygon id="000002B6361D9270" class="l101d251" points="16879,-22895.75 16879,-22980.25 16878.32,-22980.25 16876.97,-22980.28 16875.62,-22980.35 16874.27,-22980.44 16872.92,-22980.57 16871.57,-22980.73 16870.23,-22980.92 16868.9,-22981.14 16867.57,-22981.39 16866.24,-22981.68 16864.93,-22981.99 16863.62,-22982.34 16862.32,-22982.71 16861.03,-22983.12 16859.74,-22983.55 16858.47,-22984.02 16857.21,-22984.52 16855.96,-22985.04 16854.73,-22985.6 16853.51,-22986.18 16852.3,-22986.79 16851.11,-22987.43 16849.93,-22988.1 16848.77,-22988.79 16847.62,-22989.52 16846.49,-22990.27 16845.38,-22991.04 16844.29,-22991.84 16843.22,-22992.67 16842.17,-22993.52 16841.13,-22994.39 16840.12,-22995.29 16839.13,-22996.22 16838.16,-22997.16 16837.22,-22998.13 16836.29,-22999.12 16835.39,-23000.13 16834.52,-23001.17 16833.67,-23002.22 16832.84,-23003.29 16832.04,-23004.38 16831.27,-23005.49 16830.52,-23006.62 16829.79,-23007.77 16829.1,-23008.93 16828.43,-23010.11 16827.79,-23011.3 16827.18,-23012.51 16826.6,-23013.73 16826.04,-23014.96 16825.52,-23016.21 16825.02,-23017.47 16824.55,-23018.74 16824.12,-23020.03 16823.71,-23021.32 16823.34,-23022.62 16822.99,-23023.93 16822.68,-23025.24 16822.39,-23026.57 16822.14,-23027.9 16821.92,-23029.23 16821.73,-23030.57 16821.57,-23031.92 16821.44,-23033.27 16821.35,-23034.62 16821.28,-23035.97 16821.25,-23037.32 16821.25,-23038 16736.75,-23038 16736.76,-23036.33 16736.83,-23033 16736.99,-23029.67 16737.23,-23026.34 16737.54,-23023.02 16737.93,-23019.71 16738.4,-23016.41 16738.94,-23013.12 16739.56,-23009.84 16740.26,-23006.58 16741.04,-23003.33 16741.89,-23000.11 16742.81,-22996.91 16743.81,-22993.72 16744.89,-22990.57 16746.04,-22987.44 16747.26,-22984.33 16748.55,-22981.26 16749.92,-22978.22 16751.36,-22975.21 16752.86,-22972.23 16754.44,-22969.29 16756.08,-22966.39 16757.8,-22963.53 16759.58,-22960.71 16761.42,-22957.93 16763.33,-22955.2 16765.3,-22952.51 16767.34,-22949.87 16769.43,-22947.27 16771.59,-22944.73 16773.81,-22942.24 16776.08,-22939.8 16778.41,-22937.41 16780.8,-22935.08 16783.24,-22932.81 16785.73,-22930.59 16788.27,-22928.43 16790.87,-22926.34 16793.51,-22924.3 16796.2,-22922.33 16798.93,-22920.42 16801.71,-22918.58 16804.53,-22916.8 16807.39,-22915.08 16810.29,-22913.44 16813.23,-22911.86 16816.21,-22910.36 16819.22,-22908.92 16822.26,-22907.55 16825.33,-22906.26 16828.44,-22905.04 16831.57,-22903.89 16834.72,-22902.81 16837.91,-22901.81 16841.11,-22900.89 16844.33,-22900.04 16847.58,-22899.26 16850.84,-22898.56 16854.12,-22897.94 16857.41,-22897.4 16860.71,-22896.93 16864.02,-22896.54 16867.34,-22896.23 16870.67,-22895.99 16874,-22895.83 16877.33,-22895.76"/>
|
||||
<use transform="translate(15115 -25315)" xlink:href="#1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2"/>
|
||||
<use transform="translate(17164 -22938)" xlink:href="#1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2_405"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 64 KiB |
@@ -1,55 +0,0 @@
|
||||
# =============================================
|
||||
# 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: mxpic_project_1
|
||||
type: project
|
||||
version: "1.0.0"
|
||||
|
||||
# 1. External Ports (How this cell connects to the outside world)
|
||||
ports: []
|
||||
|
||||
# 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: 1511.5
|
||||
y: -2531.5
|
||||
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: 1716.4
|
||||
y: -2293.8
|
||||
rotation: 0.0
|
||||
flip: 0
|
||||
flop: 0
|
||||
mirror: false
|
||||
settings:
|
||||
length:
|
||||
|
||||
elements: {}
|
||||
|
||||
# 3. Bundles (Grouped links for multi-bus/parallel routing)
|
||||
bundles:
|
||||
output_bus:
|
||||
routing_type: euler_bend
|
||||
links:
|
||||
- from: MMI_2:a1
|
||||
to: MMI_1:b2
|
||||
xsection: strip
|
||||
family: optical
|
||||
width: 0.45
|
||||
radius: 10
|
||||
routing_type: euler_bend
|
||||
Binary file not shown.
@@ -0,0 +1,108 @@
|
||||
# Project Findings
|
||||
|
||||
## Repository Inventory
|
||||
- Static/frontend files are under `frontend/`: `login.html`, `dashboard.html`, `canvas.html`, and `canvas-helpers.js`.
|
||||
- Backend files are under `backend/`: `server.py`, `database.py`, `pdk_access.py`, `technology_manifest.py`, `gds_builder.py`, routing helpers, icons, and `directories.yaml`.
|
||||
- Persistent data appears under `database/`, including `mxpic_data.db`, saved layout YAML/SVG files, and generated `_exports/*.gds`.
|
||||
- Tests are JavaScript static/unit-style tests under `tests/`.
|
||||
- Project docs exist under `docs/`, including intranet deployment, GDS/SVG generation, and PDK technology loading notes.
|
||||
- A checked saved layout example at `database/engineer/layout/mxpic_project_1/mxpic_project_1.yml` contains unresolved merge-conflict markers, so at least some local saved data may be invalid until cleaned.
|
||||
- Conflict-marker search also found real markers in `database/engineer/layout/mxpic_project_1/mxpic_project_1.svg`, `tests/layout-ui-wiring.test.js`, and `tests/layout-backend-static.test.js`.
|
||||
- `backend/directories.yaml` is a legacy/declarative directory category file with PDK-style categories; the active docs/source show library scanning is now driven by the role-scoped PDK filesystem.
|
||||
|
||||
## Saved Layout YAML Shape
|
||||
- Saved layout YAML uses schema version `2.0.0`, `kind: cell`, `coordinate_system: gds_y_up`, `canvas_size`, `project`, `name`, `type`, and `version`.
|
||||
- Modern YAML uses `pins`, `instances`, `elements`, and `bundles`.
|
||||
- `instances` can reference basic components like `circle`, generated/forge components, other project cells, or PDK paths such as `Silterra/<technology>/primitives/...`.
|
||||
- `bundles.output_bus.links` stores routed connections with source/target pins and route settings.
|
||||
- Both checked `.project.json` metadata files (`admin` and `engineer`) select technology `Silterra/EMO1_2ML_CU_Al_RDL`.
|
||||
|
||||
## README / Deployment
|
||||
- The project is `mxpic_EDA`, an EDA layout tool for OptiHK.
|
||||
- Runtime requirement explicitly listed in README is Flask.
|
||||
- The application can launch login, dashboard, canvas editing, YAML generation, and PDK browsing without build-time router dependencies.
|
||||
- Build/export actions require `mxpic_router` and Nazca; if `mxpic_forge.Route` is unavailable, routing falls back to Nazca interconnects.
|
||||
- SVG preview generation for routed/build output requires `gdstk`.
|
||||
- Intranet server is started with `run_intranet_server.ps1` and listens on `0.0.0.0:3000` by default.
|
||||
- LAN users browse to `http://<host-computer-ip>:3000`; firewall must allow inbound TCP `3000`.
|
||||
- Default local accounts are `admin / 123456` and `engineer / 123456`.
|
||||
- User project files are stored under `database/<username>/layout`.
|
||||
- Important env vars include `MXPIC_SECRET_KEY`, `MXPIC_HOST`, `MXPIC_PORT`, `MXPIC_DEBUG`, `MXPIC_COOKIE_SECURE`, `MXPIC_PDK_PUBLIC_ROOT`, and `MXPIC_PDK_ATLAS_ROOT`.
|
||||
- Public/developer PDK roots default to `../opt_pdk_public/foundries`; manager/atlas roots default to `../opt_pdk_atlas/foundries`.
|
||||
- `run_intranet_server.ps1` sets default `MXPIC_SECRET_KEY` if absent, sets `MXPIC_HOST=0.0.0.0`, `MXPIC_PORT=3000`, `MXPIC_DEBUG=0`, then runs `python backend\server.py`.
|
||||
- `backend/server.py` initializes the local SQLite database when the server process starts.
|
||||
- Flask runs threaded on the configured host and port.
|
||||
|
||||
## GDS / SVG Generation
|
||||
- `Build Layout` serializes the active canvas page into layout YAML, posts it to `/api/save-layout`, writes `database/<username>/layout/<project>/<cell>.yml`, writes optional route sidecar data, then calls router-backed SVG preview generation.
|
||||
- Routed SVG preview uses `backend/routed_layout_preview.py`, which delegates to `mxpic_router.build_project_gds`, reads the temporary GDS with `gdstk`, and writes `<cell>.svg`.
|
||||
- `Build GDS` posts to `/api/build-gds` with only the project name. It does not automatically serialize unsaved in-memory canvas state first.
|
||||
- Final GDS export reads existing saved project YAML files, calls `backend/gds_builder.py`, delegates to `mxpic_router`, writes `database/_exports/<uuid>/<project>.gds`, returns a download URL, then cleans up the export folder after response close.
|
||||
- Saved layout files are under `database/<username>/layout/<project>/`.
|
||||
- External build dependencies are lazy: normal web usage can work without router stack, while preview/build actions require `mxpic_router`, Nazca, and sometimes `gdstk`.
|
||||
|
||||
## PDK / Technology / Xsections
|
||||
- PDK components, technology manifests, and GDS assets all come from role-scoped PDK roots.
|
||||
- Dashboard loads available technologies from `/api/technologies`, scanning `<active_role_pdk_root>/<foundry>/<technology>/technology.yml`.
|
||||
- Project creation stores selected technology in `database/<username>/layout/<project>/.project.json`.
|
||||
- Canvas loads project metadata from `/api/projects/<project>`, then loads `/api/technologies/<foundry>/<technology>/manifest`.
|
||||
- `technology.yml` supplies layer mappings, routing type options, defaults, and xsection definitions such as `strip`, `rib_low`, `metal_1`, and `metal_2`.
|
||||
- Canvas route editing uses the active technology manifest to populate xsection choices and route defaults.
|
||||
- Saved YAML stores component paths and per-link route data such as `xsection`, `family`, `width`, `radius`, and `routing_type`.
|
||||
- Build-time router applies `technology.yml` to Nazca layers/xsections before resolving PDK assets and routing links.
|
||||
|
||||
## Backend Route Surface
|
||||
- `backend/server.py` serves `/`, `/dashboard`, `/canvas`, `/canvas-helpers.js`, and `/logout`.
|
||||
- Login endpoint is `POST /login`.
|
||||
- Health check endpoint is `/api/health`.
|
||||
- Account/profile endpoints include `/api/profile`, `/api/profile/password`, and `/api/logs`.
|
||||
- Project endpoints include listing/creating `/api/projects`, reading/deleting `/api/projects/<project_name>`, cell rename/delete under `/api/projects/<project_name>/cells/<cell_name>`, and `/api/save-layout`.
|
||||
- Build/export endpoints include `/api/build-gds`, `/api/exports/<export_id>/<filename>`, and `/api/projects/<project_name>/gds/<filename>`.
|
||||
- PDK/technology endpoints include `/api/technologies`, `/api/technologies/<foundry>/<technology>/manifest`, `/api/library`, `/api/component/<component_name>`, `/api/component/<component_name>/image`, and `/api/icon/<category>`.
|
||||
- Project listing treats each `.yml`/`.yaml` inside a project folder as one saved cell/canvas.
|
||||
- Project creation sanitizes the requested name, adds a numeric suffix if needed, creates an initial project folder, writes `.project.json`, and logs `project.create`.
|
||||
- `/api/save-layout` accepts `project`, `cell`, `content`, and optional `preview` flag.
|
||||
- `/api/library` rebuilds the component tree on each request so project technology and user role are reflected immediately.
|
||||
|
||||
## Frontend Page Flow
|
||||
- `frontend/login.html` renders a login form that submits `POST /login` and then moves authenticated users into the dashboard.
|
||||
- `frontend/dashboard.html` loads recent logs from `/api/logs`, profile data from `/api/profile`, projects from `/api/projects`, and technology choices from `/api/technologies`.
|
||||
- Opening a project navigates to `/canvas?project=<name>`.
|
||||
- Creating a project posts `{ name, technology }` to `/api/projects`.
|
||||
- Dashboard also supports deleting projects, updating occupation, and changing password through backend APIs.
|
||||
- `frontend/canvas.html` loads project metadata/cells from `/api/projects/<project>`, then loads the selected technology manifest.
|
||||
- Canvas loads the component library with `/api/library?project=<project>`, component metadata from `/api/component/<component_name>?project=<project>`, and component images from `/api/component/<component_name>/image?project=<project>`.
|
||||
- Canvas save/build controls call `/api/save-layout` and `/api/build-gds`.
|
||||
- Dashboard `openProject(name)` navigates directly to `/canvas?project=<name>`.
|
||||
- Canvas `loadTechnologyManifest` falls back to `FALLBACK_TECHNOLOGY_MANIFEST` when the saved technology is missing/invalid.
|
||||
- Canvas `fetchLibrary` is project-scoped, so the visible library follows the selected project technology.
|
||||
- Canvas source comments define the control split:
|
||||
- `handleBuildLayout`: save active page, generate preview assets, show preview tab.
|
||||
- `handleSaveProjectLayouts`: save YAML for every editable page without opening previews.
|
||||
- `handleBuildGds`: build project GDS through the backend and trigger download.
|
||||
|
||||
## Database
|
||||
- `backend/database.py` uses SQLite at `database/mxpic_data.db`.
|
||||
- Tables: `users` and `user_logs`.
|
||||
- User records include password hash, creation date, credits, occupation, and `user_group`.
|
||||
- Database migrations add missing columns to older local SQLite files.
|
||||
- Default account roles are `admin -> manager`, `engineer -> developers`, and ordinary users default to `user`.
|
||||
- Audit logs record user action, project/cell context, detail, IP address, and UTC timestamp.
|
||||
|
||||
## PDK Access and Build Helpers
|
||||
- `backend/pdk_access.py` defines supported user groups: `manager`, `developers`, and `user`.
|
||||
- Managers resolve to `MXPIC_PDK_ATLAS_ROOT` or `opt_pdk_atlas/foundries`; developers/users resolve to `MXPIC_PDK_PUBLIC_ROOT` or `opt_pdk_public/foundries`.
|
||||
- Only managers prefer full component GDS assets; other roles use public/black-box style assets.
|
||||
- Temporary GDS exports are created in UUID folders and old export folders are cleaned up after an age threshold.
|
||||
- `backend/gds_builder.py` validates that saved cell YAML files exist, then delegates all final GDS work to `mxpic_router.build_project_gds`.
|
||||
- Local GDS building is not implemented as a fallback in this repository.
|
||||
|
||||
## Tests
|
||||
- Tests are JavaScript assertion scripts in `tests/`.
|
||||
- The test suite heavily checks static frontend/backend integration contracts, such as route existence, API strings, layout/GDS wiring, technology manifest loading, and PDK access behavior.
|
||||
- `tests/canvas-helpers.test.js` directly imports `frontend/canvas-helpers.js` and checks handle placement, YAML serialization, route defaults, element ports, route YAML, and route crossing helpers.
|
||||
- Several tests intentionally assert that legacy local preview/registry modules are absent and that the router-backed preview/GDS path is present.
|
||||
- Current checked test files include unresolved conflict markers in at least two files, so `node` test execution may fail before assertions run unless those files are cleaned.
|
||||
|
||||
## Open Questions
|
||||
- None remaining for the requested project usage/workflow summary.
|
||||
@@ -0,0 +1,50 @@
|
||||
# Project Understanding Progress
|
||||
|
||||
## Session Log
|
||||
- Started project read-through on 2026-06-08.
|
||||
- Loaded requested `planning-with-files` skill and project-analysis guidance.
|
||||
- Collected initial file inventory with `rg --files`.
|
||||
- Created `develop_plan` planning notes.
|
||||
- Read `README.md` and `docs/INTRANET_DEPLOYMENT.md`.
|
||||
- Read `docs/GDS_SVG_GENERATION_LOGIC.md` and `docs/PDK_TECHNOLOGY_XSECTION_LOADING.md`.
|
||||
- Listed `backend/server.py` route/function signatures.
|
||||
- Read `backend/database.py`.
|
||||
- Read `backend/pdk_access.py` and `backend/gds_builder.py`.
|
||||
- Read `run_intranet_server.ps1` and backend startup/config references.
|
||||
- Inspected login/dashboard and canvas frontend API wiring.
|
||||
- Read dashboard and canvas handler context around project open, technology load, library load, save, preview, and GDS build.
|
||||
- Read backend handler context and test structure.
|
||||
- Read a saved engineer project YAML example and `backend/directories.yaml`.
|
||||
- Searched for conflict markers and read admin/engineer `.project.json` metadata files.
|
||||
- Wrote `develop_plan/project_understanding.md`.
|
||||
|
||||
## Commands / Checks
|
||||
- `rg --files` succeeded with escalated approval after sandbox spawn failure.
|
||||
- `Get-Content -Raw README.md` succeeded.
|
||||
- `Get-Content -Raw docs\INTRANET_DEPLOYMENT.md` succeeded.
|
||||
- `Get-Content -Raw docs\GDS_SVG_GENERATION_LOGIC.md` succeeded.
|
||||
- `Get-Content -Raw docs\PDK_TECHNOLOGY_XSECTION_LOADING.md` succeeded.
|
||||
- `rg "@app\\.route|def " backend\server.py` succeeded.
|
||||
- `Get-Content -Raw backend\database.py` succeeded.
|
||||
- `Get-Content -Raw backend\pdk_access.py` succeeded.
|
||||
- `Get-Content -Raw backend\gds_builder.py` succeeded.
|
||||
- `Get-Content -Raw run_intranet_server.ps1` succeeded.
|
||||
- `rg -n "app.run|MXPIC_HOST|MXPIC_PORT|MXPIC_DEBUG|SECRET_KEY|init_db|database" backend\server.py` succeeded.
|
||||
- `rg -n "fetch\\(|window\\.location|..." frontend\login.html frontend\dashboard.html` succeeded.
|
||||
- `rg -n "fetch\\(|handleBuildLayout|handleBuildGds|..." frontend\canvas.html frontend\canvas-helpers.js` succeeded.
|
||||
- `rg -n -C 4 "function openProject|..." frontend\dashboard.html` succeeded.
|
||||
- `rg -n -C 5 "const loadProject|..." frontend\canvas.html` succeeded.
|
||||
- `rg -n -C 8 "def login|def list_projects|..." backend\server.py` succeeded.
|
||||
- `rg -n "node:test|assert|..." tests` succeeded.
|
||||
- `Get-Content -Raw database\engineer\layout\mxpic_project_1\mxpic_project_1.yml` succeeded.
|
||||
- `Get-Content -Raw backend\directories.yaml` succeeded.
|
||||
- `rg -n "<<<<<<<|=======|>>>>>>>"` succeeded.
|
||||
- `rg --files -g .project.json -g !database\_exports` succeeded.
|
||||
- `Get-Content -Raw database\admin\layout\mxpic_project_1\.project.json` succeeded.
|
||||
- `Get-Content -Raw database\engineer\layout\mxpic_project_1\.project.json` succeeded.
|
||||
|
||||
## Files Created / Modified
|
||||
- Created `develop_plan/task_plan.md`.
|
||||
- Created `develop_plan/findings.md`.
|
||||
- Created `develop_plan/progress.md`.
|
||||
- Created `develop_plan/project_understanding.md`.
|
||||
@@ -0,0 +1,333 @@
|
||||
# mxpic_EDA Project Understanding
|
||||
|
||||
## Purpose
|
||||
|
||||
`mxpic_EDA` is a local/intranet EDA web application for creating photonic integrated circuit layout projects. It combines:
|
||||
|
||||
- A Flask backend for authentication, project APIs, PDK browsing, layout persistence, preview generation, and GDS export.
|
||||
- Static frontend pages for login, dashboard, and canvas editing.
|
||||
- SQLite account/audit storage plus filesystem-based project layout storage.
|
||||
- External build-time tooling (`mxpic_router`, Nazca, and sometimes `gdstk`) for routed SVG preview and final GDS generation.
|
||||
|
||||
The project is designed so normal login, dashboard, canvas editing, YAML generation, and PDK browsing can run without importing the router stack. Build actions load those heavier dependencies only when needed.
|
||||
|
||||
## Main Entry Points
|
||||
|
||||
### Start the App
|
||||
|
||||
The intended intranet startup path is:
|
||||
|
||||
```powershell
|
||||
.\run_intranet_server.ps1
|
||||
```
|
||||
|
||||
That script sets:
|
||||
|
||||
- `MXPIC_HOST=0.0.0.0`
|
||||
- `MXPIC_PORT=3000`
|
||||
- `MXPIC_DEBUG=0`
|
||||
- a fallback `MXPIC_SECRET_KEY` if none is set
|
||||
|
||||
Then it runs:
|
||||
|
||||
```powershell
|
||||
python backend\server.py
|
||||
```
|
||||
|
||||
The server listens on:
|
||||
|
||||
```text
|
||||
http://<host-computer-ip>:3000
|
||||
```
|
||||
|
||||
Default local accounts are:
|
||||
|
||||
```text
|
||||
admin / 123456
|
||||
engineer / 123456
|
||||
```
|
||||
|
||||
`admin` is a manager account. `engineer` is a developer account.
|
||||
|
||||
## Repository Structure
|
||||
|
||||
```text
|
||||
backend/
|
||||
server.py Flask app, routes, project/layout APIs
|
||||
database.py SQLite initialization, users, profiles, audit logs
|
||||
pdk_access.py Role-aware PDK root selection and export helpers
|
||||
gds_builder.py Final GDS wrapper around mxpic_router
|
||||
routed_layout_preview.py SVG preview generation via temporary routed GDS
|
||||
router_dependency.py Lazy build-time dependency validation
|
||||
technology_manifest.py technology.yml loading/validation
|
||||
icons/ Category icons served by backend
|
||||
|
||||
frontend/
|
||||
login.html Login page
|
||||
dashboard.html Project/account dashboard
|
||||
canvas.html Main layout editor
|
||||
canvas-helpers.js Canvas/YAML/route helper functions and tests target
|
||||
|
||||
database/
|
||||
mxpic_data.db SQLite app database
|
||||
<username>/layout/... Saved project metadata, YAML cells, SVG previews
|
||||
_exports/<uuid>/... Temporary generated GDS downloads
|
||||
|
||||
docs/
|
||||
INTRANET_DEPLOYMENT.md
|
||||
GDS_SVG_GENERATION_LOGIC.md
|
||||
PDK_TECHNOLOGY_XSECTION_LOADING.md
|
||||
|
||||
tests/
|
||||
JavaScript assertion tests for static wiring and canvas helpers
|
||||
```
|
||||
|
||||
## User Workflow
|
||||
|
||||
1. User opens `/`.
|
||||
2. If not logged in, Flask serves `frontend/login.html`.
|
||||
3. Login form posts credentials to `POST /login`.
|
||||
4. On success, the session stores user id, username, and user group.
|
||||
5. User lands on `/dashboard`.
|
||||
6. Dashboard loads:
|
||||
- `/api/profile`
|
||||
- `/api/logs`
|
||||
- `/api/projects`
|
||||
- `/api/technologies`
|
||||
7. User creates or opens a project.
|
||||
8. Creating a project posts `{ name, technology }` to `POST /api/projects`.
|
||||
9. Opening a project navigates to `/canvas?project=<project>`.
|
||||
10. Canvas loads project data from `/api/projects/<project>`.
|
||||
11. Canvas loads the selected `technology.yml` manifest from `/api/technologies/<foundry>/<technology>/manifest`.
|
||||
12. Canvas loads the project-scoped PDK library from `/api/library?project=<project>`.
|
||||
13. User places components, elements, pins, and routes.
|
||||
14. Save or Build Layout writes YAML to disk through `/api/save-layout`.
|
||||
15. Build Layout also generates and opens an SVG preview.
|
||||
16. Build GDS calls `/api/build-gds` and downloads the generated GDS.
|
||||
|
||||
## Project and Data Model
|
||||
|
||||
Each user's projects live under:
|
||||
|
||||
```text
|
||||
database/<username>/layout/<project>/
|
||||
```
|
||||
|
||||
Project metadata is stored in:
|
||||
|
||||
```text
|
||||
database/<username>/layout/<project>/.project.json
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "mxpic_project_1",
|
||||
"technology": "Silterra/EMO1_2ML_CU_Al_RDL"
|
||||
}
|
||||
```
|
||||
|
||||
Saved cells are YAML files:
|
||||
|
||||
```text
|
||||
database/<username>/layout/<project>/<cell>.yml
|
||||
```
|
||||
|
||||
Generated previews are SVG files:
|
||||
|
||||
```text
|
||||
database/<username>/layout/<project>/<cell>.svg
|
||||
```
|
||||
|
||||
Modern saved layout YAML contains:
|
||||
|
||||
- `schema_version`
|
||||
- `kind`
|
||||
- `coordinate_system`
|
||||
- `canvas_size`
|
||||
- `project`
|
||||
- `name`
|
||||
- `type`
|
||||
- `version`
|
||||
- `pins`
|
||||
- `instances`
|
||||
- `elements`
|
||||
- `bundles`
|
||||
|
||||
`instances` reference basic components, forge/generated components, other project cells, or PDK component paths. `bundles` store routed links and route settings such as `xsection`, `family`, `width`, `radius`, and `routing_type`.
|
||||
|
||||
## PDK and Technology Workflow
|
||||
|
||||
The active PDK root is chosen by user role:
|
||||
|
||||
- `manager`: `MXPIC_PDK_ATLAS_ROOT` or `opt_pdk_atlas/foundries`
|
||||
- `developers` and `user`: `MXPIC_PDK_PUBLIC_ROOT` or `opt_pdk_public/foundries`
|
||||
|
||||
The dashboard lists available technologies by scanning:
|
||||
|
||||
```text
|
||||
<active_role_pdk_root>/<foundry>/<technology>/technology.yml
|
||||
```
|
||||
|
||||
The selected technology is saved in `.project.json`.
|
||||
|
||||
Canvas then uses that selected technology to:
|
||||
|
||||
- Load route defaults and xsections from `technology.yml`.
|
||||
- Scope the component library browser to the chosen foundry/technology.
|
||||
- Save component paths that can later resolve under the base role PDK root.
|
||||
|
||||
At build time, the router receives the active PDK root and the selected project's technology manifest path. The router applies technology layers/xsections to Nazca before resolving PDK assets and routing links.
|
||||
|
||||
## Build Layout vs Save vs Build GDS
|
||||
|
||||
### Save
|
||||
|
||||
The canvas "Save" path calls `handleSaveProjectLayouts`. It writes YAML for every editable project/cell page to `/api/save-layout` with preview disabled. It is for persistence, not preview.
|
||||
|
||||
### Build Layout
|
||||
|
||||
The canvas "Build Layout" path:
|
||||
|
||||
1. Validates route crossings.
|
||||
2. Serializes the active page to YAML.
|
||||
3. Posts to `/api/save-layout`.
|
||||
4. Backend writes `<cell>.yml`.
|
||||
5. Backend writes route sidecar data when available.
|
||||
6. Backend calls `routed_layout_preview.py`.
|
||||
7. `routed_layout_preview.py` calls `mxpic_router.build_project_gds`.
|
||||
8. The temporary routed GDS is converted to `<cell>.svg` with `gdstk`.
|
||||
9. Frontend opens the SVG preview tab.
|
||||
|
||||
### Build GDS
|
||||
|
||||
The canvas "Build GDS" path:
|
||||
|
||||
1. Validates route crossings across editable pages.
|
||||
2. Posts `{ project }` to `/api/build-gds`.
|
||||
3. Backend reads saved YAML files already on disk.
|
||||
4. Backend creates `database/_exports/<uuid>/<project>.gds`.
|
||||
5. `backend/gds_builder.py` delegates to `mxpic_router.build_project_gds`.
|
||||
6. Backend returns `download_url`.
|
||||
7. Frontend triggers a browser download.
|
||||
8. Backend removes the temporary export folder after serving the file.
|
||||
|
||||
Important: Build GDS does not serialize unsaved in-memory canvas state first. Users should Save or Build Layout before Build GDS if they want latest canvas edits included.
|
||||
|
||||
## Backend API Map
|
||||
|
||||
Primary pages:
|
||||
|
||||
- `GET /`
|
||||
- `POST /login`
|
||||
- `GET /dashboard`
|
||||
- `GET /canvas`
|
||||
- `GET /canvas-helpers.js`
|
||||
- `GET /logout`
|
||||
|
||||
Account and activity:
|
||||
|
||||
- `GET /api/health`
|
||||
- `GET/PATCH /api/profile`
|
||||
- `POST /api/profile/password`
|
||||
- `GET/POST /api/logs`
|
||||
|
||||
Projects and cells:
|
||||
|
||||
- `GET /api/projects`
|
||||
- `POST /api/projects`
|
||||
- `GET /api/projects/<project_name>`
|
||||
- `DELETE /api/projects/<project_name>`
|
||||
- `PATCH/DELETE /api/projects/<project_name>/cells/<cell_name>`
|
||||
- `POST /api/save-layout`
|
||||
- `GET /api/projects/<project_name>/cells/<cell_name>/layout.svg`
|
||||
|
||||
Build/export:
|
||||
|
||||
- `POST /api/build-gds`
|
||||
- `GET /api/exports/<export_id>/<filename>`
|
||||
- `GET /api/projects/<project_name>/gds/<filename>`
|
||||
|
||||
PDK/technology/library:
|
||||
|
||||
- `GET /api/technologies`
|
||||
- `GET /api/technologies/<foundry>/<technology>/manifest`
|
||||
- `GET /api/library`
|
||||
- `GET /api/component/<component_name>`
|
||||
- `GET /api/component/<component_name>/image`
|
||||
- `GET /api/icon/<category>`
|
||||
|
||||
## Persistence and Accounts
|
||||
|
||||
SQLite database:
|
||||
|
||||
```text
|
||||
database/mxpic_data.db
|
||||
```
|
||||
|
||||
Tables observed:
|
||||
|
||||
- `users`
|
||||
- `user_logs`
|
||||
|
||||
The backend initializes the database on startup. It also performs lightweight migrations for profile, credit, occupation, and group fields.
|
||||
|
||||
User groups affect PDK access:
|
||||
|
||||
- `manager` can use atlas/private PDK roots and prefers full GDS assets.
|
||||
- `developers` and `user` use the public PDK roots.
|
||||
|
||||
Audit logs record user actions with project/cell context, details, IP address, and UTC timestamp.
|
||||
|
||||
## Tests
|
||||
|
||||
Tests are JavaScript assertion scripts under `tests/`.
|
||||
|
||||
The suite mostly checks integration contracts by reading source files, including:
|
||||
|
||||
- API route strings and handler wiring.
|
||||
- Build Layout and Build GDS frontend/backend wiring.
|
||||
- Removal of legacy local preview/PDK registry modules.
|
||||
- Router-backed GDS and SVG preview paths.
|
||||
- Technology manifest and PDK access behavior.
|
||||
- Canvas helper behavior such as pin/handle placement, YAML serialization, route defaults, route YAML, element ports, and route crossing checks.
|
||||
|
||||
Likely command:
|
||||
|
||||
```powershell
|
||||
node --test tests
|
||||
```
|
||||
|
||||
## Current Repository State Notes
|
||||
|
||||
This checkout contains unresolved merge-conflict markers in some tracked files:
|
||||
|
||||
- `database/engineer/layout/mxpic_project_1/mxpic_project_1.yml`
|
||||
- `database/engineer/layout/mxpic_project_1/mxpic_project_1.svg`
|
||||
- `tests/layout-ui-wiring.test.js`
|
||||
- `tests/layout-backend-static.test.js`
|
||||
|
||||
That means some checked sample data may not parse as YAML/SVG, and the test suite may fail before reaching assertions until those files are cleaned.
|
||||
|
||||
The checked admin and engineer project metadata both select:
|
||||
|
||||
```text
|
||||
Silterra/EMO1_2ML_CU_Al_RDL
|
||||
```
|
||||
|
||||
## Mental Model
|
||||
|
||||
Think of this app as:
|
||||
|
||||
```text
|
||||
Flask session/account layer
|
||||
-> role-scoped PDK/technology selection
|
||||
-> dashboard project metadata
|
||||
-> canvas layout editing
|
||||
-> YAML saved on disk
|
||||
-> mxpic_router/Nazca build path
|
||||
-> SVG preview or downloadable GDS
|
||||
```
|
||||
|
||||
The repository itself owns the web UI, persistence, API contracts, and orchestration. The actual routed photonic layout build is intentionally delegated to the external `mxpic_router` stack.
|
||||
@@ -0,0 +1,19 @@
|
||||
# Project Understanding Plan
|
||||
|
||||
## Goal
|
||||
Read the `mxpic_EDA` project and document its usage and workflow in Markdown files under `develop_plan`.
|
||||
|
||||
## Phases
|
||||
| Phase | Status | Notes |
|
||||
|---|---|---|
|
||||
| Initialize planning files | complete | Created `develop_plan` planning notes. |
|
||||
| Inventory repository structure | complete | File list collected with `rg --files`. |
|
||||
| Read README and operational docs | complete | README, intranet, GDS/SVG, and PDK technology docs read. |
|
||||
| Read backend entry points and data layer | complete | Route surface, database, PDK access, and GDS builder inspected. |
|
||||
| Read frontend entry points | complete | Login/dashboard/canvas API wiring and handler context inspected. |
|
||||
| Summarize project usage/workflow | complete | Wrote `develop_plan/project_understanding.md`. |
|
||||
|
||||
## Errors Encountered
|
||||
| Error | Attempt | Resolution |
|
||||
|---|---|---|
|
||||
| `windows sandbox: spawn setup refresh` | Tried normal PowerShell/read commands in sandbox | Re-ran required commands with escalated approval. |
|
||||
@@ -31,15 +31,15 @@ Important functions:
|
||||
|
||||
## Generated Files
|
||||
|
||||
- Saved cell YAML: `database/<username>/layout/<project>/<cell>.yml`
|
||||
- Saved cell YAML: `mxpic_EDA_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`
|
||||
- Saved layout preview SVG: `mxpic_EDA_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`
|
||||
- Optional route sidecar: `mxpic_EDA_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`
|
||||
- Downloadable GDS export: `mxpic_EDA_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
|
||||
@@ -170,7 +170,7 @@ Important behavior:
|
||||
- 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`
|
||||
- `create_export_path` creates `mxpic_EDA_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).
|
||||
|
||||
@@ -61,9 +61,22 @@ Change these passwords from the dashboard profile panel before regular use.
|
||||
Each user stores projects under:
|
||||
|
||||
```text
|
||||
database/<username>/layout
|
||||
mxpic_EDA_database/<username>/layout
|
||||
```
|
||||
|
||||
Before starting the server, move the existing `database` folder beside the repo
|
||||
and rename it to `mxpic_EDA_database`:
|
||||
|
||||
```text
|
||||
<parent-folder>/
|
||||
mxpic_EDA/
|
||||
mxpic_EDA_database/
|
||||
```
|
||||
|
||||
The backend fails fast if the runtime database folder is missing or still inside
|
||||
the repository. Use `MXPIC_DATABASE_ROOT` only when the database must live in a
|
||||
different external location.
|
||||
|
||||
## Useful environment variables
|
||||
|
||||
```text
|
||||
@@ -72,6 +85,7 @@ MXPIC_PORT=3000
|
||||
MXPIC_DEBUG=0
|
||||
MXPIC_SECRET_KEY=<long random string>
|
||||
MXPIC_COOKIE_SECURE=0
|
||||
MXPIC_DATABASE_ROOT=<path-to-mxpic_EDA_database>
|
||||
MXPIC_PDK_PUBLIC_ROOT=<path-to-public-foundries>
|
||||
MXPIC_PDK_ATLAS_ROOT=<path-to-atlas-foundries>
|
||||
```
|
||||
@@ -28,7 +28,7 @@ There are three related but separate data paths.
|
||||
load route defaults, and register Nazca layers/xsections.
|
||||
|
||||
3. Saved project layout YAML
|
||||
- Source: `database/<username>/layout/<project>/<cell>.yml`.
|
||||
- Source: `mxpic_EDA_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.
|
||||
|
||||
@@ -102,7 +102,7 @@ Detailed path:
|
||||
|
||||
Saved metadata location:
|
||||
|
||||
`database/<username>/layout/<project>/.project.json`
|
||||
`mxpic_EDA_database/<username>/layout/<project>/.project.json`
|
||||
|
||||
Example:
|
||||
|
||||
+129
-47
@@ -22,6 +22,7 @@
|
||||
const DEFAULT_CANVAS_SIZE = { width: 5000, height: 5000 };
|
||||
// Base visual diameter and hit area used for port and anchor handles.
|
||||
const PORT_NODE_SIZE = 30;
|
||||
const FREE_WIRES_BUNDLE_GROUP = 'free_wires';
|
||||
const PORT_LABEL_MIN_CHARS = 5;
|
||||
const PORT_LABEL_CHAR_WIDTH = 7;
|
||||
const PORT_LABEL_HORIZONTAL_PADDING = 12;
|
||||
@@ -42,11 +43,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: {
|
||||
@@ -134,6 +138,26 @@
|
||||
return (technology.xsections && technology.xsections[xsection]) || technology.xsections.strip || {};
|
||||
};
|
||||
|
||||
const cleanBundleGroupName = (value) => String(value ?? '')
|
||||
.trim()
|
||||
.replace(/\s+/g, '_')
|
||||
.replace(/[^A-Za-z0-9_.-]/g, '_')
|
||||
.replace(/_+/g, '_')
|
||||
.replace(/^[._-]+|[._-]+$/g, '');
|
||||
|
||||
const normalizeBundleGroupName = (value, fallback = FREE_WIRES_BUNDLE_GROUP) => {
|
||||
const cleaned = cleanBundleGroupName(value);
|
||||
if (cleaned) return cleaned;
|
||||
const fallbackText = fallback === null || fallback === undefined ? '' : String(fallback);
|
||||
return cleanBundleGroupName(fallbackText) || (fallbackText === '' ? '' : FREE_WIRES_BUNDLE_GROUP);
|
||||
};
|
||||
|
||||
const freeWireBundleGroupName = (xsection, defaultXsection) => {
|
||||
const defaultName = normalizeBundleGroupName(defaultXsection || FALLBACK_TECHNOLOGY_MANIFEST.defaults.xsection || 'strip', 'strip');
|
||||
const currentName = normalizeBundleGroupName(xsection || defaultXsection || defaultName, defaultName);
|
||||
return currentName === defaultName ? FREE_WIRES_BUNDLE_GROUP : `${FREE_WIRES_BUNDLE_GROUP}_${currentName}`;
|
||||
};
|
||||
|
||||
// Normalize route settings so every edge has xsection, family, width, radius, and bend type.
|
||||
const createRouteSettings = (manifest, overrides) => {
|
||||
const technology = getTechnologyManifest(manifest);
|
||||
@@ -147,6 +171,7 @@
|
||||
width: Number((overrides && overrides.width) ?? xsectionInfo.default_width ?? defaults.width ?? 0.45),
|
||||
radius: Number((overrides && overrides.radius) ?? xsectionInfo.default_radius ?? defaults.radius ?? 10),
|
||||
routing_type: (overrides && overrides.routing_type) || defaults.routing_type || 'euler_bend',
|
||||
bundle_group: (overrides && (overrides.bundle_group ?? overrides.bundleGroup)) || '',
|
||||
widthEdited: Boolean(overrides && overrides.widthEdited)
|
||||
};
|
||||
};
|
||||
@@ -804,16 +829,14 @@
|
||||
}
|
||||
};
|
||||
}
|
||||
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 defaultSingleAnchor = portNumber === 1;
|
||||
const y = defaultSingleAnchor ? -PORT_NODE_SIZE / 2 : 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,57 +1007,96 @@ ${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 || {};
|
||||
const nodeMap = {};
|
||||
nodes.forEach(n => { nodeMap[n.id] = n; });
|
||||
|
||||
let linksYaml = '';
|
||||
if (edges.length > 0) {
|
||||
const linkLines = edges.map(edge => {
|
||||
const sourceNode = nodeMap[edge.source];
|
||||
const targetNode = nodeMap[edge.target];
|
||||
const sourceName = sourceNode ? (sourceNode.data.componentDisplayName || sourceNode.id) : edge.source;
|
||||
const targetName = targetNode ? (targetNode.data.componentDisplayName || targetNode.id) : edge.target;
|
||||
const fromPort = sourceNode && sourceNode.data && sourceNode.data.elementType
|
||||
? getElementPinName(sourceNode, edge.sourceHandle)
|
||||
: edge.sourceHandle || 'unknown';
|
||||
const toPort = targetNode && targetNode.data && targetNode.data.elementType
|
||||
? getElementPinName(targetNode, edge.targetHandle)
|
||||
: edge.targetHandle || 'unknown';
|
||||
const route = createRouteSettings(manifest, edge.data && edge.data.route);
|
||||
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
|
||||
? `\n points:\n${points.map(point => ` - x: ${Number(point.x || 0).toFixed(1)}\n y: ${canvasToLayoutY(point.y).toFixed(1)}`).join('\n')}`
|
||||
: '';
|
||||
const isFreeRoute = Boolean(edge.data && edge.data.freeRoute) || (!sourceNode && !targetNode && points.length >= 2);
|
||||
if (isFreeRoute) {
|
||||
return ` - id: ${toYamlScalar(edge.id)}
|
||||
const groups = new Map();
|
||||
let primaryFreeWireXsection = '';
|
||||
const freeWireGroupForRoute = (route) => {
|
||||
const xsectionName = normalizeBundleGroupName(route.xsection, 'strip');
|
||||
if (!primaryFreeWireXsection) {
|
||||
primaryFreeWireXsection = xsectionName;
|
||||
return FREE_WIRES_BUNDLE_GROUP;
|
||||
}
|
||||
return xsectionName === primaryFreeWireXsection
|
||||
? FREE_WIRES_BUNDLE_GROUP
|
||||
: `${FREE_WIRES_BUNDLE_GROUP}_${xsectionName}`;
|
||||
};
|
||||
|
||||
edges.forEach(edge => {
|
||||
const sourceNode = nodeMap[edge.source];
|
||||
const targetNode = nodeMap[edge.target];
|
||||
const sourceName = sourceNode ? (sourceNode.data.componentDisplayName || sourceNode.id) : edge.source;
|
||||
const targetName = targetNode ? (targetNode.data.componentDisplayName || targetNode.id) : edge.target;
|
||||
const fromPort = sourceNode && sourceNode.data && sourceNode.data.elementType
|
||||
? getElementPinName(sourceNode, edge.sourceHandle)
|
||||
: edge.sourceHandle || 'unknown';
|
||||
const toPort = targetNode && targetNode.data && targetNode.data.elementType
|
||||
? 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
|
||||
? `\n points:\n${points.map(point => ` - x: ${Number(point.x || 0).toFixed(1)}\n y: ${canvasToLayoutY(point.y).toFixed(1)}`).join('\n')}`
|
||||
: '';
|
||||
const isFreeRoute = Boolean(edge.data && edge.data.freeRoute) || (!sourceNode && !targetNode && points.length >= 2);
|
||||
const linkYaml = isFreeRoute
|
||||
? ` - 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}`;
|
||||
}
|
||||
return ` - from: ${sourceName}:${fromPort}
|
||||
routing_type: ${route.routing_type}${pointsYaml}`
|
||||
: ` - from: ${sourceName}:${fromPort}
|
||||
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}`;
|
||||
});
|
||||
linksYaml = linkLines.join('\n');
|
||||
}
|
||||
const routeGroupName = normalizeBundleGroupName(route.bundle_group, '');
|
||||
const groupName = routeGroupName || freeWireGroupForRoute(route);
|
||||
if (!groups.has(groupName)) {
|
||||
groups.set(groupName, {
|
||||
xsection: route.xsection,
|
||||
family: route.family,
|
||||
routing_type: route.routing_type,
|
||||
links: []
|
||||
});
|
||||
}
|
||||
groups.get(groupName).links.push(linkYaml);
|
||||
});
|
||||
|
||||
const groupsYaml = Array.from(groups.entries()).map(([groupName, group]) => ` ${groupName}:
|
||||
xsection: ${group.xsection}
|
||||
family: ${group.family}
|
||||
routing_type: ${group.routing_type}
|
||||
links:
|
||||
${group.links.join('\n')}`).join('\n');
|
||||
|
||||
return `# 3. Bundles (Grouped links for multi-bus/parallel routing)
|
||||
bundles:
|
||||
output_bus:
|
||||
routing_type: euler_bend
|
||||
links:
|
||||
${linksYaml}`;
|
||||
bundles:${groupsYaml ? `\n${groupsYaml}` : ' {}'}`;
|
||||
};
|
||||
|
||||
// Return the center point of a node when a more precise port point is unavailable.
|
||||
@@ -1220,6 +1282,22 @@ ${linksYaml}`;
|
||||
return null;
|
||||
};
|
||||
|
||||
const getRotatableNodeHandleDirection = (node, handleId) => {
|
||||
if (!node || !handleId) return null;
|
||||
if (node.type !== 'rotatableNode' && !(!node.data?.elementType && node.data?.componentName)) return null;
|
||||
const ports = node.data && node.data.ports;
|
||||
if (!ports || !ports[handleId]) return null;
|
||||
const boxSize = normalizeBoxSize({ box_size: node.data && node.data.boxSize }, DEFAULT_COMPONENT_BOX_SIZE);
|
||||
const handles = buildPortHandles(ports, {
|
||||
rotation: Number((node.data && node.data.rotation) || 0),
|
||||
flip: Boolean(node.data && node.data.flip),
|
||||
flop: Boolean(node.data && node.data.flop),
|
||||
boxSize
|
||||
});
|
||||
const found = handles.find(handle => handle.name === handleId);
|
||||
return found ? found.position : null;
|
||||
};
|
||||
|
||||
// Backward-compatible alias for same-type route crossing validation.
|
||||
const findSameFamilyRouteCrossing = findSameTypeRouteCrossing;
|
||||
|
||||
@@ -1235,6 +1313,7 @@ ${linksYaml}`;
|
||||
BASIC_COMPONENTS,
|
||||
DEFAULT_FORGE_ARGUMENTS,
|
||||
FALLBACK_TECHNOLOGY_MANIFEST,
|
||||
FREE_WIRES_BUNDLE_GROUP,
|
||||
canvasToLayoutY,
|
||||
layoutToCanvasY,
|
||||
createForgeArguments,
|
||||
@@ -1242,6 +1321,8 @@ ${linksYaml}`;
|
||||
updateRouteField,
|
||||
updateRouteXsection,
|
||||
routeStyleForSettings,
|
||||
normalizeBundleGroupName,
|
||||
freeWireBundleGroupName,
|
||||
findSameTypeRouteCrossing,
|
||||
findSameFamilyRouteCrossing,
|
||||
isForgeComponent,
|
||||
@@ -1259,6 +1340,7 @@ ${linksYaml}`;
|
||||
createComponentSymbolMetrics,
|
||||
transformPortInfo,
|
||||
getNodePortCanvasPoint,
|
||||
getRotatableNodeHandleDirection,
|
||||
buildPortHandles,
|
||||
buildElementPorts,
|
||||
buildElementPinEntries,
|
||||
|
||||
+442
-168
@@ -1566,6 +1566,7 @@ Organization : OptiHK Limited
|
||||
calculateLayoutBounds,
|
||||
calculateCompositeBoxSize,
|
||||
buildPortHandles,
|
||||
getRotatableNodeHandleDirection,
|
||||
buildElementPorts,
|
||||
getElementPinName,
|
||||
buildElementBoxSize,
|
||||
@@ -1580,6 +1581,9 @@ Organization : OptiHK Limited
|
||||
updateRouteField,
|
||||
updateRouteXsection,
|
||||
routeStyleForSettings,
|
||||
FREE_WIRES_BUNDLE_GROUP,
|
||||
normalizeBundleGroupName,
|
||||
freeWireBundleGroupName,
|
||||
findSameTypeRouteCrossing,
|
||||
createRulerMeasurement,
|
||||
createComponentSymbolMetrics,
|
||||
@@ -1589,6 +1593,15 @@ Organization : OptiHK Limited
|
||||
|
||||
const FULL_SELECTION_MODE = SelectionMode && SelectionMode.Full ? SelectionMode.Full : 'full';
|
||||
|
||||
const forEachBundleLink = (doc, callback) => {
|
||||
Object.entries(doc.bundles || {}).forEach(([bundleName, bundleData]) => {
|
||||
const bundle = bundleData && typeof bundleData === 'object' ? bundleData : {};
|
||||
const links = bundle.links;
|
||||
if (!links) return;
|
||||
const linkArray = Array.isArray(links) ? links : [links];
|
||||
linkArray.forEach(link => callback(bundleName, bundle, link || {}));
|
||||
});
|
||||
};
|
||||
|
||||
const iconPromiseCache = {};
|
||||
// Loads and caches category icons so repeated library renders do not refetch the same image.
|
||||
@@ -1622,7 +1635,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 +1684,7 @@ Organization : OptiHK Limited
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
objectFit: 'fill',
|
||||
objectFit: imgObjectFit || 'fill',
|
||||
pointerEvents: 'none',
|
||||
}}
|
||||
onError={(e) => {
|
||||
@@ -1698,8 +1711,10 @@ Organization : OptiHK Limited
|
||||
useEffect(() => {
|
||||
const transformKey = `${data.rotation || 0}:${data.flip ? 1 : 0}:${data.flop ? 1 : 0}`;
|
||||
if (prevTransformRef.current !== transformKey) {
|
||||
updateNodeInternalsRef.current(id);
|
||||
prevTransformRef.current = transformKey;
|
||||
requestAnimationFrame(() => {
|
||||
updateNodeInternalsRef.current(id);
|
||||
});
|
||||
}
|
||||
}, [data.rotation, data.flip, data.flop, id]);
|
||||
|
||||
@@ -1719,9 +1734,51 @@ Organization : OptiHK Limited
|
||||
top: Position.Top,
|
||||
bottom: Position.Bottom
|
||||
};
|
||||
const rotateHandleDirection = (dir, rot) => {
|
||||
const norm = ((rot % 360) + 360) % 360;
|
||||
const map = {
|
||||
0: { right: 'right', left: 'left', top: 'top', bottom: 'bottom' },
|
||||
90: { right: 'bottom', left: 'top', top: 'left', bottom: 'right' },
|
||||
180: { right: 'left', left: 'right', top: 'bottom', bottom: 'top' },
|
||||
270: { right: 'top', left: 'bottom', top: 'right', bottom: 'left' }
|
||||
};
|
||||
return (map[norm] || map[0])[dir] || dir;
|
||||
};
|
||||
const componentSize = normalizeBoxSize({ box_size: data.boxSize }, DEFAULT_COMPONENT_BOX_SIZE);
|
||||
const flippedPorts = useMemo(
|
||||
() => {
|
||||
const result = {};
|
||||
const ports = Object.entries(data.ports || {}).filter(([name]) => name !== 'a0' && name !== 'b0');
|
||||
if (ports.length === 0) return result;
|
||||
let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
|
||||
ports.forEach(([, info]) => {
|
||||
const x = Number(info.x || 0);
|
||||
const y = Number(info.y || 0);
|
||||
if (x < minX) minX = x;
|
||||
if (x > maxX) maxX = x;
|
||||
if (y < minY) minY = y;
|
||||
if (y > maxY) maxY = y;
|
||||
});
|
||||
ports.forEach(([name, info]) => {
|
||||
let x = Number(info.x || 0);
|
||||
let y = Number(info.y || 0);
|
||||
let a = Number(info.a || 0);
|
||||
if (data.flip) {
|
||||
y = minY + maxY - y;
|
||||
a = -a;
|
||||
}
|
||||
if (data.flop) {
|
||||
x = minX + maxX - x;
|
||||
a = normalizeAngle(180 - a);
|
||||
}
|
||||
result[name] = { ...info, x, y, a: normalizeAngle(a) };
|
||||
});
|
||||
return result;
|
||||
},
|
||||
[data.ports, data.flip, data.flop]
|
||||
);
|
||||
const portHandles = useMemo(
|
||||
() => buildPortHandles(data.ports, { rotation: data.rotation || 0, flip: Boolean(data.flip), flop: Boolean(data.flop), boxSize: componentSize }),
|
||||
() => buildPortHandles(flippedPorts, { rotation: 0, boxSize: componentSize }),
|
||||
[data.ports, data.rotation, data.flip, data.flop, componentSize]
|
||||
);
|
||||
const portDirectionMap = useMemo(
|
||||
@@ -1731,20 +1788,22 @@ Organization : OptiHK Limited
|
||||
const isAnchorElement = data.elementType === 'anchor';
|
||||
const isBasicCompactComponent = isBasicComponent(data.componentName) && ['waveguide', 'taper', '90 bend'].includes(data.componentName);
|
||||
const visualSize = isAnchorElement ? { width: PORT_NODE_SIZE, height: PORT_NODE_SIZE } : componentSize;
|
||||
const componentVisualTransform = `rotate(${data.rotation || 0}deg) scaleX(${data.flop ? -1 : 1}) scaleY(${data.flip ? -1 : 1})`;
|
||||
const componentVisualTransform = `rotate(${data.rotation || 0}deg)`;
|
||||
const componentBodyTransform = `rotate(${data.rotation || 0}deg) scaleX(${data.flop ? -1 : 1}) scaleY(${data.flip ? -1 : 1})`;
|
||||
const iconSize = createComponentSymbolMetrics(componentSize);
|
||||
const portLabelStyle = (portHandle) => {
|
||||
const base = { ...portHandle.style };
|
||||
const unrotate = `rotate(${-(data.rotation || 0)}deg)`;
|
||||
if (portHandle.position === 'left') {
|
||||
return { ...base, left: 'auto', right: 'calc(100% + 8px)', transform: 'translateY(-50%)', textAlign: 'right' };
|
||||
return { ...base, left: 'auto', right: 'calc(100% + 8px)', transform: `${unrotate} translateY(-50%)`, textAlign: 'right' };
|
||||
}
|
||||
if (portHandle.position === 'right') {
|
||||
return { ...base, left: 'calc(100% + 8px)', right: 'auto', transform: 'translateY(-50%)', textAlign: 'left' };
|
||||
return { ...base, left: 'calc(100% + 8px)', right: 'auto', transform: `${unrotate} translateY(-50%)`, textAlign: 'left' };
|
||||
}
|
||||
if (portHandle.position === 'top') {
|
||||
return { ...base, top: 'auto', bottom: 'calc(100% + 8px)', transform: 'translateX(-50%)', textAlign: 'center' };
|
||||
return { ...base, top: 'auto', bottom: 'calc(100% + 8px)', transform: `${unrotate} translateX(-50%)`, textAlign: 'center' };
|
||||
}
|
||||
return { ...base, top: 'calc(100% + 8px)', bottom: 'auto', transform: 'translateX(-50%)', textAlign: 'center' };
|
||||
return { ...base, top: 'calc(100% + 8px)', bottom: 'auto', transform: `${unrotate} translateX(-50%)`, textAlign: 'center' };
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -1764,73 +1823,80 @@ 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 ? (
|
||||
<span style={{ fontSize: 8, fontWeight: 800, color: selected ? 'var(--accent)' : 'var(--text-main)' }}>A</span>
|
||||
) : (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: '8px', minHeight: '100%' }}>
|
||||
{!data.hideIcon && data.category && (
|
||||
<div style={{ width: iconSize.width, height: iconSize.height }}>
|
||||
<IconImg category={data.category} />
|
||||
transform: componentBodyTransform,
|
||||
transformOrigin: 'center center',
|
||||
...(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 ? (
|
||||
<span style={{ fontSize: 8, fontWeight: 800, color: selected ? 'var(--accent)' : 'var(--text-main)' }}>A</span>
|
||||
) : (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: '8px', minHeight: '100%' }}>
|
||||
{!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>
|
||||
)}
|
||||
{!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={{
|
||||
position: 'absolute', inset: 0,
|
||||
position: 'absolute',
|
||||
top: 0, left: 0,
|
||||
width: componentSize.width,
|
||||
height: visualSize.height,
|
||||
transform: componentVisualTransform,
|
||||
transformOrigin: 'center center',
|
||||
pointerEvents: 'none'
|
||||
}}>
|
||||
{portHandles.map((portHandle) => (
|
||||
{portHandles.map((portHandle) => {
|
||||
const originalDir = portDirectionMap.get(portHandle.name) || portHandle.position;
|
||||
const effectiveDir = rotateHandleDirection(originalDir, data.rotation || 0);
|
||||
return (
|
||||
<React.Fragment key={portHandle.name}>
|
||||
<Handle
|
||||
type="source"
|
||||
position={handlePositionMap[portDirectionMap.get(portHandle.name) || portHandle.position]}
|
||||
position={handlePositionMap[effectiveDir]}
|
||||
id={portHandle.name}
|
||||
title={portHandle.name}
|
||||
style={{ ...baseHandleStyle, ...portHandle.style, zIndex: 10, pointerEvents: 'all' }}
|
||||
/>
|
||||
<Handle
|
||||
type="target"
|
||||
position={handlePositionMap[portDirectionMap.get(portHandle.name) || portHandle.position]}
|
||||
position={handlePositionMap[effectiveDir]}
|
||||
id={portHandle.name}
|
||||
title={portHandle.name}
|
||||
style={{ ...baseHandleStyle, ...portHandle.style, zIndex: 5, pointerEvents: 'all' }}
|
||||
/>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{portHandles.map((portHandle) => (
|
||||
<React.Fragment key={`label-${portHandle.name}`}>
|
||||
<span className="port-name-label" style={portLabelStyle(portHandle)} title={portHandle.name}>
|
||||
);
|
||||
})}
|
||||
{portHandles.map((portHandle) => (
|
||||
<span key={`label-${portHandle.name}`} className="port-name-label" style={portLabelStyle(portHandle)} title={portHandle.name}>
|
||||
{portHandle.name}
|
||||
</span>
|
||||
</React.Fragment>
|
||||
))}
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}, (prevProps, nextProps) => {
|
||||
@@ -1985,29 +2051,22 @@ Organization : OptiHK Limited
|
||||
const name = String(portName || '');
|
||||
return name.startsWith('a') || name.startsWith('left') ? 'left' : 'right';
|
||||
};
|
||||
const anchorHandleVisualStyle = (portHandle, zIndex) => {
|
||||
const visualSide = anchorPortVisualSide(portHandle.name);
|
||||
const localLeft = visualSide === 'left' ? 0 : elementSize.width;
|
||||
const localTop = portHandle.style?.top || '50%';
|
||||
return {
|
||||
...baseHandleStyle,
|
||||
zIndex,
|
||||
left: localLeft,
|
||||
top: localTop,
|
||||
right: 'auto',
|
||||
bottom: 'auto',
|
||||
transform: 'translate(-50%, -50%)'
|
||||
};
|
||||
};
|
||||
const anchorHandleVisualStyle = (portHandle, zIndex) => ({
|
||||
...baseHandleStyle,
|
||||
zIndex,
|
||||
left: portHandle.style?.left,
|
||||
top: portHandle.style?.top || '50%',
|
||||
right: portHandle.style?.right || 'auto',
|
||||
bottom: portHandle.style?.bottom || 'auto',
|
||||
transform: portHandle.style?.transform || 'translate(-50%, -50%)'
|
||||
});
|
||||
const pinLabelStyle = (portHandle) => {
|
||||
const visualSide = anchorPortVisualSide(portHandle.name);
|
||||
const localLeft = visualSide === 'left' ? 0 : elementSize.width;
|
||||
const localTop = portHandle.style?.top || '50%';
|
||||
return {
|
||||
left: localLeft,
|
||||
top: localTop,
|
||||
right: 'auto',
|
||||
bottom: 'auto',
|
||||
left: portHandle.style?.left,
|
||||
top: portHandle.style?.top || '50%',
|
||||
right: portHandle.style?.right || 'auto',
|
||||
bottom: portHandle.style?.bottom || 'auto',
|
||||
transform: visualSide === 'left' ? 'translate(calc(-100% - 5px), -50%)' : 'translate(5px, -50%)'
|
||||
};
|
||||
};
|
||||
@@ -2187,24 +2246,73 @@ Organization : OptiHK Limited
|
||||
|
||||
// Displays generated layout SVG previews with zoom and pan controls.
|
||||
const LayoutSvgPreview = ({ page }) => {
|
||||
const [layoutScale, setLayoutScale] = useState(100);
|
||||
const [layoutScale, setLayoutScale] = useState(null);
|
||||
const [previewViewport, setPreviewViewport] = useState({ width: 1, height: 1 });
|
||||
const [svgSize, setSvgSize] = useState(null);
|
||||
const previewCanvasRef = useRef(null);
|
||||
const previewBounds = useMemo(
|
||||
() => page.layoutBounds || calculateLayoutBounds(page),
|
||||
[page.layoutBounds, page.nodes, page.canvasSize]
|
||||
);
|
||||
const normalizedScale = Math.min(800, Math.max(10, Number(layoutScale) || 100));
|
||||
const stageWidth = Math.max(1, previewBounds.width) * normalizedScale / 100;
|
||||
const stageHeight = Math.max(1, previewBounds.height) * normalizedScale / 100;
|
||||
const minLayoutScale = 0.01;
|
||||
const maxLayoutScale = 800;
|
||||
const scalePrecision = 100;
|
||||
const clampLayoutScale = (value, fallback = 100) => {
|
||||
const numericValue = Number(value);
|
||||
const scale = Number.isFinite(numericValue) && numericValue > 0 ? numericValue : fallback;
|
||||
return Number(Math.min(maxLayoutScale, Math.max(minLayoutScale, scale)).toFixed(2));
|
||||
};
|
||||
const baseWidth = Math.max(1, svgSize?.width || previewBounds.width);
|
||||
const baseHeight = Math.max(1, svgSize?.height || previewBounds.height);
|
||||
const availableWidth = Math.max(1, previewViewport.width);
|
||||
const availableHeight = Math.max(1, previewViewport.height);
|
||||
const rawFitScalePercent = Math.min(availableWidth / baseWidth, availableHeight / baseHeight) * 100;
|
||||
const fitScalePercent = clampLayoutScale(Math.floor(rawFitScalePercent * scalePrecision) / scalePrecision);
|
||||
const normalizedScale = clampLayoutScale(layoutScale ?? fitScalePercent, fitScalePercent);
|
||||
const stageWidth = baseWidth * normalizedScale / 100;
|
||||
const stageHeight = baseHeight * normalizedScale / 100;
|
||||
|
||||
useEffect(() => {
|
||||
const previewCanvas = previewCanvasRef.current;
|
||||
if (!previewCanvas) return undefined;
|
||||
|
||||
const measurePreviewViewport = () => {
|
||||
const styles = window.getComputedStyle(previewCanvas);
|
||||
const paddingX = (parseFloat(styles.paddingLeft) || 0) + (parseFloat(styles.paddingRight) || 0);
|
||||
const paddingY = (parseFloat(styles.paddingTop) || 0) + (parseFloat(styles.paddingBottom) || 0);
|
||||
setPreviewViewport({
|
||||
width: Math.max(1, previewCanvas.clientWidth - paddingX),
|
||||
height: Math.max(1, previewCanvas.clientHeight - paddingY)
|
||||
});
|
||||
};
|
||||
|
||||
measurePreviewViewport();
|
||||
if (typeof ResizeObserver === 'undefined') {
|
||||
window.addEventListener('resize', measurePreviewViewport);
|
||||
return () => window.removeEventListener('resize', measurePreviewViewport);
|
||||
}
|
||||
|
||||
const observer = new ResizeObserver(measurePreviewViewport);
|
||||
observer.observe(previewCanvas);
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
|
||||
const updateScale = (value) => {
|
||||
setLayoutScale(Math.min(800, Math.max(10, Number(value) || 100)));
|
||||
setLayoutScale(clampLayoutScale(value, fitScalePercent));
|
||||
};
|
||||
|
||||
const handleWheel = (event) => {
|
||||
event.preventDefault();
|
||||
const direction = event.deltaY > 0 ? -1 : 1;
|
||||
const step = event.shiftKey ? 5 : 15;
|
||||
setLayoutScale(current => Math.min(800, Math.max(10, (Number(current) || 100) + direction * step)));
|
||||
setLayoutScale(current => clampLayoutScale((current ?? fitScalePercent) + direction * step, fitScalePercent));
|
||||
};
|
||||
|
||||
const handleSvgLoad = (event) => {
|
||||
const image = event.currentTarget;
|
||||
if (image.naturalWidth > 0 && image.naturalHeight > 0) {
|
||||
setSvgSize({ width: image.naturalWidth, height: image.naturalHeight });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -2214,9 +2322,9 @@ Organization : OptiHK Limited
|
||||
Scale
|
||||
<input
|
||||
type="range"
|
||||
min="10"
|
||||
max="800"
|
||||
step="5"
|
||||
min={minLayoutScale}
|
||||
max={maxLayoutScale}
|
||||
step="0.01"
|
||||
value={normalizedScale}
|
||||
onChange={(event) => updateScale(event.target.value)}
|
||||
aria-label="Layout SVG preview scale"
|
||||
@@ -2225,9 +2333,9 @@ Organization : OptiHK Limited
|
||||
<label>
|
||||
<input
|
||||
type="number"
|
||||
min="10"
|
||||
max="800"
|
||||
step="5"
|
||||
min={minLayoutScale}
|
||||
max={maxLayoutScale}
|
||||
step="0.01"
|
||||
value={normalizedScale}
|
||||
onChange={(event) => updateScale(event.target.value)}
|
||||
aria-label="Layout SVG preview scale percent"
|
||||
@@ -2235,7 +2343,7 @@ Organization : OptiHK Limited
|
||||
%
|
||||
</label>
|
||||
</div>
|
||||
<div className="layout-preview-canvas" onWheel={handleWheel}>
|
||||
<div className="layout-preview-canvas" ref={previewCanvasRef} onWheel={handleWheel}>
|
||||
<div className="layout-preview-scroll-area">
|
||||
<div
|
||||
className="layout-preview-stage"
|
||||
@@ -2245,6 +2353,7 @@ Organization : OptiHK Limited
|
||||
className="layout-preview-image"
|
||||
src={page.svgUrl}
|
||||
alt={`${page.name} layout preview`}
|
||||
onLoad={handleSvgLoad}
|
||||
style={{ objectFit: 'contain' }}
|
||||
/>
|
||||
</div>
|
||||
@@ -2811,12 +2920,13 @@ Organization : OptiHK Limited
|
||||
};
|
||||
|
||||
// Renders editable properties for selected nodes, ports, anchors, and routes.
|
||||
const RightPanel = ({ selectedNode, selectedNodes = [], selectedEdge, selectedEdges = [], technologyManifest, projectName, compositeNames = [], width, onRenameComponent, onUpdateNode, onUpdateEdgeRoute }) => {
|
||||
const RightPanel = ({ selectedNode, selectedNodes = [], selectedEdge, selectedEdges = [], bundleGroupOptions = [], technologyManifest, projectName, compositeNames = [], width, onRenameComponent, onUpdateNode, onUpdateEdgeRoute }) => {
|
||||
const [componentData, setComponentData] = useState(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [enlarged, setEnlarged] = useState(null);
|
||||
const [editingComponentName, setEditingComponentName] = useState(false);
|
||||
const [tempComponentName, setTempComponentName] = useState('');
|
||||
const [newBundleGroupName, setNewBundleGroupName] = useState('');
|
||||
const [localX, setLocalX] = useState('');
|
||||
const [localY, setLocalY] = useState('');
|
||||
const [localRotation, setLocalRotation] = useState('');
|
||||
@@ -3016,9 +3126,51 @@ Organization : OptiHK Limited
|
||||
family: mixedValue('family'),
|
||||
width: mixedValue('width'),
|
||||
radius: mixedValue('radius'),
|
||||
routing_type: mixedValue('routing_type')
|
||||
routing_type: mixedValue('routing_type'),
|
||||
bundle_group: mixedValue('bundle_group')
|
||||
};
|
||||
const routingTypes = (technologyManifest || FALLBACK_TECHNOLOGY_MANIFEST).routing_types || ['euler_bend', 'standard_bend'];
|
||||
const routeManifestDefaults = (technologyManifest || FALLBACK_TECHNOLOGY_MANIFEST).defaults || {};
|
||||
const selectedRouteXsection = route.xsection === '__mixed__' ? firstRoute.xsection : route.xsection;
|
||||
const selectedRouteFamily = route.family === '__mixed__' ? firstRoute.family : route.family;
|
||||
const compatibleFreeWireGroup = (xsection) => {
|
||||
const freeWireForXsection = bundleGroupOptions.find(option => (
|
||||
option.name === FREE_WIRES_BUNDLE_GROUP && option.xsection === xsection
|
||||
));
|
||||
return freeWireForXsection
|
||||
? FREE_WIRES_BUNDLE_GROUP
|
||||
: freeWireBundleGroupName(xsection, routeManifestDefaults.xsection || 'strip');
|
||||
};
|
||||
const freeWireOptionName = compatibleFreeWireGroup(selectedRouteXsection);
|
||||
const compatibleBundleGroupOptions = bundleGroupOptions
|
||||
.filter(option => option.xsection === selectedRouteXsection)
|
||||
.map(option => ({ ...option, name: normalizeBundleGroupName(option.name, freeWireOptionName) }));
|
||||
if (!compatibleBundleGroupOptions.some(option => option.name === freeWireOptionName)) {
|
||||
compatibleBundleGroupOptions.unshift({
|
||||
name: freeWireOptionName,
|
||||
xsection: selectedRouteXsection,
|
||||
family: selectedRouteFamily
|
||||
});
|
||||
}
|
||||
const selectedBundleGroupName = route.bundle_group === '__mixed__'
|
||||
? '__mixed__'
|
||||
: normalizeBundleGroupName(route.bundle_group, freeWireOptionName);
|
||||
const selectedBundleGroupOption = compatibleBundleGroupOptions.find(option => option.name === selectedBundleGroupName) || compatibleBundleGroupOptions[0];
|
||||
const bundleGroupOptionColor = (option) => routeStyleForSettings({ xsection: option.xsection, family: option.family }, false).style.stroke;
|
||||
const selectedBundleGroupColor = selectedBundleGroupOption ? bundleGroupOptionColor(selectedBundleGroupOption) : routeStyleForSettings(route, false).style.stroke;
|
||||
const onAddBundleGroup = () => {
|
||||
if (route.xsection === '__mixed__') return;
|
||||
const sanitizedName = normalizeBundleGroupName(newBundleGroupName, '');
|
||||
if (!sanitizedName) return;
|
||||
const collidesWithOtherXsection = bundleGroupOptions.some(option => (
|
||||
option.name === sanitizedName && option.xsection !== selectedRouteXsection
|
||||
));
|
||||
const finalName = collidesWithOtherXsection
|
||||
? `${sanitizedName}_${normalizeBundleGroupName(selectedRouteXsection, 'route')}`
|
||||
: sanitizedName;
|
||||
onUpdateEdgeRoute(selectedEdgeIds, currentRoute => updateRouteField(currentRoute, 'bundle_group', finalName, technologyManifest));
|
||||
setNewBundleGroupName('');
|
||||
};
|
||||
return (
|
||||
<aside style={{
|
||||
width: width, background: 'var(--bg-card)', borderLeft: '1px solid var(--border)',
|
||||
@@ -3034,7 +3186,14 @@ Organization : OptiHK Limited
|
||||
<label>XSection</label>
|
||||
<select
|
||||
value={route.xsection}
|
||||
onChange={(event) => onUpdateEdgeRoute(selectedEdgeIds, currentRoute => updateRouteXsection(currentRoute, event.target.value, technologyManifest))}
|
||||
onChange={(event) => {
|
||||
const nextXsection = event.target.value;
|
||||
const nextBundleGroup = compatibleFreeWireGroup(nextXsection);
|
||||
onUpdateEdgeRoute(selectedEdgeIds, currentRoute => ({
|
||||
...updateRouteXsection(currentRoute, nextXsection, technologyManifest),
|
||||
bundle_group: nextBundleGroup
|
||||
}));
|
||||
}}
|
||||
>
|
||||
{route.xsection === '__mixed__' && <option value="__mixed__" disabled>--</option>}
|
||||
{xsections.map(xsection => (
|
||||
@@ -3042,6 +3201,44 @@ Organization : OptiHK Limited
|
||||
))}
|
||||
</select>
|
||||
<br /><br />
|
||||
<label>Bundle Group</label>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) minmax(0, 92px) auto', gap: 6, alignItems: 'center' }}>
|
||||
<select
|
||||
value={selectedBundleGroupName}
|
||||
style={{ color: selectedBundleGroupColor }}
|
||||
onChange={(event) => onUpdateEdgeRoute(selectedEdgeIds, currentRoute => updateRouteField(
|
||||
currentRoute,
|
||||
'bundle_group',
|
||||
normalizeBundleGroupName(event.target.value, freeWireOptionName),
|
||||
technologyManifest
|
||||
))}
|
||||
>
|
||||
{route.bundle_group === '__mixed__' && <option value="__mixed__" disabled>--</option>}
|
||||
{compatibleBundleGroupOptions.map(option => (
|
||||
<option
|
||||
key={`${option.name}-${option.xsection}`}
|
||||
value={option.name}
|
||||
style={{ color: bundleGroupOptionColor(option) }}
|
||||
>
|
||||
{option.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<input
|
||||
type="text"
|
||||
value={newBundleGroupName}
|
||||
placeholder="group_A"
|
||||
onChange={(event) => setNewBundleGroupName(event.target.value)}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Enter') onAddBundleGroup();
|
||||
}}
|
||||
disabled={route.xsection === '__mixed__'}
|
||||
/>
|
||||
<button type="button" className="mini-btn" onClick={onAddBundleGroup} disabled={route.xsection === '__mixed__'}>
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
<br /><br />
|
||||
<label>Width</label>
|
||||
<input
|
||||
type="text"
|
||||
@@ -3359,6 +3556,7 @@ Organization : OptiHK Limited
|
||||
const forge = isForgeComponent(componentName);
|
||||
onUpdateNode(selectedNode.id, {
|
||||
data: {
|
||||
...selectedNode.data,
|
||||
componentName,
|
||||
label: componentName,
|
||||
ports: forge ? {} : undefined,
|
||||
@@ -3772,6 +3970,20 @@ Organization : OptiHK Limited
|
||||
const selectedEdge = selectedEdges[0] || null;
|
||||
const selectedNodes = useMemo(() => currentNodes.filter(n => n.selected), [currentNodes]);
|
||||
const selectedNode = selectedNodes[0] || null;
|
||||
const bundleGroupOptions = useMemo(() => {
|
||||
const groups = new Map();
|
||||
currentEdges.forEach(edge => {
|
||||
const route = createRouteSettings(technologyManifest, edge.data?.route);
|
||||
const name = normalizeBundleGroupName(route.bundle_group, '');
|
||||
if (!name || groups.has(name)) return;
|
||||
groups.set(name, {
|
||||
name,
|
||||
xsection: route.xsection,
|
||||
family: route.family
|
||||
});
|
||||
});
|
||||
return Array.from(groups.values());
|
||||
}, [currentEdges, technologyManifest]);
|
||||
const linkXsectionChoices = useMemo(() => {
|
||||
const manifestSections = Object.keys((technologyManifest || FALLBACK_TECHNOLOGY_MANIFEST).xsections || {});
|
||||
const preferred = ['strip', 'rib_low', 'metal_1', 'metal_2'];
|
||||
@@ -3782,7 +3994,13 @@ Organization : OptiHK Limited
|
||||
return ordered.length ? ordered : preferred;
|
||||
}, [technologyManifest]);
|
||||
const currentLinkRoute = useMemo(
|
||||
() => createRouteSettings(technologyManifest, { xsection: currentLinkXsection }),
|
||||
() => {
|
||||
const manifestDefaults = (technologyManifest || FALLBACK_TECHNOLOGY_MANIFEST).defaults || {};
|
||||
return createRouteSettings(technologyManifest, {
|
||||
xsection: currentLinkXsection,
|
||||
bundle_group: freeWireBundleGroupName(currentLinkXsection, manifestDefaults.xsection || 'strip')
|
||||
});
|
||||
},
|
||||
[technologyManifest, currentLinkXsection]
|
||||
);
|
||||
useEffect(() => {
|
||||
@@ -3805,6 +4023,14 @@ Organization : OptiHK Limited
|
||||
}
|
||||
: null
|
||||
), [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.
|
||||
const compactRoutePoints = useCallback((points) => {
|
||||
return (points || [])
|
||||
@@ -3968,8 +4194,10 @@ Organization : OptiHK Limited
|
||||
const targetEndpoint = `${edge.target}:${edge.targetHandle || ''}`;
|
||||
const key = [sourceEndpoint, targetEndpoint].sort().join('<>');
|
||||
const group = groups.get(key) || [];
|
||||
const sourceDirection = getAnchorHandleRouteDirection(nodeMap[edge.source], edge.sourceHandle);
|
||||
const targetDirection = getAnchorHandleRouteDirection(nodeMap[edge.target], edge.targetHandle);
|
||||
const sourceDirection = getAnchorHandleRouteDirection(nodeMap[edge.source], edge.sourceHandle)
|
||||
|| getRotatableNodeHandleDirection(nodeMap[edge.source], edge.sourceHandle);
|
||||
const targetDirection = getAnchorHandleRouteDirection(nodeMap[edge.target], edge.targetHandle)
|
||||
|| getRotatableNodeHandleDirection(nodeMap[edge.target], edge.targetHandle);
|
||||
const usesAnchorDirection = Boolean(sourceDirection || targetDirection);
|
||||
const hasRoutePoints = edge.data && Array.isArray(edge.data.points) && edge.data.points.length >= 2;
|
||||
const directionalEdge = usesAnchorDirection
|
||||
@@ -3992,7 +4220,7 @@ Organization : OptiHK Limited
|
||||
};
|
||||
});
|
||||
return [...separatedEdges, ...rulerEdges];
|
||||
}, [currentEdges, currentNodes, getAnchorHandleRouteDirection, rulerEdges]);
|
||||
}, [currentEdges, currentNodes, getAnchorHandleRouteDirection, getRotatableNodeHandleDirection, rulerEdges]);
|
||||
|
||||
const [projectCompositeMap, setProjectCompositeMap] = useState({});
|
||||
const [standaloneComposites, setStandaloneComposites] = useState([]);
|
||||
@@ -4766,40 +4994,36 @@ Organization : OptiHK Limited
|
||||
newNodes.push(...buildElementNodesFromYaml(doc, usesGdsYUp, nodeNameMap));
|
||||
|
||||
if (!isProject) {
|
||||
const links = doc.bundles?.output_bus?.links;
|
||||
if (links) {
|
||||
const linkArray = Array.isArray(links) ? links : [links];
|
||||
linkArray.forEach(link => {
|
||||
const route = createRouteSettings(technologyManifest, link);
|
||||
const routePoints = normalizeRoutePoints(link.points, doc.coordinate_system === 'gds_y_up');
|
||||
if (link.from && link.to) {
|
||||
const [fromInst, fromPort] = link.from.split(':');
|
||||
const [toInst, toPort] = link.to.split(':');
|
||||
const sourceId = nodeNameMap[fromInst];
|
||||
const targetId = nodeNameMap[toInst];
|
||||
if (sourceId && targetId) {
|
||||
const sourceNode = newNodes.find(node => node.id === sourceId);
|
||||
const targetNode = newNodes.find(node => node.id === targetId);
|
||||
const sourceHandle = resolveLoadedPinHandle(sourceNode, fromPort);
|
||||
const targetHandle = resolveLoadedPinHandle(targetNode, toPort);
|
||||
const view = routeStyleForSettings(route, false);
|
||||
newEdges.push({
|
||||
id: `edge-${sourceId}-${sourceHandle}-${targetId}-${targetHandle}`,
|
||||
source: sourceId,
|
||||
target: targetId,
|
||||
sourceHandle,
|
||||
targetHandle,
|
||||
type: view.type,
|
||||
style: view.style,
|
||||
data: { route, points: routePoints },
|
||||
});
|
||||
}
|
||||
} else if (routePoints.length >= 2) {
|
||||
const edgeId = link.id || `route-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`;
|
||||
newEdges.push(makeFreeRouteEdge(edgeId, routePoints, route));
|
||||
forEachBundleLink(doc, (bundleName, bundle, link) => {
|
||||
const route = createRouteSettings(technologyManifest, { ...bundle, ...link, bundle_group: bundleName });
|
||||
const routePoints = normalizeRoutePoints(link.points, doc.coordinate_system === 'gds_y_up');
|
||||
if (link.from && link.to) {
|
||||
const [fromInst, fromPort] = link.from.split(':');
|
||||
const [toInst, toPort] = link.to.split(':');
|
||||
const sourceId = nodeNameMap[fromInst];
|
||||
const targetId = nodeNameMap[toInst];
|
||||
if (sourceId && targetId) {
|
||||
const sourceNode = newNodes.find(node => node.id === sourceId);
|
||||
const targetNode = newNodes.find(node => node.id === targetId);
|
||||
const sourceHandle = resolveLoadedPinHandle(sourceNode, fromPort);
|
||||
const targetHandle = resolveLoadedPinHandle(targetNode, toPort);
|
||||
const view = routeStyleForSettings(route, false);
|
||||
newEdges.push({
|
||||
id: `edge-${sourceId}-${sourceHandle}-${targetId}-${targetHandle}`,
|
||||
source: sourceId,
|
||||
target: targetId,
|
||||
sourceHandle,
|
||||
targetHandle,
|
||||
type: view.type,
|
||||
style: view.style,
|
||||
data: { route, points: routePoints },
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (routePoints.length >= 2) {
|
||||
const edgeId = link.id || `route-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`;
|
||||
newEdges.push(makeFreeRouteEdge(edgeId, routePoints, route));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const newPageId = Date.now().toString() + Math.random().toString(36).substr(2, 5);
|
||||
@@ -4982,39 +5206,35 @@ Organization : OptiHK Limited
|
||||
});
|
||||
nodes.push(...buildElementNodesFromYaml(doc, usesGdsYUp, nodeNameMap));
|
||||
|
||||
const links = doc.bundles?.output_bus?.links;
|
||||
if (links) {
|
||||
const linkArray = Array.isArray(links) ? links : [links];
|
||||
linkArray.forEach(link => {
|
||||
const route = createRouteSettings(manifest, link);
|
||||
const routePoints = normalizeRoutePoints(link.points, usesGdsYUp);
|
||||
if (link.from && link.to) {
|
||||
const [fromInst, fromPort] = link.from.split(':');
|
||||
const [toInst, toPort] = link.to.split(':');
|
||||
const sourceId = nodeNameMap[fromInst];
|
||||
const targetId = nodeNameMap[toInst];
|
||||
if (!sourceId || !targetId) return;
|
||||
const sourceNode = nodes.find(node => node.id === sourceId);
|
||||
const targetNode = nodes.find(node => node.id === targetId);
|
||||
const sourceHandle = resolveLoadedPinHandle(sourceNode, fromPort);
|
||||
const targetHandle = resolveLoadedPinHandle(targetNode, toPort);
|
||||
const view = routeStyleForSettings(route, false);
|
||||
edges.push({
|
||||
id: `edge-${sourceId}-${sourceHandle}-${targetId}-${targetHandle}`,
|
||||
source: sourceId,
|
||||
target: targetId,
|
||||
sourceHandle,
|
||||
targetHandle,
|
||||
type: view.type,
|
||||
style: view.style,
|
||||
data: { route, points: routePoints },
|
||||
});
|
||||
} else if (routePoints.length >= 2) {
|
||||
const edgeId = link.id || `route-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`;
|
||||
edges.push(makeFreeRouteEdge(edgeId, routePoints, route));
|
||||
}
|
||||
});
|
||||
}
|
||||
forEachBundleLink(doc, (bundleName, bundle, link) => {
|
||||
const route = createRouteSettings(manifest, { ...bundle, ...link, bundle_group: bundleName });
|
||||
const routePoints = normalizeRoutePoints(link.points, usesGdsYUp);
|
||||
if (link.from && link.to) {
|
||||
const [fromInst, fromPort] = link.from.split(':');
|
||||
const [toInst, toPort] = link.to.split(':');
|
||||
const sourceId = nodeNameMap[fromInst];
|
||||
const targetId = nodeNameMap[toInst];
|
||||
if (!sourceId || !targetId) return;
|
||||
const sourceNode = nodes.find(node => node.id === sourceId);
|
||||
const targetNode = nodes.find(node => node.id === targetId);
|
||||
const sourceHandle = resolveLoadedPinHandle(sourceNode, fromPort);
|
||||
const targetHandle = resolveLoadedPinHandle(targetNode, toPort);
|
||||
const view = routeStyleForSettings(route, false);
|
||||
edges.push({
|
||||
id: `edge-${sourceId}-${sourceHandle}-${targetId}-${targetHandle}`,
|
||||
source: sourceId,
|
||||
target: targetId,
|
||||
sourceHandle,
|
||||
targetHandle,
|
||||
type: view.type,
|
||||
style: view.style,
|
||||
data: { route, points: routePoints },
|
||||
});
|
||||
} else if (routePoints.length >= 2) {
|
||||
const edgeId = link.id || `route-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`;
|
||||
edges.push(makeFreeRouteEdge(edgeId, routePoints, route));
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
id: `cell-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`,
|
||||
@@ -5041,11 +5261,20 @@ Organization : OptiHK Limited
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
(data.warnings || []).forEach(warning => addLog(warning));
|
||||
const technology = data.technology || '';
|
||||
setProjectTechnology(technology);
|
||||
const manifest = await loadTechnologyManifest(technology);
|
||||
const knownCompositeNames = new Set((data.cells || []).map(cell => cell.name).filter(name => name !== currentProjectName));
|
||||
const parsedCellPages = (data.cells || []).map(cell => pageFromYaml(cell.name, cell.content, manifest, knownCompositeNames));
|
||||
const loadedCells = data.cells || [];
|
||||
const knownCompositeNames = new Set(loadedCells.map(cell => cell.name).filter(name => name !== currentProjectName));
|
||||
const parsedCellPages = [];
|
||||
loadedCells.forEach(cell => {
|
||||
try {
|
||||
parsedCellPages.push(pageFromYaml(cell.name, cell.content, manifest, knownCompositeNames));
|
||||
} catch (error) {
|
||||
addLog(`Skipped saved cell "${cell.name}": ${error.message}`);
|
||||
}
|
||||
});
|
||||
const compositeBoxSizes = new Map(parsedCellPages
|
||||
.filter(page => page.type === 'composite')
|
||||
.map(page => [page.name, calculateCompositeBoxSize(page)]));
|
||||
@@ -5056,6 +5285,35 @@ Organization : OptiHK Limited
|
||||
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 nonProjectPages = cellPages.filter(page => page !== loadedProjectPage);
|
||||
const resolvedProjectPage = loadedProjectPage || projectPage;
|
||||
@@ -5078,16 +5336,22 @@ Organization : OptiHK Limited
|
||||
};
|
||||
|
||||
loadProject();
|
||||
}, [library, currentProjectName, loadTechnologyManifest, toBooleanFlag, makeFreeRouteEdge, buildElementNodesFromYaml, getAvailableComponentsForLoadedComponent, resolveLoadedPinHandle]);
|
||||
}, [library, currentProjectName, loadTechnologyManifest, toBooleanFlag, makeFreeRouteEdge, buildElementNodesFromYaml, getAvailableComponentsForLoadedComponent, resolveLoadedPinHandle, addLog]);
|
||||
|
||||
useEffect(() => {
|
||||
if (activePage && activePage.type !== 'layoutPreview' && reactFlowInstance) {
|
||||
reactFlowInstance.fitBounds(
|
||||
{ x: 0, y: 0, width: activeCanvasSize.width, height: activeCanvasSize.height },
|
||||
{ padding: 0.12, duration: 0 }
|
||||
);
|
||||
if (activePage.viewport) {
|
||||
window.requestAnimationFrame(() => {
|
||||
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(() => {
|
||||
setRulerStartPoint(null);
|
||||
@@ -5174,12 +5438,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 () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [pages, loadComponentMetadata]);
|
||||
}, [pages, loadComponentMetadata, reactFlowInstance]);
|
||||
|
||||
const openTabs = useMemo(() => pages.filter(page => !page.isClosed), [pages]);
|
||||
|
||||
@@ -5936,6 +6208,7 @@ Organization : OptiHK Limited
|
||||
const route = currentLinkRoute;
|
||||
const view = routeStyleForSettings(route, false);
|
||||
const edgeId = `edge-${connection.source}-${connection.sourceHandle}-${connection.target}-${connection.targetHandle}-${Date.now()}`;
|
||||
const nodeMap = Object.fromEntries(activePage.nodes.map(node => [node.id, node]));
|
||||
const candidate = {
|
||||
id: edgeId,
|
||||
source: connection.source,
|
||||
@@ -5945,9 +6218,8 @@ Organization : OptiHK Limited
|
||||
type: view.type,
|
||||
selectable: true,
|
||||
style: view.style,
|
||||
data: { route }
|
||||
data: { route },
|
||||
};
|
||||
const nodeMap = Object.fromEntries(activePage.nodes.map(node => [node.id, node]));
|
||||
const conflict = findSameTypeRouteCrossing(candidate, activePage.edges, nodeMap, technologyManifest);
|
||||
if (conflict) {
|
||||
const source = nodeMap[conflict.conflictEdge.source]?.data?.componentDisplayName || conflict.conflictEdge.source;
|
||||
@@ -5961,7 +6233,7 @@ Organization : OptiHK Limited
|
||||
: p
|
||||
)));
|
||||
addLog(`Connected ${connection.sourceHandle} to ${connection.targetHandle}.`);
|
||||
}, [activePageId, activePage, rulerMode, currentLinkRoute, technologyManifest, addLog]);
|
||||
}, [activePageId, activePage, rulerMode, currentLinkRoute, technologyManifest, addLog, getAnchorHandleRouteDirection]);
|
||||
|
||||
// Select custom route edges from their SVG hit target.
|
||||
const handleRouteEdgeMouseDown = useCallback((event) => {
|
||||
@@ -6534,6 +6806,7 @@ ${bundlesBlock}`;
|
||||
minZoom={0.02}
|
||||
maxZoom={4}
|
||||
defaultViewport={{ x: 80, y: 80, zoom: 0.12 }}
|
||||
onMoveEnd={handleCanvasViewportMoveEnd}
|
||||
panOnDrag={false}
|
||||
selectionOnDrag={true}
|
||||
selectionMode={FULL_SELECTION_MODE}
|
||||
@@ -6558,6 +6831,7 @@ ${bundlesBlock}`;
|
||||
selectedNodes={selectedNodes}
|
||||
selectedEdge={selectedEdge}
|
||||
selectedEdges={selectedEdges}
|
||||
bundleGroupOptions={bundleGroupOptions}
|
||||
technologyManifest={technologyManifest}
|
||||
projectName={currentProjectName}
|
||||
compositeNames={compositePageNames}
|
||||
|
||||
@@ -63,3 +63,20 @@ assert(
|
||||
!canvasHtml.includes("activePage.nodes.filter(n => n.selected && n.id !== 'page-port')"),
|
||||
'copy/delete should not exclude port nodes'
|
||||
);
|
||||
assert(
|
||||
canvasHtml.includes('Bundle Group') &&
|
||||
canvasHtml.includes('bundleGroupOptions') &&
|
||||
canvasHtml.includes('compatibleBundleGroupOptions'),
|
||||
'route editor should expose a Bundle Group dropdown filtered by compatible xsection'
|
||||
);
|
||||
assert(
|
||||
canvasHtml.includes('newBundleGroupName') &&
|
||||
canvasHtml.includes('normalizeBundleGroupName') &&
|
||||
canvasHtml.includes('onAddBundleGroup'),
|
||||
'route editor should provide an add flow that sanitizes new bundle group names'
|
||||
);
|
||||
assert(
|
||||
canvasHtml.includes('routeStyleForSettings({ xsection: option.xsection') ||
|
||||
canvasHtml.includes('routeStyleForSettings({ xsection: group.xsection'),
|
||||
'bundle group dropdown options should use route xsection colors'
|
||||
);
|
||||
|
||||
@@ -588,9 +588,13 @@ assert.deepStrictEqual(routeDefaults, {
|
||||
width: 0.45,
|
||||
radius: 10,
|
||||
routing_type: 'euler_bend',
|
||||
bundle_group: '',
|
||||
widthEdited: false
|
||||
});
|
||||
|
||||
const groupedRouteDefaults = helpers.createRouteSettings(technologyManifest, { bundle_group: 'group_A' });
|
||||
assert.strictEqual(groupedRouteDefaults.bundle_group, 'group_A');
|
||||
|
||||
const metalRoute = helpers.updateRouteXsection(routeDefaults, 'metal_1', technologyManifest);
|
||||
assert.strictEqual(metalRoute.family, 'electrical');
|
||||
assert.strictEqual(metalRoute.width, 5);
|
||||
@@ -692,6 +696,90 @@ assert(freeRouteYaml.includes('points:'));
|
||||
assert(freeRouteYaml.includes('x: 80.0'));
|
||||
assert(freeRouteYaml.includes('y: -120.0'));
|
||||
|
||||
const groupedBundlesYaml = helpers.buildBundlesYaml({
|
||||
nodes: [
|
||||
{ id: 'a', data: { componentDisplayName: 'inst_a' } },
|
||||
{ id: 'b', data: { componentDisplayName: 'inst_b' } },
|
||||
{ id: 'c', data: { componentDisplayName: 'inst_c' } },
|
||||
{ id: 'd', data: { componentDisplayName: 'inst_d' } }
|
||||
],
|
||||
edges: [
|
||||
{
|
||||
id: 'edge-group-a',
|
||||
source: 'a',
|
||||
target: 'b',
|
||||
sourceHandle: 'out',
|
||||
targetHandle: 'in',
|
||||
data: {
|
||||
route: {
|
||||
xsection: 'strip',
|
||||
family: 'optical',
|
||||
width: 0.45,
|
||||
radius: 10,
|
||||
routing_type: 'euler_bend',
|
||||
bundle_group: 'optical_bus'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'edge-group-b',
|
||||
source: 'c',
|
||||
target: 'd',
|
||||
sourceHandle: 'out',
|
||||
targetHandle: 'in',
|
||||
data: {
|
||||
route: {
|
||||
xsection: 'metal_1',
|
||||
family: 'electrical',
|
||||
width: 5,
|
||||
radius: 20,
|
||||
routing_type: 'standard_bend',
|
||||
bundle_group: 'electrical_bus'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}, technologyManifest);
|
||||
assert(groupedBundlesYaml.includes(' optical_bus:\n xsection: strip\n family: optical\n routing_type: euler_bend\n links:'));
|
||||
assert(groupedBundlesYaml.includes(' electrical_bus:\n xsection: metal_1\n family: electrical\n routing_type: standard_bend\n links:'));
|
||||
assert(groupedBundlesYaml.includes('from: inst_a:out'));
|
||||
assert(groupedBundlesYaml.includes('from: inst_c:out'));
|
||||
assert(!groupedBundlesYaml.includes('bundle_group:'), 'bundle_group should choose the YAML key, not be written inside links');
|
||||
|
||||
const splitFreeWireBundlesYaml = helpers.buildBundlesYaml({
|
||||
nodes: [
|
||||
{ id: 'a', data: { componentDisplayName: 'inst_a' } },
|
||||
{ id: 'b', data: { componentDisplayName: 'inst_b' } },
|
||||
{ id: 'c', data: { componentDisplayName: 'inst_c' } },
|
||||
{ id: 'd', data: { componentDisplayName: 'inst_d' } }
|
||||
],
|
||||
edges: [
|
||||
{
|
||||
id: 'edge-free-strip',
|
||||
source: 'a',
|
||||
target: 'b',
|
||||
sourceHandle: 'out',
|
||||
targetHandle: 'in',
|
||||
data: {
|
||||
route: { xsection: 'strip', family: 'optical', width: 0.45, radius: 10, routing_type: 'euler_bend' }
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'edge-free-metal',
|
||||
source: 'c',
|
||||
target: 'd',
|
||||
sourceHandle: 'out',
|
||||
targetHandle: 'in',
|
||||
data: {
|
||||
route: { xsection: 'metal_1', family: 'electrical', width: 5, radius: 20, routing_type: 'standard_bend' }
|
||||
}
|
||||
}
|
||||
]
|
||||
}, technologyManifest);
|
||||
assert(splitFreeWireBundlesYaml.includes(' free_wires:\n xsection: strip\n family: optical\n routing_type: euler_bend\n links:'));
|
||||
assert(splitFreeWireBundlesYaml.includes(' free_wires_metal_1:\n xsection: metal_1\n family: electrical\n routing_type: standard_bend\n links:'));
|
||||
assert(!splitFreeWireBundlesYaml.includes('bundle_group:'), 'free-wire bundle names should not be duplicated into link metadata');
|
||||
|
||||
const edgeA = {
|
||||
id: 'edge-a-b',
|
||||
source: 'a',
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Description: Static regression tests for keeping mxPIC EDA runtime data outside the repository.
|
||||
* Inside functions: N/A - assertion-based test/module script.
|
||||
* Developer : Qin Yue @ 2026
|
||||
* Organization : OptiHK Limited
|
||||
*/
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const root = path.resolve(__dirname, '..');
|
||||
const backend = path.join(root, 'backend');
|
||||
const storagePathsPath = path.join(backend, 'storage_paths.py');
|
||||
const storagePathsPy = fs.existsSync(storagePathsPath)
|
||||
? fs.readFileSync(storagePathsPath, 'utf8')
|
||||
: '';
|
||||
const databasePy = fs.readFileSync(path.join(backend, 'database.py'), 'utf8');
|
||||
const serverPy = fs.readFileSync(path.join(backend, 'server.py'), 'utf8');
|
||||
const gitignore = fs.readFileSync(path.join(root, '.gitignore'), 'utf8');
|
||||
|
||||
assert(
|
||||
fs.existsSync(storagePathsPath),
|
||||
'backend/storage_paths.py should be the single source of truth for runtime storage paths'
|
||||
);
|
||||
assert(
|
||||
storagePathsPy.includes('MXPIC_DATABASE_ROOT'),
|
||||
'storage path layer should support MXPIC_DATABASE_ROOT override'
|
||||
);
|
||||
assert(
|
||||
storagePathsPy.includes('mxpic_EDA_database'),
|
||||
'default runtime database folder should be sibling mxpic_EDA_database'
|
||||
);
|
||||
assert(
|
||||
storagePathsPy.includes('DB_FILE') && storagePathsPy.includes('EXPORT_ROOT'),
|
||||
'storage path layer should export DB_FILE and EXPORT_ROOT'
|
||||
);
|
||||
assert(
|
||||
storagePathsPy.includes('raise RuntimeError'),
|
||||
'storage path layer should fail fast when the external database root is invalid'
|
||||
);
|
||||
assert(
|
||||
!serverPy.includes("DATABASE_ROOT = os.path.abspath(os.path.join(BASE_DIR, '..', 'database'))"),
|
||||
'server.py should not hardcode in-repo database/ as DATABASE_ROOT'
|
||||
);
|
||||
assert(
|
||||
serverPy.includes('from storage_paths import DATABASE_ROOT, EXPORT_ROOT'),
|
||||
'server.py should import runtime storage roots from storage_paths'
|
||||
);
|
||||
assert(
|
||||
!databasePy.includes('"..", "database", "mxpic_data.db"') &&
|
||||
!databasePy.includes("'..', 'database', 'mxpic_data.db'"),
|
||||
'database.py should not hardcode database/mxpic_data.db inside the repository'
|
||||
);
|
||||
assert(
|
||||
databasePy.includes('from storage_paths import DB_FILE'),
|
||||
'database.py should import DB_FILE from storage_paths'
|
||||
);
|
||||
assert(
|
||||
gitignore.includes('/database/') &&
|
||||
gitignore.includes('/backend/*.db') &&
|
||||
gitignore.includes('*.db-wal') &&
|
||||
gitignore.includes('/mxpic_EDA_database/'),
|
||||
'.gitignore should ignore old and accidental in-repo runtime database artifacts'
|
||||
);
|
||||
@@ -60,6 +60,18 @@ assert(
|
||||
serverPy.includes('svg_url'),
|
||||
'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(
|
||||
serverPy.includes('RouterStackUnavailable') &&
|
||||
serverPy.includes('except RouterStackUnavailable as e') &&
|
||||
|
||||
@@ -50,8 +50,18 @@ assert(
|
||||
'layout SVG preview should expose an editable scale value'
|
||||
);
|
||||
assert(
|
||||
canvasHtml.includes('objectFit: \'contain\''),
|
||||
'100% layout preview scale should fit the full SVG within the screen'
|
||||
canvasHtml.includes('objectFit: \'contain\'') &&
|
||||
canvasHtml.includes('previewCanvasRef') &&
|
||||
canvasHtml.includes('ResizeObserver') &&
|
||||
canvasHtml.includes('svgSize') &&
|
||||
canvasHtml.includes('naturalWidth') &&
|
||||
canvasHtml.includes('naturalHeight') &&
|
||||
canvasHtml.includes('const fitScalePercent = clampLayoutScale(Math.floor') &&
|
||||
canvasHtml.includes('layoutScale ?? fitScalePercent') &&
|
||||
canvasHtml.includes('const stageWidth = baseWidth * normalizedScale / 100') &&
|
||||
!canvasHtml.includes('useState(100)') &&
|
||||
!canvasHtml.includes('baseWidth * fitScale * normalizedScale / 100'),
|
||||
'layout preview should default to the actual fitted SVG scale percent instead of redefining 100% as fit'
|
||||
);
|
||||
assert(
|
||||
canvasHtml.includes('className="build-gds-btn"'),
|
||||
@@ -344,7 +354,7 @@ assert(
|
||||
canvasHtml.includes('layoutBounds') &&
|
||||
canvasHtml.includes('stageWidth') &&
|
||||
canvasHtml.includes('stageHeight'),
|
||||
'layout preview should mouse-wheel zoom and size 100% from calculated box_size layout bounds'
|
||||
'layout preview should mouse-wheel zoom from the actual calculated SVG scale'
|
||||
);
|
||||
assert(
|
||||
canvasHtml.includes('reactFlowInstance.fitBounds') &&
|
||||
|
||||
@@ -47,3 +47,15 @@ assert(
|
||||
canvasHtml.includes('Array.from(new Set([FORGE_COMPONENT_LABEL, ...sameCategoryComponents'),
|
||||
'loaded PDK selector choices should include forge and same-category library components'
|
||||
);
|
||||
assert(
|
||||
canvasHtml.includes('Object.entries(doc.bundles || {})'),
|
||||
'project and YAML loading should iterate all saved bundle groups, not only output_bus'
|
||||
);
|
||||
assert(
|
||||
!canvasHtml.includes('doc.bundles?.output_bus?.links'),
|
||||
'project and YAML loading should not hardcode bundles.output_bus.links'
|
||||
);
|
||||
assert(
|
||||
canvasHtml.includes('bundle_group: bundleName'),
|
||||
'loaded route metadata should remember the YAML bundle key as route.bundle_group'
|
||||
);
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Description: Static regression tests for resilient project save/reopen behavior.
|
||||
* Inside functions: N/A - assertion-based test/module script.
|
||||
* Developer : Qin Yue @ 2026
|
||||
* Organization : OptiHK Limited
|
||||
*/
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const root = path.resolve(__dirname, '..');
|
||||
const backend = path.join(root, 'backend');
|
||||
const layoutFilesPath = path.join(backend, 'layout_files.py');
|
||||
const layoutFilesPy = fs.readFileSync(layoutFilesPath, 'utf8');
|
||||
const serverPy = fs.readFileSync(path.join(backend, 'server.py'), 'utf8');
|
||||
const gdsBuilderPy = fs.readFileSync(path.join(backend, 'gds_builder.py'), 'utf8');
|
||||
const routedPreviewPy = fs.readFileSync(path.join(backend, 'routed_layout_preview.py'), 'utf8');
|
||||
const canvasHtml = fs.readFileSync(path.join(root, 'frontend', 'canvas.html'), 'utf8');
|
||||
|
||||
assert(
|
||||
fs.existsSync(layoutFilesPath),
|
||||
'backend/layout_files.py should centralize saved layout YAML filtering and parsing'
|
||||
);
|
||||
assert(
|
||||
layoutFilesPy.includes('ROUTE_SIDECAR_SUFFIXES') &&
|
||||
layoutFilesPy.includes('def is_layout_cell_filename') &&
|
||||
layoutFilesPy.includes('def parse_layout_cell_content') &&
|
||||
layoutFilesPy.includes('def load_layout_cell_files'),
|
||||
'layout file helpers should exclude route sidecars and validate saved layout cells'
|
||||
);
|
||||
assert(
|
||||
serverPy.includes('is_layout_cell_filename') &&
|
||||
serverPy.includes('load_layout_cell_files(root)') &&
|
||||
serverPy.includes('parse_layout_cell_content(content') &&
|
||||
serverPy.includes('except LayoutFileError as e'),
|
||||
'project list/load/save endpoints should filter invalid YAML and reject malformed saves'
|
||||
);
|
||||
assert(
|
||||
gdsBuilderPy.includes('TemporaryDirectory(prefix="mxpic_gds_project_"') &&
|
||||
gdsBuilderPy.includes('write_layout_cells_to_directory(cells, staged_project_dir)'),
|
||||
'Build GDS should call mxpic_router with a clean staged project directory'
|
||||
);
|
||||
assert(
|
||||
routedPreviewPy.includes('staged_project_dir') &&
|
||||
routedPreviewPy.includes('write_layout_cells_to_directory(staged_cells, staged_project_dir)') &&
|
||||
routedPreviewPy.includes('project_dir=staged_project_dir'),
|
||||
'Build Layout preview should stage valid cells before calling mxpic_router'
|
||||
);
|
||||
assert(
|
||||
canvasHtml.includes('(data.warnings || []).forEach(warning => addLog(warning))') &&
|
||||
canvasHtml.includes('const parsedCellPages = [];') &&
|
||||
canvasHtml.includes('Skipped saved cell'),
|
||||
'canvas project loading should report skipped files and keep loading remaining valid cells'
|
||||
);
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user