From 80d75147405844710fb535de129e507fe0b9d46a Mon Sep 17 00:00:00 2001 From: PotatoMaxwell Date: Fri, 29 May 2026 23:42:10 +0800 Subject: [PATCH] auto-renaming process changed to abbreviation names with catagoreis, like DC, MMI and PD... --- backend/__pycache__/database.cpython-39.pyc | Bin 5437 -> 5436 bytes .../__pycache__/gds_builder.cpython-39.pyc | Bin 6691 -> 6690 bytes .../__pycache__/layout_preview.cpython-39.pyc | Bin 4663 -> 4662 bytes backend/__pycache__/pdk_access.cpython-39.pyc | Bin 2060 -> 2059 bytes .../__pycache__/pdk_registry.cpython-39.pyc | Bin 3757 -> 3756 bytes .../routed_layout_preview.cpython-39.pyc | Bin 1631 -> 1630 bytes .../technology_manifest.cpython-39.pyc | Bin 1313 -> 1312 bytes .../mxpic_project_1/mxpic_project_1.svg | 161 ------------------ .../mxpic_project_1/mxpic_project_1.yml | 117 ------------- database/mxpic_data.db | Bin 65536 -> 69632 bytes frontend/canvas.html | 150 ++++++++++++++-- tests/layout-ui-wiring.test.js | 26 +++ 12 files changed, 162 insertions(+), 292 deletions(-) delete mode 100644 database/admin/layout/mxpic_project_1/mxpic_project_1.svg delete mode 100644 database/admin/layout/mxpic_project_1/mxpic_project_1.yml diff --git a/backend/__pycache__/database.cpython-39.pyc b/backend/__pycache__/database.cpython-39.pyc index 43fd2832b72ae93aaf2ee84d95d7ebaba29f4d74..e2b00e664e1725d78da62a453a2bba9a762af535 100644 GIT binary patch delta 47 xcmdn1wMUCLk(ZZ?0SGwPNM^m>$a|MjSS`gWCbyy>GZ{k0ySg}T{>Nx93;;`@4;=si delta 48 ycmdm^wO5Nbk(ZZ?0SJ8cC9(oG^4?_>QBSdo$*m~JOoov0t}c!doBuM}3j+W^!VaMT diff --git a/backend/__pycache__/gds_builder.cpython-39.pyc b/backend/__pycache__/gds_builder.cpython-39.pyc index 7559eded5f470c83236690b96969692b68042295..16b82fc022ae2b39d5b3e050a5d352c8bb57f20d 100644 GIT binary patch delta 476 zcmZ2%vdDxlk(ZZ?0SGwPNM?D#DCC+CS+GGz5l>&t|nTzy5 zvLI&`1%d?Fip)XmlF4D>#*FckyToPL;z0t{lh=xe%7eWKR!|8N6owNbAXfflMF~;H z{K@7Lr9hk3O4u}v6?_c J!sM-zc>u$-Z;t=~ delta 477 zcmZ2vve<+#k(ZZ?0SNj{B(h3mH}aii646Sripi}g$V`Ti@vbh85u0Bz9pK`MVoxf~ zOUX$sj+(rMSD7(t^9|kyj3S{x?YCIdQ;JKni*$f&P0^yr$!GZYGe%DC5%3WR1PT=e zgNP6i5jy#WKptb_zWJC1d*J%0Ee0e|F?o%6s65z7 z%$aN^Q3|wajf5Rz?Bv%HlENUDYclz{X)+eoftWHyag)U*o25XZRHOzXz*gjdSoI(x JcJda!=$lJ**tdn9DlUq@cnG7N0U0obEuVU`y0{}Ot4s`$k delta 48 ycmdm{vR#EYk(ZZ?0SFvUiD&6<}9CPT=0R~N_4D;QJQ03o{#$p8QV delta 48 xcmeAc=n>#exfKPO$q+K$)x|Ml^K!-%HUK2~4aWcg diff --git a/backend/__pycache__/pdk_registry.cpython-39.pyc b/backend/__pycache__/pdk_registry.cpython-39.pyc index ff2970987b95f78392711b1d5a1298f134e457e1..33473b9b54949208fee9096e9496d899b2e55f02 100644 GIT binary patch delta 47 xcmZ20yGE8Tk(ZZ?0SGwPNMm|_)^TTzgi3?buPT^u*_GYYW)03F*5`v3p{ delta 47 xcmZ3$wUCQ9k(ZZ?0SHd8OJs#k - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/database/admin/layout/mxpic_project_1/mxpic_project_1.yml b/database/admin/layout/mxpic_project_1/mxpic_project_1.yml deleted file mode 100644 index b021f2a..0000000 --- a/database/admin/layout/mxpic_project_1/mxpic_project_1.yml +++ /dev/null @@ -1,117 +0,0 @@ -# ============================================= -# mxPIC Cell/Project Definition File -# ============================================= -schema_version: "2.0.0" -kind: cell -coordinate_system: gds_y_up -canvas_size: - width: 5000 - height: 5000 -project: mxpic_project_1 -name: mxpic_project_1 -type: project -version: "1.0.0" - -# 1. External Ports (How this cell connects to the outside world) -ports: -- name: port - layer: WG_CORE - x: 50.0 - y: -150.0 - angle: 0.0 - width: 0.5 - -# 2. Instances (The sub-components dropped onto this canvas) -instances: - component_1: - component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/multimode_interferometers/1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2 - x: 100.0 - y: -2290.0 - rotation: 0.0 - flip: 0 - flop: 0 - mirror: false - settings: - length: - - component_4: - component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/multimode_interferometers/1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2 - x: 100.0 - y: -1970.0 - rotation: 0.0 - flip: 0 - flop: 0 - mirror: false - settings: - length: - - component_2: - component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/multimode_interferometers/1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2 - x: 100.0 - y: -2560.0 - rotation: 0.0 - flip: 0 - flop: 0 - mirror: false - settings: - length: - -elements: - port: - type: port - x: 50.0 - y: -150.0 - angle: 0.0 - layer: WG_CORE - width: 0.5 - description: "" - anchor_1: - type: anchor - x: 120.0 - y: -2150.0 - angle: 0.0 - layer: WG_CORE - width: 0.5 - description: "" - anchor_2: - type: anchor - x: 130.0 - y: -2430.0 - angle: 0.0 - layer: WG_CORE - width: 0.5 - description: "" - -# 3. Bundles (Grouped links for multi-bus/parallel routing) -bundles: - output_bus: - routing_type: euler_bend - links: - - from: anchor_1:right - to: component_4:b2 - xsection: strip - family: optical - width: 0.45 - radius: 10 - routing_type: euler_bend - - from: anchor_1:left - to: component_1:a1 - xsection: strip - family: optical - width: 0.45 - radius: 10 - routing_type: euler_bend - - from: component_1:b2 - to: anchor_2:right - xsection: strip - family: optical - width: 0.45 - radius: 10 - routing_type: euler_bend - - from: anchor_2:left - to: component_2:a1 - xsection: strip - family: optical - width: 0.45 - radius: 10 - routing_type: euler_bend \ No newline at end of file diff --git a/database/mxpic_data.db b/database/mxpic_data.db index 9d36722039dbfe0fb7b14b1b35cfbe177d350ece..c9a734f2b36178bfab2e8b08b431d06a37da1c00 100644 GIT binary patch delta 1065 zcmaKrUr19?9LMi1ppme;DOcFG3&$VGv3}&>skb2z|(x3i_S1yPPS(;c_{@@A-W{zjJ=SxjAL7 zs4TR)HA#{}i;=XfJ-u*0eX`RlyEJ*Gkh@-}aYIN&EAW*<=qK7i+vpqmfyvr7*LTv5%DL+nFu=FIs z)XPGd<7#S*Z<$siK}z=a?O<;J^0i3w5}FPE3esRQ@WTJuuc{lq@4gmqwr;cTQtds@ zOV6nLz5BAVqug;lcJ8kMGh#q+5v;7e)7oT#$*qSxRdH*#uD+vHi5&aVv1QHtvR_ zCn8Z{Tlq1ZAGTF$Zr~Vj*d%!x*+**pCDknx@{iDC2SPMeAWTfAX`7R$hchUH0}~<* zcZw%0LkGptYDLZ909%{X$~nr4JUvp)B5JLc;1uAtR!ep-*73pJAZghr5h#H&3QiDv zm^QQQtFvt8Op=pg_GG9#EUEz#tn-q?|0x+ndKVaJ)kgo|il>>TO^#&mtq2rQBsofB z1%+S9qan)y5zhl44F`hkilCKp^spnM9lVv;kB4F2B|%Y=0oR{`UO&Wpy?l;u#fd#e zLzQzfKd0ob=B8NwbR!=V&S>aX+#oPO^#=?PL}n>s@|DT557#HjFb%To?~E#D%rMCf s8epTJt~o=8ld$1u?WJ+nzaHimViDUzRrYmxT;YAo^at(EOhO!!KR4|*umAu6 delta 95 zcmZozz|zpbGC`VEjDdkcV4{LOqu9oTDfT)XoQ%xkjHSh?Ma4|4Kq)T%6F`zdfd4oD yH~x?OZ}^|_Kj6Q`e}(@X|B1~C0W0`7pYXR?z%0txIGO!}_Qpnk#?6f%>-hk{njPK% diff --git a/frontend/canvas.html b/frontend/canvas.html index 63eec67..bf06dfa 100644 --- a/frontend/canvas.html +++ b/frontend/canvas.html @@ -3646,6 +3646,10 @@ if (!activePageId) return; const relevantChanges = changes.filter(change => change.id !== '__canvas-boundary__'); if (relevantChanges.length === 0) return; + const removedNodeIds = new Set(relevantChanges.filter(change => change.type === 'remove').map(change => change.id)); + if (removedNodeIds.size > 0 && activePage) { + releaseComponentDisplayNames(activePage.nodes.filter(node => removedNodeIds.has(node.id))); + } setPages(prev => prev.map(p => { if (p.id !== activePageId) return p; const newNodes = applyNodeChanges(relevantChanges, p.nodes).map(node => { @@ -3667,7 +3671,7 @@ } return { ...p, nodes: newNodes, port: newPort }; })); - }, [activePageId, activeCanvasSize]); + }, [activePageId, activePage, activeCanvasSize]); const onEdgesChange = useCallback((changes) => { if (!activePageId) return; @@ -3812,6 +3816,7 @@ const selectedNodes = activePage.nodes.filter(n => n.selected); if (selectedNodes.length > 0) { setClipboard({ nodes: JSON.parse(JSON.stringify(selectedNodes)) }); + releaseComponentDisplayNames(selectedNodes); const selectedNodeIds = new Set(selectedNodes.map(n => n.id)); const newNodes = activePage.nodes.filter(n => !selectedNodeIds.has(n.id)); const newEdges = activePage.edges.filter(e => !selectedNodeIds.has(e.source) && !selectedNodeIds.has(e.target)); @@ -3822,7 +3827,13 @@ const handlePaste = useCallback(() => { if (!activePage || clipboard.nodes.length === 0) return; const newNodes = clipboard.nodes.map(node => { - const copiedName = generateComponentDisplayName(); + const copyCategory = node.data?.libraryCategory && node.data.libraryCategory !== 'basic' + ? node.data.libraryCategory + : (node.data?.category && node.data.category !== 'basic' ? node.data.category : ''); + const copiedName = generateComponentDisplayName( + copyCategory || node.data?.componentName || node.data?.elementType, + { singularize: Boolean(copyCategory), abbreviate: Boolean(copyCategory) } + ); const copiedData = { ...node.data, componentDisplayName: copiedName @@ -3852,6 +3863,7 @@ const selectedNodes = activePage.nodes.filter(n => n.selected); const selectedNodeIds = new Set(selectedNodes.map(n => n.id)); if (selectedNodeIds.size > 0) { + releaseComponentDisplayNames(selectedNodes); const newNodes = activePage.nodes.filter(n => !selectedNodeIds.has(n.id)); const newEdges = activePage.edges.filter(e => !selectedNodeIds.has(e.source) && !selectedNodeIds.has(e.target)); setPages(prev => prev.map(p => p.id === activePage.id ? { ...p, nodes: newNodes, edges: newEdges } : p)); @@ -3899,16 +3911,122 @@ }; }, [handleCopy, handleCut, handlePaste, handleDelete, rotateComponentByNinety, getSpaceRotationTarget, clearSpaceRotateNode]); - const componentCounterRef = useRef(1); + const componentIndexesByPrefixRef = useRef({}); - const generateComponentDisplayName = useCallback(() => { - const name = `component_${componentCounterRef.current}`; - componentCounterRef.current += 1; - return name; + const COMPONENT_CATEGORY_PREFIX_ABBREVIATIONS = { + directional_coupler: 'DC', + directional_couplers: 'DC', + multimode_interferometer: 'MMI', + multimode_interferometers: 'MMI', + photodetector: 'PD', + photodetectors: 'PD', + waveguide: 'WG', + waveguides: 'WG', + transition: 'TRX', + transitions: 'TRX', + transistion: 'TRX', + transistions: 'TRX', + Mach_Zender_Modulator: 'MZM', + Mach_Zender_Modulators: 'MZM', + Mach_Zender_modulator: 'MZM', + Mach_Zender_modulators: 'MZM', + mach_zender_modulator: 'MZM', + mach_zender_modulators: 'MZM', + bending: 'BD', + bendings: 'BD', + edge_coupler: 'EC', + edge_couplers: 'EC', + grating_coupler: 'GC', + grating_couplers: 'GC', + termination: 'TERM', + terminations: 'TERM' + }; + + function parseComponentDisplayName(displayName) { + const match = String(displayName || '').match(/^(.+)_(\d+)$/); + if (!match) return null; + const index = Number(match[2]); + if (!Number.isInteger(index) || index < 1) return null; + return { prefix: match[1], index }; + } + + function reserveComponentDisplayName(displayName) { + const parsed = parseComponentDisplayName(displayName); + if (!parsed) return; + const usedIndexes = componentIndexesByPrefixRef.current[parsed.prefix] || new Set(); + usedIndexes.add(parsed.index); + componentIndexesByPrefixRef.current[parsed.prefix] = usedIndexes; + } + + function releaseComponentDisplayName(displayName) { + const parsed = parseComponentDisplayName(displayName); + if (!parsed) return; + const usedIndexes = componentIndexesByPrefixRef.current[parsed.prefix]; + if (!usedIndexes) return; + usedIndexes.delete(parsed.index); + if (usedIndexes.size === 0) { + delete componentIndexesByPrefixRef.current[parsed.prefix]; + } + } + + function releaseComponentDisplayNames(nodes = []) { + nodes.forEach(node => releaseComponentDisplayName(node?.data?.componentDisplayName)); + } + + function reserveComponentDisplayNamesFromPages() { + pages.forEach(page => { + (page.nodes || []).forEach(node => reserveComponentDisplayName(node?.data?.componentDisplayName)); + }); + } + + const normalizeComponentDisplayNamePrefix = useCallback((prefixSource, options = {}) => { + const cleanedPrefix = String(prefixSource || 'element') + .trim() + .replace(/[\\/]+/g, '_') + .replace(/\s+/g, '_') + .replace(/[^A-Za-z0-9_]+/g, '_') + .replace(/_+/g, '_') + .replace(/^_+|_+$/g, ''); + if (!cleanedPrefix) return 'element'; + const abbreviation = options.abbreviate + ? COMPONENT_CATEGORY_PREFIX_ABBREVIATIONS[cleanedPrefix] || COMPONENT_CATEGORY_PREFIX_ABBREVIATIONS[cleanedPrefix.toLowerCase()] + : ''; + if (abbreviation) return abbreviation; + if (!options.singularize) return cleanedPrefix; + const parts = cleanedPrefix.split('_'); + const lastIndex = parts.length - 1; + const last = parts[lastIndex]; + if (last.length > 3 && last.endsWith('ies')) { + parts[lastIndex] = `${last.slice(0, -3)}y`; + } else if (last.length > 1 && last.endsWith('s')) { + parts[lastIndex] = last.slice(0, -1); + } + const singularPrefix = parts.join('_') || 'element'; + if (options.abbreviate) { + return COMPONENT_CATEGORY_PREFIX_ABBREVIATIONS[singularPrefix] || COMPONENT_CATEGORY_PREFIX_ABBREVIATIONS[singularPrefix.toLowerCase()] || singularPrefix; + } + return singularPrefix; }, []); + const generateComponentDisplayName = useCallback((prefixSource = 'element', options = {}) => { + const prefix = normalizeComponentDisplayNamePrefix(prefixSource, options); + reserveComponentDisplayNamesFromPages(); + const usedIndexes = componentIndexesByPrefixRef.current[prefix] || new Set(); + let nextIndex = 1; + while (usedIndexes.has(nextIndex)) nextIndex += 1; + const name = `${prefix}_${nextIndex}`; + usedIndexes.add(nextIndex); + componentIndexesByPrefixRef.current[prefix] = usedIndexes; + return name; + }, [normalizeComponentDisplayNamePrefix, pages]); + const renameComponent = useCallback((nodeId, newComponentDisplayName) => { if (!activePageId) return; + const oldDisplayName = activePage?.nodes.find(node => node.id === nodeId)?.data?.componentDisplayName; + if (oldDisplayName !== newComponentDisplayName) { + releaseComponentDisplayName(oldDisplayName); + reserveComponentDisplayName(newComponentDisplayName); + } setPages(prev => prev.map(p => { if (p.id !== activePageId) return p; return { @@ -3916,7 +4034,7 @@ nodes: p.nodes.map(n => n.id === nodeId ? { ...n, data: { ...n.data, componentDisplayName: newComponentDisplayName } } : n) }; })); - }, [activePageId]); + }, [activePageId, activePage]); const fetchLibrary = useCallback(async () => { try { @@ -4788,8 +4906,7 @@ const componentName = parsedData.componentName || parsedData.name; const basicArguments = createBasicSettings(componentName, parsedData.settings); const metadata = getBasicComponentMetadata(componentName, basicArguments); - const componentDisplayName = `${componentName.replace(/\s+/g, '_')}_${componentCounterRef.current}`; - componentCounterRef.current += 1; + const componentDisplayName = generateComponentDisplayName(componentName); const newNode = { id: Date.now().toString(), type: 'rotatableNode', @@ -4815,8 +4932,7 @@ return; } if (parsedData.type === 'element') { - const elementName = parsedData.elementType === 'anchor' ? `anchor_${componentCounterRef.current}` : `port_${componentCounterRef.current}`; - componentCounterRef.current += 1; + const elementName = generateComponentDisplayName(parsedData.elementType === 'anchor' ? 'anchor' : 'port'); const isPort = parsedData.elementType === 'port'; const newNode = isPort ? { @@ -4876,7 +4992,10 @@ return; } const selectedIsForge = isForgeComponent(selectedComponent); - const componentDisplayName = generateComponentDisplayName(); + const componentDisplayName = generateComponentDisplayName(parsedData.category || selectedComponent, { + singularize: Boolean(parsedData.category), + abbreviate: Boolean(parsedData.category) + }); const newNode = { id: Date.now().toString(), type: 'rotatableNode', @@ -4904,7 +5023,10 @@ }); return; } - const componentDisplayName = generateComponentDisplayName(); + const componentDisplayName = generateComponentDisplayName(parsedData.category || parsedData.name, { + singularize: Boolean(parsedData.category), + abbreviate: Boolean(parsedData.category) + }); const newNode = { id: Date.now().toString(), type: 'rotatableNode', diff --git a/tests/layout-ui-wiring.test.js b/tests/layout-ui-wiring.test.js index c4b5b7a..8cc46f6 100644 --- a/tests/layout-ui-wiring.test.js +++ b/tests/layout-ui-wiring.test.js @@ -197,6 +197,32 @@ assert( canvasHtml.includes('getSpaceRotationTarget') && canvasHtml.includes('selectedSpaceNode'), 'Space rotation should also use the currently selected component when no mouse-hold target is active' ); +assert( + canvasHtml.includes('const componentIndexesByPrefixRef = useRef({});') && + canvasHtml.includes('const usedIndexes = componentIndexesByPrefixRef.current[prefix] || new Set();') && + canvasHtml.includes('while (usedIndexes.has(nextIndex)) nextIndex += 1;') && + canvasHtml.includes('usedIndexes.add(nextIndex);') && + canvasHtml.includes('const name = `${prefix}_${nextIndex}`;') && + canvasHtml.includes('releaseComponentDisplayNames(selectedNodes);') && + canvasHtml.includes('releaseComponentDisplayName(oldDisplayName);') && + canvasHtml.includes('reserveComponentDisplayName(newComponentDisplayName);') && + !canvasHtml.includes('componentCounterRef.current') && + !canvasHtml.includes('componentCountersByPrefixRef') && + canvasHtml.includes('COMPONENT_CATEGORY_PREFIX_ABBREVIATIONS') && + canvasHtml.includes("directional_coupler: 'DC'") && + canvasHtml.includes("multimode_interferometers: 'MMI'") && + canvasHtml.includes("photodetectors: 'PD'") && + canvasHtml.includes("waveguides: 'WG'") && + canvasHtml.includes("transitions: 'TRX'") && + canvasHtml.includes("Mach_Zender_modulators: 'MZM'") && + canvasHtml.includes("bendings: 'BD'") && + canvasHtml.includes("edge_couplers: 'EC'") && + canvasHtml.includes("grating_couplers: 'GC'") && + canvasHtml.includes("terminations: 'TERM'") && + canvasHtml.includes('abbreviate: Boolean(parsedData.category)') && + canvasHtml.includes('abbreviate: Boolean(copyCategory)'), + 'new PDK component instances should use their component category abbreviation as the display-name prefix' +); assert( canvasHtml.includes('normalizeAngle,') && canvasHtml.includes('normalizeAngle(Number(node.data?.rotation || 0) + 90)'), 'Space rotation should import normalizeAngle before using it'