1. Anchor routing added with mutiport
This commit is contained in:
@@ -142,22 +142,22 @@ assert.strictEqual(
|
||||
);
|
||||
assert.deepStrictEqual(
|
||||
{
|
||||
left: helpers.buildElementPorts('anchor').left.a,
|
||||
right: helpers.buildElementPorts('anchor').right.a,
|
||||
a1: helpers.buildElementPorts('anchor').a1.a,
|
||||
b1: helpers.buildElementPorts('anchor').b1.a,
|
||||
},
|
||||
{ left: 180, right: 0 },
|
||||
'Anchor objects should default to 180 degree left port and 0 degree right port'
|
||||
{ a1: 180, b1: 0 },
|
||||
'Anchor objects should default to a1 for the left port and b1 for the right port'
|
||||
);
|
||||
assert.deepStrictEqual(
|
||||
{
|
||||
left: helpers.buildElementPorts('anchor').left,
|
||||
right: helpers.buildElementPorts('anchor').right,
|
||||
a1: helpers.buildElementPorts('anchor').a1,
|
||||
b1: helpers.buildElementPorts('anchor').b1,
|
||||
},
|
||||
{
|
||||
left: { x: 0, y: -15, a: 180, width: 0.5 },
|
||||
right: { x: 30, y: -15, a: 0, width: 0.5 }
|
||||
a1: { x: 0, y: -15, a: 180, width: 0.5 },
|
||||
b1: { x: 0, y: -15, a: 0, width: 0.5 }
|
||||
},
|
||||
'Anchor ports should sit on the left and right edges of a port-sized circle'
|
||||
'Anchor a/b port pairs should share coordinates and keep opposite directions'
|
||||
);
|
||||
assert.deepStrictEqual(
|
||||
helpers.buildBasicComponentPorts('waveguide', { length: 120, width: 0.6 }).b1,
|
||||
@@ -300,7 +300,46 @@ const elementNodes = [
|
||||
assert.deepStrictEqual(helpers.buildElementPorts('port', { angle: 90, width: 0.8 }), {
|
||||
port: { x: 0, y: 0, a: 90, width: 0.8 }
|
||||
});
|
||||
assert.deepStrictEqual(Object.keys(helpers.buildElementPorts('anchor')), ['left', 'right']);
|
||||
assert.deepStrictEqual(Object.keys(helpers.buildElementPorts('anchor')), ['a1', 'b1']);
|
||||
assert.deepStrictEqual(Object.keys(helpers.buildElementPorts('port', { portNumber: 3, pitch: 10 })), ['port_1', 'port_2', 'port_3']);
|
||||
assert.deepStrictEqual(helpers.buildElementPorts('port', { portNumber: 3, pitch: 10 }).port_1, { x: 0, y: 10, a: 0, width: 0.5 });
|
||||
assert.deepStrictEqual(helpers.buildElementPorts('port', { portNumber: 3 }).port_1, { x: 0, y: 10, a: 0, width: 0.5 });
|
||||
assert.deepStrictEqual(Object.keys(helpers.buildElementPorts('anchor', { portNumber: 2, pitch: 12 })), ['a1', 'b1', 'a2', 'b2']);
|
||||
assert.deepStrictEqual(helpers.buildElementPorts('anchor', { portNumber: 2, pitch: 12 }).b2, { x: 0, y: -21, a: 0, width: 0.5 });
|
||||
assert.deepStrictEqual(helpers.buildElementPorts('anchor', { portNumber: 2, pitch: 12 }).a2, { x: 0, y: -21, a: 180, width: 0.5 });
|
||||
assert.deepStrictEqual(
|
||||
helpers.getNodePortCanvasPoint({
|
||||
id: 'anchor-rotated',
|
||||
type: 'anchorNode',
|
||||
position: { x: 100, y: 200 },
|
||||
data: {
|
||||
elementType: 'anchor',
|
||||
rotation: 90,
|
||||
portNumber: 1,
|
||||
pitch: 10,
|
||||
ports: helpers.buildElementPorts('anchor', { portNumber: 1, pitch: 10 })
|
||||
}
|
||||
}, 'a1'),
|
||||
{ x: 115, y: 200 },
|
||||
'Anchor port endpoint coordinates should rotate with the anchor body'
|
||||
);
|
||||
assert.deepStrictEqual(helpers.buildElementBoxSize({ portNumber: 1 }), { width: 30, height: 30 });
|
||||
assert.deepStrictEqual(helpers.buildElementBoxSize({ elementType: 'anchor', portNumber: 1 }), { width: 8, height: 30 });
|
||||
assert.deepStrictEqual(helpers.buildElementBoxSize({ elementType: 'anchor', portNumber: 4, pitch: 10 }), { width: 8, height: 72 });
|
||||
assert.deepStrictEqual(helpers.buildElementBoxSize({ portNumber: 4, pitch: 10 }), { width: 30, height: 72 });
|
||||
assert.deepStrictEqual(
|
||||
helpers.buildPageComponentPorts(null, [{
|
||||
id: 'port-array',
|
||||
type: 'portNode',
|
||||
position: { x: 100, y: 200 },
|
||||
data: { componentDisplayName: 'array', elementType: 'port', portNumber: 3, pitch: 10, width: 0.6 }
|
||||
}]),
|
||||
{
|
||||
array_1: { x: 100, y: 190, a: 0, width: 0.6 },
|
||||
array_2: { x: 100, y: 200, a: 0, width: 0.6 },
|
||||
array_3: { x: 100, y: 210, a: 0, width: 0.6 }
|
||||
}
|
||||
);
|
||||
|
||||
const canvasPortsYaml = helpers.buildCanvasPortsYaml(elementNodes);
|
||||
assert(canvasPortsYaml.includes('name: in0'));
|
||||
@@ -314,6 +353,8 @@ assert(elementsYaml.includes('type: port'));
|
||||
assert(elementsYaml.includes('anchor_1:'));
|
||||
assert(elementsYaml.includes('type: anchor'));
|
||||
assert(elementsYaml.includes('y: -20.0'));
|
||||
assert(elementsYaml.includes('port_number: 1'));
|
||||
assert(elementsYaml.includes('pitch: 10'));
|
||||
|
||||
const instancesWithoutElements = helpers.buildInstancesYaml({
|
||||
nodes: elementNodes,
|
||||
@@ -484,7 +525,20 @@ const edgeD = {
|
||||
target: 'f',
|
||||
data: { route: { xsection: 'rib_low', family: 'optical' } }
|
||||
};
|
||||
const edgeE = {
|
||||
id: 'edge-metal-alias',
|
||||
source: 'e',
|
||||
target: 'f',
|
||||
data: { route: { xsection: 'metal1', family: 'electrical' } }
|
||||
};
|
||||
const edgeF = {
|
||||
id: 'edge-metal-underscore',
|
||||
source: 'a',
|
||||
target: 'b',
|
||||
data: { route: { xsection: 'metal_1', family: 'electrical' } }
|
||||
};
|
||||
assert.strictEqual(helpers.findSameTypeRouteCrossing(edgeB, [edgeA], crossingNodes).conflictEdge.id, 'edge-a-b');
|
||||
assert.strictEqual(helpers.findSameTypeRouteCrossing(edgeC, [edgeA], crossingNodes), null);
|
||||
assert.strictEqual(helpers.findSameTypeRouteCrossing(edgeD, [edgeA], crossingNodes), null);
|
||||
assert.strictEqual(helpers.findSameTypeRouteCrossing(edgeE, [edgeF], crossingNodes).conflictEdge.id, 'edge-metal-underscore');
|
||||
assert.strictEqual(helpers.findSameFamilyRouteCrossing(edgeB, [edgeA], crossingNodes).conflictEdge.id, 'edge-a-b');
|
||||
|
||||
@@ -85,6 +85,24 @@ assert(
|
||||
'Build GDS should not silently fall back to unrouted gdstk when links are present'
|
||||
);
|
||||
|
||||
const routerDir = path.resolve(root, '..', 'mxpic_router', 'mxpic_router');
|
||||
if (fs.existsSync(routerDir)) {
|
||||
const routerLoaderPy = fs.readFileSync(path.join(routerDir, 'eda_loader.py'), 'utf8');
|
||||
const routerBuilderPy = fs.readFileSync(path.join(routerDir, 'builder.py'), 'utf8');
|
||||
assert(
|
||||
routerLoaderPy.includes('port_number: int = 1') &&
|
||||
routerLoaderPy.includes('pitch: float = 10.0') &&
|
||||
routerLoaderPy.includes('port_number=_int(element.get("port_number"'),
|
||||
'mxpic_router loader should parse multi-port anchor metadata from exported elements'
|
||||
);
|
||||
assert(
|
||||
routerBuilderPy.includes('for index in range(port_number):') &&
|
||||
routerBuilderPy.includes('a{index + 1}') &&
|
||||
routerBuilderPy.includes('b{index + 1}'),
|
||||
'mxpic_router builder should register aN/bN pins for multi-port anchors'
|
||||
);
|
||||
}
|
||||
|
||||
assert(
|
||||
serverPy.includes('def scoped_pdk_root_for_project') &&
|
||||
serverPy.includes('read_project_meta(project_name).get("technology")') &&
|
||||
|
||||
@@ -196,8 +196,51 @@ assert(
|
||||
'holding a component and pressing Space should rotate it by 90 degrees'
|
||||
);
|
||||
assert(
|
||||
canvasHtml.includes('getSpaceRotationTarget') && canvasHtml.includes('selectedSpaceNode'),
|
||||
'Space rotation should also use the currently selected component when no mouse-hold target is active'
|
||||
canvasHtml.includes('getSpaceRotationTarget') &&
|
||||
canvasHtml.includes('selectedSpaceNode') &&
|
||||
canvasHtml.includes('node.type !== \'rotatableNode\' && node.type !== \'portNode\' && node.type !== \'anchorNode\'') &&
|
||||
canvasHtml.includes('node.type === \'portNode\' || node.data?.elementType === \'port\'') &&
|
||||
canvasHtml.includes('angle: normalizeAngle(Number(node.data?.angle || 0) + 90)'),
|
||||
'Space rotation should also rotate selected Port and Anchor elements'
|
||||
);
|
||||
assert(
|
||||
canvasHtml.includes('const anchorRotation = data.rotation || 0') &&
|
||||
canvasHtml.includes('const anchorVisualRotation = -Number(anchorRotation || 0)') &&
|
||||
canvasHtml.includes('transform: `rotate(${anchorVisualRotation}deg)`') &&
|
||||
canvasHtml.includes('buildPortHandles(localAnchorHandlePorts, { rotation: 0') &&
|
||||
canvasHtml.includes('anchorDirectionHandles') &&
|
||||
canvasHtml.includes('rotation: Number(anchorRotation || 0)') &&
|
||||
canvasHtml.includes('anchorHandleVisualStyle(portHandle') &&
|
||||
canvasHtml.includes('anchorPortVisualSide') &&
|
||||
canvasHtml.includes('portHandle.name') &&
|
||||
canvasHtml.includes('visualSide === \'left\' ? 0 : elementSize.width') &&
|
||||
canvasHtml.includes('anchorPortVisualTop') &&
|
||||
canvasHtml.includes('(index - 1) / (portCount - 1)') &&
|
||||
canvasHtml.includes('elementSize.height - baseHandleStyle.height') &&
|
||||
canvasHtml.includes('localLeft') &&
|
||||
canvasHtml.includes('localTop') &&
|
||||
canvasHtml.includes('handlePositionMap[anchorDirectionHandles.get(portHandle.name) || portHandle.position]') &&
|
||||
canvasHtml.includes('getAnchorHandleRouteDirection') &&
|
||||
canvasHtml.includes('rotation: Number(node.data?.rotation || 0)') &&
|
||||
canvasHtml.includes('directionToReactFlowPosition') &&
|
||||
canvasHtml.includes('sourcePosition: directionToReactFlowPosition(sourceDirection)') &&
|
||||
canvasHtml.includes('targetPosition: directionToReactFlowPosition(targetDirection)') &&
|
||||
!canvasHtml.includes('type: \'parallelRoute\',\n data: {\n ...(edge.data || {}),\n parallelOffset: offset,\n sourceDirection,\n targetDirection') &&
|
||||
!canvasHtml.includes('rotatedAnchorHandlePositions'),
|
||||
'Anchor port circles should split into side columns and spread across the full anchor body while built-in rectangular links use rotated directions'
|
||||
);
|
||||
assert(
|
||||
canvasHtml.includes('Port Number') &&
|
||||
canvasHtml.includes('Pitch') &&
|
||||
canvasHtml.includes('portNumber') &&
|
||||
canvasHtml.includes('pitch') &&
|
||||
canvasHtml.includes('DEFAULT_ELEMENT_PITCH') &&
|
||||
canvasHtml.includes('buildElementBoxSize') &&
|
||||
canvasHtml.includes('height: elementSize.height') &&
|
||||
canvasHtml.includes('elementType: \'anchor\'') &&
|
||||
canvasHtml.includes('pitch: DEFAULT_ELEMENT_PITCH') &&
|
||||
canvasHtml.includes('ports: buildElementPorts'),
|
||||
'Port and Anchor inspectors should expose port number and pitch, default to 10 um pitch, and grow in height'
|
||||
);
|
||||
assert(
|
||||
canvasHtml.includes('const componentIndexesByPrefixRef = useRef({});') &&
|
||||
|
||||
@@ -17,3 +17,27 @@ assert(
|
||||
canvasHtml.includes('layoutToCanvasY'),
|
||||
'loading saved layout YAML should convert GDS/layout Y coordinates back to canvas coordinates'
|
||||
);
|
||||
assert(
|
||||
canvasHtml.includes('buildElementNodesFromYaml'),
|
||||
'project loading should rebuild saved anchor/port element nodes from YAML elements'
|
||||
);
|
||||
assert(
|
||||
canvasHtml.includes('Object.entries(doc.elements || {})'),
|
||||
'project loading should read doc.elements, not only doc.instances'
|
||||
);
|
||||
assert(
|
||||
canvasHtml.includes('nodeNameMap[elementName] = nodeId'),
|
||||
'loaded element names should be registered so saved links can reconnect to anchors and ports'
|
||||
);
|
||||
assert(
|
||||
canvasHtml.includes('getAvailableComponentsForLoadedComponent'),
|
||||
'project loading should reconstruct PDK component selection options for saved instances'
|
||||
);
|
||||
assert(
|
||||
canvasHtml.includes('availableComponents: instIsForge ? [FORGE_COMPONENT_LABEL] : loadedAvailableComponents'),
|
||||
'loaded PDK instances should keep availableComponents so the right panel can show the PDK selector'
|
||||
);
|
||||
assert(
|
||||
canvasHtml.includes('Array.from(new Set([FORGE_COMPONENT_LABEL, ...sameCategoryComponents'),
|
||||
'loaded PDK selector choices should include forge and same-category library components'
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user