撤销标记管理是 SunvStation 地理编辑系统中的一项核心功能,它通过在执行可撤销操作前创建标记,支持用户对编辑操作进行撤销和重做。该机制基于命令模式,将每次可撤销操作封装为独立的命令单元,存储在 Undo/Redo 栈中,确保数据编辑的安全性和可追溯性。 撤销标记管理在批量地理对象编辑、属性修改和几何变换等场景中尤为重要,能够有效防止误操作导致的数据丢失,提升编辑流程的容错能力和用户体验。 ## 撤销标记系统架构 撤销标记系统采用双层架构设计:上层通过 `SSProcessManager` 提供统一的 Python 接口,下层由 `ScaleMap` 的 C++ 核心实现命令存储和状态管理。系统维护两个命令栈:Undo 栈存储已执行的操作历史,Redo 栈存储被撤销的操作,两者协同工作实现操作的双向回溯。 ```mermaid flowchart TD A[用户操作] --> B[SSProcessManager
pushUndoMark] B --> C[ScaleMap
pushUndoMark] C --> D{创建命令标记} D --> E[命令序列化] E --> F[Undo栈
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()`,形成完整的编辑闭环,保持与系统标准编辑流程的一致性。