Appearance
选择与拾取
viewer.selection 维护节点 / 面 / 边三种粒度的选中集与悬停状态:点击画布即自动选中(默认开),也可用 pick / pickBox 自行拾取、apply / clear 编程式修改选中集。状态变化统一经 selection-changed / hover-changed 事件广播。
下面的 demo 中点击零件即可选中,底部面板可切换合并模式(Set / Add / Toggle)、高亮样式与可拾取粒度:
核心 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?)在画布坐标处做一次拾取,返回命中的SelectionItem或null,只拾取、不改选中集;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(移出模型时item为null)。 - 编程式选中:
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-ui的wireContextMenu已处理好这些。
相关 API
- 概览:viewer.selection — 选择域的设计意图与典型用法。
- 事件系统 —
selection-changed/hover-changed的 payload 细节。 SelectionManager— 本页全部方法的完整签名。SelectionStyle— 高亮样式的全部字段与默认值。SelectionItem— 选中项工厂(node / face / edge)。