diff --git a/backend/dir_test.py b/backend/dir_test.py new file mode 100644 index 0000000..2980d50 --- /dev/null +++ b/backend/dir_test.py @@ -0,0 +1,79 @@ +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 + + +if os.path.isdir(COMPS_ROOT): + compMap = findComps(COMPS_ROOT) + fresh_tree = addCompsToTree(compMap) + +print(compMap) +print(fresh_tree) + + + diff --git a/backend/icons/Mach_Zender_modulators.png b/backend/icons/Mach_Zender_modulators.png new file mode 100644 index 0000000..5ae486f Binary files /dev/null and b/backend/icons/Mach_Zender_modulators.png differ diff --git a/backend/icons/bendings.png b/backend/icons/bendings.png new file mode 100644 index 0000000..61449c0 Binary files /dev/null and b/backend/icons/bendings.png differ diff --git a/backend/icons/capacitors.png b/backend/icons/capacitors.png new file mode 100644 index 0000000..3d1b9fc Binary files /dev/null and b/backend/icons/capacitors.png differ diff --git a/frontend/icons/directional_couplers.png b/backend/icons/directional_couplers.png similarity index 100% rename from frontend/icons/directional_couplers.png rename to backend/icons/directional_couplers.png diff --git a/frontend/icons/edge_couplers.png b/backend/icons/edge_couplers.png similarity index 100% rename from frontend/icons/edge_couplers.png rename to backend/icons/edge_couplers.png diff --git a/frontend/icons/grating_couplers.png b/backend/icons/grating_couplers.png similarity index 100% rename from frontend/icons/grating_couplers.png rename to backend/icons/grating_couplers.png diff --git a/backend/icons/inductors.png b/backend/icons/inductors.png new file mode 100644 index 0000000..5a6fb51 Binary files /dev/null and b/backend/icons/inductors.png differ diff --git a/frontend/icons/multimode_interferometers.png b/backend/icons/multimode_interferometers.png similarity index 100% rename from frontend/icons/multimode_interferometers.png rename to backend/icons/multimode_interferometers.png diff --git a/backend/icons/pads.png b/backend/icons/pads.png new file mode 100644 index 0000000..b4d9772 Binary files /dev/null and b/backend/icons/pads.png differ diff --git a/frontend/icons/phase_shifters.png b/backend/icons/phase_shifters.png similarity index 100% rename from frontend/icons/phase_shifters.png rename to backend/icons/phase_shifters.png diff --git a/backend/icons/photodetectors.png b/backend/icons/photodetectors.png new file mode 100644 index 0000000..52c46ec Binary files /dev/null and b/backend/icons/photodetectors.png differ diff --git a/backend/icons/resistors.png b/backend/icons/resistors.png new file mode 100644 index 0000000..8925935 Binary files /dev/null and b/backend/icons/resistors.png differ diff --git a/frontend/icons/terminations.png b/backend/icons/terminations.png similarity index 100% rename from frontend/icons/terminations.png rename to backend/icons/terminations.png diff --git a/frontend/icons/waveguides.png b/backend/icons/waveguides.png similarity index 100% rename from frontend/icons/waveguides.png rename to backend/icons/waveguides.png diff --git a/backend/server_new.py b/backend/server_new.py new file mode 100644 index 0000000..4190432 --- /dev/null +++ b/backend/server_new.py @@ -0,0 +1,287 @@ +# 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) \ No newline at end of file diff --git a/frontend/canvas.html b/frontend/canvas.html index 540d9eb..c0bba2c 100644 --- a/frontend/canvas.html +++ b/frontend/canvas.html @@ -196,6 +196,7 @@ useUpdateNodeInternals, } = window.ReactFlow; + // --- NODE DESIGN (Dark CAD Style) --- // --- NODE DESIGN (Dark CAD Style) --- const RotatableNode = ({ id, data, selected }) => { const updateNodeInternals = useUpdateNodeInternals(); @@ -216,20 +217,52 @@ return (
-
{data.componentDisplayName}
+ + {/* Updated Icon and Title Layout */} +
+ {data.category && ( + {data.category} { + e.currentTarget.style.display = 'none'; + }} + /> + )} + + {/* Shrunk the text and added ellipsis for long names */} +
+ {data.componentDisplayName} +
+
+ @@ -242,19 +275,29 @@ ); }; + + const TreeNode = ({ name, children }) => { if (children && children.__type__ === 'component') { const componentName = children.__name__; + // Fallback to 'default' if category is somehow missing + const componentCategory = children.__category__ || 'default'; + const handleDragStart = (event) => { - event.dataTransfer.setData('application/reactflow', componentName); + const dragData = JSON.stringify({ name: componentName, category: componentCategory }); + console.log("🚀 DRAG START: Sending data ->", dragData); // <--- DEBUG LOG + event.dataTransfer.setData('application/reactflow', dragData); event.dataTransfer.effectAllowed = 'move'; }; + return (
{name}
); } + // ... keep the rest of TreeNode unchanged + // ... keep the rest of TreeNode unchanged const hasChildren = children && Object.keys(children).length > 0; return ( @@ -778,24 +821,44 @@ const onDrop = useCallback((event) => { event.preventDefault(); - const type = event.dataTransfer.getData('application/reactflow'); - if (!type) return; + const rawData = event.dataTransfer.getData('application/reactflow'); + console.log("📥 DROP EVENT: Received raw data ->", rawData); // <--- DEBUG LOG + + if (!rawData) { + console.error("Drop failed: No data received!"); + return; + } + + let parsedData; + try { + parsedData = JSON.parse(rawData); + console.log("✅ PARSED JSON SUCCESS:", parsedData); // <--- DEBUG LOG + } catch (error) { + console.warn("⚠️ JSON Parse Failed. Falling back to string format.", rawData); + parsedData = { name: rawData, category: 'default' }; + } + const position = reactFlowInstance.screenToFlowPosition({ x: event.clientX, y: event.clientY, }); + const componentDisplayName = generateComponentDisplayName(); + const newNode = { id: Date.now().toString(), type: 'rotatableNode', position, data: { - label: type, - componentName: type, + label: parsedData.name, + componentName: parsedData.name, + category: parsedData.category, rotation: 0, componentDisplayName: componentDisplayName }, }; + + console.log("✨ ADDING NEW NODE TO CANVAS:", newNode); // <--- DEBUG LOG setNodes((nds) => nds.concat(newNode)); }, [setNodes, reactFlowInstance, generateComponentDisplayName]);