完成例化的展开

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

View File

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

View File

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

View File

@ -11,6 +11,7 @@ import { ConnectionRender } from './connection';
import { WireRender } from './wire'; import { WireRender } from './wire';
import { pinkLog, redLog } from '../utils'; import { pinkLog, redLog } from '../utils';
import { treeviewData } from '@/components/treeview/tree'; import { treeviewData } from '@/components/treeview/tree';
import { dotConnect } from './yosys';
export class NetlistRender { export class NetlistRender {
/** /**
@ -20,14 +21,7 @@ export class NetlistRender {
* @param {number} renderWidth * @param {number} renderWidth
*/ */
constructor() { constructor() {
/** this.defaultLayoutOptions = {
* @type {ElkGraph}
*/
this.elkGraph = {
id: 'root',
children: [],
edges: [],
layoutOptions: {
// ... // ...
'org.eclipse.elk.layered.spacing.nodeNodeBetweenLayers': 35, 'org.eclipse.elk.layered.spacing.nodeNodeBetweenLayers': 35,
// node 之间的最小间距 // node 之间的最小间距
@ -45,6 +39,14 @@ export class NetlistRender {
// 激活 node 分区功能,可以通过 partitioning.partition 赋予 id 的方式给 node 进行分组 // 激活 node 分区功能,可以通过 partitioning.partition 赋予 id 的方式给 node 进行分组
'elk.partitioning.activate': true 'elk.partitioning.activate': true
} }
/**
* @type {ElkGraph}
*/
this.elkGraph = {
id: 'root',
children: [],
edges: [],
layoutOptions: this.defaultLayoutOptions
}; };
/** /**
@ -187,9 +189,6 @@ export class NetlistRender {
this.renderWidth = element.clientWidth; this.renderWidth = element.clientWidth;
this.registerResizeHandler(element); this.registerResizeHandler(element);
// 根据 height 进行放缩(可以通过设置进行调整)
const ratio = this.renderHeight / virtualHeight;
// 遍历计算布局进行创建 // 遍历计算布局进行创建
const svg = d3.select(container) const svg = d3.select(container)
.selectAll('svg') .selectAll('svg')
@ -203,13 +202,14 @@ export class NetlistRender {
console.log(computedLayout); 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 // svg 挂载为全局注册的 selection
this.selection = svg; this.selection = svg;
this.g = g;
// 注册平移和缩放 // 注册平移和缩放
this.registerRenderTransform(g); this.registerRenderTransform(g);
@ -229,9 +229,8 @@ export class NetlistRender {
* @description 绘制实体 * @description 绘制实体
* @param {d3.Selection} parentSelection * @param {d3.Selection} parentSelection
* @param {ElkNode} computedLayout * @param {ElkNode} computedLayout
* @param {number} ratio
*/ */
async renderEntity(parentSelection, computedLayout, ratio) { async renderEntity(parentSelection, computedLayout) {
// node 可能是如下的几类 // node 可能是如下的几类
// - module 的 port // - module 的 port
// - 器件(基础器件 & 例化模块) // - 器件(基础器件 & 例化模块)
@ -251,7 +250,8 @@ export class NetlistRender {
const skin = skinManager.querySkin(node.renderName); const skin = skinManager.querySkin(node.renderName);
if (skin) { if (skin) {
// 具有 skin 的器件 // 具有 skin 的器件
this.cellRender.addAsD3DataItem(node, skin.meta.svgDoc.documentElement); const element = skin.meta.svgDoc.documentElement.cloneNode(true);
this.cellRender.addAsD3DataItem(node, element);
} else { } else {
if (node.renderType === 'port') { if (node.renderType === 'port') {
this.portRender.addAsD3DataItem(node); this.portRender.addAsD3DataItem(node);
@ -267,19 +267,20 @@ export class NetlistRender {
} }
} }
this.portRender.render(); const ports = this.portRender.render();
this.instantiationRender.render(); const instances = this.instantiationRender.render();
this.cellRender.render(); const cells = this.cellRender.render();
this.connectionRender.render(); const connections = this.connectionRender.render();
return { ports, instances, cells, connections };
} }
/** /**
* @description 绘制连线 * @description 绘制连线
* @param {d3.Selection} parentSelection * @param {d3.Selection} parentSelection
* @param {ElkNode} computedLayout * @param {ElkNode} computedLayout
* @param {number} ratio
*/ */
async renderLine(parentSelection, computedLayout, ratio) { async renderLine(parentSelection, computedLayout) {
this.wireRender = new WireRender(parentSelection, this); this.wireRender = new WireRender(parentSelection, this);
for (const edge of computedLayout.edges) { for (const edge of computedLayout.edges) {
@ -455,16 +456,167 @@ export class NetlistRender {
/** /**
* @description 展开例化模块 * @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 关闭例化模块 * @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, portnames,
rx: 3, rx: 3,
ry: 3, ry: 3,
expandText: '📌' expandText: '📌',
_self: node
}); });
} }
@ -114,7 +115,10 @@ export class InstantiationRender {
.attr('y', LAYOUT_CONSTANT.INSTANCE_TITLE_HEIGHT) .attr('y', LAYOUT_CONSTANT.INSTANCE_TITLE_HEIGHT)
.attr('width', data => data.width) .attr('width', data => data.width)
.attr('height', data => data.height - LAYOUT_CONSTANT.INSTANCE_TITLE_HEIGHT) .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') let texts = instantiationSelections.append('text')
@ -138,7 +142,15 @@ export class InstantiationRender {
.attr('fill', 'var(--main-color)'); .attr('fill', 'var(--main-color)');
}) })
.on('click', function(event) { .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, x: 0,
y: yOffset y: yOffset
}); });
} }
// 计算右侧的 // 计算右侧的

View File

@ -45,6 +45,7 @@ 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); const cell = new Cell(this, cellName, rawCell);
this._nameToCell.set(cellName, cell); this._nameToCell.set(cellName, cell);

View File

@ -67,6 +67,9 @@ class SkinMeta {
// 颜色替换 // 颜色替换
// 填充颜色 // 填充颜色
svgString = svgString.replace(/#279BB0/g, 'var(--main-dark-color)'); 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)'); svgString = svgString.replace(/#000000/g, 'var(--main-color)');
// 字体颜色 // 字体颜色