Files
StandardSence/.cursor/skills/cyclegui-app-development/references/workspace-interaction.md
T
zhaowei.huangandCursor 158d770c54 docs: 添加 cyclegui-app-development 项目技能
将 CycleGUI 开发技能(含 Duplicated id 防碰撞规范)纳入 .cursor/skills,并调整 gitignore 仅放行 skills 目录可提交。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-30 22:38:18 +08:00

141 lines
4.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`:允许三轴平移和三轴旋转。