唯杰地图 logo唯杰地图
VJMAP
EN

snap

vjmapext/references/drawing/snap.md 6.6 KB169 行
原文

本文回答:SnapManager 对象捕捉 —— 10 种模式、位标志组合、开关、querySnap。

一、10 种捕捉模式

SnapMode 是位标志枚举,成员名为缩写(END/MID/…不是 Endpoint/Midpoint):

成员 位标志 SnapType 说明
SnapMode.END 1 'end' 线 / 弧 端点
SnapMode.MID 2 'mid' 线段中点
SnapMode.CEN 4 'cen' 圆 / 弧 圆心
SnapMode.NOD 8 'nod' 点实体节点
SnapMode.QUA 16 'qua' 圆 / 椭圆象限点
SnapMode.INT 32 'int' 实体间交点
SnapMode.INS 64 'ins' 块引用插入点
SnapMode.PER 128 'per' 垂足
SnapMode.TAN 256 'tan' 切点
SnapMode.NEA 512 'nea' 实体上最近点
SnapMode.ALL 1023 — 全部 10 种(1|2|…|512)
SnapMode.NONE 16384 — 特殊值,不与其他位组合

二、启用 / 模式

const { SnapMode } = vjmapext;

mapcadLayer.setSnapEnabled(true);
mapcadLayer.setSnapMode(
  SnapMode.END | SnapMode.MID | SnapMode.CEN | SnapMode.INT
);

// 全启 / 全关
mapcadLayer.setSnapMode(SnapMode.ALL);   // 1023
mapcadLayer.setSnapMode(0);              // 全关

// 关开关但保留模式
mapcadLayer.setSnapEnabled(false);

等价直读 / 直写:

mapcadLayer.snapManager.enabled;      // bool
mapcadLayer.snapManager.snapMode;     // 当前位掩码
mapcadLayer.snapManager.setSnapMode(SnapMode.END | SnapMode.INT);
mapcadLayer.snapManager.setOnChange(() => refreshSnapUI());  // 模式变化回调

三、阈值(像素)

mapcadLayer.snapManager.snapPixelRadius;   // 当前像素半径
mapcadLayer.snapManager.setSnapRadius(15); // 像素,不是 CAD 单位

querySnap 内部把像素半径换算成 CAD 距离 snapRadiusCad,zoom 变化不影响命中手感。snapPixelRadius 默认 10 像素。

补充:snapMode 会持久化到 localStorage['mapcad-snap-mode'](setSnapMode 自动写入、构造时读回)——刷新页面后捕捉模式仍在;enabled 开关不持久化。

四、querySnap:手动查捕捉点

const hit = mapcadLayer.snapManager.querySnap(
  [120, 88],              // 光标 CAD 坐标(Point2DLike = Point2D | [x,y])
  snapRadiusCad,          // CAD 单位半径
  referencePoint          // 可选:参考点(PER/TAN 需要),参数名 referencePoint
);
// hit = SnapPoint | null

SnapPoint 结构(字段是 position 不是 point):

interface SnapPoint {
  type: SnapType;           // 'end'|'mid'|'cen'|'nod'|'qua'|'int'|'ins'|'per'|'tan'|'nea'
  position: Point2D;
  entityId: string;         // 命中的实体
  primary?: boolean;        // 距离最近的候选点
}

五、getSnapModeList(用于 UI)

const { getSnapModeList } = vjmapext;

getSnapModeList();
// [{ mode: 1, type: 'end', label: '端点' },
//  { mode: 2, type: 'mid', label: '中点' },
//  ... 共 10 项,字段名是 label 不是 name]

可直接拿来渲染"对象捕捉"对话框。

六、和 getPoint 的关系

getPoint 默认走 SnapManager.querySnap:鼠标靠近实体特征点时橡皮筋终点会"吸"过去,result.value 就是捕捉点的精确坐标。

const r = await im.getPoint({ message: '指定终点', useBasePoint: true, basePoint: p1 });

某一步需要禁用捕捉:

mapcadLayer.setSnapEnabled(false);
const r = await im.getPoint({ message: '指定任意位置' });
mapcadLayer.setSnapEnabled(true);

七、bindDraw:把捕捉挂到 UI 控件

const btn = document.getElementById('btn-snap-endpoint');
btn.onclick = () => {
  const cur = mapcadLayer.snapManager.snapMode;
  mapcadLayer.setSnapMode(cur ^ SnapMode.END);   // 按位异或切换
  btn.classList.toggle('active');
};

八、临时点捕捉

setTemporaryPoints 注入临时捕捉源(如绘制中的辅助点):

mapcadLayer.snapManager.setTemporaryPoints([[0, 0], [50, 0]]);
mapcadLayer.snapManager.clearTemporaryPoints();

⚠️ 在 getPoint 等待期间,每次鼠标移动 InputManager 都会执行 snapManager.setTemporaryPoints(previewManager.polylineVertices)——手动注入的点下一次 mousemove 就被覆盖;且临时点只在 SnapMode.END 开启时参与(按 end 类型)。要在 getPoint 中捕捉自定义辅助点,改喂 PreviewManager:

ctx.previewManager.setPolylineVertices(pts);   // pts: Point2D[];会被当作临时捕捉点
// 注意:useBasePoint 橡皮筋会把这些点连成折线预览;previewManager.clear() 会清掉它们

九、内部机制

用户鼠标移动 → SnapManager.querySnap:
  1. SpatialIndex.search(bbox) 取候选实体
  2. 每个实体算各模式候选点(NEA 单独计算,与其他点去重)
  3. 合并 → 过滤距离 > snapRadiusCad 的
  4. 按距离排序,最近的标 primary

十、关于正交 / F 键

1.0.1 没有内置正交模式(PointInputOptions.ignoreOrthoMode 是预留字段,引擎侧没有 ortho 实现),也没有 F8/F3 快捷键——ShortcutMap 的 19 个动作里不含 ortho/snap 切换,要自己监听 keydown 再调 setSnapEnabled / useForceAngle。

十一、常见坑

  1. 枚举名是缩写:写 SnapMode.Endpoint 会得到 undefined,位或后等于没开——用 END/MID/CEN/NOD/QUA/INT/INS/PER/TAN/NEA。
  2. SnapPoint.point 不存在:字段叫 position,取坐标写 hit.position。
  3. snapPixelRadius 是像素:不是 CAD 单位;setSnapRadius 同样按像素。
  4. PER/TAN 需要参考点:querySnap 第三参 referencePoint 缺省时这两个模式算不出。
  5. 复杂实体多候选:多段线每个顶点都算 END,命中后 primary 标记最近的那个。
  6. 栅格底图实体不参与捕捉:捕捉只作用于 MapCadLayer 内部实体(addEntity 进去的);DWG 底图原实体不在 SpatialIndex 里。
  7. 模式变化要刷新 UI:捕捉对话框勾选态不会自动同步,用 snapManager.setOnChange 监听。

相关

  1. snapManager.snap(...) 是内部方法:实例上存在 snap 但 snap(0.1,0.1) 静默返 undefined——它不吃 (x,y) 坐标参;公开查询入口只有 querySnap(cursorCadPt, snapRadiusCad, referencePoint?)。snapManager 宿主是 mapcadLayer.snapManager(已实例化),setTemporaryPoints/setSnapMode/setSnapRadius/setOnChange 均可程序化直调。