141 lines
4.6 KiB
Markdown
141 lines
4.6 KiB
Markdown
# Workspace 交互、对象选择和坐标拾取
|
||||
|
|
|
|||
|
|
## namePattern 语法
|
|||
|
|
|
|||
|
|
所有 `namePattern` 使用 glob 语义,不是 regex:
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
selectOp.SetObjectSelectable("UISite-*"); // 命中 UISite-3
|
|||
|
|
selectOp.SetObjectSelectable("UISite-.*"); // 错:这不是 regex
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
需要正则时,在 C# 侧用 `Regex` 先筛出具体名字,再逐个传具体名字给 CycleGUI API。
|
|||
|
|
|
|||
|
|
## 对象选择
|
|||
|
|
|
|||
|
|
画布或 Workspace 中的业务对象需要点击、选择、框选或刷选时,优先把它们绘制成 CycleGUI 原生可选对象,然后使用 `SelectObject`。常见做法是用 `PutModelObject`、`PutPointCloud`、`PutHandleIcon`、`PutStraightLine`,或其它可被 `SetObjectSelectable` 命中的 Workspace/Painter 对象来承载可视化主体。
|
|||
|
|
|
|||
|
|
无特殊情况,不要用 `GetPosition` 读取鼠标坐标再手写命中测试来选择画布对象。`GetPosition` 只用于自由坐标拾取、放置点、测量、绘制轮廓、吸附到点位等确实需要返回坐标的位置交互。
|
|||
|
|
|
|||
|
|
Painter 更适合调试绘制和非交互 overlay;需要被用户点击选择的业务对象,应尽量有对应的原生可选对象作为主表示。
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
var selectOp = new SelectObject { fineSelectOnPointClouds = true };
|
|||
|
|
selectOp.feedback = (result, op) =>
|
|||
|
|
{
|
|||
|
|
foreach (var (name, selector, firstSub) in result)
|
|||
|
|
Console.WriteLine(name);
|
|||
|
|
};
|
|||
|
|
selectOp.Start();
|
|||
|
|
selectOp.SetSelectionMode(SelectObject.SelectionMode.Click);
|
|||
|
|
selectOp.SetObjectSelectable("UISite-*");
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`SetSelectionMode` 支持 `Click`、`Rectangle`、`Paint`。
|
|||
|
|
|
|||
|
|
修饰键语义:Shift + 左键追加选择,Alt + 左键从选择集中移除,左键点击空白清空选择。不要重新发明 Ctrl 多选;Ctrl 留给其它操作。
|
|||
|
|
|
|||
|
|
## sticky pattern
|
|||
|
|
|
|||
|
|
`SetObjectSelectable(pattern)` 是 sticky:
|
|||
|
|
|
|||
|
|
- 调用时对已存在对象做一次 glob 匹配并设为可选。
|
|||
|
|
- 之后新增对象时自动按该 pattern 匹配并设为可选。
|
|||
|
|
- 不需要在每次 `Workspace.AddProp` 后重复调用。
|
|||
|
|
|
|||
|
|
取消 sticky 必须用同一个 pattern 字符串:
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
selectOp.SetObjectUnselectable("UISite-*");
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`SetObjectUnselectable("UISite-3")` 只清具体对象,不会移除 `"UISite-*"` 的 sticky 规则。
|
|||
|
|
|
|||
|
|
## 拾取世界坐标
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
var gp = new GetPosition
|
|||
|
|
{
|
|||
|
|
method = GetPosition.PickMode.GridPlane,
|
|||
|
|
snaps = new[] { "UISite-*" },
|
|||
|
|
realtime = true,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
gp.feedback = (wp, _) =>
|
|||
|
|
{
|
|||
|
|
var xy = new Vector2(wp.mouse_pos.X, wp.mouse_pos.Y);
|
|||
|
|
};
|
|||
|
|
gp.finished = () => { };
|
|||
|
|
gp.terminated = () => { };
|
|||
|
|
gp.Start();
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`realtime = true` 时,鼠标移动也会触发 `feedback`,适合用 Painter 做橡皮筋预览;最终左键点击时再触发 `finished`。
|
|||
|
|
|
|||
|
|
## SelectObject 和 GetPosition 互斥
|
|||
|
|
|
|||
|
|
Workspace 输入由当前 UI operation 消费。进入放置/拾取模式前先 `selectOp.End()`;结束后再创建并启动新的 `SelectObject`。sticky pattern 是 per-op 状态,重启选择 op 后需要重新设置 pattern。
|
|||
|
|
|
|||
|
|
## async/await 包装
|
|||
|
|
|
|||
|
|
把 `WorkspaceUIOperation` 包成 `Task` 时,`feedback` 只更新数据或 `TrySetResult`,不要在 `feedback` 后半段重启新 op 或做全局清理。清理放到调用方 `try/finally`。
|
|||
|
|
|
|||
|
|
推荐模式:
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
private static Task<Vector2?> PickPoint(State st, Action<Vector2>? onPreview = null)
|
|||
|
|
{
|
|||
|
|
var tcs = new TaskCompletionSource<Vector2?>();
|
|||
|
|
Vector2 last = default;
|
|||
|
|
bool got = false;
|
|||
|
|
|
|||
|
|
var gp = new GetPosition { realtime = onPreview != null };
|
|||
|
|
gp.feedback = (wp, _) =>
|
|||
|
|
{
|
|||
|
|
last = new Vector2(wp.mouse_pos.X, wp.mouse_pos.Y);
|
|||
|
|
got = true;
|
|||
|
|
onPreview?.Invoke(last);
|
|||
|
|
};
|
|||
|
|
gp.finished = () => tcs.TrySetResult(got ? last : null);
|
|||
|
|
gp.terminated = () => tcs.TrySetResult(null);
|
|||
|
|
gp.Start();
|
|||
|
|
st.CurrentOp = gp;
|
|||
|
|
return tcs.Task;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
业务取消按钮必须显式唤醒 pending task:
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
st.CurrentOp?.End();
|
|||
|
|
st.PendingTcs?.TrySetResult(null);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`End()` 不会触发 `terminated`。
|
|||
|
|
|
|||
|
|
## Guizmo 自由度限制
|
|||
|
|
|
|||
|
|
`GuizmoAction` 使用 `dof` 声明允许的平移/旋转自由度。CycleGUI 是 Z-up 语义,`Yaw` 等价于 `RotateZ`,常用于地面 XY 平面编辑。
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
new GuizmoAction
|
|||
|
|
{
|
|||
|
|
dof = GuizmoAction.GuizmoDof.PlanarXYYaw,
|
|||
|
|
realtimeResult = true,
|
|||
|
|
feedback = (items, _) =>
|
|||
|
|
{
|
|||
|
|
foreach (var (name, pos, rot) in items)
|
|||
|
|
Console.WriteLine($"{name}: {pos}, {rot}");
|
|||
|
|
},
|
|||
|
|
}.Start();
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
常用组合:
|
|||
|
|
|
|||
|
|
- `TranslateXYZ`:只允许三轴平移。
|
|||
|
|
- `PlanarXY`:只允许 X/Y 平面平移。
|
|||
|
|
- `PlanarXYYaw`:只允许 X/Y 平移和绕 Z 轴旋转。
|
|||
|
|
- `Yaw`:只允许绕 Z 轴旋转。
|
|||
|
|
- `RotateXYZ`:只允许三轴旋转。
|
|||
|
|
- `All`:允许三轴平移和三轴旋转。
|