diff --git a/README.md b/README.md index f1e3ce4..5d7ad96 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,2 @@ # mxpic_EDA - -web application EDA building repository \ No newline at end of file +The EDA coding for the layout for optihk diff --git a/backend/PDK_libs/primitives/edge_couplers/EC_SiN400_1310_1p0dB_L635_A0_QY_202604/EC_SiN400_1310_1p0dB_L635_A0_QY_202604.png b/backend/PDK_libs/primitives/edge_couplers/EC_SiN400_1310_1p0dB_L635_A0_QY_202604/EC_SiN400_1310_1p0dB_L635_A0_QY_202604.png new file mode 100644 index 0000000..5208d8c Binary files /dev/null and b/backend/PDK_libs/primitives/edge_couplers/EC_SiN400_1310_1p0dB_L635_A0_QY_202604/EC_SiN400_1310_1p0dB_L635_A0_QY_202604.png differ diff --git a/backend/PDK_libs/primitives/edge_couplers/EC_SiN400_1310_1p0dB_L635_A0_QY_202604/EC_SiN400_1310_1p0dB_L635_A0_QY_202604.yml b/backend/PDK_libs/primitives/edge_couplers/EC_SiN400_1310_1p0dB_L635_A0_QY_202604/EC_SiN400_1310_1p0dB_L635_A0_QY_202604.yml new file mode 100644 index 0000000..d701aa5 --- /dev/null +++ b/backend/PDK_libs/primitives/edge_couplers/EC_SiN400_1310_1p0dB_L635_A0_QY_202604/EC_SiN400_1310_1p0dB_L635_A0_QY_202604.yml @@ -0,0 +1,35 @@ +name: EC_SiN400_1310_1p0dB_L635_A0_QY_202604 +foundry: Silterra +process: EMO1_2ML_Cu_RDL +year: '2026' +type: primitive +dependency: None +maturity: development +tapeout_history: +- run: Silterra_EMO1_2ML_Cu_RDL_2026_Q2 + status: Pending testing +center_wavelength: 1310 +version: 1.0 +designer: Qin Yue +update_notes: New SiN edge couplers with high efficiency +ports: + a1: + x: -642.6 + y: 0.0 + a: 180.0 + width: 0.7 + b0: + x: 0.0 + y: 0.0 + a: 0.0 + width: None + a0: + x: 0.0 + y: 0.0 + a: 180.0 + width: 0.0 +time: 20260505-170136 +box_size: +- 646.0 +- 75.0 +file_size: 1.36 KB diff --git a/backend/__pycache__/database.cpython-39.pyc b/backend/__pycache__/database.cpython-39.pyc new file mode 100644 index 0000000..98258fa Binary files /dev/null and b/backend/__pycache__/database.cpython-39.pyc differ diff --git a/backend/database.py b/backend/database.py new file mode 100644 index 0000000..ac5a6fd --- /dev/null +++ b/backend/database.py @@ -0,0 +1,42 @@ +# backend/database.py +import sqlite3 +import os +from werkzeug.security import generate_password_hash + +# Save the database in the backend folder +DB_FILE = os.path.join(os.path.dirname(__file__), "mxpic_data.db") + +def init_db(): + conn = sqlite3.connect(DB_FILE) + cursor = conn.cursor() + + # Create Users Table + cursor.execute(''' + CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT UNIQUE NOT NULL, + password_hash TEXT NOT NULL + ) + ''') + + # Insert a test user if the table is empty + cursor.execute("SELECT * FROM users WHERE username = 'admin'") + if not cursor.fetchone(): + test_hash = generate_password_hash("123456") + cursor.execute("INSERT INTO users (username, password_hash) VALUES (?, ?)", ("admin", test_hash)) + print("Test user created. Username: admin | Password: 123456") + + conn.commit() + conn.close() + +def get_user(username): + conn = sqlite3.connect(DB_FILE) + cursor = conn.cursor() + cursor.execute("SELECT id, username, password_hash FROM users WHERE username = ?", (username,)) + user = cursor.fetchone() + conn.close() + return user + +if __name__ == "__main__": + init_db() + print("Database initialized successfully.") \ No newline at end of file diff --git a/backend/directories.yaml b/backend/directories.yaml new file mode 100644 index 0000000..7311315 --- /dev/null +++ b/backend/directories.yaml @@ -0,0 +1,17 @@ +level : 4 + +root : + - PDK_libs + - primitives + - directional_couplers + - edge_couplers + - crossings + - multimode_interferometers + - photodectors + - compotites + - MZIs + - electronics + - resistors + - capacitors + - others + - logos diff --git a/backend/mxpic_data.db b/backend/mxpic_data.db new file mode 100644 index 0000000..c2a697c Binary files /dev/null and b/backend/mxpic_data.db differ diff --git a/backend/server.py b/backend/server.py new file mode 100644 index 0000000..f6625cf --- /dev/null +++ b/backend/server.py @@ -0,0 +1,199 @@ +import os +import yaml +from collections import OrderedDict +from flask import Flask, jsonify, send_from_directory, request, redirect, url_for, session, render_template +from werkzeug.security import check_password_hash +import database # Imports the database.py you created earlier + +# --- Path Configurations --- +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) +FRONTEND_DIR = os.path.join(BASE_DIR, '..', 'frontend') +YML_PATH = os.path.join(BASE_DIR, 'directories.yaml') +COMPS_ROOT = os.path.join(BASE_DIR, 'PDK_libs') + +# Initialize Flask, pointing to the frontend folder for HTML/CSS/JS +app = Flask(__name__, template_folder=FRONTEND_DIR, static_folder=FRONTEND_DIR) +app.secret_key = 'super_secret_mxpic_key' # Required for session management +app.json.sort_keys = False # Keep dictionary order + +# Ensure database tables exist when the server boots +database.init_db() + +# --- YAML & PDK Parsing Helper Functions (Unchanged) --- +def countSpaces(line): + """Count leading spaces (tab=4).""" + expanded = line.expandtabs(4) + return len(expanded) - len(expanded.lstrip(' ')) + +def buildTree(filepath): + """Build nested tree from indented yaml.""" + if not os.path.exists(filepath): + return OrderedDict() + + with open(filepath, 'r', encoding='utf-8') as f: + lines = f.readlines() + + rootIdx = None + for i, line in enumerate(lines): + if line.strip().startswith('root') and ':' in line.strip(): + rootIdx = i + break + if rootIdx is None: + return OrderedDict() + + entries = [] + for line in lines[rootIdx + 1:]: + stripped = line.strip() + if not stripped or stripped.startswith('#'): + continue + if stripped.startswith('- '): + spaceNum = countSpaces(line) + name = stripped[2:].strip() + if name.strip(): + entries.append((spaceNum, name)) + + if not entries: + return OrderedDict() + + minIndent = min(indent for indent, _ in entries) + nest = OrderedDict() + levelStack = [(minIndent - 1, nest)] + + for spaceNum, name in entries: + while levelStack and levelStack[-1][0] >= spaceNum: + levelStack.pop() + parent = levelStack[-1][1] + child = OrderedDict() + parent[name] = child + levelStack.append((spaceNum, child)) + + return nest + +def findComps(baseDir): + """Scan component folders, return map of paths -> component info.""" + compMap = {} + refDir = os.path.dirname(baseDir) + for root, dirs, files in os.walk(baseDir): + ymlFiles = [f for f in files if f.endswith('.yml')] + if ymlFiles: + parentDir = os.path.dirname(root) + relPath = os.path.relpath(parentDir, refDir) + parts = () if relPath == '.' else tuple(relPath.split(os.sep)) + compName = os.path.basename(root) + compMap[parts] = { + 'folder': compName, + 'yml': ymlFiles[0] + } + dirs.clear() + return compMap + +def addCompsToTree(tree, compMap): + """Insert component nodes into the tree.""" + for pathSeg, compItem in compMap.items(): + compName = compItem['folder'] + curNode = tree + try: + for seg in pathSeg: + curNode = curNode[seg] + except KeyError: + continue + curNode[compName] = OrderedDict({ + "__type__": "component", + "__name__": compName, + "__yml__": compItem['yml'] + }) + return tree + +def readCompYaml(compName): + """Load YAML from component folder.""" + for root, dirs, files in os.walk(COMPS_ROOT): + if os.path.basename(root) == compName: + dirs.clear() + ymlFiles = [f for f in files if f.endswith('.yml')] + if ymlFiles: + ymlPath = os.path.join(root, ymlFiles[0]) + with open(ymlPath, 'r', encoding='utf-8') as f: + return yaml.safe_load(f) + return None + +# --- AUTHENTICATION & PAGE ROUTES --- +@app.route('/') +def home(): + """Route to login page, or bypass to dashboard if already authenticated.""" + if 'user_id' in session: + return redirect(url_for('dashboard')) + return render_template('login.html') + +@app.route('/login', methods=['POST']) +def login(): + """Verify credentials against the database.""" + username = request.form.get('username') + password = request.form.get('password') + + user = database.get_user(username) + + # Verify hash from database matches entered password + if user and check_password_hash(user[2], password): + session['user_id'] = user[0] + session['username'] = user[1] + return redirect(url_for('dashboard')) + else: + return render_template('login.html', error="Invalid username or password") + +@app.route('/dashboard') +def dashboard(): + """User project list.""" + if 'user_id' not in session: + return redirect(url_for('home')) + + return render_template('dashboard.html', username=session['username']) + +@app.route('/canvas') +def canvas(): + """The main EDA editor.""" + if 'user_id' not in session: + return redirect(url_for('home')) + + # Note: Ensure your old index.html is renamed to canvas.html in the frontend folder + return render_template('canvas.html') + +@app.route('/logout') +def logout(): + """Clear session and return to login.""" + session.clear() + return redirect(url_for('home')) + +# --- API ROUTES (Library & Components) --- +@app.route('/api/library') +def getLib(): + """Get library structure.""" + tree = buildTree(YML_PATH) + if os.path.isdir(COMPS_ROOT): + compMap = findComps(COMPS_ROOT) + addCompsToTree(tree, compMap) + return jsonify(tree) + +@app.route('/api/component/') +def getComp(component_name): + """Return component YAML data.""" + data = readCompYaml(component_name) + if data is None: + return jsonify({"error": "Component not found"}), 404 + return jsonify(data) + +@app.route('/api/component//image') +def getCompImg(component_name): + """Return first image in component folder.""" + for root, dirs, files in os.walk(COMPS_ROOT): + if os.path.basename(root) == component_name: + dirs.clear() + for ext in ('.png', '.jpg', '.jpeg', '.svg'): + for f in files: + if f.lower().endswith(ext): + return send_from_directory(root, f) + break + return jsonify({"error": "No image found"}), 404 + +if __name__ == '__main__': + print("Starting mxpic EDA Server on http://127.0.0.1:3000") + app.run(host='127.0.0.1', port=3000, debug=True) \ No newline at end of file diff --git a/frontend/canvas.html b/frontend/canvas.html new file mode 100644 index 0000000..7fcaebd --- /dev/null +++ b/frontend/canvas.html @@ -0,0 +1,750 @@ + + + +{% raw %} + + + + Canvas with PDK Library – Component Name & Rotation + + + + + + + + + +
+ + + + + +{% endraw %} diff --git a/frontend/dashboard.html b/frontend/dashboard.html new file mode 100644 index 0000000..e0d64a2 --- /dev/null +++ b/frontend/dashboard.html @@ -0,0 +1,22 @@ + + + + Dashboard + + +

Welcome, {{ username }}!

+

Your Recent Designs:

+ + + +
+ +
+ +
+ Logout + + \ No newline at end of file diff --git a/frontend/login.html b/frontend/login.html new file mode 100644 index 0000000..1c655e6 --- /dev/null +++ b/frontend/login.html @@ -0,0 +1,23 @@ + + + + mxPIC EDA - Login + + +

Login to mxPIC System

+ +
+
+

+ +
+

+ + +
+ + {% if error %} +

{{ error }}

+ {% endif %} + + \ No newline at end of file diff --git a/web_pages.pptx b/web_pages.pptx new file mode 100644 index 0000000..ae8d7f2 Binary files /dev/null and b/web_pages.pptx differ