From 64fdc7408bb54d460ff57a4f71a798da126cf9cd Mon Sep 17 00:00:00 2001 From: Kirigaya <1193466151@qq.com> Date: Tue, 31 Dec 2024 20:31:20 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E5=8F=96=E6=B6=88=20port=20?= =?UTF-8?q?=E8=8A=82=E7=82=B9=EF=BC=8C=E5=92=8C=E5=A4=96=E9=83=A8=E7=9B=B4?= =?UTF-8?q?=E8=BF=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hook/jsdoc.js | 2 ++ src/hook/render/cell.js | 2 +- src/hook/render/connection.js | 20 ++++++----- src/hook/render/index.js | 49 +++++++++++++++++++++++++-- src/hook/render/instantiation.js | 13 ++++---- src/hook/render/layout.js | 57 ++++++++++++++++++++++++++++++-- src/hook/render/wire.js | 22 +++++++++--- 7 files changed, 140 insertions(+), 25 deletions(-) diff --git a/src/hook/jsdoc.js b/src/hook/jsdoc.js index ecb404b..e881a88 100644 --- a/src/hook/jsdoc.js +++ b/src/hook/jsdoc.js @@ -171,6 +171,8 @@ import { Module } from "./render/layout"; * @property {number} [y] 端口的 Y 坐标(可选,由布局算法生成)。 * @property {number} [width] 端口的宽度(可选)。 * @property {number} [height] 端口的高度(可选)。 + * @property {'input' | 'output'} [direction] 端口在这个实体上的方向 + * @property {'port' | 'instance' | 'cell'} [source] 这是谁的端口 * @property {ElkLayoutOptions} [layoutOptions] 端口的布局选项(可选)。 */ diff --git a/src/hook/render/cell.js b/src/hook/render/cell.js index e4e116e..d9b69af 100644 --- a/src/hook/render/cell.js +++ b/src/hook/render/cell.js @@ -53,7 +53,7 @@ export class CellRender { .enter() .append(data => { const element = data.element; - element.setAttribute('x', data.x); + element.setAttribute('x', data.x + LAYOUT_CONSTANT.CELL_LEFT_MARGIN); element.setAttribute('y', data.y); if (globalSetting.renderAnimation) { element.setAttribute('opacity', 0); diff --git a/src/hook/render/connection.js b/src/hook/render/connection.js index b40b976..a43588e 100644 --- a/src/hook/render/connection.js +++ b/src/hook/render/connection.js @@ -1,6 +1,7 @@ import * as d3 from 'd3'; import { globalSetting } from '../global'; import { NetlistRender } from '.'; +import { getMarginParamter, LAYOUT_CONSTANT } from './layout'; export class ConnectionRender { /** @@ -25,16 +26,19 @@ export class ConnectionRender { /** * @description 将 elknode 关于 module connection 的数据添加为 d3 数据项目 - * @param {ElkPort} cellPort 连接点对象 - * @param {ElkNode} node 当前的实体(port/例化模块/器件) + * @param {import('../jsdoc').ElkPort} cellPort 连接点对象 + * @param {import('../jsdoc').ElkNode} node 当前的实体(port/例化模块/器件) */ - addAsD3DataItem(cellPort, node) { + addAsD3DataItem(port, node) { + const marginParamter = getMarginParamter(port); + const marginOffset = port.direction === 'input' ? marginParamter.leftMargin: - marginParamter.rightMargin; + this.data.push({ - id: cellPort.id, - x: cellPort.x + node.x, - y: cellPort.y + node.y + 0.5, // 0.5 是为了线宽 - width: cellPort.width, - height: cellPort.height, + id: port.id, + x: port.x + node.x + marginOffset, + y: port.y + node.y + 0.5, // 0.5 是为了线宽 + width: port.width, + height: port.height, fill: 'var(--main-color)', text: '', r: 3.5 diff --git a/src/hook/render/index.js b/src/hook/render/index.js index 1db285d..98568c7 100644 --- a/src/hook/render/index.js +++ b/src/hook/render/index.js @@ -288,6 +288,14 @@ export class NetlistRender { this.wireRender = new WireRender(parentSelection, this); this.crossDotRender = new CrossDotRender(parentSelection, this); + // 建立关于 port 的索引 + const id2port = new Map(); + for (const node of computedLayout.children || []) { + for (const port of node.ports || []) { + id2port.set(port.id, port); + } + } + const rangeTree = new RangeTreeMap(); for (const edge of computedLayout.edges) { @@ -298,7 +306,7 @@ export class NetlistRender { points.push(point); } points.push(section.endPoint); - this.wireRender.addAsD3DataItems(points, edge); + this.wireRender.addAsD3DataItems(points, edge, id2port); // 加入 range tree 中 for (let i = 0; i < points.length - 1; ++ i) { @@ -502,15 +510,17 @@ export class NetlistRender { const originX = elkNode.x; const originY = elkNode.y; + // 给所有的子图的节点增加前缀名 function addNodeIdPrefix(nodes) { for (const node of nodes) { node.id = dotConnect(elkNode.name, node.id); for (const port of node.ports || []) { - port.id = dotConnect(elkNode.name, port.id); + port.id = dotConnect(elkNode.name, port.id); } } } + // 给所有的子图的边增加前缀名 function addEdgeIdPrefix(edges) { for (const edge of edges) { edge.id = dotConnect(elkNode.name, edge.id); @@ -531,11 +541,41 @@ export class NetlistRender { addNodeIdPrefix(constantNodes); addEdgeIdPrefix(connectionEdges); + // 对于子图而言,不需要创建 ports,只需要把对应的连线接到所在例化模块的接口即可. + // 只需要对当前 node 中的 port 进行 index,然后对于 connectionEdges 中对应的部分的 id 进行替换即可 + const portName2port = new Map(); + for (const port of elkNode.ports || []) { + portName2port.set(port.renderName, port); + } + + const innerPortId2outterPortId = new Map(); + for (const innerPort of portNodes) { + const outterPort = portName2port.get(innerPort.name); + if (outterPort) { + innerPortId2outterPortId.set(innerPort.id, outterPort.id); + } + } + + + for (const edge of connectionEdges) { + if (innerPortId2outterPortId.has(edge.source)) { + const outterId = innerPortId2outterPortId.get(edge.source); + edge.source = elkNode.id; + edge.sourcePort = outterId; + } + + if (innerPortId2outterPortId.has(edge.target)) { + const outterId = innerPortId2outterPortId.get(edge.target); + edge.target = elkNode.id; + edge.targetPort = outterId; + } + } + + // 加入到布局图中 elkNode.children = []; elkNode.edges = []; elkNode.layoutOptions = this.defaultLayoutOptions; - elkNode.children.push(...portNodes); elkNode.children.push(...cellNodes); elkNode.children.push(...constantNodes); elkNode.edges.push(...connectionEdges); @@ -575,8 +615,11 @@ export class NetlistRender { const s = renderStack.pop(); const parentSelection = s.parentSelection; const layout = s.layout; + await this.renderLine(parentSelection, layout); + const { instances } = await this.renderEntity(parentSelection, layout); + const id2selection = new Map(); instances.each(function(data) { const selection = d3.select(this); diff --git a/src/hook/render/instantiation.js b/src/hook/render/instantiation.js index 52d520b..8f53381 100644 --- a/src/hook/render/instantiation.js +++ b/src/hook/render/instantiation.js @@ -40,7 +40,7 @@ export class InstantiationRender { for (const port of node.ports || []) { const isInput = port.x < LAYOUT_CONSTANT.INSTANTIATION_WIDTH; const align = isInput ? 'left': 'end'; - const portX = isInput ? port.x + textPadding : port.x - textPadding; + const portX = isInput ? port.x + textPadding : port.x - textPadding - LAYOUT_CONSTANT.INSTANCE_LEFT_MARGIN - LAYOUT_CONSTANT.INSTANCE_RIGHT_MARGIN; portnames.push({ name: port.renderName, x: portX, @@ -82,14 +82,15 @@ export class InstantiationRender { // 例化模块的方块 let instances = instantiationSelections.append('rect') + .attr('x', LAYOUT_CONSTANT.INSTANCE_LEFT_MARGIN) .attr('y', LAYOUT_CONSTANT.INSTANCE_TITLE_HEIGHT) - .attr('width', data => data.width) + .attr('width', data => data.width - LAYOUT_CONSTANT.INSTANCE_LEFT_MARGIN - LAYOUT_CONSTANT.INSTANCE_RIGHT_MARGIN) .attr('height', data => data.height - LAYOUT_CONSTANT.INSTANCE_TITLE_HEIGHT); // 说明文字 let texts = instantiationSelections.append('text') - .attr('x', 0) + .attr('x', LAYOUT_CONSTANT.INSTANCE_LEFT_MARGIN) .attr('y', LAYOUT_CONSTANT.INSTANCE_TITLE_HEIGHT - 8) .attr('dominant-baseline', 'middle') .attr('text-anchor', 'left') @@ -113,7 +114,7 @@ export class InstantiationRender { .enter() .append('text') .attr('class', 'port-name') - .attr('x', d => d.x) + .attr('x', d => d.x + LAYOUT_CONSTANT.INSTANCE_LEFT_MARGIN) .attr('y', d => d.y) .attr('dominant-baseline', 'middle') .attr('text-anchor', d => d.align) @@ -125,7 +126,7 @@ export class InstantiationRender { // 增加一个背景方块,防止 svg 点不到 let bgFullScreenSelections = instantiationSelections.append('rect') - .attr('x', 5) + .attr('x', 5 + LAYOUT_CONSTANT.INSTANCE_LEFT_MARGIN) .attr('y', LAYOUT_CONSTANT.INSTANCE_TITLE_HEIGHT + 5) .attr('width', 20) .attr('height', 20) @@ -149,7 +150,7 @@ export class InstantiationRender { let fullSreenSelections = instantiationSelections.append(data => { // 获取全屏图标 const element = svgResource.get('full-screen'); - element.setAttribute('x', 5); + element.setAttribute('x', 5 + LAYOUT_CONSTANT.INSTANCE_LEFT_MARGIN); element.setAttribute('y', LAYOUT_CONSTANT.INSTANCE_TITLE_HEIGHT + 5); element.setAttribute('width', 20); element.setAttribute('height', 20); diff --git a/src/hook/render/layout.js b/src/hook/render/layout.js index 5ea5761..d4d47f3 100644 --- a/src/hook/render/layout.js +++ b/src/hook/render/layout.js @@ -99,6 +99,9 @@ export class Module { const portConnection = { id: dotConnect(port.id, '0'), renderType: 'portConnection', + source: 'port', + // 对于 input 的 port 而言,它的端口只有一个且为输出 + direction: 'output', width: LAYOUT_CONSTANT.CELL_PORT_WIDTH, height: LAYOUT_CONSTANT.CELL_PORT_HEIGHT, x: LAYOUT_CONSTANT.PORT_WIDTH, @@ -110,6 +113,9 @@ export class Module { name: port.name, renderName: name, renderType: 'port', + source: 'port', + // 对于 input 的 port 而言,它的端口只有一个且为输入 + direction: 'input', type: port.direction, width: LAYOUT_CONSTANT.PORT_WIDTH, height: LAYOUT_CONSTANT.PORT_HEIGHT, @@ -312,7 +318,7 @@ export class Module { const meta = skin.meta; const height = meta.height; - const width = meta.width; + const width = meta.width + LAYOUT_CONSTANT.CELL_LEFT_MARGIN + LAYOUT_CONSTANT.CELL_RIGHT_MARGIN; const ports = []; @@ -337,6 +343,8 @@ export class Module { id: connection.id, renderName: connection.name, renderType: 'cellPort', + direction: 'input', + source: 'cell', width: LAYOUT_CONSTANT.CELL_PORT_WIDTH, height: LAYOUT_CONSTANT.CELL_PORT_HEIGHT, x: 0, @@ -353,6 +361,8 @@ export class Module { id: connection.id, renderName: connection.name, renderType: 'cellPort', + direction: 'output', + source: 'cell', width: LAYOUT_CONSTANT.CELL_PORT_WIDTH, height: LAYOUT_CONSTANT.CELL_PORT_HEIGHT, x: width, @@ -407,7 +417,7 @@ export class Module { 2 * LAYOUT_CONSTANT.PORT_INNER_PADDING + LAYOUT_CONSTANT.INSTANCE_LEFT_MARGIN + LAYOUT_CONSTANT.INSTANCE_RIGHT_MARGIN; - + // 创建器件节点的 port, port 和 connection 一一对应 const ports = []; let inputCount = 0; @@ -425,6 +435,8 @@ export class Module { id: connection.id, renderName: connectionName, renderType: 'cellPort', + direction: 'input', + source: 'instance', width: LAYOUT_CONSTANT.CELL_PORT_WIDTH, height: LAYOUT_CONSTANT.CELL_PORT_HEIGHT, x: 0, @@ -440,6 +452,8 @@ export class Module { id: connection.id, renderName: connectionName, renderType: 'cellPort', + direction: 'output', + source: 'instance', width: LAYOUT_CONSTANT.CELL_PORT_WIDTH, height: LAYOUT_CONSTANT.CELL_PORT_HEIGHT, x: instanceWidth, @@ -469,3 +483,42 @@ export class Module { return node; } } + +/** + * + * @param {import('../jsdoc').ElkPort} port + * @returns {ConnectionMarginParameter} + */ +export function getMarginParamter(port) { + if (!port) { + // 如果是例化模块,那么当前的 port 就是空 + // 因为例化模块内部的变量一定和外部直连,所以例化模块内部是没有 port 的 + return { + leftMargin: 0, + rightMargin: 0 + } + } + switch (port.source) { + case 'instance': + return { + leftMargin: LAYOUT_CONSTANT.INSTANCE_LEFT_MARGIN, + rightMargin: LAYOUT_CONSTANT.INSTANCE_RIGHT_MARGIN + } + case 'cell': + return { + leftMargin: LAYOUT_CONSTANT.CELL_LEFT_MARGIN, + rightMargin: LAYOUT_CONSTANT.CELL_RIGHT_MARGIN + } + default: + return { + leftMargin: 0, + rightMargin: 0 + } + } +} + +/** + * @typedef ConnectionMarginParameter + * @property {number} leftMargin + * @property {number} rightMargin + */ \ No newline at end of file diff --git a/src/hook/render/wire.js b/src/hook/render/wire.js index afb8625..941d7e5 100644 --- a/src/hook/render/wire.js +++ b/src/hook/render/wire.js @@ -3,7 +3,7 @@ import * as d3 from 'd3'; import { globalSetting } from '../global'; import { NetlistRender } from '.'; -import { LAYOUT_CONSTANT, LINE_WIDTH } from './layout'; +import { getMarginParamter, LAYOUT_CONSTANT, LINE_WIDTH } from './layout'; import { svgResource } from '../skin/draw'; import { PulseLine } from '../skin/plusation'; @@ -36,11 +36,24 @@ export class WireRender { /** * @description 将 elknode 关于 wire 的数据添加为 d3 数据项目 - * @param {ElkPoint[]} points - * @param {ElkEdge} edge + * @param {import('../jsdoc').ElkPoint[]} points 长度至少为 2 的数组,代表经历过的点集 + * @param {import('../jsdoc').ElkEdge} edge + * @param {Map} id2port */ - addAsD3DataItems(points, edge) { + addAsD3DataItems(points, edge, id2port) { const linePaths = []; + + const beginPoint = points.at(0); + const endPoint = points.at(-1); + + const sourcePort = id2port.get(edge.sourcePort); + const targetPort = id2port.get(edge.targetPort); + const sourceMargin = getMarginParamter(sourcePort); + const targetMargin = getMarginParamter(targetPort); + + beginPoint.x -= sourceMargin.rightMargin; + endPoint.x += targetMargin.leftMargin; + for (let i = 0; i < points.length; ++ i) { // 根据点的信息创建 path const command = i === 0 ? 'M': 'L'; @@ -52,7 +65,6 @@ export class WireRender { const lineSvg = linePaths.join(' '); // 判断当前的朝向 - const endPoint = points.at(-1); const direction = endPoint.x > points.at(-2).x ? 'right' : 'left'; const arrowX = direction === 'right' ? endPoint.x - LAYOUT_CONSTANT.CELL_PORT_WIDTH - this.arrowWidth + 2.5: endPoint.x + LAYOUT_CONSTANT.CELL_PORT_WIDTH - 2.5