JavaScript is required

PCB元件连线编辑器

这个示例展示如何构建 PCB 风元件布线工作区:节点固定定位、组件面板拖入创建,以及暴露多引脚连接目标的芯片/元件自定义渲染。它还在同一 relation-graph 画布上结合了多选、选中导线路径编辑、运行时画布设置和图片导出。

构建一个具有引脚级目标的 PCB 风格元件布线工作区

这个示例构建了什么

这个示例构建的是一个轻量级、PCB 风格的布线工作区,而不是一个通用的关系查看器。界面展示了一个固定的电路板画布、一个浮动的说明与设置窗口、左侧元件面板、自定义渲染的芯片与无源器件,以及让整个表面更像制图工具的标尺覆盖层。

用户可以将元件从面板拖入画布,选择一个或多个节点,删除当前选中的节点,在暴露出来的引脚或焊盘之间绘制连线,并编辑选中连线的路径。这个示例最值得关注的点,是在一个紧凑的 relation-graph 画布上同时结合了高密度的自定义连接目标和编辑器行为。

数据如何组织

初始图数据来自 my-data-api.ts,它是一个普通的 RGJsonData 对象,包含 nodeslinesfakeLines。这些节点是扁平记录,带有 idtexttypexy,因此固定布局可以直接使用保存好的坐标放置每个元件,而不需要在运行时计算布局。

最关键的预处理选择是结构性的,而不是算法性的。面板模板只定义 typetext,而 startCreatingNodePlot(...) 会在拖拽落点时通过生成新 id 并分配 xy,把一次面板拖拽变成一个已定位的节点。初始连接保存在 fakeLines 中,并引用特定的连接器 id,例如 N-pvrcW-pin-17N-zjCzE-capacitor-output,这意味着它的连接模型是引脚到引脚或引脚到焊盘,而不是节点到节点。

在生产系统中,同样的数据结构可以表示板级布线草图、连接器线束图、控制柜布局、嵌入式系统原型设计工作区,或任何元件带有多个具名附着点的技术编辑器。

如何使用 relation-graph

这个示例用 RGProvider 包裹整个页面,然后通过 RGHooks.useGraphInstance() 获取实时图实例。图运行在 layoutName: 'fixed' 模式下,因此 relation-graph 会根据通过 setJsonData(...) 加载的显式坐标渲染元件,而初始化后的首次视口调整只是一次简单的 zoomToFit()

图选项被调校为一个编辑器外壳。defaultNodeBorderWidth 被禁用,因此自定义 PCB 渲染器可以完全接管可见外形;defaultLineShape 被设置为 RGLineShape.StandardCurvedefaultJunctionPoint 被设置为 RGJunctionPoint.lrdefaultLineWidth 加宽到 3,内置工具栏被移到右侧,滚轮行为默认是缩放模式,拖拽行为默认是移动模式。

节点插槽是最核心的渲染扩展。RGSlotOnNode 会根据 node.type 进行分支,并挂载用于电阻、电容、二极管、晶体管、电感以及多种芯片封装的自定义 React 组件。这些渲染器通过 RGConnectTarget 暴露出大量连接锚点,有时挂在 SVG 元素上,有时挂在 HTML 引脚条上,因此 relation-graph 可以把自定义元件本体上的任意点视为合法的连线端点。

视图插槽承载的是应保持在屏幕空间中、而不是随图内容一起移动的编辑 UI。RGSlotOnView 挂载了左侧元件栏、RGEditingReferenceLineRGEditingNodeControllerRGEditingLineController、单节点删除工具栏,以及自定义的 MyCanvasCaliper 标尺覆盖层。标尺并不是 relation-graph 内置的;它们是通过读取图视口 getViewBoundingClientRect(),再用 getCanvasXyByViewXy(...) 将视图坐标转换为画布坐标来计算出来的。

运行时 API 驱动了大部分编辑器行为。这个示例使用 startCreatingNodePlot(...) 处理面板拖放,使用 generateNewNodeId()addNodes(...) 创建节点,使用 getNodeById(...) 将新节点交给编辑模式,使用 removeNode(...) 删除节点,使用 toggleEditingNode(...)setEditingNodes(...) 管理选择状态,使用 setEditingLine(...) 编辑选中的连线,使用 getNodesInSelectionView(...) 处理框选,使用 clearChecked() 重置画布,并通过 generateNewUUID() 加上 addFakeLines(...) 持久化新绘制的连线。共享的浮动面板还通过 setOptions(...) 在运行时切换滚轮与拖拽行为,并借助 prepareForImageGeneration()restoreAfterImageGeneration() 将画布导出为图片。

样式表定制的是图的外壳,而不是临时逐个重绘节点。它为画布提供了灰色的电路板背景,把内置工具栏染成紫色,移除了默认节点外观,并为已选中的连线增加了明显的高亮效果,即粉色前景描边和一层宽的半透明背景描边。

关键交互

  • 将一个元件预览从左侧元件栏拖到画布上,会创建一个新的已定位节点,并立即把该节点交给编辑状态。
  • 不带修饰键点击节点时,只选中该节点;按住 ShiftCtrlMeta 点击时,会将其切换进或切换出当前多选集合。
  • 在浮动设置面板中把画布拖拽模式切换为 selection 后,就会启用拖框选择,而 onCanvasSelectionEnd 会把框选结果设为当前的编辑节点集合。
  • 点击一条连线会将其设为当前活动的编辑连线,而 RGEditingLineController 会为它暴露路径编辑控制柄。
  • 在暴露的连接目标之间绘制新连线时,接受后的结果会作为带有生成 id 与端点目标类型元数据的 fake line 被持久化。
  • 点击画布空白处会清除已编辑节点、清除当前活动连线,并移除 checked 高亮。
  • 当且仅当选中一个节点时,会通过 RGEditingNodeControllerMyNodeToolbar 在其上方显示一个删除按钮。
  • 打开浮动设置面板后,用户可以切换滚轮模式、切换拖拽模式,并下载当前画布的导出图片。

关键代码片段

这段代码说明,这个工作区有意采用固定定位,并被配置为一个编辑器,而不是自动布局演示。

const graphOptions: RGOptions = {
    debug: false,
    layout: {
        layoutName: 'fixed'
    },
    defaultNodeBorderWidth: 0,
    defaultLineShape: RGLineShape.StandardCurve,
    defaultJunctionPoint: RGJunctionPoint.lr,
    defaultLineWidth: 3,
    toolBarPositionH: 'right',
    wheelEventAction: 'zoom',
    dragEventAction: 'move',
};

这段代码展示了,一个面板拖拽手势如何变成一个带有坐标并立即进入编辑焦点的真实图节点。

const onToolItemMouseDown = (e, nodeTemplate: JsonNodeLike) => {
    graphInstance.startCreatingNodePlot(e.nativeEvent, {
        templateNode: {
            ...nodeTemplate
        },
        onCreateNode: (x, y, nodeTemplate) => {
            const newNode = {
                ...nodeTemplate,
                id: `N-${graphInstance.generateNewNodeId()}`,
                x: x - 20,
                y: y - 20
            };
            graphInstance.addNodes([newNode]);
            graphInstance.setEditingNodes([graphInstance.getNodeById(newNode.id)]);
        }
    });
};

这段代码说明,连接目标存在于自定义元件渲染器本身上,而不是挂在一个通用节点边界上。

<RGConnectTarget forSvg={true} targetId={`${id}-resistor-input`}>
  <circle cx="2" cy="30" r="4" fill="#ef4444" />
</RGConnectTarget>
<RGConnectTarget forSvg={true} targetId={`${id}-resistor-output`}>
  <circle cx="198" cy="30" r="4" fill="#ef4444" />
</RGConnectTarget>

这段代码展示了同样的模式如何应用在芯片封装上,其中每个具名引脚都成为自己的连接锚点。

{leftPins.map((pin) => (
  <div key={pin.label} className="flex items-center justify-end">
    <span className="text-xs font-mono mr-2 text-gray-500">{pin.name}</span>
    <RGConnectTarget targetId={`${id}-pin-${pin.name}`} junctionPoint={RGJunctionPoint.left}>
      <div className="w-8 h-4 bg-gray-400 rounded-sm shadow-inner" />
    </RGConnectTarget>
  </div>
))}

这段代码展示了,新绘制的连线如何在被接受后,以带有端点目标元数据的 fake line 形式持久化。

const onLineBeCreated = (lineInfo: {
    lineJson: JsonLine,
    fromNode: RGLineTarget | RGNode,
    toNode: RGLineTarget | RGNode
}) => {
    if (lineInfo.toNode && lineInfo.toNode.id) {
        const newLine = {
            ...lineInfo.lineJson,
            fromType: lineInfo.fromNode.targetType,
            toType: lineInfo.toNode.targetType,
            id: graphInstance.generateNewUUID(16)
        };
        graphInstance.addFakeLines([newLine]);
    }
};

这段代码展示了挂接在图上的编辑栈:自定义节点渲染、屏幕空间视图插槽、吸附参考线、节点删除控件、连线路径编辑以及标尺覆盖层。

<RGSlotOnNode>
    {({ node }: RGNodeSlotProps) => {
        const type = node.type;
        return <PCBItem id={node.id} type={type} />
    }}
</RGSlotOnNode>
<RGSlotOnView>
    <RGEditingReferenceLine adsorption={true} showText={true} />
    <RGEditingNodeController>
        <MyNodeToolbar onRemoveNode={onRemoveNode} />
    </RGEditingNodeController>
    <RGEditingLineController textEditable={false} pathEditable={true} onMoveLineVertexEnd={onMoveLineVertexEnd} />
    <MyCanvasCaliper />
</RGSlotOnView>

这段代码说明,标尺覆盖层是通过 relation-graph 的视口 API 计算得出的,而不是静态装饰。

const graphViewBox = graphInstance.getViewBoundingClientRect();
const visibleAreaStart = graphInstance.getCanvasXyByViewXy({
    x: 0,
    y: 0
});
const visibleAreaEnd = graphInstance.getCanvasXyByViewXy({
    x: graphViewBox.width,
    y: graphViewBox.height
});

这个示例的独特之处

对比数据表明,这个示例最接近 editor-button-on-linecreate-line-from-nodeline-vertex-on-nodechange-line-pathgee-node-alignment-guides 这类紧凑型编辑器演示,但它与它们各自的侧重点都不同。与 editor-button-on-line 相比,这里可复用的经验并不是通用的节点与连线创作,也不是行内边操作,而是领域化元件本体上的高密度引脚级布线,以及将新连线存储为 fakeLines

create-line-from-nodeline-vertex-on-node 相比,这个示例并不教授如何从选中节点工具栏发起连接,或如何在普通节点上使用目标端点选择器。它教授的是另一种模式:把大量预定义目标直接嵌入自定义 PCB 渲染器,让用户在固定表面上放置这些元件,并针对这些暴露出来的目标接受新连线。

change-line-pathgee-node-alignment-guides 相比,路径编辑与参考线是辅助特性,而不是主角。它们当然重要,但更强的组合是:由面板驱动的元件放置、固定坐标、任意引脚到引脚的连接目标、fake line 持久化,以及同处于一个技术画布上的制图风格标尺。

这组组合正是这个示例作为起点尤其有价值的原因。它不是一个完整的 PCB CAD 工具,但它比通用图编辑器更具领域特性,也比单一用途的微型演示在行为上更完整。

这一模式还适用于哪里

这种模式非常适合布线和连接器编辑器,在这类场景中,每个元件都需要许多具名附着点,例如控制柜原理图、传感器线束规划、测试夹具布局或嵌入式原型板。相同的方法也适用于教学型电子工具,在这类工具里,用户需要连接的是可识别的器件,而不是抽象节点。

第二类扩展场景是任何需要外部部件插入加固定位置编辑的技术工作区,即使不在电子领域内也是如此。团队可以通过替换元件渲染器,同时保留相同的目标、选择和 fake line 持久化模型,把它改造成设备面板设计、机架面板跳线工具、机器人 I/O 映射或实验室仪器接线规划器。