Skip to content

选择与拾取

viewer.selection 维护节点 / 面 / 边三种粒度的选中集与悬停状态:点击画布即自动选中(默认开),也可用 pick / pickBox 自行拾取、apply / clear 编程式修改选中集。状态变化统一经 selection-changed / hover-changed 事件广播。

下面的 demo 中点击零件即可选中,底部面板可切换合并模式(Set / Add / Toggle)、高亮样式与可拾取粒度:

可交互 demo · 点击零件选中,底部面板切合并模式/高亮/粒度,空格键 fit在新窗口打开 ↗

核心 API

ts
import { SelectionItem, SelectionMode, SelectionMask } from "@modelcubes/viewer-core";

// 订阅选中 / 悬停变化(点击画布自动选中,默认开)
viewer.on("selection-changed", (e) => console.log("当前选中", e.current.length, "项"));
viewer.on("hover-changed", (e) => console.log("悬停", e.item));

// 手动拾取:坐标是画布局部坐标(相对画布左上角,CSS 像素),
// 从鼠标事件接线时需用 getBoundingClientRect 换算
canvas.addEventListener("click", (e) => {
  const r = canvas.getBoundingClientRect();
  const item = viewer.selection.pick(e.clientX - r.left, e.clientY - r.top, SelectionMask.Node);
  if (item) viewer.selection.apply(item, SelectionMode.Add);
});

// 编程式修改选中集 / 清空
viewer.selection.apply(SelectionItem.node(someNodeId), SelectionMode.Set);
viewer.selection.clear();

行为要点

  • 点击拾取:pick(x, y, mask?) 在画布坐标处做一次拾取,返回命中的 SelectionItemnull,只拾取、不改选中集;mask 缺省时用当前 setPickableMask 设置的粒度。点击画布的自动选中(Set 语义)可经 setAutoSelectOnClick(false) 关闭后自行驱动。
  • 两组枚举:SelectionMode 决定 apply 的合并方式(Set 替换 / Add 追加 / Toggle 反选);SelectionMask 是可拾取粒度位掩码(Node / Face / Edge 可按位或,All 全开),经 setPickableMask 设置,引擎默认仅 Node(零件级)。
  • 事件:选中集净变化时触发 selection-changed(payload 含 added / removed / current),无净变化不触发;悬停对象变化时触发 hover-changed(移出模型时 itemnull)。
  • 编程式选中:apply(items, mode) 接受单个或一组 SelectionItem(用 SelectionItem.node/face/edge 工厂构造),item 非法(nodeId 不存在等)抛 ViewerError;clear() 清空选中集。getSelection() / has() / size() 查询当前状态。
  • 框选:pickBox(x1, y1, x2, y2, opts?) 返回画布矩形内命中的 item 列表(不改选中集,可再 apply);v1 仅支持 Node 粒度,mask 含 Face / Edge 位会降级并告警;opts.mustBeFullyInside 要求节点完全落入矩形才算命中(默认相交即命中)。
  • 高亮样式:setHighlightMode 切换视觉反馈方式(仅染色 / 仅描边 / 染色+描边,默认两者皆有);setStyle(patch) 增量调整选中 / 悬停的颜色、描边强度与染色混合度,默认为橙系染色 + 描边(见 DEFAULT_SELECTION_STYLE)。
  • 装配节点选中的高亮传播:apply 选中无几何的装配节点(NodeInfo.meshId === null)时,染色(tint)与描边(outline)传播到其整个后代叶子子树——视觉上等同于子树内全部零件被高亮;选择集中保持的仍是装配节点本身(getSelection() 返回装配节点,属性栏 / 右键菜单等基于选择集的语义不受影响)。此时 model.getHighlightedNodeIds() 返回的是被染色的叶子集合,而非装配节点 id。

触控长按菜单

触控设备没有右键,画布长按就是上下文菜单的入口。引擎本身没有 UI,长按 500ms 会发 context-menu-requested 事件,宿主据此弹自己的菜单即可 —— 不需要自行处理 touch 事件:

ts
viewer.on("context-menu-requested", (e) => {
  // x / y 是画布局部坐标,可直接喂 pick();clientX / clientY 是视口坐标,用来定位浮层
  const item = viewer.selection.pick(e.x, e.y);
  if (!item) return; // 空白处长按:不弹
  if (!viewer.selection.has(item)) viewer.selection.apply(item, SelectionMode.Set);
  myMenu.showAt(e.clientX, e.clientY);
});
  • 长按成立后,该次手势的轻点选中会被抑制 —— 不会出现「弹了菜单又顺手改了选中集」。
  • 必须响应 context-menu-dismissed:菜单弹出后用户又落下第二根手指时引擎会发它, 宿主要立刻收起菜单。菜单是 pointer-events: auto 的浮层且弹在手指附近,留在原地会吃掉第二指的 pointerdown,双指缩放/平移随之完全失效(用户看到的是「双指没有任何反应」)。注意该次手势救不回来 —— 第二指的 pointerdown 在菜单消失前就已被它接收, 用户需抬手重做;收起菜单是为了让状态回到可用。
  • 手指在长按判定期内移动超过 10px,或第二根手指落下,长按取消,手势回落为导航。
  • 桌面右键仍走 DOM 原生 contextmenu,引擎不代管;两条路径互不干扰。
  • 注意:移动端浏览器合成的 click 实测可迟到 ~600ms 才到达。若用 document 上的 click 关闭菜单,它会把刚弹出的菜单立刻关掉 —— 改用 pointerdown 关闭,并忽略本次长按手势尾随的那一个 click。@modelcubes/viewer-uiwireContextMenu 已处理好这些。

相关 API