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) # 2. Instances (The sub-components dropped onto this canvas)
instances: instances:
EC_1: MZM_1:
component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/edge_couplers/EC_SiN400_1310_1p0dB_L635_A0_QY_202604 component: Silterra/EMO1_2ML_CU_Al_RDL/composites/Mach_Zender_modulators/MZI_SiN400_Si220_PIN_mod_1310_L1300_QY_202603
x: 0.0 x: 1740.0
y: -2660.0 y: -2350.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
rotation: 0.0 rotation: 0.0
flip: 0 flip: 0
flop: 0 flop: 0
@@ -45,87 +34,10 @@ instances:
settings: settings:
length: length:
MMI_2: canvas_1:
component: Silterra/EMO1_2ML_CU_Al_RDL/primitives/multimode_interferometers/1x2MMI_1310nm_TE_Silterra_202603_ZKY_v2 component: canvas_1
x: 1089.2 x: 903.5
y: -2247.3 y: -2681.6
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
rotation: 0.0 rotation: 0.0
flip: 0 flip: 0
flop: 0 flop: 0
@@ -138,86 +50,27 @@ elements:
type: port type: port
x: 50.0 x: 50.0
y: -150.0 y: -150.0
angle: 0.0 angle: 180.0
port_number: 1 port_number: 1
pitch: 10 pitch: 10
layer: WG_CORE layer: WG_CORE
width: 0.5 width: 0.5
description: "" 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) # 3. Bundles (Grouped links for multi-bus/parallel routing)
bundles: bundles:
output_bus: output_bus:
routing_type: euler_bend routing_type: euler_bend
links: links:
- from: MMI_1:b1 - from: canvas_1:port_1
to: MMI_2:a1 to: MZM_1:a1
xsection: strip xsection: strip
family: optical family: optical
width: 0.45 width: 0.45
radius: 10 radius: 10
routing_type: euler_bend routing_type: euler_bend
- from: MMI_1:b2 - from: canvas_1:port_3
to: MMI_3:a1 to: MZM_1:a2
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
xsection: strip xsection: strip
family: optical family: optical
width: 0.45 width: 0.45
Binary file not shown.
+35 -10
View File
@@ -446,7 +446,8 @@
const vertical = side === 'left' || side === 'right'; const vertical = side === 'left' || side === 'right';
return ports.map((port, index) => { 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 percentValue = `${percent}%`;
const style = vertical const style = vertical
? { top: percentValue, transform: side === 'left' ? 'translate(-50%, -50%)' : 'translate(50%, -50%)' } ? { top: percentValue, transform: side === 'left' ? 'translate(-50%, -50%)' : 'translate(50%, -50%)' }
@@ -587,6 +588,24 @@
// Calculate the centered offset for one repeated port index. // Calculate the centered offset for one repeated port index.
const elementPortOffset = (index, count, pitch) => ((count - 1) / 2 - index) * pitch; 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. // Grow port and anchor visual bodies so repeated port circles do not overlap.
const buildElementBoxSize = (data) => { const buildElementBoxSize = (data) => {
const portNumber = normalizePortNumber(data && data.portNumber); const portNumber = normalizePortNumber(data && data.portNumber);
@@ -643,6 +662,7 @@
const values = createBasicSettings(componentName, settings); const values = createBasicSettings(componentName, settings);
const length = Number(values.length || 0); const length = Number(values.length || 0);
const radius = Number(values.radius || 10); const radius = Number(values.radius || 10);
const bendSize = basicBendRadiusSize(radius);
const width = Number(values.width ?? values.width1 ?? 0.5); const width = Number(values.width ?? values.width1 ?? 0.5);
const xsection = values.xsection || values.xs || 'strip'; const xsection = values.xsection || values.xs || 'strip';
if (componentName === 'waveguide') { if (componentName === 'waveguide') {
@@ -653,14 +673,14 @@
} }
if (componentName === '90 bend') { if (componentName === '90 bend') {
return { return {
a1: { x: 0, y: 0, a: 180, width, xsection, description: 'Optical power input' }, a1: { x: 0, y: bendSize / 2, a: 180, width, xsection, description: 'Optical power input' },
b1: { x: radius, y: radius, a: 90, width, xsection, description: 'Optical power output' } b1: { x: bendSize / 2, y: 0, a: 90, width, xsection, description: 'Optical power output' }
}; };
} }
if (componentName === '180 bend') { if (componentName === '180 bend') {
return { return {
a1: { x: 0, y: 0, a: 180, width, xsection, description: 'Optical power input' }, 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') { if (componentName === 'cricle' || componentName === 'circle') {
@@ -686,13 +706,14 @@
const radius = Number(values.radius || 10); const radius = Number(values.radius || 10);
const width = Number(values.width ?? values.width1 ?? 0.5); const width = Number(values.width ?? values.width1 ?? 0.5);
const width2 = Number(values.width2 ?? width); const width2 = Number(values.width2 ?? width);
const bendSize = basicBendRadiusSize(radius);
const boxSize = componentName === 'waveguide' const boxSize = componentName === 'waveguide'
? [Math.max(length, 10), Math.max(width * 4, 4)] ? [Math.max(length, 10), basicLineComponentHeight(width)]
: componentName === 'taper' : componentName === 'taper'
? [Math.max(length, 10), Math.max(width, width2) * 10 + 18] ? [Math.max(length, 10), basicLineComponentHeight(width, width2)]
: componentName === '180 bend' : componentName === '180 bend'
? [radius, radius * 2] ? [bendSize, bendSize * 2]
: [radius, radius]; : [bendSize, bendSize];
return { return {
name: componentName, name: componentName,
foundry: 'mxpic', 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. // Convert standalone port nodes into page-level layout ports.
const buildPageComponentPorts = (port, nodes) => { const buildPageComponentPorts = (port, nodes) => {
const portNodes = (nodes || []).filter(isPortElementNode); const portNodes = (nodes || []).filter(isPortElementNode);
@@ -723,7 +748,7 @@
ports[exportName] = { ports[exportName] = {
x: Number(point.x || 0), x: Number(point.x || 0),
y: Number(point.y || 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) width: Number(portInfo.width || data.width || 0.5)
}; };
}); });
@@ -735,7 +760,7 @@
port: { port: {
x: Number(port.x || 0), x: Number(port.x || 0),
y: Number(port.y || 0), y: Number(port.y || 0),
a: Number(port.a || 0), a: externalPortAngle(port.a || 0),
width: Number(port.width || 0.5) width: Number(port.width || 0.5)
} }
}; };
+54 -18
View File
@@ -1186,7 +1186,7 @@ Organization : OptiHK Limited
border: 1px solid var(--floating-label-border); border: 1px solid var(--floating-label-border);
color: var(--port-label-text); color: var(--port-label-text);
box-shadow: 0 5px 12px rgba(0, 0, 0, 0.18); box-shadow: 0 5px 12px rgba(0, 0, 0, 0.18);
font-size: 0.42rem; font-size: 0.3rem;
line-height: 1.2; line-height: 1.2;
font-family: 'IBM Plex Mono', Consolas, Monaco, monospace; font-family: 'IBM Plex Mono', Consolas, Monaco, monospace;
white-space: nowrap; white-space: nowrap;
@@ -1278,6 +1278,10 @@ Organization : OptiHK Limited
box-shadow: var(--floating-label-shadow); box-shadow: var(--floating-label-shadow);
} }
.canvas-text-hidden .component-floating-label {
display: none;
}
body.light-mode .port-name-label { body.light-mode .port-name-label {
background: var(--port-label-bg); background: var(--port-label-bg);
border-color: var(--floating-label-border); border-color: var(--floating-label-border);
@@ -1312,14 +1316,14 @@ Organization : OptiHK Limited
} }
.component-floating-label strong { .component-floating-label strong {
font-size: 0.52rem; font-size: 0.4rem;
font-weight: 650; font-weight: 650;
} }
.component-floating-label span { .component-floating-label span {
margin-top: 1px; margin-top: 1px;
color: var(--text-muted); color: var(--text-muted);
font-size: 0.44rem; font-size: 0.32rem;
} }
.canvas-size-panel { .canvas-size-panel {
@@ -1379,7 +1383,7 @@ Organization : OptiHK Limited
background: rgba(9, 18, 28, 0.94); background: rgba(9, 18, 28, 0.94);
color: #e2f7f3; color: #e2f7f3;
box-shadow: 0 10px 24px rgba(0, 0, 0, 0.3); 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; text-align: center;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
pointer-events: none; pointer-events: none;
@@ -1594,7 +1598,7 @@ Organization : OptiHK Limited
}, [id, data.ports, data.componentName, data.boxSize]); }, [id, data.ports, data.componentName, data.boxSize]);
const baseHandleStyle = { const baseHandleStyle = {
width: 10, height: 10, width: 8, height: 8,
background: 'var(--bg-main)', background: 'var(--bg-main)',
border: '2px solid var(--accent)', border: '2px solid var(--accent)',
borderRadius: '50%', borderRadius: '50%',
@@ -1611,6 +1615,7 @@ Organization : OptiHK Limited
); );
const componentSize = normalizeBoxSize({ box_size: data.boxSize }, DEFAULT_COMPONENT_BOX_SIZE); const componentSize = normalizeBoxSize({ box_size: data.boxSize }, DEFAULT_COMPONENT_BOX_SIZE);
const isAnchorElement = data.elementType === 'anchor'; 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 visualSize = isAnchorElement ? { width: PORT_NODE_SIZE, height: PORT_NODE_SIZE } : componentSize;
const iconSize = createComponentSymbolMetrics(componentSize); const iconSize = createComponentSymbolMetrics(componentSize);
const portLabelStyle = (portHandle) => { const portLabelStyle = (portHandle) => {
@@ -1643,9 +1648,16 @@ Organization : OptiHK Limited
style={{ style={{
width: componentSize.width, width: componentSize.width,
height: visualSize.height, height: visualSize.height,
minHeight: visualSize.height,
border: selected ? '2px solid var(--accent)' : '1px solid var(--border)', 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})`, 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)', 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 ? { ...(isAnchorElement ? {
width: PORT_NODE_SIZE, width: PORT_NODE_SIZE,
minHeight: PORT_NODE_SIZE, minHeight: PORT_NODE_SIZE,
@@ -1658,7 +1670,7 @@ Organization : OptiHK Limited
}} }}
> >
{isAnchorElement ? ( {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%' }}> <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: '8px', minHeight: '100%' }}>
{!data.hideIcon && data.category && ( {!data.hideIcon && data.category && (
@@ -1733,8 +1745,8 @@ Organization : OptiHK Limited
}; };
const baseHandleStyle = { const baseHandleStyle = {
background: 'var(--accent)', background: 'var(--accent)',
width: 8, width: 6,
height: 8 height: 6
}; };
return ( return (
<div style={{ <div style={{
@@ -1743,7 +1755,7 @@ Organization : OptiHK Limited
border: selected ? '2px solid white' : '2px solid var(--accent)', border: selected ? '2px solid white' : '2px solid var(--accent)',
display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
color: selected ? 'white' : 'var(--accent)', 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', boxShadow: selected ? '0 0 10px rgba(56,189,248,0.4)' : 'none',
transform: `rotate(${angle}deg)`, transform: `rotate(${angle}deg)`,
}}> }}>
@@ -1783,8 +1795,8 @@ Organization : OptiHK Limited
bottom: Position.Bottom bottom: Position.Bottom
}; };
const baseHandleStyle = { const baseHandleStyle = {
width: 8, width: 6,
height: 8, height: 6,
background: 'var(--accent)', background: 'var(--accent)',
border: '1px solid var(--bg-main)', border: '1px solid var(--bg-main)',
borderRadius: '50%' borderRadius: '50%'
@@ -2729,6 +2741,7 @@ Organization : OptiHK Limited
const selectedNodeBoxSize = selectedNode?.data?.componentName && !selectedNode?.data?.elementType const selectedNodeBoxSize = selectedNode?.data?.componentName && !selectedNode?.data?.elementType
? normalizeBoxSize({ box_size: selectedNode.data?.boxSize }, DEFAULT_COMPONENT_BOX_SIZE) ? normalizeBoxSize({ box_size: selectedNode.data?.boxSize }, DEFAULT_COMPONENT_BOX_SIZE)
: null; : null;
const xsections = Object.keys((technologyManifest || FALLBACK_TECHNOLOGY_MANIFEST).xsections || {});
const selectedRouteEdges = selectedEdges.length > 0 ? selectedEdges : (selectedEdge ? [selectedEdge] : []); const selectedRouteEdges = selectedEdges.length > 0 ? selectedEdges : (selectedEdge ? [selectedEdge] : []);
if (selectedRouteEdges.length > 0) { if (selectedRouteEdges.length > 0) {
@@ -2744,7 +2757,6 @@ Organization : OptiHK Limited
radius: mixedValue('radius'), radius: mixedValue('radius'),
routing_type: mixedValue('routing_type') 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']; const routingTypes = (technologyManifest || FALLBACK_TECHNOLOGY_MANIFEST).routing_types || ['euler_bend', 'standard_bend'];
return ( return (
<aside style={{ <aside style={{
@@ -3164,12 +3176,26 @@ Organization : OptiHK Limited
{Object.entries(basicArguments).map(([key, value]) => ( {Object.entries(basicArguments).map(([key, value]) => (
<label key={key} style={{ display: 'grid', gap: 4 }}> <label key={key} style={{ display: 'grid', gap: 4 }}>
<span style={{ color: 'var(--text-muted)' }}>{key}</span> <span style={{ color: 'var(--text-muted)' }}>{key}</span>
<input {key === 'xsection' ? (
type={typeof value === 'number' ? 'number' : 'text'} <select
step="any" value={value ?? ''}
value={value ?? ''} onChange={(event) => updateBasicArgument(key, event.target.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> </label>
))} ))}
</div> </div>
@@ -3461,6 +3487,7 @@ Organization : OptiHK Limited
const [dragging, setDragging] = useState(null); const [dragging, setDragging] = useState(null);
const [gridSnap, setGridSnap] = useState(false); const [gridSnap, setGridSnap] = useState(false);
const [canvasTextVisible, setCanvasTextVisible] = useState(true);
const [themeMode, setThemeMode] = useState(() => localStorage.getItem('mxpic-theme') || 'dark'); const [themeMode, setThemeMode] = useState(() => localStorage.getItem('mxpic-theme') || 'dark');
const [logs, setLogs] = useState([{ time: new Date().toLocaleTimeString(), message: 'Editor ready.' }]); const [logs, setLogs] = useState([{ time: new Date().toLocaleTimeString(), message: 'Editor ready.' }]);
const [buildLayoutBusy, setBuildLayoutBusy] = useState(false); const [buildLayoutBusy, setBuildLayoutBusy] = useState(false);
@@ -5463,6 +5490,11 @@ Organization : OptiHK Limited
setGridSnap(prev => !prev); 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. // Toggle the measurement ruler and clear partial measurements.
const toggleRulerMode = useCallback(() => { const toggleRulerMode = useCallback(() => {
setRulerMode(prev => { setRulerMode(prev => {
@@ -5999,6 +6031,9 @@ ${bundlesBlock}`;
transition: 'transform 0.2s', transition: 'transform 0.2s',
}} /> }} />
</div> </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"> <details className="link-mode-tabs" title="Route type used for new links">
<summary className="link-mode-summary"> <summary className="link-mode-summary">
<span className="link-mode-label">Link</span> <span className="link-mode-label">Link</span>
@@ -6069,6 +6104,7 @@ ${bundlesBlock}`;
<LayoutSvgPreview page={activePage} /> <LayoutSvgPreview page={activePage} />
) : ( ) : (
<ReactFlow <ReactFlow
className={canvasTextVisible ? '' : 'canvas-text-hidden'}
nodes={renderNodes} nodes={renderNodes}
edges={renderEdges} edges={renderEdges}
onNodesChange={onNodesChange} onNodesChange={onNodesChange}
+41 -12
View File
@@ -172,18 +172,46 @@ assert.deepStrictEqual(
); );
assert.deepStrictEqual( assert.deepStrictEqual(
helpers.getBasicComponentMetadata('waveguide', { length: 120, width: 0.5 }).box_size, helpers.getBasicComponentMetadata('waveguide', { length: 120, width: 0.5 }).box_size,
[120, 4], [120, 20],
'basic waveguide symbol should use a narrow default height' 'basic waveguide symbol should use a height that is two port-circle diameters'
); );
assert.deepStrictEqual( assert.deepStrictEqual(
helpers.getBasicComponentMetadata('90 bend', { radius: 15 }).box_size, helpers.getBasicComponentMetadata('90 bend', { radius: 15 }).box_size,
[15, 15], [25, 25],
'90 bend symbol should be square with side length equal to radius' '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( assert.deepStrictEqual(
helpers.getBasicComponentMetadata('180 bend', { radius: 15 }).box_size, helpers.getBasicComponentMetadata('180 bend', { radius: 15 }).box_size,
[15, 30], [25, 50],
'180 bend symbol should be one radius wide and two radii tall' '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( assert.deepStrictEqual(
helpers.getBasicComponentMetadata('taper', { length: 80, width1: 0.4, width2: 1.2 }).ports.a1.description, 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('- name: port'));
assert(pagePortsYaml.includes('x: 50.0')); assert(pagePortsYaml.includes('x: 50.0'));
assert(pagePortsYaml.includes('y: -150.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 }); const componentPorts = helpers.buildPageComponentPorts({ x: 12, y: -6, a: 180 });
assert.deepStrictEqual(componentPorts, { 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 = [ const elementNodes = [
@@ -341,9 +369,9 @@ assert.deepStrictEqual(
data: { componentDisplayName: 'array', elementType: 'port', portNumber: 3, pitch: 10, width: 0.6 } data: { componentDisplayName: 'array', elementType: 'port', portNumber: 3, pitch: 10, width: 0.6 }
}]), }]),
{ {
array_1: { x: 100, y: 190, a: 0, width: 0.6 }, array_1: { x: 100, y: 190, a: 180, width: 0.6 },
array_2: { x: 100, y: 200, a: 0, width: 0.6 }, array_2: { x: 100, y: 200, a: 180, width: 0.6 },
array_3: { x: 100, y: 210, a: 0, 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('description: "input port"'));
assert(canvasPortsYaml.includes('width: 0.7')); assert(canvasPortsYaml.includes('width: 0.7'));
assert(canvasPortsYaml.includes('y: -20.0')); assert(canvasPortsYaml.includes('y: -20.0'));
assert(canvasPortsYaml.includes('angle: 0.0'));
const elementsYaml = helpers.buildElementsYaml(elementNodes); const elementsYaml = helpers.buildElementsYaml(elementNodes);
assert(elementsYaml.includes('in0:')); assert(elementsYaml.includes('in0:'));
@@ -372,7 +401,7 @@ assert(instancesWithoutElements.includes('component_1:'));
assert(instancesWithoutElements.includes('y: -60.0')); assert(instancesWithoutElements.includes('y: -60.0'));
const multiPortComponentPorts = helpers.buildPageComponentPorts(null, elementNodes); 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 = { const technologyManifest = {
defaults: { xsection: 'strip', width: 0.45, radius: 10, routing_type: 'euler_bend' }, 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'), canvasHtml.includes('component-floating-label') && canvasHtml.includes('component-visual-body'),
'component labels should float outside the rotated 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( assert(
canvasHtml.includes('--floating-label-bg') && canvasHtml.includes('--port-label-bg') && canvasHtml.includes('--mini-button-bg'), 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' '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' : ''"), canvasHtml.includes("isUserCell ? 'compact-tree-card' : ''"),
'Basic, Port, and Anchor entries should render as consistent 2D cards instead of compact list rows' '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( assert(
canvasHtml.includes('ParallelRouteEdge') && canvasHtml.includes('ParallelRouteEdge') &&
canvasHtml.includes('parallelOffset') && canvasHtml.includes('parallelOffset') &&