完成例化的展开

This commit is contained in:
锦恢 2024-12-29 06:13:22 +08:00
parent 9886b8c881
commit 0bcb475812
8 changed files with 238 additions and 48 deletions

View File

@ -5,7 +5,7 @@
<div :class="expandManage.expandTagClass"></div>
</span>
<span :class="`iconfont ${makeIconClass(module)}`"></span>
&ensp;{{ module.name }}
&ensp;{{ renderName }}
</div>
<!-- 渲染这个 module scope 有的组件 -->
@ -27,7 +27,8 @@
<!-- 例化模块 -->
<modules
v-for="(cell, index) in cells"
:module="cell.view"
:module="cell.view"
:render-name="cell.renderName"
:key="index"
></modules>
</div>
@ -39,7 +40,7 @@
/* eslint-disable */
import { globalLookup } from '@/hook/global';
import { ModuleView } from '@/hook/render/yosys';
import { defineComponent, reactive } from 'vue';
import { defineComponent, reactive, computed } from 'vue';
defineComponent({ name: 'modules' });
@ -47,11 +48,18 @@ const props = defineProps({
module: {
type: ModuleView,
required: true
}
},
renderName: {
type: String
}
});
const module = props.module;
const renderName = computed(() => {
return props.renderName ? props.renderName : module.name
});
// scope
const ports = [];
const cells = [];
@ -63,20 +71,25 @@ for (const portName of module.nameToPort.keys()) {
});
}
for (const cellName of module.nameToCell.keys()) {
const cell = module.nameToCell.get(cellName);
if (cell.view === module) {
if (cell.belongModuleView === module) {
//
continue;
}
if (cell.isInstantiation) {
if (cell.isInstantiation) {
cells.push({
name: cellName,
view: cell.belongModuleView
view: cell.belongModuleView,
renderName: `${cellName} (${cell.belongModuleView.name})`
});
}
}
function clickItem() {
}

View File

@ -255,3 +255,8 @@ import { Module } from "./render/layout";
* @typedef DragConnection
* @property {d3.Selection} selection
*/
/**
* @typedef ElkMakerConfig
* @param {string} [parentId]
*/

View File

@ -48,11 +48,17 @@ export class CellRender {
const id2manager = this.id2manager;
const _this = this;
console.log(data);
let cellSelections = this.parentSelection.selectAll('svg')
.data(data)
.enter()
.append(data => {
const element = data.element;
console.log(data);
element.setAttribute('x', data.x);
element.setAttribute('y', data.y);
if (globalSetting.renderAnimation) {

View File

@ -11,6 +11,7 @@ import { ConnectionRender } from './connection';
import { WireRender } from './wire';
import { pinkLog, redLog } from '../utils';
import { treeviewData } from '@/components/treeview/tree';
import { dotConnect } from './yosys';
export class NetlistRender {
/**
@ -20,6 +21,24 @@ export class NetlistRender {
* @param {number} renderWidth
*/
constructor() {
this.defaultLayoutOptions = {
// ...
'org.eclipse.elk.layered.spacing.nodeNodeBetweenLayers': 35,
// node 之间的最小间距
'elk.spacing.nodeNode': 35,
// layered 算法布局,尽可能让全体布局从左向右
'elk.algorithm': 'layered',
// layered 算法的风格
'elk.layered.layering.strategy': 'NETWORK_SIMPLEX',
// ...
'elk.layered.considerModelOrder.longEdgeStrategy': 'DUMMY_NODE_UNDER',
// edge 的生成采用正交路由算法
'elk.edgeRouting': 'ORTHOGONAL',
// 指定 layered 算法的方向为从左到右
'elk.direction': 'RIGHT',
// 激活 node 分区功能,可以通过 partitioning.partition 赋予 id 的方式给 node 进行分组
'elk.partitioning.activate': true
}
/**
* @type {ElkGraph}
*/
@ -27,24 +46,7 @@ export class NetlistRender {
id: 'root',
children: [],
edges: [],
layoutOptions: {
// ...
'org.eclipse.elk.layered.spacing.nodeNodeBetweenLayers': 35,
// node 之间的最小间距
'elk.spacing.nodeNode': 35,
// layered 算法布局,尽可能让全体布局从左向右
'elk.algorithm': 'layered',
// layered 算法的风格
'elk.layered.layering.strategy': 'NETWORK_SIMPLEX',
// ...
'elk.layered.considerModelOrder.longEdgeStrategy': 'DUMMY_NODE_UNDER',
// edge 的生成采用正交路由算法
'elk.edgeRouting': 'ORTHOGONAL',
// 指定 layered 算法的方向为从左到右
'elk.direction': 'RIGHT',
// 激活 node 分区功能,可以通过 partitioning.partition 赋予 id 的方式给 node 进行分组
'elk.partitioning.activate': true
}
layoutOptions: this.defaultLayoutOptions
};
/**
@ -187,9 +189,6 @@ export class NetlistRender {
this.renderWidth = element.clientWidth;
this.registerResizeHandler(element);
// 根据 height 进行放缩(可以通过设置进行调整)
const ratio = this.renderHeight / virtualHeight;
// 遍历计算布局进行创建
const svg = d3.select(container)
.selectAll('svg')
@ -203,13 +202,14 @@ export class NetlistRender {
console.log(computedLayout);
// 生成连接
await this.renderLine(g, computedLayout, ratio);
await this.renderLine(g, computedLayout);
// 生成实体
await this.renderEntity(g, computedLayout, ratio);
await this.renderEntity(g, computedLayout);
// svg 挂载为全局注册的 selection
this.selection = svg;
this.g = g;
// 注册平移和缩放
this.registerRenderTransform(g);
@ -229,9 +229,8 @@ export class NetlistRender {
* @description 绘制实体
* @param {d3.Selection} parentSelection
* @param {ElkNode} computedLayout
* @param {number} ratio
*/
async renderEntity(parentSelection, computedLayout, ratio) {
async renderEntity(parentSelection, computedLayout) {
// node 可能是如下的几类
// - module 的 port
// - 器件(基础器件 & 例化模块)
@ -251,7 +250,8 @@ export class NetlistRender {
const skin = skinManager.querySkin(node.renderName);
if (skin) {
// 具有 skin 的器件
this.cellRender.addAsD3DataItem(node, skin.meta.svgDoc.documentElement);
const element = skin.meta.svgDoc.documentElement.cloneNode(true);
this.cellRender.addAsD3DataItem(node, element);
} else {
if (node.renderType === 'port') {
this.portRender.addAsD3DataItem(node);
@ -267,19 +267,20 @@ export class NetlistRender {
}
}
this.portRender.render();
this.instantiationRender.render();
this.cellRender.render();
this.connectionRender.render();
const ports = this.portRender.render();
const instances = this.instantiationRender.render();
const cells = this.cellRender.render();
const connections = this.connectionRender.render();
return { ports, instances, cells, connections };
}
/**
* @description 绘制连线
* @param {d3.Selection} parentSelection
* @param {ElkNode} computedLayout
* @param {number} ratio
*/
async renderLine(parentSelection, computedLayout, ratio) {
async renderLine(parentSelection, computedLayout) {
this.wireRender = new WireRender(parentSelection, this);
for (const edge of computedLayout.edges) {
@ -455,16 +456,167 @@ export class NetlistRender {
/**
* @description 展开例化模块
* @param {d3.Selection} instanceSelection
*/
expandInstance() {
async expandInstance(instanceSelection) {
const data = instanceSelection.datum();
const elkNode = data._self;
const moduleName = elkNode.renderName;
const module = this.nameToModule.get(moduleName);
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);
}
}
}
function addEdgeIdPrefix(edges) {
for (const edge of edges) {
edge.id = dotConnect(elkNode.name, edge.id);
edge.source = dotConnect(elkNode.name, edge.source);
edge.sourcePort = dotConnect(elkNode.name, edge.sourcePort);
edge.target = dotConnect(elkNode.name, edge.target);
edge.targetPort = dotConnect(elkNode.name, edge.targetPort);
}
}
const portNodes = module.makeNetsElkNodes();
const cellNodes = module.makeCellsElkNodes();
const [constantNodes, connectionEdges] = module.makeConnectionElkNodes();
// 增加前缀
addNodeIdPrefix(portNodes);
addNodeIdPrefix(cellNodes);
addNodeIdPrefix(constantNodes);
addEdgeIdPrefix(connectionEdges);
elkNode.children = [];
elkNode.edges = [];
elkNode.layoutOptions = this.defaultLayoutOptions;
elkNode.children.push(...portNodes);
elkNode.children.push(...cellNodes);
elkNode.children.push(...constantNodes);
elkNode.edges.push(...connectionEdges);
const start = performance.now();
// 更新布局
await this.elk.layout(this.elkGraph);
const computedLayout = this.elkGraph;
const timecost = (performance.now() - start).toFixed(3);
pinkLog(`expandInstance 布局计算耗时 ${timecost} ms`);
console.log(computedLayout);
const svg = this.selection;
const g = this.g;
g.selectAll('*').remove();
// 开始递归地进行渲染
const renderStack = [
{
parentSelection: g,
layout: computedLayout
}
];
while (renderStack.length > 0) {
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);
id2selection.set(data.id, selection);
});
// 将需要渲染子图的部分扔进渲染器
for (const node of layout.children || []) {
const subChildren = node.children || [];
if (subChildren.length > 0) {
const selection = id2selection.get(node.id);
renderStack.push({
parentSelection: selection,
layout: node
});
}
}
}
}
/**
* @description 关闭例化模块
* @param {d3.Selection} instanceSelection
*/
closeInstance() {
async collapseInstance(instanceSelection) {
const data = instanceSelection.datum();
const elkNode = data._self;
const moduleName = elkNode.renderName;
const module = this.nameToModule.get(moduleName);
elkNode.children = [];
elkNode.edges = [];
// 重新计算 elkNode 的宽度和高度
const start = performance.now();
// 更新布局
await this.elk.layout(this.elkGraph);
const computedLayout = this.elkGraph;
const timecost = (performance.now() - start).toFixed(3);
pinkLog(`collapseInstance 布局计算耗时 ${timecost} ms`);
console.log(computedLayout);
const svg = this.selection;
const g = this.g;
g.selectAll('*').remove();
// 开始递归地进行渲染
const renderStack = [
{
parentSelection: g,
layout: computedLayout
}
];
while (renderStack.length > 0) {
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);
id2selection.set(data.id, selection);
});
// 将需要渲染子图的部分扔进渲染器
for (const node of layout.children || []) {
const subChildren = node.children || [];
if (subChildren.length > 0) {
const selection = id2selection.get(node.id);
renderStack.push({
parentSelection: selection,
layout: node
});
}
}
}
}
}

View File

@ -93,7 +93,8 @@ export class InstantiationRender {
portnames,
rx: 3,
ry: 3,
expandText: '📌'
expandText: '📌',
_self: node
});
}
@ -114,7 +115,10 @@ export class InstantiationRender {
.attr('y', LAYOUT_CONSTANT.INSTANCE_TITLE_HEIGHT)
.attr('width', data => data.width)
.attr('height', data => data.height - LAYOUT_CONSTANT.INSTANCE_TITLE_HEIGHT)
.attr('fill', d => d.fill);
.attr('fill', d => {
const children = d._self.children || [];
return children.length > 0 ? 'rgba(203, 129, 218, 0.1)' : d.fill;
});
let texts = instantiationSelections.append('text')
@ -138,7 +142,15 @@ export class InstantiationRender {
.attr('fill', 'var(--main-color)');
})
.on('click', function(event) {
const selection = d3.select(this);
const d = selection.datum();
const children = d._self.children || [];
if (children.length > 0) {
rootRender.collapseInstance(selection);
} else {
rootRender.expandInstance(selection);
}
});

View File

@ -202,8 +202,6 @@ export class Module {
x: 0,
y: yOffset
});
}
// 计算右侧的

View File

@ -45,7 +45,8 @@ export class ModuleView {
}
}
for (const [cellName, rawCell] of Object.entries(rawModule.cells)) {
for (const [cellName, rawCell] of Object.entries(rawModule.cells)) {
const cell = new Cell(this, cellName, rawCell);
this._nameToCell.set(cellName, cell);
// 构建映射
@ -128,7 +129,7 @@ export class Cell {
*/
constructor(view, name, rawCell) {
this.view = view;
this.name = name;
this.name = name;
this.rawCell = rawCell;
/**

View File

@ -67,6 +67,9 @@ class SkinMeta {
// 颜色替换
// 填充颜色
svgString = svgString.replace(/#279BB0/g, 'var(--main-dark-color)');
svgString = svgString.replace(/#41C9A0/g, 'var(--main-dark-color)');
// 边缘颜色
svgString = svgString.replace(/#000000/g, 'var(--main-color)');
// 字体颜色