319 lines
11 KiB
Markdown
319 lines
11 KiB
Markdown
|
|
撤销标记管理是 SunvStation 地理编辑系统中的一项核心功能,它通过在执行可撤销操作前创建标记,支持用户对编辑操作进行撤销和重做。该机制基于命令模式,将每次可撤销操作封装为独立的命令单元,存储在 Undo/Redo 栈中,确保数据编辑的安全性和可追溯性。
|
|||
|
|
|
|||
|
|
撤销标记管理在批量地理对象编辑、属性修改和几何变换等场景中尤为重要,能够有效防止误操作导致的数据丢失,提升编辑流程的容错能力和用户体验。
|
|||
|
|
|
|||
|
|
## 撤销标记系统架构
|
|||
|
|
|
|||
|
|
撤销标记系统采用双层架构设计:上层通过 `SSProcessManager` 提供统一的 Python 接口,下层由 `ScaleMap` 的 C++ 核心实现命令存储和状态管理。系统维护两个命令栈:Undo 栈存储已执行的操作历史,Redo 栈存储被撤销的操作,两者协同工作实现操作的双向回溯。
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
flowchart TD
|
|||
|
|
A[用户操作] --> B[SSProcessManager<br/>pushUndoMark]
|
|||
|
|
B --> C[ScaleMap<br/>pushUndoMark]
|
|||
|
|
C --> D{创建命令标记}
|
|||
|
|
D --> E[命令序列化]
|
|||
|
|
E --> F[Undo栈<br/>LIFO结构]
|
|||
|
|
F --> G[操作执行]
|
|||
|
|
G --> H{用户触发撤销?}
|
|||
|
|
H -->|是| I[undo方法]
|
|||
|
|
H -->|否| J{用户触发重做?}
|
|||
|
|
I --> K[弹出Undo栈顶命令]
|
|||
|
|
K --> L[执行逆向操作]
|
|||
|
|
L --> M[压入Redo栈]
|
|||
|
|
J -->|是| N[redo方法]
|
|||
|
|
N --> O[弹出Redo栈顶命令]
|
|||
|
|
O --> P[执行正向操作]
|
|||
|
|
P --> Q[压入Undo栈]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 核心 API 方法
|
|||
|
|
|
|||
|
|
### 创建撤销标记
|
|||
|
|
|
|||
|
|
`pushUndoMark()` 方法是撤销标记管理的入口,在执行任何可撤销操作前调用,为后续操作建立撤销点。该方法接受可选的操作描述参数,用于标识命令组的用途。
|
|||
|
|
|
|||
|
|
| 方法 | 参数 | 返回值 | 说明 |
|
|||
|
|
|------|------|--------|------|
|
|||
|
|
| `pushUndoMark(action="CommandGroup")` | `action`: 命令组名称(可选) | void | 在 Undo 栈中创建新的标记位置 |
|
|||
|
|
|
|||
|
|
**使用示例**:
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# 批量修改前创建撤销标记
|
|||
|
|
SSProcess.pushUndoMark("批量修改管线属性")
|
|||
|
|
|
|||
|
|
for i in range(SSProcess.getSelGeoCount()):
|
|||
|
|
SSProcess.setSelGeoValue(i, "SSObj_Color", "16777215")
|
|||
|
|
SSProcess.setSelGeoValue(i, "SSObj_LineWidth", "2")
|
|||
|
|
|
|||
|
|
# 修改完成后,用户可通过 Ctrl+Z 或调用 undo() 方法撤销
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Sources: [ssprocess_mixins/project_mixin.py](ssprocess_mixins/project_mixin.py#L50-L61)
|
|||
|
|
|
|||
|
|
### 执行撤销操作
|
|||
|
|
|
|||
|
|
`undo()` 方法从 Undo 栈中弹出最近执行的命令并执行逆向操作,恢复数据到操作前的状态。支持指定步数或使用标记进行批量撤销。
|
|||
|
|
|
|||
|
|
| 方法 | 参数 | 返回值 | 说明 |
|
|||
|
|
|------|------|--------|------|
|
|||
|
|
| `undo(step=1)` | `step`: 执行步数,-1 时使用 undo 标签 | void | 撤销指定步数的操作 |
|
|||
|
|
|
|||
|
|
**使用示例**:
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# 撤销最近一次操作
|
|||
|
|
SSProcess.undo()
|
|||
|
|
|
|||
|
|
# 撤销最近 5 次操作
|
|||
|
|
SSProcess.undo(5)
|
|||
|
|
|
|||
|
|
# 撤销到上一个标记位置
|
|||
|
|
SSProcess.undo(-1)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Sources: [PySSMap.py](PySSMap.py#L1633-L1641)
|
|||
|
|
|
|||
|
|
### 执行重做操作
|
|||
|
|
|
|||
|
|
`redo()` 方法从 Redo 栈中弹出被撤销的命令并重新执行,恢复之前撤销的操作。与 `undo()` 方法形成对称操作。
|
|||
|
|
|
|||
|
|
| 方法 | 参数 | 返回值 | 说明 |
|
|||
|
|
|------|------|--------|------|
|
|||
|
|
| `redo(step=1)` | `step`: 执行步数,-1 时使用 undo 标签 | void | 重做指定步数的操作 |
|
|||
|
|
|
|||
|
|
**使用示例**:
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# 重做最近一次撤销的操作
|
|||
|
|
SSProcess.redo()
|
|||
|
|
|
|||
|
|
# 重做最近 3 次撤销的操作
|
|||
|
|
SSProcess.redo(3)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Sources: [PySSMap.py](PySSMap.py#L1643-L1648)
|
|||
|
|
|
|||
|
|
### 栈状态查询与控制
|
|||
|
|
|
|||
|
|
系统提供多个方法用于查询 Undo/Redo 栈的状态,以及重置栈内容,便于用户了解可撤销/重做的操作范围。
|
|||
|
|
|
|||
|
|
| 方法 | 返回值 | 说明 |
|
|||
|
|
|------|--------|------|
|
|||
|
|
| `getUndoStackSize()` | int | 获取 Undo 栈中的命令数量 |
|
|||
|
|
| `getRedoStackSize()` | int | 获取 Redo 栈中的命令数量 |
|
|||
|
|
| `getUndoStack()` | StringArray | 获取 Undo 栈中的命令描述列表 |
|
|||
|
|
| `getRedoStack()` | StringArray | 获取 Redo 栈中的命令描述列表 |
|
|||
|
|
| `resetUndoRedoStack()` | void | 清空 Undo/Redo 栈,删除所有历史记录 |
|
|||
|
|
|
|||
|
|
**使用示例**:
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# 查询可撤销的操作数量
|
|||
|
|
undo_count = SSProcess.getUndoStackSize()
|
|||
|
|
print(f"可撤销操作数: {undo_count}")
|
|||
|
|
|
|||
|
|
# 查询可重做的操作数量
|
|||
|
|
redo_count = SSProcess.getRedoStackSize()
|
|||
|
|
print(f"可重做操作数: {redo_count}")
|
|||
|
|
|
|||
|
|
# 获取 Undo 栈中的命令描述
|
|||
|
|
undo_stack = SSProcess.getUndoStack()
|
|||
|
|
for i in range(undo_stack.size()):
|
|||
|
|
print(f" {i+1}. {undo_stack[i]}")
|
|||
|
|
|
|||
|
|
# 清空所有撤销/重做历史(谨慎使用)
|
|||
|
|
# SSProcess.resetUndoRedoStack()
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Sources: [PySSMap.py](PySSMap.py#L1650-L1675)
|
|||
|
|
|
|||
|
|
### 手动压栈操作
|
|||
|
|
|
|||
|
|
`pushUndoAddobj()` 方法提供手动压栈功能,用于将指定地物对象列表显式地加入 Undo 栈,通常用于自定义编辑场景。
|
|||
|
|
|
|||
|
|
| 方法 | 参数 | 返回值 | 说明 |
|
|||
|
|
|------|------|--------|------|
|
|||
|
|
| `pushUndoAddobj(geoList)` | `geoList`: GeoBaseList 对象列表 | void | 手动将对象列表压入 Undo 栈 |
|
|||
|
|
|
|||
|
|
**使用示例**:
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# 创建新对象后手动压栈
|
|||
|
|
obj, geo = SSProcess.createNewObjByCode(3103013)
|
|||
|
|
SSProcess.addNewObjPoint(x, y, z, 0, "Point1")
|
|||
|
|
|
|||
|
|
# 手动压栈以便后续撤销
|
|||
|
|
geo_list = GeoBaseList()
|
|||
|
|
geo_list.append(geo)
|
|||
|
|
SSProcess.pushUndoAddobj(geo_list)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Sources: [PySSMap.py](PySSMap.py#L1686-L1692)
|
|||
|
|
|
|||
|
|
## 操作流程规范
|
|||
|
|
|
|||
|
|
### 标准编辑流程
|
|||
|
|
|
|||
|
|
遵循标准的撤销标记管理流程可确保操作的可追溯性和数据安全性。下图展示了完整的编辑操作生命周期:
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
sequenceDiagram
|
|||
|
|
participant U as 用户
|
|||
|
|
participant S as SSProcess
|
|||
|
|
participant M as ScaleMap
|
|||
|
|
participant UStack as Undo栈
|
|||
|
|
participant RStack as Redo栈
|
|||
|
|
|
|||
|
|
U->>S: 1. pushUndoMark("操作描述")
|
|||
|
|
S->>M: 2. pushUndoMark()
|
|||
|
|
M->>UStack: 3. 创建标记并压栈
|
|||
|
|
M->>RStack: 4. 清空 Redo 栈
|
|||
|
|
|
|||
|
|
U->>S: 5. 执行编辑操作
|
|||
|
|
S->>M: 6. 修改数据
|
|||
|
|
|
|||
|
|
opt 用户请求撤销
|
|||
|
|
U->>S: 7. undo()
|
|||
|
|
S->>M: 8. undo()
|
|||
|
|
M->>UStack: 9. 弹出命令
|
|||
|
|
M->>M: 10. 执行逆向操作
|
|||
|
|
M->>RStack: 11. 压入 Redo 栈
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
opt 用户请求重做
|
|||
|
|
U->>S: 12. redo()
|
|||
|
|
S->>M: 13. redo()
|
|||
|
|
M->>RStack: 14. 弹出命令
|
|||
|
|
M->>M: 15. 执行正向操作
|
|||
|
|
M->>UStack: 16. 压入 Undo 栈
|
|||
|
|
end
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 标记创建时机
|
|||
|
|
|
|||
|
|
撤销标记应在以下关键节点创建:
|
|||
|
|
|
|||
|
|
1. **批量操作前**:批量修改属性、移动对象、删除对象等操作前
|
|||
|
|
2. **复合操作起始点**:由多个子操作组成的复杂编辑流程开始时
|
|||
|
|
3. **重要编辑前**:不可逆或影响范围较大的编辑操作前
|
|||
|
|
4. **事务边界**:定义明确的编辑事务单元,便于整体撤销
|
|||
|
|
|
|||
|
|
## 典型应用场景
|
|||
|
|
|
|||
|
|
### 批量属性修改
|
|||
|
|
|
|||
|
|
批量修改选择集中地物的属性值时,应在循环前创建撤销标记,确保所有修改可作为一个整体撤销。
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# 场景:批量修改管线的颜色和线宽
|
|||
|
|
SSProcess.pushUndoMark("批量修改管线样式")
|
|||
|
|
|
|||
|
|
for i in range(SSProcess.getSelGeoCount()):
|
|||
|
|
SSProcess.setSelGeoValue(i, "SSObj_Color", "16711680") # 红色
|
|||
|
|
SSProcess.setSelGeoValue(i, "SSObj_LineWidth", "3")
|
|||
|
|
|
|||
|
|
# 如需撤销所有修改,调用:
|
|||
|
|
# SSProcess.undo(-1)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Sources: [ssprocess_mixins/geo_edit_mixin.py](ssprocess_mixins/geo_edit_mixin.py#L321-L400)
|
|||
|
|
|
|||
|
|
### 对象创建与删除
|
|||
|
|
|
|||
|
|
创建或删除对象时,通过撤销标记管理可快速回退到操作前的状态。
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# 场景:创建新注记对象
|
|||
|
|
SSProcess.pushUndoMark("创建注记对象")
|
|||
|
|
|
|||
|
|
obj, geo = SSProcess.createNewObjByClass("说明注记")
|
|||
|
|
note = castToMarkNote(geo)
|
|||
|
|
note.setNote("新建管线节点")
|
|||
|
|
SSProcess.addNewObjPoint(x, y, 0, 0, "节点1")
|
|||
|
|
SSProcess.addNewObjToSaveObjList()
|
|||
|
|
SSProcess.saveBufferObjToDatabase()
|
|||
|
|
|
|||
|
|
# 如需撤销创建操作:
|
|||
|
|
# SSProcess.undo()
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Sources: [ssprocess_mixins/geo_edit_mixin.py](ssprocess_mixins/geo_edit_mixin.py#L159-L201)
|
|||
|
|
|
|||
|
|
### 几何变换操作
|
|||
|
|
|
|||
|
|
执行对象的平移、旋转、缩放等几何变换时,撤销标记管理提供快速恢复机制。
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# 场景:批量旋转选择集对象
|
|||
|
|
from sunvpy.PySSMath import Point3D
|
|||
|
|
|
|||
|
|
SSProcess.pushUndoMark("旋转选择集对象")
|
|||
|
|
|
|||
|
|
# 获取旋转中心(假设第一个对象为参考点)
|
|||
|
|
center_geo = SSProcess.selGeoList[0]
|
|||
|
|
points = center_geo.getPoints()
|
|||
|
|
center_pt = points[0]
|
|||
|
|
|
|||
|
|
# 执行旋转
|
|||
|
|
angle = 1.5708 # 90度,弧度制
|
|||
|
|
SSProcess.map.rotate(SSProcess.selGeoList, center_pt, angle, False)
|
|||
|
|
|
|||
|
|
# 请求视图更新
|
|||
|
|
SSProcess.updateRequest()
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Sources: [PySSMap.py](PySSMap.py#L1558-L1564)
|
|||
|
|
|
|||
|
|
## 最佳实践建议
|
|||
|
|
|
|||
|
|
### 标记命名规范
|
|||
|
|
|
|||
|
|
为撤销标记提供清晰的描述性名称,便于用户识别和调试。命名应简洁明了,反映操作的实质内容。
|
|||
|
|
|
|||
|
|
| 推荐命名格式 | 示例 | 说明 |
|
|||
|
|
|--------------|------|------|
|
|||
|
|
| "动作+对象类型" | "修改管线属性" | 动词+名词结构 |
|
|||
|
|
| "批量+具体操作" | "批量删除注记" | 明确操作类型 |
|
|||
|
|
| "事务名称" | "节点编辑事务" | 用于复合操作 |
|
|||
|
|
| "功能模块+操作" | "数据导入操作" | 模块化命名 |
|
|||
|
|
|
|||
|
|
### 性能优化建议
|
|||
|
|
|
|||
|
|
1. **避免频繁创建标记**:在循环外创建标记,而非每次迭代都创建
|
|||
|
|
2. **合理使用标记边界**:根据业务逻辑划分事务单元,而非滥用标记
|
|||
|
|
3. **定期清空栈**:在长时间编辑会话中,适时调用 `resetUndoRedoStack()` 释放内存
|
|||
|
|
4. **栈容量监控**:通过 `getUndoStackSize()` 监控栈深度,避免过度占用资源
|
|||
|
|
|
|||
|
|
### 错误处理机制
|
|||
|
|
|
|||
|
|
在创建撤销标记时,应检查地图实例的有效性,并在操作失败时进行适当的错误处理。
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
def safe_edit_operation():
|
|||
|
|
"""安全的编辑操作封装"""
|
|||
|
|
if SSProcess.map is None:
|
|||
|
|
print("错误:无法获取当前地图实例")
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
SSProcess.pushUndoMark("安全编辑操作")
|
|||
|
|
|
|||
|
|
# 执行编辑操作
|
|||
|
|
# ...
|
|||
|
|
|
|||
|
|
SSProcess.updateRequest()
|
|||
|
|
return True
|
|||
|
|
except Exception as e:
|
|||
|
|
print(f"编辑操作失败:{str(e)}")
|
|||
|
|
# 可选择在此处调用 undo() 回滚部分修改
|
|||
|
|
return False
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Sources: [ssprocess_mixins/project_mixin.py](ssprocess_mixins/project_mixin.py#L50-L61)
|
|||
|
|
|
|||
|
|
## 与其他模块的集成
|
|||
|
|
|
|||
|
|
撤销标记管理与[对象缓存机制](23-dui-xiang-huan-cun-ji-zhi)紧密协同,在批量保存到数据库前提供撤销能力。同时,它与[地物属性读写](15-huo-qu-di-wu-shu-xing-zhi)和[地理对象编辑](19-chuang-jian-mo-ren-di-wu-dui-xiang)模块集成,确保所有编辑操作的可追溯性。
|
|||
|
|
|
|||
|
|
在实现自定义编辑功能时,建议在操作前后分别调用 `pushUndoMark()` 和 `updateRequest()`,形成完整的编辑闭环,保持与系统标准编辑流程的一致性。
|