# 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') # # Use os.path.join exclusively for cross-platform safety # YML_PATH = os.path.join(BASE_DIR, '..', 'mxpic', 'PDKs', 'Silterra', 'directories.yaml') # COMPS_ROOT = os.path.join(BASE_DIR, '..', 'mxpic', 'PDKs', 'Silterra') # # 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) # # FIX 1: Strip trailing colons off the string so 'composites:' becomes 'composites' # name = stripped[2:].strip().rstrip(':') # if name: # 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 addCompsToTree(compMap): # """ # Build a completely fresh tree from scratch and insert component nodes. # No previous tree object or inspection required. # """ # # Initialize a clean, empty root tree # fresh_tree = OrderedDict() # for pathSeg, compItem in compMap.items(): # compName = compItem['folder'] # curNode = fresh_tree # # Sequentially build the nested path segments dynamically # for seg in pathSeg: # if seg not in curNode: # curNode[seg] = OrderedDict() # curNode = curNode[seg] # # Place the component metadata dictionary into its leaf node # curNode[compName] = OrderedDict({ # "__type__": "component", # "__name__": compName, # "__yml__": compItem['yml'] # }) # return fresh_tree 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 # --- 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, '..', 'mxpic', 'PDKs', 'Silterra', 'directories.yaml') COMPS_ROOT = os.path.join(BASE_DIR, '..', 'mxpic', 'PDKs', 'Silterra') # Define where your new icons folder is located (adjust if it's placed elsewhere) ICONS_DIR = os.path.join(BASE_DIR, 'icons') app = Flask(__name__, template_folder=FRONTEND_DIR, static_folder=FRONTEND_DIR) app.secret_key = 'super_secret_mxpic_key' app.json.sort_keys = False database.init_db() # ... [Keep countSpaces and buildTree exactly as they are] ... def findComps(baseDir): """Scan component folders, return map of paths -> component info.""" compMap = {} refDir = 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) # Extract the category (the mother folder's name) category = os.path.basename(parentDir) compMap[parts] = { 'folder': compName, 'yml': ymlFiles[0], 'category': category # Save the category to the map } dirs.clear() return compMap def addCompsToTree(compMap): """Build a completely fresh tree from scratch and insert component nodes.""" fresh_tree = OrderedDict() for pathSeg, compItem in compMap.items(): compName = compItem['folder'] curNode = fresh_tree for seg in pathSeg: if seg not in curNode: curNode[seg] = OrderedDict() curNode = curNode[seg] curNode[compName] = OrderedDict({ "__type__": "component", "__name__": compName, "__yml__": compItem['yml'], "__category__": compItem['category'] # Inject category into the tree }) return fresh_tree # ... [Keep readCompYaml and Page Routes exactly as they are] ... # --- API ROUTES (Library, Components & Icons) --- @app.route('/api/icon/') def getIcon(category): """Serve the icon corresponding to the component category.""" # Look for an image matching the category name (e.g., edge_coupler.png) for ext in ('.png', '.svg', '.jpg'): icon_path = os.path.join(ICONS_DIR, f"{category}{ext}") if os.path.exists(icon_path): return send_from_directory(ICONS_DIR, f"{category}{ext}") # Optional: Return a default fallback icon if the specific one is missing fallback = os.path.join(ICONS_DIR, "default.png") if os.path.exists(fallback): return send_from_directory(ICONS_DIR, "default.png") return jsonify({"error": "Icon not found"}), 404 # ... [Keep existing API routes below] ... 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) fresh_tree = addCompsToTree(compMap) return jsonify(fresh_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)