This commit is contained in:
2026-05-28 17:53:41 +08:00
parent 48555f5686
commit e6e9e13cf2
22 changed files with 1743 additions and 186 deletions
+112 -4
View File
@@ -10,18 +10,29 @@ from flask import Flask, jsonify, send_from_directory, request, redirect, url_fo
from werkzeug.security import check_password_hash
import database
from flask import Response
from gds_builder import build_project_gds
from layout_preview import create_layout_svg_from_gds
from pdk_registry import PdkRegistry
from technology_manifest import TechnologyManifestError, read_technology_manifest
# --- Path Configurations ---
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
FRONTEND_DIR = os.path.join(BASE_DIR, '..', 'frontend')
REPO_ROOT = os.path.abspath(os.path.join(BASE_DIR, '..', '..'))
PDK_PUBLIC_ROOT = os.path.abspath(os.environ.get(
'MXPIC_PDK_PUBLIC_ROOT',
os.path.join(REPO_ROOT, 'opt_pdk_public', 'foundries')
))
EDA_PDK_ROOT = os.path.abspath(os.path.join(BASE_DIR, '..', 'mxpic', 'PDKs'))
YML_PATH = os.path.join(BASE_DIR, '..', 'mxpic', 'PDKs', 'Silterra', 'directories.yaml')
COMPS_ROOT = os.path.join(BASE_DIR, '..', 'mxpic', 'PDKs', 'Silterra')
COMPS_ROOT = os.environ.get('MXPIC_COMPONENT_ROOT', PDK_PUBLIC_ROOT)
# Define where your new icons folder is located (adjust if it's placed elsewhere)
ICONS_DIR = os.path.join(BASE_DIR, 'icons')
#build layout save path
DATABASE_ROOT = os.path.abspath(os.path.join(BASE_DIR, '..', 'database'))
PDK_REGISTRY = PdkRegistry(PDK_PUBLIC_ROOT)
app = Flask(__name__, template_folder=FRONTEND_DIR, static_folder=FRONTEND_DIR)
@@ -101,6 +112,14 @@ def cell_file_path(project_name, cell_name):
return os.path.join(project_root(project_name), f"{safe_name(cell_name, 'canvas_1')}.yml")
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 project_gds_path(project_name):
return os.path.join(project_root(project_name), f"{safe_name(project_name, 'project_1')}.gds")
def project_meta_path(project_name):
return os.path.join(project_root(project_name), ".project.json")
@@ -118,6 +137,14 @@ def write_project_meta(project_name, meta):
with open(project_meta_path(project_name), 'w', encoding='utf-8') as f:
json.dump(meta, f, indent=2)
def ensure_project_path(project_name):
layout_root = os.path.abspath(user_layout_root())
target = os.path.abspath(project_root(project_name))
if target != layout_root and not target.startswith(layout_root + os.sep):
raise ValueError("Invalid project path")
return target
# ... [Keep countSpaces and buildTree exactly as they are] ...
def findComps(baseDir):
@@ -271,7 +298,7 @@ def health_check():
def list_technologies():
"""List technology choices from mxpic/PDKs/<foundry>/<technology>."""
technologies = []
pdks_root = os.path.join(BASE_DIR, '..', 'mxpic', 'PDKs')
pdks_root = EDA_PDK_ROOT
if os.path.isdir(pdks_root):
for foundry in sorted(os.listdir(pdks_root)):
foundry_path = os.path.join(pdks_root, foundry)
@@ -290,6 +317,20 @@ def list_technologies():
return jsonify({"technologies": technologies})
@app.route('/api/technologies/<foundry>/<technology>/manifest', methods=['GET'])
@login_required_json
def get_technology_manifest(foundry, technology):
try:
manifest = read_technology_manifest(
EDA_PDK_ROOT,
safe_name(foundry, ''),
safe_name(technology, '')
)
return jsonify({"manifest": manifest})
except TechnologyManifestError as e:
return jsonify({"error": str(e)}), 404
@app.route('/api/profile', methods=['GET', 'PATCH'])
@login_required_json
def account_profile():
@@ -526,16 +567,83 @@ def save_layout():
with open(save_path, 'w', encoding='utf-8') as f:
f.write(content)
record_action('layout.save', project=project, cell=cell, detail={"bytes": len(content)})
svg_path = cell_svg_path(project, cell)
create_layout_svg_from_gds(content, svg_path, pdk_registry=PDK_REGISTRY, project_dir=project_root(project))
record_action('layout.save', project=project, cell=cell, detail={"bytes": len(content), "svg": svg_path})
return jsonify({
"message": "successfully saved",
"project": project,
"cell": cell,
"path": save_path
"path": save_path,
"svg_path": svg_path,
"svg_url": url_for('get_layout_svg', project_name=project, cell_name=cell)
}), 200
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.route('/api/projects/<project_name>/cells/<cell_name>/layout.svg')
@login_required_json
def get_layout_svg(project_name, cell_name):
try:
project_dir = ensure_project_path(project_name)
svg_path = os.path.abspath(cell_svg_path(project_name, cell_name))
if not svg_path.startswith(project_dir + os.sep):
return jsonify({"error": "Invalid SVG path"}), 400
if not os.path.exists(svg_path):
return jsonify({"error": "Layout SVG not found"}), 404
return no_cache_response(send_from_directory(os.path.dirname(svg_path), os.path.basename(svg_path), mimetype='image/svg+xml'))
except ValueError as e:
return jsonify({"error": str(e)}), 400
@app.route('/api/build-gds', methods=['POST'])
@login_required_json
def build_gds():
data = request.get_json(silent=True) or {}
project = safe_name(data.get('project'), 'project_1')
try:
project_dir = ensure_project_path(project)
if not os.path.isdir(project_dir):
return jsonify({"error": "Project not found"}), 404
output_path = project_gds_path(project)
result = build_project_gds(project_dir, output_path, PDK_PUBLIC_ROOT)
record_action('gds.build', project=project, detail={
"path": result.output_path,
"engine": result.engine,
"warnings": result.warnings
})
return jsonify({
"message": "GDS built",
"project": project,
"path": result.output_path,
"gds_url": url_for('get_project_gds', project_name=project, filename=os.path.basename(result.output_path)),
"engine": result.engine,
"cells_built": result.cells_built,
"warnings": result.warnings
})
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.route('/api/projects/<project_name>/gds/<filename>')
@login_required_json
def get_project_gds(project_name, filename):
try:
project_dir = ensure_project_path(project_name)
safe_filename = safe_name(filename, f"{safe_name(project_name, 'project_1')}.gds")
if not safe_filename.lower().endswith('.gds'):
return jsonify({"error": "Invalid GDS filename"}), 400
gds_path = os.path.abspath(os.path.join(project_dir, safe_filename))
if not gds_path.startswith(project_dir + os.sep):
return jsonify({"error": "Invalid GDS path"}), 400
if not os.path.exists(gds_path):
return jsonify({"error": "GDS not found"}), 404
return send_from_directory(project_dir, safe_filename, as_attachment=True)
except ValueError as e:
return jsonify({"error": str(e)}), 400