CODEX revised with following function: 1. GDS building, 2. different user group with different authority.

This commit is contained in:
2026-05-28 20:35:49 +08:00
parent e6e9e13cf2
commit 1215bf978a
25 changed files with 439 additions and 196 deletions
+81 -13
View File
@@ -13,6 +13,13 @@ 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 pdk_access import (
cleanup_expired_exports,
create_export_path,
pdk_root_for_session,
prefer_full_gds_for_session,
)
from routed_layout_preview import create_routed_layout_svg, layout_has_links
from technology_manifest import TechnologyManifestError, read_technology_manifest
# --- Path Configurations ---
@@ -26,13 +33,12 @@ PDK_PUBLIC_ROOT = os.path.abspath(os.environ.get(
))
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.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)
EXPORT_ROOT = os.path.abspath(os.path.join(DATABASE_ROOT, '_exports'))
app = Flask(__name__, template_folder=FRONTEND_DIR, static_folder=FRONTEND_DIR)
@@ -120,6 +126,25 @@ def project_gds_path(project_name):
return os.path.join(project_root(project_name), f"{safe_name(project_name, 'project_1')}.gds")
def technology_manifest_path_for_project(project_name):
technology_id = read_project_meta(project_name).get("technology") or ""
if "/" not in technology_id:
return None
foundry, technology = technology_id.split("/", 1)
path = os.path.abspath(os.path.join(EDA_PDK_ROOT, safe_name(foundry, ''), safe_name(technology, ''), "technology.yml"))
if path.startswith(EDA_PDK_ROOT + os.sep) and os.path.exists(path):
return path
return None
def current_pdk_root():
return pdk_root_for_session(session, REPO_ROOT)
def current_pdk_registry():
return PdkRegistry(current_pdk_root(), prefer_full_gds=prefer_full_gds_for_session(session))
def project_meta_path(project_name):
return os.path.join(project_root(project_name), ".project.json")
@@ -220,9 +245,10 @@ def getIcon(category):
# ... [Keep existing API routes below] ...
def readCompYaml(compName):
def readCompYaml(compName, comps_root=None):
"""Load YAML from component folder."""
for root, dirs, files in os.walk(COMPS_ROOT):
search_root = comps_root or current_pdk_root()
for root, dirs, files in os.walk(search_root):
if os.path.basename(root) == compName:
dirs.clear()
ymlFiles = [f for f in files if f.endswith('.yml')]
@@ -252,6 +278,7 @@ def login():
if user and check_password_hash(user[2], password):
session['user_id'] = user[0]
session['username'] = user[1]
session['user_group'] = user[3] or 'user'
record_action('login')
return redirect(url_for('dashboard'))
else:
@@ -355,6 +382,7 @@ def account_profile():
"created_at": profile[2],
"credits": profile[3] or 0,
"occupation": profile[4] or "intern",
"user_group": profile[5] or "user",
"occupations": sorted(occupations)
})
@@ -568,7 +596,17 @@ def save_layout():
f.write(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))
if layout_has_links(content):
create_routed_layout_svg(
content,
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),
)
else:
create_layout_svg_from_gds(content, svg_path, pdk_registry=current_pdk_registry(), project_dir=project_root(project))
record_action('layout.save', project=project, cell=cell, detail={"bytes": len(content), "svg": svg_path})
return jsonify({
@@ -605,11 +643,18 @@ def build_gds():
data = request.get_json(silent=True) or {}
project = safe_name(data.get('project'), 'project_1')
try:
cleanup_expired_exports(EXPORT_ROOT)
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)
export_id, filename, output_path = create_export_path(EXPORT_ROOT, safe_name(project, 'project_1'))
result = build_project_gds(
project_dir,
output_path,
current_pdk_root(),
technology_manifest_path=technology_manifest_path_for_project(project),
prefer_full_gds=prefer_full_gds_for_session(session),
)
record_action('gds.build', project=project, detail={
"path": result.output_path,
"engine": result.engine,
@@ -619,7 +664,8 @@ def build_gds():
"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)),
"filename": filename,
"download_url": url_for('download_export', export_id=export_id, filename=filename),
"engine": result.engine,
"cells_built": result.cells_built,
"warnings": result.warnings
@@ -628,6 +674,24 @@ def build_gds():
return jsonify({"error": str(e)}), 500
@app.route('/api/exports/<export_id>/<filename>')
@login_required_json
def download_export(export_id, filename):
export_id = safe_name(export_id, '')
safe_filename = safe_name(filename, 'layout.gds')
export_dir = os.path.abspath(os.path.join(EXPORT_ROOT, export_id))
if not export_id or not export_dir.startswith(EXPORT_ROOT + os.sep):
return jsonify({"error": "Invalid export path"}), 400
if not safe_filename.lower().endswith('.gds'):
return jsonify({"error": "Invalid GDS filename"}), 400
gds_path = os.path.abspath(os.path.join(export_dir, safe_filename))
if not gds_path.startswith(export_dir + os.sep) or not os.path.exists(gds_path):
return jsonify({"error": "GDS export not found"}), 404
response = send_from_directory(export_dir, safe_filename, as_attachment=True)
response.call_on_close(lambda: shutil.rmtree(export_dir, ignore_errors=True))
return response
@app.route('/api/projects/<project_name>/gds/<filename>')
@login_required_json
def get_project_gds(project_name, filename):
@@ -649,28 +713,32 @@ def get_project_gds(project_name, filename):
# --- API ROUTES (Library & Components) ---
@app.route('/api/library')
@login_required_json
def getLib():
"""Get library structure."""
# tree = buildTree(YML_PATH)
if os.path.isdir(COMPS_ROOT):
compMap = findComps(COMPS_ROOT)
comps_root = current_pdk_root()
fresh_tree = {}
if os.path.isdir(comps_root):
compMap = findComps(comps_root)
fresh_tree = addCompsToTree(compMap)
return jsonify(fresh_tree)
@app.route('/api/component/<component_name>')
@login_required_json
def getComp(component_name):
"""Return component YAML data."""
data = readCompYaml(component_name)
data = readCompYaml(component_name, current_pdk_root())
if data is None:
return jsonify({"error": "Component not found"}), 404
return jsonify(data)
@app.route('/api/component/<component_name>/image')
@login_required_json
def getCompImg(component_name):
"""Return first image in component folder."""
for root, dirs, files in os.walk(COMPS_ROOT):
for root, dirs, files in os.walk(current_pdk_root()):
if os.path.basename(root) == component_name:
dirs.clear()
for ext in ('.png', '.jpg', '.jpeg', '.svg'):