Pixerate 画布
画布是一个 xyflow(React Flow / SvelteFlow)图:由节点和边构成的数组。
节点为 { id, type, position: { x, y }, width?, height?, parentId?, zIndex?, data };边为
{ id, source, target, label? }。所有设计功能相关的信息都存储在 node.data 中,因此可承受复制、粘贴和 JSON 操作:
data.label/data.name:文本内容和图层名称。data.shape:形状节点 ({ kind: rect | ellipse | polygon | star | path, fill, stroke, strokeWidth, ... })。data.layout:自动布局框架(mode水平 / 垂直 / 网格,gap,padding,alignMain,alignCross,sizing);其子节点是parentId为该框架的节点, 按节点数组顺序排列。data.childLayout:子节点的尺寸设置(固定 / 自适应 / 填充)和约束。data.component(主组件),data.componentSet(变体集合),data.instance({ componentId, properties })和data.componentOverrides。data.locked,data.animation(关键帧轨迹),data.tokenBindings(设计令牌)。
位置以画布(流)单位表示;子节点的位置相对于其父节点。节点 ID 来自 get_editor_state;组件和集合 ID 来自 list_components。需要 ids 或 id 的工具在省略参数时默认使用当前选中项,因此除非刚刚使用 canvas_select 选中的内容,请显式传递 ID。请参阅 pixerate-agent-tools 了解命名空间、run_commands 批量操作和错误代码。
工作流技能
对于以下任务,请同时加载工作流技能:其中包含方法、步骤间的检查点和陷阱规避方法,并使用下面的工具。
pixerate-canvas-design-system-components:组件、实例、变体集合和覆盖属性。pixerate-canvas-layout:对齐、分布、排序和锁定。
工作方法
- 读取。
get_editor_state加上includeDocument: false返回canvas.nodes和canvas.edges。对于设计系统,还需要调用list_components,并在更改实例前调用inspect_instance。 - 先规划结构:确定哪些框架、哪些可复用组件和变体、使用何种间距标尺。先构建主组件一次,然后放置实例;切勿手动复制节点。
- 写入。 使用专门的工具进行图编辑(
canvas_add_node,canvas_connect,canvas_update_node)和组件操作(create_component,create_instance,...)。使用run_commands加上atomic: true来处理排列、形状和动画命令,以及当多个步骤应合并为一个撤销步骤时(参数"$0.ids[0]"使用前一步返回的 ID)。 - 整理。 对图使用
canvas_layout;对设计使用canvas.align*/canvas.distribute*; 使用canvas.bringToFront等命令调整图层顺序;使用canvas.lock锁定已完成背景。 - 查看。 批次完成后使用
view_editor(默认视口,JPEG,1024 像素);使用scope: "canvas"查看整个绘图。修正重叠、错位和裁剪的文本,然后通过canvas_select或canvas_highlight向用户展示。
与用户协作(用户正在构建,而你未经请求就添加内容):建议而非编辑。propose_nodes 会显示幽灵节点,用户可接受或拒绝,在此之前不会影响画布或其历史记录。保持每个提案小而精,聚焦一个想法(文字说明、系列中的下一张卡片、缺少的连接器),为其指定 title 和一句 rationale,并通过 nearNodeId 将其锚定到所补充的节点。除非用户要求,否则切勿接受自己的提案。使用 reject_proposal 撤回过时的提案:当用户更改提案所基于的内容时,list_proposals 会将其显示在 missing 下。
最佳实践
-
设计系统思维。 任何出现两次的内容都应成为组件:先构建一个主组件,使用
combine_as_variants处理状态和尺寸(State=Default,State=Hover,Size=Small),然后通过create_instance放置到各处。修改主组件即可更新所有实例;仅将覆盖属性用于每次使用的独特内容(标签文本、图标替换)。变体属性和值命名要保持一致(属性使用 Title Case,值简短),并保持组合唯一。 -
将在变体切换中保留的图层命名为稳定的
data.name。匹配会回退到data.label,对于文本而言其内容在不同变体间会有所不同。 -
使用组件属性,而非分离。 使用
set_instance_properties切换变体并设置文本或布尔型属性;使用swap_instance切换到另一个组件。仅当实例必须停止跟随其主组件时才进行分离。 -
间距标尺。 选择一个基础单位(4 或 8),并使用其倍数作为间距和填充。将行和堆叠放入自动布局框架(
data.layout)中,而不是手动定位子节点,这样内容变化时会自动重排。 -
对齐和分布。 先对齐到共享边缘或中心,然后以等间距分布;边缘对齐时设计看起来有序。将想要作为锚点的节点锁定:锁定的节点不会移动,但仍计入选择边界。
-
图层管理。 背景在最底层,内容在中间,覆盖在最上层。使用重新堆叠命令;它们按同级组设置
zIndex,且不会重新排序数组。 -
图形布局。 将流程图沿一个方向布局(
LR用于管道,TB用于层次结构),保持边短小,仅当关系不明显时才标注边。 -
动效。 进入时使用 ease-out(或加一点 overshoot 的
ease-out-back),离开时使用 ease-in,在两个静止位置间移动时使用 ease-in-out,仅对旋转等连续运动使用linear。将 UI 过渡保持在 150 到 400 毫秒之间,进入动画约在 600 毫秒以内;通过delay为相关元素设置 30 到 80 毫秒的错开,而不是让所有元素同时移动。对opacity、x、y、scale和rotation进行动画;颜色渐变在 OKLab 中插值。 -
画笔笔触。 当用户希望获得手绘效果(素描、墨水、铅笔、马克笔)时使用画笔;对于图表和 UI 则保持普通笔触。沿曲线每 2 到 8 个画布单位发送一个点,并使用较短的
pressure配置文件(锥形端点:[0.2, 0.9, 1, 0.9, 0.2])来塑造线条,而不是使用许多不同宽度的点。省略width以使用预设自身的尺寸(canvas.listBrushes提供);仅在需要使标记更细或更粗时传入。 -
画笔特性。 每种预设都有其独特的绘制特性:木炭和粉彩宽大而颗粒感强(它们捕捉纸张的纹理),蜡笔和粉笔也是如此;铅笔细腻且略带颗粒感;墨水、马克笔、毛笔、圆珠笔、凝胶笔和极细笔干净流畅(极细笔保持单一宽度),喷枪为柔和喷雾。颗粒感和抖动按笔触种子的方式生成,因此笔触每次看起来都一样,相邻笔触共享同一张纸的纹理。根据外观选择画笔,然后选择颜色;对于素描,使用一到两种画笔,而不是全部。具有默认颜色(圆珠笔蓝色、荧光笔黄色、粉笔)的画笔在未传递
color时会使用该颜色;传递color则可使用其他颜色。浅色 / 深色默认值(粉笔)会根据画布的颜色方案解析(canvas.setColorScheme;宿主通常与其主题匹配)。 -
为用户设置画笔,而非直接绘制。
canvas.selectBrush选择用户手动绘制的画笔,如同画笔选择器一样:除非用户锁定了自己的颜色(canvas.setColorLocked,"保留我的颜色")或你传递了keepColour,否则它会使用画笔的默认颜色。以这种方式获取的默认颜色会跟随方案,直到设置其他颜色。尊重用户设置的颜色锁定;不要为了更改颜色而解锁。这些是编辑器状态,不是撤销步骤,不会影响canvas.drawFreehand,后者使用自己的brush和color。 -
使用手势标注,而非点。 要以设计师在打印稿上标注的方式标记工作,请使用手势命令(
canvas.circle,canvas.underline,canvas.arrow,canvas.bracket,canvas.strike,canvas.cross,canvas.check)配合节点 ID({ "nodeId": "..." })或选框,而不是为canvas.drawFreehand计算点。每次调用绘制一个手绘标记。当你指着画布上的某样东西谈论时,注释会有所帮助:圈出选项,给重要的线条添加下划线,从色板指向其使用位置画箭头,给正在描述的一组元素加括号,划掉或叉掉被拒绝的选项,勾选批准的选项。当画布上没有要指代的内容时跳过,并保持每点只有少量标记:一页布满标记等于什么都没说。 -
手势风格。 在一次审阅批次中使用一种颜色(红色或深蓝色笔在大多数设计上看起来像标记),仅对圆形和下划线使用荧光笔,并在一个批次中使用相同的
brush(marker或ink-pen)使每个标记看起来都像手绘。标记按请求设定种子,因此再次请求会绘制相同的标记;传递另一个seed以获得不同的效果。当标记需要文字说明时,在括号旁或箭头的尾部放置一个页边注释(文本节点)。
工具参考
读取
| 工具 | 用途 |
|---|---|
get_editor_state | 读取画布图(canvas.nodes,canvas.edges),默认还会读取提示文档。 |
对仅画布的任务传递 includeDocument: false 以保持结果精简,传递 includeCanvas: false 则仅获取提示。 | |
| 在用户进行编辑后首先调用此工具,并在编辑后再次调用。 | |
view_editor | 获取编辑器图像:scope viewport(默认)或 canvas(整个绘图的边界),format jpeg(默认)或 png,maxEdge 以像素为单位(默认 1024)。 |
| 批次后使用它来判断重叠、平衡和可读性;代理光标被排除在外。 | |
capture_view | 截取画布的一部分以检查结果:region viewport(默认,即屏幕上所见)、content(每个节点)、selection、nodes(使用 get_editor_state 中的 ids)或 rect(画布单位);padding(默认 16)、maxEdge(默认 1024)、format png(默认)或 jpeg。 |
结果提供捕获的 rect 和 scale,因此图像像素 (px, py) 对应画布点 (rect.x + px / scale, rect.y + py / scale)。 | |
优先使用 nodes 以完整细节检查刚添加的内容。选择和代理光标被排除在外,除非 includeOverlays。 | |
list_components | 列出主组件(id、名称、变体值、实例数量)及其变体选项的组件集合。 |
在调用 create_instance、swap_instance 或任何变体工具之前调用,以获取 ID 和准确的属性名称与值。 | |
inspect_instance | 描述一个实例(或其内部的图层;默认选取第一个选中节点):其主组件、变体、组件属性值及类型、以及每图层的覆盖属性。 |
在调用 set_instance_properties 或 reset_overrides 之前调用,以确保使用正确的属性名称和覆盖组。 |
写入:图形
| 工具 | 用途 |
|---|---|
canvas_add_node | 添加节点:node(id 可选且必须未被使用,type 由宿主注册,data,position,width,height,框架的 parentId)。 |
省略 position 以使其自动放置在 nearNodeId 的 side(默认 right)旁边且不重叠,同时从该节点连接一条边,或放置在上一个节点旁边。 | |
| 返回新节点 ID 和边 ID。 | |
canvas_connect | 在 source 和 target(两个不同的现有节点)之间添加一条边,带可选的 label。返回边 ID。 |
canvas_update_node | 将字段合并到节点的 data 中(例如 { "label": "Checkout" })。 |
合并是浅层的:对于嵌套对象如 shape 或 layout,请整体传递。 | |
| 此操作不会移动或调整节点大小。 | |
canvas_remove_nodes | 删除节点及其边;每个 ID 都必须存在。 |
| 具有破坏性:在删除用户制作的内容之前请确认。 | |
canvas_layout | 自动排列图形:direction LR(默认)、TB、RL 或 BT。 |
incremental(默认为 true)保留已有位置的节点;传递 false 以重新布局所有内容。 | |
| 确切位于 (0, 0) 的节点被视为未放置。 | |
canvas_select | 替换选区(空列表可清除选区)。用于向用户展示结果,以及定位那些作用于选区的命令。 |
canvas_highlight | 高亮闪烁节点以指示用户:color(默认 #3b82f6)、durationMs(默认 2000,最大 60000)。 |
| 不改变任何内容。 |
展开与折叠
支持可展开节点的宿主(任务可展开为子任务、甘特条可展开为其步骤)会添加以下功能。 展开会显示节点的子节点,并移动周围节点以腾出空间;折叠会移除子节点并将移动的节点恢复原位。 这是视图更改,而非编辑:它不是撤销步骤,不会随文档保存。宿主决定子节点是什么。
| 工具 | 用途 |
|---|---|
list_explodable_nodes | 列出可展开的节点(id、label、exploded、collapsing)、当前 exploded 和 collapsing 的 ID、 |
多个是否可同时打开(multiple)、以及如果展开被禁用的 readOnly 原因。 | |
| 首先调用此工具:它是可展开 ID 的唯一来源。 | |
explode_node | 展开 node(来自 list_explodable_nodes 的 ID)。 |
除非 multiple 为 true,否则之前展开的节点会折叠(它会出现在 collapsed 中)。 | |
展开一个已展开的节点不产生效果(changed: false)。 | |
返回当前 exploded 的 ID。然后使用 get_editor_state 或 capture_view 读取或查看子节点。 | |
collapse_node | 折叠一个已展开的 node。 |
返回仍处于 exploded 状态的 ID 和仍处于 collapsing 状态的 ID(它们的子节点会在折叠动画结束时离开)。 | |
| 当节点未展开时失败。 |
提案(建议,而非编辑)
允许代理建议添加内容的宿主会添加以下功能。提案是显示为幽灵节点的新节点和边(不可拖动、不可选择、不可保存)。
接受会将其作为一个撤销步骤添加,记录为由提出者(agent:<id>)创建;拒绝则保持画布及其历史记录不变。
| 工具 | 用途 |
|---|---|
propose_nodes | 提议在画布上添加节点,以幽灵节点形式显示:nodes 和 edges(可选),title,rationale,nearNodeId(将幽灵节点锚定到现有节点,缩放和平移会跟随该节点)。 |
提议会自动分组:同一个 nearNodeId 的提议会一起接受或拒绝。 | |
| 每个调用形成一个独立的提议,可以单独接受或拒绝。 | |
工具返回一个 proposalId;接受时会记录为编辑历史。 | |
list_proposals | 列出当前可见的提案:每个提案的 proposalId、title、rationale、nearNodeId,以及 missing(如果锚定节点已不存在)。 |
accept_proposal | 接受 proposalId 作为画布的编辑,将幽灵节点转换为真实节点,作为由代理执行的一个撤销步骤。只会影响那个提议中的节点和边。 |
reject_proposal | 拒绝 proposalId,移除幽灵节点而不影响画布或其历史记录。 |
写入:画笔和手势
| 工具 | 用途 |
|---|---|
canvas.drawFreehand | 绘制一条自由画笔触:points({x,y},至少 2 个),brush(预设名称,必需),color(字符串,可选;省略则使用画笔的默认),width(毫米,可选;省略则使用预设的)。 |
points 应相隔 2 到 20 个画布单位;对于平滑曲线,每个曲线段约 8 个点。 | |
canvas.listBrushes 给出可用预设及其 width。 | |
canvas.circle | 围绕节点或框绘制一个手绘圆形:nodeId(参见 get_editor_state 中的节点 ID)或 box({left, top, right, bottom} 画布单位)。可选:colour,brush,padding(默认 0),seed(确定性重绘)。 |
canvas.underline | 在节点或框下面绘制一个手绘下划线。参数同上,外加 tilt('straight' 默认 / 'wavy')。 |
canvas.arrow | 绘制一个手绘箭头:fromNodeId(或 fromBox),toNodeId(或 toBox),或直接 from 和 to 点。可选:colour,brush。 |
canvas.bracket | 绘制一个手绘括号:nodeId(或 box),side('left' 默认 / 'right' / 'top' / 'bottom')。可选:colour,brush。 |
canvas.strike | 在节点或框上绘制一个手绘删除线。参数:nodeId(或 box),可选参数同上。 |
canvas.cross | 在节点或框上绘制一个手绘叉号。参数同上。 |
canvas.check | 在节点或框上绘制一个手绘勾号。参数同上。 |
写入:组件(参见 pixerate-canvas-design-system-components 工作流技能了解工作流程和常见陷阱)
| 工具 | 用途 |
|---|---|
create_component | 从 nodeId 创建一个主组件。返回 componentId。 |
create_instance | 在 node 处创建 componentId 的实例(可选 properties 映射到变体,overrides 映射覆盖属性)。返回 instanceId。 |
combine_as_variants | 将多个主组件合并为一个变体集合:name(集合名称),property(变体属性的名称,例如 'State'),value 数组(例如 ['Default', 'Hover'])对应组件 ID。返回 setId。 |
add_variant | 向变体集合 setId 添加一个变体,属性值为 value:使用 componentId(主组件)或从头创建。返回 variantId。 |
set_instance_properties | 将实例 instanceId 切换到同一集合中的一个变体,通过设置 properties(例如 {'State': 'Hover'})。 |
| 可选地设置实例的属性值(文本、布尔值、数字)。 | |
| 它会保留与变体保持不变的现有覆盖属性,但会移除与变体冲突的那些。 | |
swap_instance | 将实例 instanceId 切换到一个不同的 componentId(主组件或另一集合中的实例)。 |
reset_overrides | 移除实例 instanceId 上的所有覆盖属性,使其完全跟随其主组件。 |
detach_instance | 分离实例 instanceId,使其变为一个普通节点,不再跟随任何主组件。 |
list_variant_options | 列出变体集合 setId 的属性名称。返回 property 名称和该集合中每个变体的 value。 |
写入:框架和布局(参见 pixerate-canvas-layout 工作流技能了解工作流程和常见陷阱)
| 工具 | 用途 |
|---|---|
canvas.createFrame | 创建一个自动布局框架:children(节点 ID),layout(将写入 data.layout),position(可选),parentId(可选)。 |
| 返回框架 ID。 | |
canvas.removeFromFrame | 将节点 ID 从其父框架中移除,将其放置到画布上。 |
canvas.setLayout | 为一个框架节点设置 data.layout:nodeId,layout(框架的布局设置)。 |
canvas.setChildLayout | 为一个子节点设置相对于其框架的 data.childLayout:nodeId,childLayout({sizing: {width: 'hug' |
canvas.alignLeft | 将选中节点左对齐到最左节点的 x 坐标。 |
canvas.alignCenter | 将选中节点垂直居中对齐到中心。 |
canvas.alignRight | 将选中节点右对齐到最右节点的 x 坐标。 |
canvas.alignTop | 将选中节点顶部对齐到最上节点的 y 坐标。 |
canvas.alignMiddle | 将选中节点水平居中对齐到中间。 |
canvas.alignBottom | 将选中节点底部对齐到最下节点的 y 坐标。 |
canvas.distributeHorizontal | 在选中节点之间水平均匀分布,基于它们的水平边界。 |
canvas.distributeVertical | 在选中节点之间垂直均匀分布,基于它们的垂直边界。 |
canvas.bringToFront | 将节点移至其 z 层前面,在其同级节点之上。 |
canvas.sendToBack | 将节点移至其 z 层后面,在其同级节点之下。 |
canvas.bringForward | 将节点在其同级节点中向上一层。 |
canvas.sendBackward | 将节点在其同级节点中向下一层。 |
canvas.lock | 锁定节点(不可选择、不可移动、不可编辑)。 |
canvas.unlock | 解锁节点。 |
写入:形状
| 工具 | 用途 |
|---|---|
canvas.shapeUnite | 对选中的形状进行联合布尔运算,创建一个合并形状。 |
canvas.shapeSubtract | 对选中的形状进行减去布尔运算(从第一个形状中减去后续形状)。 |
canvas.shapeIntersect | 对选中的形状进行相交布尔运算。 |
canvas.shapeExclude | 对选中的形状进行排除重叠布尔运算。 |
canvas.offsetPath | 对路径节点 nodeId 创建一个偏移路径,偏移量为 offset(正数向外,负数向内)。 |
canvas.outlineStroke | 将节点 nodeId 的描边转换为填充路径。 |
canvas.roundCorners | 为节点 nodeId 设置圆角,半径为 radius。 |
写入:动画
| 工具 | 用途 |
|---|---|
canvas.addKeyframe | 为节点 nodeId 添加一个关键帧:time(毫秒),property(例如 'x'、'y'、'opacity'、'scale'、'rotation' 或 'fill'),value,easing(可选,例如 'ease-out'、'ease-in-out'、'linear'、'ease-out-back')。 |
canvas.removeKeyframe | 从节点 nodeId 的 time 处移除指定 property 的关键帧。 |
canvas.clearAnimation | 移除节点 nodeId 上的所有关键帧。 |
canvas.setAnimation | 替换节点 nodeId 的整个动画数据:animation(包含 tracks 数组,每个轨迹有 property 和 keyframes)。 |
canvas.exportCSSAnimation | 将节点 nodeId 的动画导出为 CSS @keyframes 规则,并返回 CSS 字符串。 |
通用
| 工具 | 用途 |
|---|---|
canvas.listBrushes | 列出可用的画笔预设:每个预设返回 id、name、width(默认毫米)、color(默认,如果有的话)。用于 canvas.drawFreehand 和手势工具。 |
canvas.selectBrush | 设置编辑器的活动画笔(用户手动绘制的画笔):brush(预设 ID),参数可选的 keepColour(布尔值,当用户锁定颜色时传递 true)。 |
| 这是编辑器状态,不是撤销步骤。 | |
canvas.setColorLocked | 切换用户手动绘制的颜色锁定(locked 布尔值)。当锁定时,canvas.selectBrush 不会更改他们的颜色。 |
| 这是编辑器状态,不是撤销步骤。 | |
canvas.setColorScheme | 设置画布的颜色方案('light' 或 'dark' 字符串)。这会影响默认颜色为方案感知的画笔。 |
| 这是编辑器状态,不是撤销步骤。 | |
run_commands | 作为单个原子撤销步骤运行一个命令数组:commands({tool, params} 对象的数组),atomic(布尔值,默认 true)。 |
命令可以引用前一步的 ID 作为参数(例如 "$0.ids[0]" 引用第一个命令的 ids 结果中的第一个 ID)。 | |
当 atomic 为 true 时,所有命令成为一次撤销操作。返回一个包含每个命令结果的对象。 |