1. Anchor routing added with mutiport

This commit is contained in:
2026-05-30 12:04:02 +08:00
parent 2d9b2b0983
commit 5a3a80700f
23 changed files with 1226 additions and 234 deletions
+109 -12
View File
@@ -10,6 +10,8 @@
const DEFAULT_COMPONENT_BOX_SIZE = { width: 132, height: 82 };
const DEFAULT_CANVAS_SIZE = { width: 5000, height: 5000 };
const PORT_NODE_SIZE = 30;
const ANCHOR_NODE_WIDTH = 8;
const DEFAULT_ELEMENT_PITCH = 10;
const ELEMENT_COMPONENTS = {
Port: {
name: 'Port',
@@ -22,8 +24,8 @@
name: 'Anchor',
elementType: 'anchor',
ports: {
left: { x: 0, y: -PORT_NODE_SIZE / 2, a: 180, width: 0.5 },
right: { x: PORT_NODE_SIZE, y: -PORT_NODE_SIZE / 2, a: 0, width: 0.5 }
a1: { x: 0, y: -PORT_NODE_SIZE / 2, a: 180, width: 0.5 },
b1: { x: 0, y: -PORT_NODE_SIZE / 2, a: 0, width: 0.5 }
}
}
};
@@ -514,19 +516,64 @@
const isPortElementNode = (node) => node && (node.data && node.data.elementType === 'port' || node.id === 'page-port' || node.type === 'portNode');
const isElementNode = (node) => node && node.data && (node.data.elementType === 'port' || node.data.elementType === 'anchor');
const normalizePortNumber = (value) => {
const number = Math.floor(Number(value));
return Number.isFinite(number) ? Math.max(1, number) : 1;
};
const normalizePitch = (value) => {
const number = Number(value);
return Number.isFinite(number) ? Math.max(0, number) : DEFAULT_ELEMENT_PITCH;
};
const elementPortOffset = (index, count, pitch) => ((count - 1) / 2 - index) * pitch;
const buildElementBoxSize = (data) => {
const portNumber = normalizePortNumber(data && data.portNumber);
const pitch = normalizePitch(data && data.pitch);
const handleClearance = Math.max(pitch, 14);
return {
width: data && data.elementType === 'anchor' ? ANCHOR_NODE_WIDTH : PORT_NODE_SIZE,
height: Math.max(PORT_NODE_SIZE, PORT_NODE_SIZE + Math.max(0, portNumber - 1) * handleClearance)
};
};
const buildElementPorts = (elementType, data) => {
const element = ELEMENT_COMPONENTS[elementType === 'anchor' ? 'Anchor' : 'Port'];
if (!element) return {};
const portNumber = normalizePortNumber(data && data.portNumber);
const pitch = normalizePitch(data && data.pitch);
const width = Number((data && data.width) || 0.5);
if (element.elementType === 'port') {
if (portNumber > 1) {
return Object.fromEntries(Array.from({ length: portNumber }, (_, index) => [
`port_${index + 1}`,
{
x: 0,
y: elementPortOffset(index, portNumber, pitch),
a: Number((data && (data.angle ?? data.a)) ?? 0),
width
}
]));
}
return {
port: {
x: 0,
y: 0,
a: Number((data && (data.angle ?? data.a)) ?? 0),
width: Number((data && data.width) || 0.5)
width
}
};
}
if (portNumber > 1) {
const entries = [];
Array.from({ length: portNumber }, (_, index) => {
const y = -PORT_NODE_SIZE / 2 + elementPortOffset(index, portNumber, pitch);
entries.push([`a${index + 1}`, { x: 0, y, a: 180, width }]);
entries.push([`b${index + 1}`, { x: 0, y, a: 0, width }]);
});
return Object.fromEntries(entries);
}
return JSON.parse(JSON.stringify(element.ports));
};
@@ -598,12 +645,24 @@
if (portNodes.length > 0) {
return portNodes.reduce((ports, node) => {
const data = node.data || {};
ports[getNodePortName(node)] = {
x: Number((node.position && node.position.x) || 0),
y: Number((node.position && node.position.y) || 0),
a: Number(data.angle ?? data.a ?? 0),
width: Number(data.width || 0.5)
};
const baseName = getNodePortName(node);
const elementPorts = buildElementPorts('port', data);
const entries = Object.entries(elementPorts);
entries.forEach(([portName, portInfo]) => {
const exportName = entries.length === 1
? baseName
: `${baseName}_${portName.replace(/^port_/, '')}`;
const point = getNodePortCanvasPoint(node, portName) || {
x: Number((node.position && node.position.x) || 0),
y: Number((node.position && node.position.y) || 0)
};
ports[exportName] = {
x: Number(point.x || 0),
y: Number(point.y || 0),
a: Number(portInfo.a ?? data.angle ?? data.a ?? 0),
width: Number(portInfo.width || data.width || 0.5)
};
});
return ports;
}, {});
}
@@ -645,11 +704,15 @@
const data = node.data || {};
const name = data.componentDisplayName || data.portName || node.id;
const angle = data.elementType === 'port' ? data.angle : data.rotation;
const portNumber = normalizePortNumber(data.portNumber);
const pitch = normalizePitch(data.pitch);
return ` ${name}:
type: ${data.elementType}
x: ${Number((node.position && node.position.x) || 0).toFixed(1)}
y: ${canvasToLayoutY((node.position && node.position.y) || 0).toFixed(1)}
angle: ${Number(angle || 0).toFixed(1)}
port_number: ${portNumber}
pitch: ${Number(pitch)}
layer: ${data.layer || 'WG_CORE'}
width: ${Number(data.width || 0.5)}
description: ${toYamlScalar(data.description || '')}`;
@@ -718,7 +781,28 @@ ${linksYaml}`;
const x = Number((node.position && node.position.x) || 0);
const y = Number((node.position && node.position.y) || 0);
if (node.type === 'portNode' || (node.data && node.data.elementType === 'port')) {
return { x: roundMeasureValue(x), y: roundMeasureValue(y) };
const ports = buildElementPorts('port', node.data);
const portInfo = ports && portName ? ports[portName] : ports.port;
if (!portInfo) return { x: roundMeasureValue(x), y: roundMeasureValue(y) };
const transformedInfo = transformPortInfo(portInfo, { rotation: 0 });
return {
x: roundMeasureValue(x + Number(transformedInfo.x || 0)),
y: roundMeasureValue(y - Number(transformedInfo.y || 0))
};
}
if (node.type === 'anchorNode' || (node.data && node.data.elementType === 'anchor')) {
const ports = buildElementPorts('anchor', node.data);
const portInfo = ports && portName ? ports[portName] : null;
if (!portInfo) return null;
const transformedInfo = transformPortInfo(portInfo, {
rotation: (node.data && node.data.rotation) || 0,
flip: Boolean(node.data && node.data.flip),
flop: Boolean(node.data && node.data.flop)
});
return {
x: roundMeasureValue(x + Number(transformedInfo.x || 0)),
y: roundMeasureValue(y - Number(transformedInfo.y || 0))
};
}
const ports = node.data && node.data.ports;
const portInfo = ports && portName ? ports[portName] : null;
@@ -758,7 +842,9 @@ ${linksYaml}`;
});
const handle = handles.find(item => item.name === handleId);
if (handle) {
const componentSize = normalizeBoxSize({ box_size: node.data && node.data.boxSize }, DEFAULT_COMPONENT_BOX_SIZE);
const componentSize = node.data && node.data.elementType
? buildElementBoxSize(node.data)
: normalizeBoxSize({ box_size: node.data && node.data.boxSize }, DEFAULT_COMPONENT_BOX_SIZE);
let x = Number((node.position && node.position.x) || 0);
let y = Number((node.position && node.position.y) || 0);
if (handle.position === 'left') {
@@ -825,14 +911,23 @@ ${linksYaml}`;
return o1 !== o2 && o3 !== o4;
};
const routeTypeKey = (route) => {
const xsection = String((route && route.xsection) || '').trim().toLowerCase();
if (xsection === 'metal1') return 'metal_1';
if (xsection === 'metal2') return 'metal_2';
if (xsection === 'rib') return 'rib_low';
return xsection;
};
const findSameTypeRouteCrossing = (candidateEdge, existingEdges, nodeMap, manifest) => {
const candidateRoute = createRouteSettings(manifest, candidateEdge.data && candidateEdge.data.route);
const candidateType = routeTypeKey(candidateRoute);
const candidatePoints = getEdgeRoutePoints(candidateEdge, nodeMap);
for (const edge of existingEdges || []) {
if (!edge || edge.id === candidateEdge.id) continue;
if (edge.source === candidateEdge.source || edge.source === candidateEdge.target || edge.target === candidateEdge.source || edge.target === candidateEdge.target) continue;
const route = createRouteSettings(manifest, edge.data && edge.data.route);
if (route.xsection !== candidateRoute.xsection) continue;
if (routeTypeKey(route) !== candidateType) continue;
const points = getEdgeRoutePoints(edge, nodeMap);
if (routeSegmentsIntersect(candidatePoints, points)) {
return { conflictEdge: edge, xsection: route.xsection };
@@ -849,6 +944,7 @@ ${linksYaml}`;
DEFAULT_COMPONENT_BOX_SIZE,
DEFAULT_CANVAS_SIZE,
PORT_NODE_SIZE,
DEFAULT_ELEMENT_PITCH,
ELEMENT_COMPONENTS,
BASIC_COMPONENTS,
DEFAULT_FORGE_ARGUMENTS,
@@ -878,6 +974,7 @@ ${linksYaml}`;
getNodePortCanvasPoint,
buildPortHandles,
buildElementPorts,
buildElementBoxSize,
buildBasicComponentPorts,
getBasicComponentMetadata,
buildInstanceYaml,