routing for canvase level object is finished.

This commit is contained in:
2026-05-30 16:37:37 +08:00
parent bf223b52ac
commit e3f708a1a7
10 changed files with 1647 additions and 567 deletions
Binary file not shown.
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 439 KiB

@@ -0,0 +1,243 @@
# =============================================
# mxPIC Cell/Project Definition File
# =============================================
schema_version: "2.0.0"
kind: cell
coordinate_system: gds_y_up
canvas_size:
width: 1000
height: 1000
project: mxpic_project_1
name: canvas_1
type: composite
version: "1.0.0"
# 1. External Ports (How this cell connects to the outside world)
ports:
- name: port
layer: WG_CORE
x: 200.0
y: -370.0
angle: 180.0
width: 0.5
- name: port_2
layer: WG_CORE
x: 200.0
y: -370.0
angle: 180.0
width: 0.5
- name: port_1
layer: WG_CORE
x: 200.0
y: -420.0
angle: 180.0
width: 0.5
- name: port_3
layer: WG_CORE
x: 691.9
y: -267.5
angle: 0.0
width: 0.5
# 2. Instances (The sub-components dropped onto this canvas)
instances:
MMI_7:
component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/multimode_interferometers/1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2
x: 310.0
y: -370.0
rotation: 0.0
flip: 0
flop: 0
mirror: false
settings:
length:
MMI_8:
component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/multimode_interferometers/1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2
x: 560.0
y: -130.0
rotation: 0.0
flip: 0
flop: 0
mirror: false
settings:
length:
MMI_9:
component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/multimode_interferometers/1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2
x: 560.0
y: -180.0
rotation: 0.0
flip: 0
flop: 0
mirror: false
settings:
length:
MMI_10:
component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/multimode_interferometers/1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2
x: 560.0
y: -230.0
rotation: 0.0
flip: 0
flop: 0
mirror: false
settings:
length:
MMI_11:
component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/multimode_interferometers/1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2
x: 560.0
y: -280.0
rotation: 0.0
flip: 0
flop: 0
mirror: false
settings:
length:
MMI_12:
component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/multimode_interferometers/1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2
x: 310.0
y: -420.0
rotation: 0.0
flip: 0
flop: 0
mirror: false
settings:
length:
elements:
port:
type: port
x: 200.0
y: -370.0
angle: 0.0
port_number: 1
pitch: 10
layer: WG_CORE
width: 0.5
description: ""
Anchor_1:
type: anchor
x: 460.0
y: -300.0
angle: 90.0
port_number: 4
pitch: 10
layer: WG_CORE
width: 0.5
description: ""
port_2:
type: port
x: 200.0
y: -370.0
angle: 0.0
port_number: 1
pitch: 10
layer: WG_CORE
width: 0.5
description: ""
port_1:
type: port
x: 200.0
y: -420.0
angle: 0.0
port_number: 1
pitch: 10
layer: WG_CORE
width: 0.5
description: ""
port_3:
type: port
x: 691.9
y: -267.5
angle: 180.0
port_number: 1
pitch: 10
layer: WG_CORE
width: 0.5
description: ""
# 3. Bundles (Grouped links for multi-bus/parallel routing)
bundles:
output_bus:
routing_type: euler_bend
links:
- from: MMI_7:a1
to: port_2:port
xsection: strip
family: optical
width: 0.45
radius: 10
routing_type: euler_bend
- from: MMI_12:a1
to: port_1:port
xsection: strip
family: optical
width: 0.45
radius: 10
routing_type: euler_bend
- from: MMI_7:b1
to: Anchor_1:a1
xsection: strip
family: optical
width: 0.45
radius: 10
routing_type: euler_bend
- from: MMI_7:b2
to: Anchor_1:a2
xsection: strip
family: optical
width: 0.45
radius: 10
routing_type: euler_bend
- from: MMI_12:b1
to: Anchor_1:a3
xsection: strip
family: optical
width: 0.45
radius: 10
routing_type: euler_bend
- from: MMI_12:b2
to: Anchor_1:a4
xsection: strip
family: optical
width: 0.45
radius: 10
routing_type: euler_bend
- from: Anchor_1:b4
to: MMI_11:a1
xsection: strip
family: optical
width: 0.45
radius: 10
routing_type: euler_bend
- from: Anchor_1:b3
to: MMI_10:a1
xsection: strip
family: optical
width: 0.45
radius: 10
routing_type: euler_bend
- from: Anchor_1:b2
to: MMI_9:a1
xsection: strip
family: optical
width: 0.45
radius: 10
routing_type: euler_bend
- from: Anchor_1:b1
to: MMI_8:a1
xsection: strip
family: optical
width: 0.45
radius: 10
routing_type: euler_bend
- from: MMI_11:b1
to: port_3:port
xsection: strip
family: optical
width: 0.45
radius: 10
routing_type: euler_bend
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 538 KiB

After

Width:  |  Height:  |  Size: 579 KiB

@@ -23,21 +23,10 @@ ports:
# 2. Instances (The sub-components dropped onto this canvas)
instances:
EC_1:
component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/edge_couplers/EC_SiN400_1310_1p0dB_L635_A0_QY_202604
x: 0.0
y: -2660.0
rotation: 180.0
flip: 0
flop: 0
mirror: false
settings:
length:
MMI_1:
component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/multimode_interferometers/1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2
x: 936.8
y: -2358.5
MZM_1:
component: Silterra/EMO1_2ML_CU_Al_RDL/composites/Mach_Zender_modulators/MZI_SiN400_Si220_PIN_mod_1310_L1300_QY_202603
x: 1740.0
y: -2350.0
rotation: 0.0
flip: 0
flop: 0
@@ -45,87 +34,10 @@ instances:
settings:
length:
MMI_2:
component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/multimode_interferometers/1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2
x: 1089.2
y: -2247.3
rotation: 0.0
flip: 0
flop: 0
mirror: false
settings:
length:
MMI_3:
component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/multimode_interferometers/1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2
x: 1096.8
y: -2598.0
rotation: 0.0
flip: 0
flop: 0
mirror: false
settings:
length:
MMI_4:
component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/multimode_interferometers/1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2
x: 735.0
y: -2541.1
rotation: 90.0
flip: 0
flop: 0
mirror: false
settings:
length:
MMI_5:
component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/multimode_interferometers/1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2
x: 1086.5
y: -2097.1
rotation: 0.0
flip: 0
flop: 0
mirror: false
settings:
length:
EC_2:
component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/edge_couplers/EC_SiN400_1310_1p0dB_L635_A0_QY_202604
x: 0.0
y: -2825.7
rotation: 180.0
flip: 0
flop: 0
mirror: false
settings:
length:
MMI_6:
component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/multimode_interferometers/1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2
x: 913.7
y: -2537.3
rotation: 90.0
flip: 0
flop: 0
mirror: false
settings:
length:
MMI_7:
component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/multimode_interferometers/1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2
x: 1027.2
y: -2736.7
rotation: 0.0
flip: 0
flop: 0
mirror: false
settings:
length:
MMI_8:
component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/multimode_interferometers/1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2
x: 842.6
y: -2941.6
canvas_1:
component: canvas_1
x: 903.5
y: -2681.6
rotation: 0.0
flip: 0
flop: 0
@@ -138,86 +50,27 @@ elements:
type: port
x: 50.0
y: -150.0
angle: 0.0
angle: 180.0
port_number: 1
pitch: 10
layer: WG_CORE
width: 0.5
description: ""
anchor_1:
type: anchor
x: 732.7
y: -2824.7
angle: 0.0
port_number: 5
pitch: 10
layer: WG_CORE
width: 0.5
description: ""
# 3. Bundles (Grouped links for multi-bus/parallel routing)
bundles:
output_bus:
routing_type: euler_bend
links:
- from: MMI_1:b1
to: MMI_2:a1
- from: canvas_1:port_1
to: MZM_1:a1
xsection: strip
family: optical
width: 0.45
radius: 10
routing_type: euler_bend
- from: MMI_1:b2
to: MMI_3:a1
xsection: strip
family: optical
width: 0.45
radius: 10
routing_type: euler_bend
- from: EC_1:a1
to: anchor_1:a1
xsection: strip
family: optical
width: 0.45
radius: 10
routing_type: euler_bend
- from: anchor_1:b1
to: MMI_4:a1
xsection: strip
family: optical
width: 0.45
radius: 10
routing_type: euler_bend
- from: MMI_4:b2
to: MMI_1:a1
xsection: strip
family: optical
width: 0.45
radius: 10
routing_type: euler_bend
- from: MMI_5:a1
to: MMI_4:b1
xsection: strip
family: optical
width: 0.45
radius: 10
routing_type: euler_bend
- from: EC_2:a1
to: anchor_1:a2
xsection: strip
family: optical
width: 0.45
radius: 10
routing_type: euler_bend
- from: anchor_1:b2
to: MMI_6:a1
xsection: strip
family: optical
width: 0.45
radius: 10
routing_type: euler_bend
- from: MMI_8:b1
to: MMI_7:a1
- from: canvas_1:port_3
to: MZM_1:a2
xsection: strip
family: optical
width: 0.45
Binary file not shown.
+35 -10
View File
@@ -446,7 +446,8 @@
const vertical = side === 'left' || side === 'right';
return ports.map((port, index) => {
const percent = fallbackPercent(index, ports.length);
const explicitPercent = Number(port.info && port.info.handlePercent);
const percent = Number.isFinite(explicitPercent) ? explicitPercent : fallbackPercent(index, ports.length);
const percentValue = `${percent}%`;
const style = vertical
? { top: percentValue, transform: side === 'left' ? 'translate(-50%, -50%)' : 'translate(50%, -50%)' }
@@ -587,6 +588,24 @@
// Calculate the centered offset for one repeated port index.
const elementPortOffset = (index, count, pitch) => ((count - 1) / 2 - index) * pitch;
// Keep Basic line-like components at twice the 10 px canvas port-circle size.
const BASIC_LINE_COMPONENT_HEIGHT = 20;
// Keep Basic line-like components visually slim with a stable canvas hit area.
const basicLineComponentHeight = () => BASIC_LINE_COMPONENT_HEIGHT;
// Keep bend components readable by using the radius-25 footprint as the
// minimum canvas size, while still allowing larger radii to grow.
const BASIC_BEND_MIN_CANVAS_RADIUS = 25;
// Normalize bend radius into a positive canvas footprint dimension.
const basicBendRadiusSize = (radius) => {
const numericRadius = Number(radius || 0);
return Number.isFinite(numericRadius) && numericRadius > 0
? Math.max(BASIC_BEND_MIN_CANVAS_RADIUS, numericRadius)
: BASIC_BEND_MIN_CANVAS_RADIUS;
};
// Grow port and anchor visual bodies so repeated port circles do not overlap.
const buildElementBoxSize = (data) => {
const portNumber = normalizePortNumber(data && data.portNumber);
@@ -643,6 +662,7 @@
const values = createBasicSettings(componentName, settings);
const length = Number(values.length || 0);
const radius = Number(values.radius || 10);
const bendSize = basicBendRadiusSize(radius);
const width = Number(values.width ?? values.width1 ?? 0.5);
const xsection = values.xsection || values.xs || 'strip';
if (componentName === 'waveguide') {
@@ -653,14 +673,14 @@
}
if (componentName === '90 bend') {
return {
a1: { x: 0, y: 0, a: 180, width, xsection, description: 'Optical power input' },
b1: { x: radius, y: radius, a: 90, width, xsection, description: 'Optical power output' }
a1: { x: 0, y: bendSize / 2, a: 180, width, xsection, description: 'Optical power input' },
b1: { x: bendSize / 2, y: 0, a: 90, width, xsection, description: 'Optical power output' }
};
}
if (componentName === '180 bend') {
return {
a1: { x: 0, y: 0, a: 180, width, xsection, description: 'Optical power input' },
b1: { x: 0, y: 2 * radius, a: 180, width, xsection, description: 'Optical power output' }
b1: { x: 0, y: 2 * bendSize, a: 180, width, xsection, description: 'Optical power output' }
};
}
if (componentName === 'cricle' || componentName === 'circle') {
@@ -686,13 +706,14 @@
const radius = Number(values.radius || 10);
const width = Number(values.width ?? values.width1 ?? 0.5);
const width2 = Number(values.width2 ?? width);
const bendSize = basicBendRadiusSize(radius);
const boxSize = componentName === 'waveguide'
? [Math.max(length, 10), Math.max(width * 4, 4)]
? [Math.max(length, 10), basicLineComponentHeight(width)]
: componentName === 'taper'
? [Math.max(length, 10), Math.max(width, width2) * 10 + 18]
? [Math.max(length, 10), basicLineComponentHeight(width, width2)]
: componentName === '180 bend'
? [radius, radius * 2]
: [radius, radius];
? [bendSize, bendSize * 2]
: [bendSize, bendSize];
return {
name: componentName,
foundry: 'mxpic',
@@ -703,6 +724,10 @@
};
};
// Flip an internal standalone Port angle into the outward-facing cell port
// angle used when this canvas is placed as a component elsewhere.
const externalPortAngle = (angle) => normalizeAngle(Number(angle ?? 0) + 180);
// Convert standalone port nodes into page-level layout ports.
const buildPageComponentPorts = (port, nodes) => {
const portNodes = (nodes || []).filter(isPortElementNode);
@@ -723,7 +748,7 @@
ports[exportName] = {
x: Number(point.x || 0),
y: Number(point.y || 0),
a: Number(portInfo.a ?? data.angle ?? data.a ?? 0),
a: externalPortAngle(portInfo.a ?? data.angle ?? data.a ?? 0),
width: Number(portInfo.width || data.width || 0.5)
};
});
@@ -735,7 +760,7 @@
port: {
x: Number(port.x || 0),
y: Number(port.y || 0),
a: Number(port.a || 0),
a: externalPortAngle(port.a || 0),
width: Number(port.width || 0.5)
}
};
+54 -18
View File
@@ -1186,7 +1186,7 @@ Organization : OptiHK Limited
border: 1px solid var(--floating-label-border);
color: var(--port-label-text);
box-shadow: 0 5px 12px rgba(0, 0, 0, 0.18);
font-size: 0.42rem;
font-size: 0.3rem;
line-height: 1.2;
font-family: 'IBM Plex Mono', Consolas, Monaco, monospace;
white-space: nowrap;
@@ -1278,6 +1278,10 @@ Organization : OptiHK Limited
box-shadow: var(--floating-label-shadow);
}
.canvas-text-hidden .component-floating-label {
display: none;
}
body.light-mode .port-name-label {
background: var(--port-label-bg);
border-color: var(--floating-label-border);
@@ -1312,14 +1316,14 @@ Organization : OptiHK Limited
}
.component-floating-label strong {
font-size: 0.52rem;
font-size: 0.4rem;
font-weight: 650;
}
.component-floating-label span {
margin-top: 1px;
color: var(--text-muted);
font-size: 0.44rem;
font-size: 0.32rem;
}
.canvas-size-panel {
@@ -1379,7 +1383,7 @@ Organization : OptiHK Limited
background: rgba(9, 18, 28, 0.94);
color: #e2f7f3;
box-shadow: 0 10px 24px rgba(0, 0, 0, 0.3);
font: 600 0.62rem/1.35 'IBM Plex Mono', Consolas, Monaco, monospace;
font: 600 0.5rem/1.35 'IBM Plex Mono', Consolas, Monaco, monospace;
text-align: center;
transform: translate(-50%, -50%);
pointer-events: none;
@@ -1594,7 +1598,7 @@ Organization : OptiHK Limited
}, [id, data.ports, data.componentName, data.boxSize]);
const baseHandleStyle = {
width: 10, height: 10,
width: 8, height: 8,
background: 'var(--bg-main)',
border: '2px solid var(--accent)',
borderRadius: '50%',
@@ -1611,6 +1615,7 @@ Organization : OptiHK Limited
);
const componentSize = normalizeBoxSize({ box_size: data.boxSize }, DEFAULT_COMPONENT_BOX_SIZE);
const isAnchorElement = data.elementType === 'anchor';
const isBasicCompactComponent = isBasicComponent(data.componentName) && ['waveguide', 'taper', '90 bend'].includes(data.componentName);
const visualSize = isAnchorElement ? { width: PORT_NODE_SIZE, height: PORT_NODE_SIZE } : componentSize;
const iconSize = createComponentSymbolMetrics(componentSize);
const portLabelStyle = (portHandle) => {
@@ -1643,9 +1648,16 @@ Organization : OptiHK Limited
style={{
width: componentSize.width,
height: visualSize.height,
minHeight: visualSize.height,
border: selected ? '2px solid var(--accent)' : '1px solid var(--border)',
transform: `rotate(${data.rotation || 0}deg) scaleX(${data.flop ? -1 : 1}) scaleY(${data.flip ? -1 : 1})`,
boxShadow: selected ? '0 0 15px rgba(56, 189, 248, 0.2)' : '0 4px 6px rgba(0,0,0,0.3)',
...(isBasicCompactComponent ? {
padding: 0,
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
} : {}),
...(isAnchorElement ? {
width: PORT_NODE_SIZE,
minHeight: PORT_NODE_SIZE,
@@ -1658,7 +1670,7 @@ Organization : OptiHK Limited
}}
>
{isAnchorElement ? (
<span style={{ fontSize: 10, fontWeight: 800, color: selected ? 'var(--accent)' : 'var(--text-main)' }}>A</span>
<span style={{ fontSize: 8, fontWeight: 800, color: selected ? 'var(--accent)' : 'var(--text-main)' }}>A</span>
) : (
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: '8px', minHeight: '100%' }}>
{!data.hideIcon && data.category && (
@@ -1733,8 +1745,8 @@ Organization : OptiHK Limited
};
const baseHandleStyle = {
background: 'var(--accent)',
width: 8,
height: 8
width: 6,
height: 6
};
return (
<div style={{
@@ -1743,7 +1755,7 @@ Organization : OptiHK Limited
border: selected ? '2px solid white' : '2px solid var(--accent)',
display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
color: selected ? 'white' : 'var(--accent)',
fontSize: 10, fontWeight: 'bold',
fontSize: 8, fontWeight: 'bold',
boxShadow: selected ? '0 0 10px rgba(56,189,248,0.4)' : 'none',
transform: `rotate(${angle}deg)`,
}}>
@@ -1783,8 +1795,8 @@ Organization : OptiHK Limited
bottom: Position.Bottom
};
const baseHandleStyle = {
width: 8,
height: 8,
width: 6,
height: 6,
background: 'var(--accent)',
border: '1px solid var(--bg-main)',
borderRadius: '50%'
@@ -2729,6 +2741,7 @@ Organization : OptiHK Limited
const selectedNodeBoxSize = selectedNode?.data?.componentName && !selectedNode?.data?.elementType
? normalizeBoxSize({ box_size: selectedNode.data?.boxSize }, DEFAULT_COMPONENT_BOX_SIZE)
: null;
const xsections = Object.keys((technologyManifest || FALLBACK_TECHNOLOGY_MANIFEST).xsections || {});
const selectedRouteEdges = selectedEdges.length > 0 ? selectedEdges : (selectedEdge ? [selectedEdge] : []);
if (selectedRouteEdges.length > 0) {
@@ -2744,7 +2757,6 @@ Organization : OptiHK Limited
radius: mixedValue('radius'),
routing_type: mixedValue('routing_type')
};
const xsections = Object.keys((technologyManifest || FALLBACK_TECHNOLOGY_MANIFEST).xsections || {});
const routingTypes = (technologyManifest || FALLBACK_TECHNOLOGY_MANIFEST).routing_types || ['euler_bend', 'standard_bend'];
return (
<aside style={{
@@ -3164,12 +3176,26 @@ Organization : OptiHK Limited
{Object.entries(basicArguments).map(([key, value]) => (
<label key={key} style={{ display: 'grid', gap: 4 }}>
<span style={{ color: 'var(--text-muted)' }}>{key}</span>
<input
type={typeof value === 'number' ? 'number' : 'text'}
step="any"
value={value ?? ''}
onChange={(event) => updateBasicArgument(key, event.target.value)}
/>
{key === 'xsection' ? (
<select
value={value ?? ''}
onChange={(event) => updateBasicArgument(key, event.target.value)}
>
{value && !xsections.includes(value) && (
<option value={value}>{value}</option>
)}
{xsections.map(xsection => (
<option key={xsection} value={xsection}>{xsection}</option>
))}
</select>
) : (
<input
type={typeof value === 'number' ? 'number' : 'text'}
step="any"
value={value ?? ''}
onChange={(event) => updateBasicArgument(key, event.target.value)}
/>
)}
</label>
))}
</div>
@@ -3461,6 +3487,7 @@ Organization : OptiHK Limited
const [dragging, setDragging] = useState(null);
const [gridSnap, setGridSnap] = useState(false);
const [canvasTextVisible, setCanvasTextVisible] = useState(true);
const [themeMode, setThemeMode] = useState(() => localStorage.getItem('mxpic-theme') || 'dark');
const [logs, setLogs] = useState([{ time: new Date().toLocaleTimeString(), message: 'Editor ready.' }]);
const [buildLayoutBusy, setBuildLayoutBusy] = useState(false);
@@ -5463,6 +5490,11 @@ Organization : OptiHK Limited
setGridSnap(prev => !prev);
}, []);
// Toggle the instance name/PDK labels shown above canvas components.
const toggleCanvasText = useCallback(() => {
setCanvasTextVisible(prev => !prev);
}, []);
// Toggle the measurement ruler and clear partial measurements.
const toggleRulerMode = useCallback(() => {
setRulerMode(prev => {
@@ -5999,6 +6031,9 @@ ${bundlesBlock}`;
transition: 'transform 0.2s',
}} />
</div>
<button className="mini-btn" onClick={toggleCanvasText} aria-pressed={canvasTextVisible ? 'true' : 'false'}>
{canvasTextVisible ? 'Text On' : 'Text Off'}
</button>
<details className="link-mode-tabs" title="Route type used for new links">
<summary className="link-mode-summary">
<span className="link-mode-label">Link</span>
@@ -6069,6 +6104,7 @@ ${bundlesBlock}`;
<LayoutSvgPreview page={activePage} />
) : (
<ReactFlow
className={canvasTextVisible ? '' : 'canvas-text-hidden'}
nodes={renderNodes}
edges={renderEdges}
onNodesChange={onNodesChange}
+41 -12
View File
@@ -172,18 +172,46 @@ assert.deepStrictEqual(
);
assert.deepStrictEqual(
helpers.getBasicComponentMetadata('waveguide', { length: 120, width: 0.5 }).box_size,
[120, 4],
'basic waveguide symbol should use a narrow default height'
[120, 20],
'basic waveguide symbol should use a height that is two port-circle diameters'
);
assert.deepStrictEqual(
helpers.getBasicComponentMetadata('90 bend', { radius: 15 }).box_size,
[15, 15],
'90 bend symbol should be square with side length equal to radius'
[25, 25],
'90 bend symbol should not shrink below the radius-25 canvas size'
);
assert.deepStrictEqual(
helpers.getBasicComponentMetadata('90 bend', { radius: 30 }).box_size,
[30, 30],
'90 bend symbol should still scale above radius 25'
);
assert.deepStrictEqual(
helpers.buildBasicComponentPorts('90 bend', { radius: 15, width: 0.6 }),
{
a1: { x: 0, y: 12.5, a: 180, width: 0.6, xsection: 'strip', description: 'Optical power input' },
b1: { x: 12.5, y: 0, a: 90, width: 0.6, xsection: 'strip', description: 'Optical power output' }
},
'90 bend ports should sit at the middle of the left and top sides of the square'
);
const ninetyBendHandles = helpers.buildPortHandles(helpers.buildBasicComponentPorts('90 bend', { radius: 15 }));
assert.strictEqual(ninetyBendHandles.find(handle => handle.name === 'a1').position, 'left');
assert.strictEqual(ninetyBendHandles.find(handle => handle.name === 'a1').style.top, '50%');
assert.strictEqual(ninetyBendHandles.find(handle => handle.name === 'b1').position, 'top');
assert.strictEqual(ninetyBendHandles.find(handle => handle.name === 'b1').style.left, '50%');
assert.deepStrictEqual(
helpers.getBasicComponentMetadata('180 bend', { radius: 15 }).box_size,
[15, 30],
'180 bend symbol should be one radius wide and two radii tall'
[25, 50],
'180 bend symbol should not shrink below the radius-25 canvas size'
);
assert.deepStrictEqual(
helpers.getBasicComponentMetadata('180 bend', { radius: 30 }).box_size,
[30, 60],
'180 bend symbol should still scale above radius 25'
);
assert.deepStrictEqual(
helpers.getBasicComponentMetadata('taper', { length: 80, width1: 0.4, width2: 1.2 }).box_size,
[80, 20],
'basic taper symbol should use a height that is two port-circle diameters'
);
assert.deepStrictEqual(
helpers.getBasicComponentMetadata('taper', { length: 80, width1: 0.4, width2: 1.2 }).ports.a1.description,
@@ -259,11 +287,11 @@ const pagePortsYaml = helpers.buildPortsYaml({ x: 50, y: 150, a: 90 });
assert(pagePortsYaml.includes('- name: port'));
assert(pagePortsYaml.includes('x: 50.0'));
assert(pagePortsYaml.includes('y: -150.0'));
assert(pagePortsYaml.includes('angle: 90.0'));
assert(pagePortsYaml.includes('angle: -90.0'));
const componentPorts = helpers.buildPageComponentPorts({ x: 12, y: -6, a: 180 });
assert.deepStrictEqual(componentPorts, {
port: { x: 12, y: -6, a: 180, width: 0.5 }
port: { x: 12, y: -6, a: 0, width: 0.5 }
});
const elementNodes = [
@@ -341,9 +369,9 @@ assert.deepStrictEqual(
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 }
array_1: { x: 100, y: 190, a: 180, width: 0.6 },
array_2: { x: 100, y: 200, a: 180, width: 0.6 },
array_3: { x: 100, y: 210, a: 180, width: 0.6 }
}
);
@@ -352,6 +380,7 @@ assert(canvasPortsYaml.includes('name: in0'));
assert(canvasPortsYaml.includes('description: "input port"'));
assert(canvasPortsYaml.includes('width: 0.7'));
assert(canvasPortsYaml.includes('y: -20.0'));
assert(canvasPortsYaml.includes('angle: 0.0'));
const elementsYaml = helpers.buildElementsYaml(elementNodes);
assert(elementsYaml.includes('in0:'));
@@ -372,7 +401,7 @@ assert(instancesWithoutElements.includes('component_1:'));
assert(instancesWithoutElements.includes('y: -60.0'));
const multiPortComponentPorts = helpers.buildPageComponentPorts(null, elementNodes);
assert.deepStrictEqual(multiPortComponentPorts.in0, { x: 10, y: 20, a: 180, width: 0.7 });
assert.deepStrictEqual(multiPortComponentPorts.in0, { x: 10, y: 20, a: 0, width: 0.7 });
const technologyManifest = {
defaults: { xsection: 'strip', width: 0.45, radius: 10, routing_type: 'euler_bend' },
+37
View File
@@ -282,6 +282,15 @@ assert(
canvasHtml.includes('component-floating-label') && canvasHtml.includes('component-visual-body'),
'component labels should float outside the rotated body'
);
assert(
canvasHtml.includes('canvasTextVisible') &&
canvasHtml.includes('toggleCanvasText') &&
canvasHtml.includes('Text On') &&
canvasHtml.includes('Text Off') &&
canvasHtml.includes('canvas-text-hidden') &&
canvasHtml.includes('.canvas-text-hidden .component-floating-label'),
'canvas toolbar should toggle instance name and PDK text above components'
);
assert(
canvasHtml.includes('--floating-label-bg') && canvasHtml.includes('--port-label-bg') && canvasHtml.includes('--mini-button-bg'),
'theme variables should keep labels, port chips, and header buttons readable in light and dark modes'
@@ -400,6 +409,34 @@ assert(
canvasHtml.includes("isUserCell ? 'compact-tree-card' : ''"),
'Basic, Port, and Anchor entries should render as consistent 2D cards instead of compact list rows'
);
assert(
canvasHtml.includes("key === 'xsection'") &&
canvasHtml.includes('<select') &&
canvasHtml.includes('xsections.map(xsection =>') &&
canvasHtml.includes('updateBasicArgument(key, event.target.value)'),
'Basic component xsection should be selected from technology manifest xsections instead of free text'
);
assert(
canvasHtml.indexOf('const xsections = Object.keys') <
canvasHtml.indexOf('if (selectedRouteEdges.length > 0)'),
'Basic and route property panels should share the same xsection list from RightPanel scope'
);
assert(
canvasHtml.includes("['waveguide', 'taper', '90 bend'].includes(data.componentName)") &&
canvasHtml.includes('minHeight: visualSize.height') &&
canvasHtml.includes('isBasicCompactComponent ?'),
'waveguide, taper, and 90 bend nodes should override the default component min-height and padding on the canvas'
);
assert(
canvasHtml.includes('font-size: 0.3rem;') &&
canvasHtml.includes('font-size: 0.4rem;') &&
canvasHtml.includes('font-size: 0.32rem;') &&
canvasHtml.includes("font: 600 0.5rem/1.35") &&
canvasHtml.includes('width: 8, height: 8') &&
canvasHtml.includes('width: 6,') &&
canvasHtml.includes('fontSize: 8'),
'canvas labels and port circles should render smaller than the previous sizing'
);
assert(
canvasHtml.includes('ParallelRouteEdge') &&
canvasHtml.includes('parallelOffset') &&