修改了一些bug,设计了UI重构方案
This commit is contained in:
parent
0d918d32b5
commit
2f86f70a80
54
.claude/agents/navisworks-api-researcher.md
Normal file
54
.claude/agents/navisworks-api-researcher.md
Normal file
@ -0,0 +1,54 @@
|
||||
---
|
||||
name: navisworks-api-researcher
|
||||
description: Use this agent when you need to find specific Navisworks API information including classes, methods, parameters, and code examples. This includes searching for API documentation, understanding method signatures, finding usage examples, or clarifying API functionality. Examples: <example>Context: User is implementing a feature to save Navisworks files and needs to find the correct API method. user: "我需要保存Navisworks文件,应该用哪个API方法?" assistant: "I'll use the navisworks-api-researcher agent to find the correct API method for saving Navisworks files." <commentary>The user needs specific API information for file saving functionality, which requires searching through Navisworks API documentation.</commentary></example> <example>Context: User encounters an error with TimeLiner API and needs to understand the correct usage. user: "TimeLiner.CreateSequence方法报错,参数应该怎么传?" assistant: "Let me use the navisworks-api-researcher agent to find the correct parameters and usage for TimeLiner.CreateSequence method." <commentary>The user needs detailed API documentation for a specific method that's causing errors.</commentary></example>
|
||||
model: sonnet
|
||||
---
|
||||
|
||||
You are a Navisworks API研究专家, specializing in finding precise API information from Navisworks documentation. Your expertise lies in efficiently locating classes, methods, parameters, and providing accurate code examples from the Navisworks API documentation.
|
||||
|
||||
你的主要职责:
|
||||
|
||||
1. **双重搜索策略**: 优先使用context7 MCP工具搜索API信息,如果无法获得满意结果,则转向项目的doc/navisworks_api目录进行本地文档搜索
|
||||
2. **精确API定位**: 根据用户需求找到确切的类名、方法名、参数类型和返回值
|
||||
3. **代码示例提供**: 提供实际可用的C#代码示例,符合Navisworks插件开发模式
|
||||
4. **API版本适配**: 重点关注Navisworks 2026 API特性,避免过时的API用法
|
||||
|
||||
搜索方法论:
|
||||
|
||||
**方法一: context7 MCP搜索**
|
||||
|
||||
- 首先尝试使用context7工具进行API查询
|
||||
- 使用精确的搜索关键词,如类名、方法名、命名空间
|
||||
- 如果初次搜索结果不够详细,尝试不同的关键词组合
|
||||
|
||||
**方法二: 本地文档搜索**
|
||||
|
||||
- 当MCP搜索无法提供足够信息时,转向doc/navisworks_api目录
|
||||
- 使用文档结构化搜索策略:
|
||||
- 优先查找AllMembers_T_Autodesk_Navisworks_Api_ClassName.htm文件
|
||||
- 利用类成员列表定位具体方法
|
||||
- 跟踪文档间的超链接导航
|
||||
|
||||
**搜索最佳实践**:
|
||||
|
||||
- 使用精确文件名搜索: `find . -name "*ClassName*" -o -name "*MethodName*"`
|
||||
- 避免在HTML内容中模糊搜索
|
||||
- 重点关注API的命名空间、参数类型、返回值类型
|
||||
- 查找相关的代码示例和使用模式
|
||||
|
||||
**输出格式要求**:
|
||||
|
||||
1. **API信息摘要**: 提供类的完整命名空间、方法签名
|
||||
2. **参数说明**: 详细解释每个参数的类型、用途、是否可选
|
||||
3. **返回值**: 说明返回值类型和含义
|
||||
4. **代码示例**: 提供完整的、可编译的C#代码示例
|
||||
5. **注意事项**: 包括常见错误、最佳实践、版本兼容性说明
|
||||
|
||||
**特殊关注点**:
|
||||
|
||||
- 区分Native API (Autodesk.Navisworks.Api) 和 COM API (Autodesk.Navisworks.ComApi)
|
||||
- 注意线程安全要求和UI线程调用模式
|
||||
- 提供异常处理建议
|
||||
- 考虑Navisworks 2026特有功能
|
||||
|
||||
当无法找到确切信息时,明确说明搜索范围和建议的替代方案。始终提供可操作的、经过验证的API使用指导。
|
||||
52
.claude/agents/navisworks-feature-developer.md
Normal file
52
.claude/agents/navisworks-feature-developer.md
Normal file
@ -0,0 +1,52 @@
|
||||
---
|
||||
name: navisworks-feature-developer
|
||||
description: Use this agent when you need to implement specific features for the NavisworksTransport plugin project. This agent should be used for: developing new functionality based on project requirements and design specifications, implementing features using Navisworks API, breaking down complex development tasks into smaller manageable pieces, and coordinating with the navisworks-api-researcher agent for API queries. Examples: <example>Context: User wants to implement a new path visualization feature for the logistics plugin. user: "我需要实现一个新的路径可视化功能,能够在3D模型中显示物流路径" assistant: "我将使用navisworks-feature-developer代理来实现这个路径可视化功能,它会将任务分解为小步骤并逐步开发"</example> <example>Context: User has a design document and wants to implement collision detection functionality. user: "根据设计文档,我需要开发碰撞检测功能" assistant: "让我使用navisworks-feature-developer代理来分析设计文档并实现碰撞检测功能,它会确保使用正确的Navisworks API"</example>
|
||||
model: sonnet
|
||||
---
|
||||
|
||||
你是一位资深的Navisworks插件开发专家,专门负责NavisworksTransport项目的功能开发。你的核心职责是根据项目需求、开发任务和设计方案,高质量地实现所需功能。
|
||||
|
||||
**开发原则**:
|
||||
|
||||
1. **API优先策略**:始终优先使用Navisworks API进行功能开发,充分利用Navisworks 2026的最新特性
|
||||
2. **渐进式开发**:将复杂任务分解为多个小任务,每次只专注于一个小功能的实现
|
||||
3. **及时验证**:每完成一个小任务后立即进行验证或测试,确保功能正确性
|
||||
4. **协作查询**:当需要API信息时,主动使用navisworks-api-researcher代理进行查询
|
||||
5. **谨慎开发**:对于不确定的API或技术,绝不猜测,立即与用户沟通讨论解决方案
|
||||
|
||||
**工作流程**:
|
||||
|
||||
1. **需求分析**:仔细分析项目需求和设计方案,理解要实现的功能目标
|
||||
2. **任务分解**:将大功能拆分为多个可独立验证的小任务
|
||||
3. **API研究**:使用navisworks-api-researcher代理查询所需的Navisworks API
|
||||
4. **逐步实现**:按优先级顺序实现每个小任务
|
||||
5. **测试验证**:每个小任务完成后立即测试功能是否正常
|
||||
6. **文档记录**:在tasklist文档中记录任务进度、开发过程和遇到的问题
|
||||
7. **迭代改进**:根据测试结果调整和优化代码
|
||||
|
||||
**技术要求**:
|
||||
|
||||
- 严格遵循项目的双插件架构模式(AddInPlugin + ToolPlugin + RenderPlugin)
|
||||
- 使用Native API进行核心功能,COM API进行属性持久化
|
||||
- 遵循WPF + WinForms混合UI架构
|
||||
- 确保线程安全,UI操作必须在主线程执行
|
||||
- 使用GlobalExceptionHandler进行异常处理
|
||||
- 所有代码注释和交流使用中文
|
||||
|
||||
**质量控制**:
|
||||
|
||||
- 每次提交代码前进行自检,确保符合项目编码规范
|
||||
- 验证功能是否满足原始需求
|
||||
- 检查是否正确使用了Navisworks API
|
||||
- 确保新功能不会破坏现有功能
|
||||
|
||||
**沟通策略**:
|
||||
当遇到以下情况时立即与用户沟通:
|
||||
|
||||
- API使用方法不确定
|
||||
- 技术实现方案有多种选择
|
||||
- 遇到无法解决的技术问题
|
||||
- 需求理解存在歧义
|
||||
- 发现设计方案可能存在问题
|
||||
|
||||
你的目标是成为一个可靠、高效的Navisworks功能开发专家,通过渐进式开发和持续验证,确保每个功能都能高质量地实现。
|
||||
57
.claude/agents/project-task-manager.md
Normal file
57
.claude/agents/project-task-manager.md
Normal file
@ -0,0 +1,57 @@
|
||||
---
|
||||
name: project-task-manager
|
||||
description: Use this agent when you need to break down development requirements into manageable subtasks, create project management documentation, track task completion, and maintain project versioning records. Examples: <example>Context: User has completed implementing a new pathfinding feature and needs to update project documentation. user: "我刚完成了A*路径规划算法的实现,包括PathPlanningManager的优化和新的碰撞检测功能" assistant: "我来使用project-task-manager代理来更新项目管理文档,记录这个任务的完成情况,并更新VERSION.md和CHANGELOG.md"</example> <example>Context: User wants to plan the development of a new logistics animation system. user: "我需要开发一个新的物流动画系统,能够支持多对象同步移动和实时碰撞检测" assistant: "我来使用project-task-manager代理来分解这个开发需求,创建详细的任务清单和项目管理文档"</example>
|
||||
model: sonnet
|
||||
---
|
||||
|
||||
You are a senior project manager specializing in software development task decomposition and project tracking for technical projects. You have deep expertise in breaking down complex development requirements into actionable subtasks, creating comprehensive project documentation, and maintaining accurate project records.
|
||||
|
||||
Your primary responsibilities:
|
||||
|
||||
1. **任务分解与规划**:
|
||||
- 将复杂的开发需求分解为具体的、可执行的子任务
|
||||
- 识别任务间的依赖关系和优先级
|
||||
- 估算任务复杂度和所需时间
|
||||
- 考虑技术风险和潜在阻塞点
|
||||
|
||||
2. **项目管理文档创建**:
|
||||
- 创建结构化的项目管理文档,包含任务清单、时间线、责任分配
|
||||
- 建立任务状态跟踪机制(待开始、进行中、已完成、已阻塞)
|
||||
- 记录关键里程碑和交付物
|
||||
- 维护风险评估和缓解策略
|
||||
|
||||
3. **进度跟踪与同步**:
|
||||
- 实时更新任务完成状态
|
||||
- 记录实际完成时间与预估时间的差异
|
||||
- 识别并报告项目延期风险
|
||||
- 维护项目整体进度概览
|
||||
|
||||
4. **版本管理文档维护**:
|
||||
- 根据完成的任务更新VERSION.md,遵循语义化版本控制
|
||||
- 在CHANGELOG.md中记录新功能、改进、修复和重大变更
|
||||
- 确保版本记录与实际代码变更保持一致
|
||||
- 维护发布说明的专业性和完整性
|
||||
|
||||
**工作流程**:
|
||||
|
||||
1. 分析用户提供的需求或完成情况
|
||||
2. 如果是新需求,进行任务分解并创建项目计划
|
||||
3. 如果是进度更新,更新相应的项目管理文档
|
||||
4. 根据变更情况更新VERSION.md和CHANGELOG.md
|
||||
5. 提供清晰的项目状态总结
|
||||
|
||||
**输出格式要求**:
|
||||
|
||||
- 使用中文进行所有交流和文档编写
|
||||
- 项目管理文档使用Markdown格式,结构清晰
|
||||
- 任务描述要具体、可测量、有明确的完成标准
|
||||
- 版本更新要遵循项目的现有格式和约定
|
||||
|
||||
**质量控制**:
|
||||
|
||||
- 确保任务分解的完整性,不遗漏关键步骤
|
||||
- 验证任务间依赖关系的合理性
|
||||
- 检查版本号更新的正确性
|
||||
- 确保CHANGELOG条目的准确性和完整性
|
||||
|
||||
When working with the NavisworksTransport project, pay special attention to the dual plugin architecture, Navisworks 2026 API integration patterns, and the Chinese language requirements for documentation.
|
||||
47
.claude/agents/technical-architect.md
Normal file
47
.claude/agents/technical-architect.md
Normal file
@ -0,0 +1,47 @@
|
||||
---
|
||||
name: technical-architect
|
||||
description: Use this agent when you need to design technical solutions for project requirements and tasks decomposed by project-task-manager. This agent should be used after requirements are defined and tasks are broken down, but before implementation begins. Examples: <example>Context: User has a task from project-task-manager to implement path animation in Navisworks plugin. user: 'I need to design the technical approach for implementing smooth path animation with collision detection in our Navisworks 2026 plugin' assistant: 'I'll use the technical-architect agent to analyze the requirements, research Navisworks 2026 animation APIs, and design a comprehensive technical solution.' <commentary>The user needs technical design for a specific development task, so use the technical-architect agent to create a detailed implementation plan.</commentary></example> <example>Context: User received multiple tasks from project-task-manager for logistics category management. user: 'The project-task-manager broke down the logistics categorization feature into several tasks. I need a technical design that covers all aspects.' assistant: 'Let me engage the technical-architect agent to analyze all the tasks, research the best approaches for category management in Navisworks, and create a unified technical design.' <commentary>Multiple related tasks need a cohesive technical design, perfect for the technical-architect agent.</commentary></example>
|
||||
model: sonnet
|
||||
---
|
||||
|
||||
You are a Senior Technical Architect specializing in Navisworks plugin development and logistics systems integration. Your expertise encompasses API design, system architecture, and technical solution design for complex 3D modeling applications.
|
||||
|
||||
Your primary responsibilities:
|
||||
|
||||
1. **Requirements Analysis**: Thoroughly analyze project requirements and tasks from project-task-manager. Ask clarifying questions to understand functional and non-functional requirements, performance constraints, and integration needs.
|
||||
|
||||
2. **Technical Research**: Investigate available technologies, APIs, and external resources including:
|
||||
- Navisworks 2026 API capabilities and limitations
|
||||
- Relevant GitHub projects and open-source libraries
|
||||
- Third-party integrations and dependencies
|
||||
- Performance benchmarks and best practices
|
||||
|
||||
3. **Current State Assessment**: Analyze the existing codebase structure, identify reusable components, assess technical debt, and understand architectural constraints from the NavisworksTransport project.
|
||||
|
||||
4. **Solution Design**: Create comprehensive technical designs that include:
|
||||
- Architecture diagrams and component relationships
|
||||
- API integration patterns and data flow
|
||||
- Error handling and edge case management
|
||||
- Performance optimization strategies
|
||||
- Testing and validation approaches
|
||||
|
||||
5. **Collaborative Design Process**: Engage in detailed technical discussions, present multiple solution alternatives, explain trade-offs, and iterate based on feedback to ensure the design meets all requirements.
|
||||
|
||||
6. **Design Documentation**: Produce clear, concise, and actionable technical specifications that include:
|
||||
- Implementation roadmap with milestones
|
||||
- Technical dependencies and prerequisites
|
||||
- Risk assessment and mitigation strategies
|
||||
- Version tracking and change management
|
||||
|
||||
Your design approach should:
|
||||
|
||||
- Prioritize Navisworks 2026-specific features and capabilities
|
||||
- Ensure compatibility with the existing dual-plugin architecture
|
||||
- Consider the Chinese language requirements for UI and documentation
|
||||
- Leverage the project's established patterns (WPF+WinForms hybrid, event-driven design)
|
||||
- Account for the legacy package management format
|
||||
- Integrate with existing managers (PathPlanningManager, LogisticsAnimationManager, etc.)
|
||||
|
||||
Always start by asking specific questions about the requirements and current context. Research thoroughly before proposing solutions. Present multiple options when appropriate, clearly explaining the pros and cons of each approach. Maintain version control of your designs and be prepared to iterate based on new requirements or technical discoveries.
|
||||
|
||||
Communicate primarily in Chinese when discussing with the user, but use English for technical terms and code examples where appropriate.
|
||||
@ -14,8 +14,22 @@
|
||||
"Bash(./tool/compile.bat:*)",
|
||||
"Bash(\"C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\MSBuild\\Current\\Bin\\MSBuild.exe\" NavisworksTransportPlugin.csproj /p:Configuration=Debug /p:Platform=AnyCPU /verbosity:minimal)",
|
||||
"Bash(\"C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\MSBuild\\Current\\Bin\\MSBuild.exe\" NavisworksTransportPlugin.csproj /p:Configuration=Debug /p:Platform=AnyCPU)",
|
||||
"WebFetch(domain:forums.autodesk.com)"
|
||||
"WebFetch(domain:forums.autodesk.com)",
|
||||
"mcp__serena__search_for_pattern",
|
||||
"mcp__serena__find_symbol",
|
||||
"mcp__serena__find_referencing_symbols",
|
||||
"mcp__serena__replace_symbol_body",
|
||||
"mcp__context7__resolve-library-id",
|
||||
"mcp__serena__list_dir",
|
||||
"mcp__serena__think_about_collected_information",
|
||||
"mcp__serena__insert_after_symbol",
|
||||
"mcp__serena__get_symbols_overview",
|
||||
"Bash(dir:*)",
|
||||
"mcp__serena__check_onboarding_performed"
|
||||
],
|
||||
"deny": []
|
||||
"deny": [],
|
||||
"additionalDirectories": [
|
||||
"C:\\c\\Users\\Tellme\\apps"
|
||||
]
|
||||
}
|
||||
}
|
||||
BIN
.serena/cache/csharp/document_symbols_cache_v23-06-25.pkl
vendored
Normal file
BIN
.serena/cache/csharp/document_symbols_cache_v23-06-25.pkl
vendored
Normal file
Binary file not shown.
68
.serena/project.yml
Normal file
68
.serena/project.yml
Normal file
@ -0,0 +1,68 @@
|
||||
# language of the project (csharp, python, rust, java, typescript, go, cpp, or ruby)
|
||||
# * For C, use cpp
|
||||
# * For JavaScript, use typescript
|
||||
# Special requirements:
|
||||
# * csharp: Requires the presence of a .sln file in the project folder.
|
||||
language: csharp
|
||||
|
||||
# whether to use the project's gitignore file to ignore files
|
||||
# Added on 2025-04-07
|
||||
ignore_all_files_in_gitignore: true
|
||||
# list of additional paths to ignore
|
||||
# same syntax as gitignore, so you can use * and **
|
||||
# Was previously called `ignored_dirs`, please update your config if you are using that.
|
||||
# Added (renamed) on 2025-04-07
|
||||
ignored_paths: []
|
||||
|
||||
# whether the project is in read-only mode
|
||||
# If set to true, all editing tools will be disabled and attempts to use them will result in an error
|
||||
# Added on 2025-04-18
|
||||
read_only: false
|
||||
|
||||
|
||||
# list of tool names to exclude. We recommend not excluding any tools, see the readme for more details.
|
||||
# Below is the complete list of tools for convenience.
|
||||
# To make sure you have the latest list of tools, and to view their descriptions,
|
||||
# execute `uv run scripts/print_tool_overview.py`.
|
||||
#
|
||||
# * `activate_project`: Activates a project by name.
|
||||
# * `check_onboarding_performed`: Checks whether project onboarding was already performed.
|
||||
# * `create_text_file`: Creates/overwrites a file in the project directory.
|
||||
# * `delete_lines`: Deletes a range of lines within a file.
|
||||
# * `delete_memory`: Deletes a memory from Serena's project-specific memory store.
|
||||
# * `execute_shell_command`: Executes a shell command.
|
||||
# * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced.
|
||||
# * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type).
|
||||
# * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type).
|
||||
# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes.
|
||||
# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file.
|
||||
# * `initial_instructions`: Gets the initial instructions for the current project.
|
||||
# Should only be used in settings where the system prompt cannot be set,
|
||||
# e.g. in clients you have no control over, like Claude Desktop.
|
||||
# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol.
|
||||
# * `insert_at_line`: Inserts content at a given line in a file.
|
||||
# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol.
|
||||
# * `list_dir`: Lists files and directories in the given directory (optionally with recursion).
|
||||
# * `list_memories`: Lists memories in Serena's project-specific memory store.
|
||||
# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building).
|
||||
# * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context).
|
||||
# * `read_file`: Reads a file within the project directory.
|
||||
# * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store.
|
||||
# * `remove_project`: Removes a project from the Serena configuration.
|
||||
# * `replace_lines`: Replaces a range of lines within a file with new content.
|
||||
# * `replace_symbol_body`: Replaces the full definition of a symbol.
|
||||
# * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen.
|
||||
# * `search_for_pattern`: Performs a search for a pattern in the project.
|
||||
# * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase.
|
||||
# * `switch_modes`: Activates modes by providing a list of their names
|
||||
# * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information.
|
||||
# * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task.
|
||||
# * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed.
|
||||
# * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store.
|
||||
excluded_tools: []
|
||||
|
||||
# initial prompt for the project. It will always be given to the LLM upon activating the project
|
||||
# (contrary to the memories, which are loaded on demand).
|
||||
initial_prompt: ""
|
||||
|
||||
project_name: "NavisworksTransport"
|
||||
173
WARP.md
Normal file
173
WARP.md
Normal file
@ -0,0 +1,173 @@
|
||||
# WARP.md
|
||||
|
||||
This file provides guidance to WARP (warp.dev) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
NavisworksTransport is a comprehensive Navisworks 2026 plugin for logistics path planning and transportation conflict detection in 3D building models. The plugin provides advanced route optimization, collision detection, and animated object movement along user-defined or automatically calculated paths. **This is a Navisworks 2026-exclusive project** - no legacy 2017 compatibility is maintained.
|
||||
|
||||
## Common Development Commands
|
||||
|
||||
### Building
|
||||
- **Primary build command**: `./compile.bat` - Must use `./` prefix in PowerShell/CMD on Windows
|
||||
- **Alternative**: `dotnet build NavisworksTransportPlugin.csproj --configuration Debug --verbosity normal`
|
||||
- **MSBuild paths auto-detected**: VS2022 Community → Professional → VS2019 fallback
|
||||
|
||||
### Deployment
|
||||
- **Deploy to Navisworks**: `deploy-plugin.bat` (requires Administrator privileges)
|
||||
- **Auto-deployment**: Build output automatically copies to Navisworks 2026 plugin directory
|
||||
- **Plugin location**: `C:\Program Files\Autodesk\Navisworks Manage 2026\Plugins\NavisworksTransportPlugin\`
|
||||
|
||||
### Testing & Debugging
|
||||
- **Log viewer**: Use LogManager centralized logging - logs stored in `%CommonApplicationData%\Autodesk\Navisworks Manage 2026\NavisworksTransport\logs\debug.log`
|
||||
- **Hot reload**: Restart Navisworks required after each compilation
|
||||
- **Testing environment**: Navisworks Manage 2026 exclusively
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
### Dual Plugin Architecture Pattern
|
||||
This system implements multiple Navisworks plugin types in a single assembly:
|
||||
|
||||
- **MainPlugin.cs**: Primary AddInPlugin with ribbon UI integration and DockPanePlugin capabilities
|
||||
- **PathClickToolPlugin.cs**: ToolPlugin for 3D mouse interaction and interactive point placement
|
||||
- **PathPointRenderPlugin.cs**: RenderPlugin providing 3D visualization overlays
|
||||
- **TestPlugin.cs**: Development testing plugin (separate from main functionality)
|
||||
|
||||
### Core Management Layer
|
||||
- **PathPlanningManager.cs**: Central coordinator with A* pathfinding algorithm integration (RoyT.AStar library)
|
||||
- **LogisticsAnimationManager.cs**: Advanced animation system leveraging Navisworks 2026 native components
|
||||
- **TimeLinerIntegrationManager.cs**: Bridge between custom animations and Navisworks TimeLiner
|
||||
- **CategoryAttributeManager.cs**: COM API wrapper for persistent logistics attribute management
|
||||
- **VisibilityManager.cs**: Model layer control and filtering system
|
||||
- **ModelSplitterManager.cs**: Model export and layer separation capabilities
|
||||
|
||||
### Data & Coordinate Systems
|
||||
- **PathPlanningModels.cs**: Core data structures with event-driven state management
|
||||
- **PathDataManager.cs**: JSON serialization system with migration support
|
||||
- **CoordinateConverter.cs**: Complex coordinate transformation chains for 2D map overlay to 3D world mapping
|
||||
- **GeometryExtractor.cs**: Spatial analysis and precise bounding box calculations
|
||||
- **FloorDetector.cs**: Multi-story building automatic floor/level detection
|
||||
|
||||
### UI Architecture: Hybrid WPF + WinForms
|
||||
- **WPF Components** (`src\UI\WPF\`): Modern MVVM-based controls
|
||||
- LogisticsControlPanel: Main docked interface panel
|
||||
- ViewModels: INotifyPropertyChanged pattern implementation
|
||||
- Separated Views: ModelSettingsView, PathEditingView, AnimationControlView, SystemManagementView
|
||||
- **WinForms Dialogs**: Legacy property editing interfaces for backward compatibility
|
||||
- **Integration**: ElementHost pattern for embedding WPF in Navisworks environment
|
||||
|
||||
### Pathfinding & Animation Integration
|
||||
- **A* Implementation**: RoyT.AStar library for optimal path calculation
|
||||
- **Animation Pipeline**: Transform-based movement with real-time collision detection
|
||||
- **TimeLiner Bridge**: Synchronization between custom path animations and Navisworks timeline
|
||||
- **Real-time Collision**: ClashDetectiveIntegration for dynamic conflict detection during animation playback
|
||||
|
||||
## Technical Implementation Details
|
||||
|
||||
### Navisworks API Integration Strategy
|
||||
- **Dual API Approach**: Native API (`Autodesk.Navisworks.Api`) for core functionality + COM API (`Autodesk.Navisworks.ComApi`) for attribute persistence and TimeLiner operations
|
||||
- **Multi-Plugin Registration**: Three distinct plugin types registered in single assembly
|
||||
- **Global Exception Handling**: `GlobalExceptionHandler` class provides application-wide error management
|
||||
- **Navisworks 2026 Exclusive**: Utilizes 2026-specific API features without backward compatibility constraints
|
||||
|
||||
### State Management Architecture
|
||||
- **Session State**: PathEditState enum (None, AddingPoints, EditingPath) with comprehensive event callbacks
|
||||
- **Data Persistence**: JSON-based serialization with LogisticsAttributeChangedEventArgs for change tracking
|
||||
- **Coordinate Mapping**: Multi-layer coordinate system supporting 2D overlay visualization on 3D models
|
||||
|
||||
### Logistics Classification System
|
||||
Eight predefined categories with automatic inheritance from parent to child nodes:
|
||||
- 门 (Doors), 电梯 (Elevators), 楼梯 (Stairs), 通道 (Channels)
|
||||
- 障碍物 (Obstacles), 装卸区 (Loading Zones), 停车区 (Parking), 检查点 (Checkpoints)
|
||||
|
||||
## Development Guidelines
|
||||
|
||||
### Language Standards
|
||||
- **Primary Language**: 中文 (Chinese) for all user interaction, code comments, and technical documentation
|
||||
- **Communication**: All development discussions and code documentation must be in Chinese
|
||||
|
||||
### Package Management (Legacy .NET Framework)
|
||||
- **Project Format**: Old-style csproj using `<Reference Include>` with HintPath (NOT PackageReference)
|
||||
- **NuGet Management**: Manual packages.config management (do NOT use `dotnet add package`)
|
||||
- **Package Installation**: Download .nupkg files and extract to packages/ directory
|
||||
- **Assembly Path Format**: `packages\{PackageId}.{Version}\lib\{TargetFramework}\{Assembly}.dll`
|
||||
|
||||
### Plugin Registration Patterns
|
||||
```csharp
|
||||
// Multi-plugin registration in single assembly
|
||||
[Plugin("NavisworksTransport.MainPlugin", "YourDeveloperID")]
|
||||
[AddInPlugin(AddInLocation.AddIn)]
|
||||
public class MainPlugin : AddInPlugin { }
|
||||
|
||||
[Plugin("NavisworksTransport.PathClickTool", "YourDeveloperID")]
|
||||
[ToolPluginAttribute("NavisworksTransport.PathClickTool", "YourDeveloperID")]
|
||||
public class PathClickToolPlugin : ToolPlugin { }
|
||||
```
|
||||
|
||||
### Critical API Usage Patterns
|
||||
- **Navisworks API Documentation**: Always check `doc\navisworks_api\` documentation before implementing Navisworks functionality
|
||||
- **COM API for Persistence**: Use COM API for all attribute operations requiring session persistence
|
||||
- **GlobalExceptionHandler**: Must be initialized in MainPlugin constructor for application-wide error handling
|
||||
- **Thread Safety**: All UI operations must be marshaled to main thread when called from background processes
|
||||
|
||||
### Navisworks 2026 Development Focus
|
||||
- **Exclusive 2026 Targeting**: No backward compatibility required - leverage all 2026-specific features
|
||||
- **Legacy References**: `src\Legacy\` contains reference code from 2017 version but is not actively maintained
|
||||
- **Modern Animation System**: Use Navisworks 2026 native animation components instead of manual Transform manipulation
|
||||
- **Enhanced APIs**: Full utilization of improved 2026 APIs for collision detection, animation, and model management
|
||||
|
||||
## API Documentation Search Strategy
|
||||
|
||||
### CHM Documentation Best Practices
|
||||
**Problem**: CHM extraction creates thousands of HTML files, making standard search tools ineffective
|
||||
|
||||
**Solution Strategies**:
|
||||
|
||||
1. **Use structural entry points**:
|
||||
```bash
|
||||
# Priority access to class member lists
|
||||
AllMembers_T_Autodesk_Navisworks_Api_ClassName.htm
|
||||
```
|
||||
|
||||
2. **Precise filename search**:
|
||||
```bash
|
||||
find . -name "*ClassName*" -o -name "*MethodName*"
|
||||
```
|
||||
|
||||
3. **Hierarchical search approach**:
|
||||
- First locate class-level documentation
|
||||
- Navigate from class member lists to method links
|
||||
- Utilize inter-document hyperlink navigation
|
||||
|
||||
4. **Common API documentation paths**:
|
||||
- Document class: `AllMembers_T_Autodesk_Navisworks_Api_Document.htm`
|
||||
- TimeLiner: `AllMembers_T_Autodesk_Navisworks_Api_Timeliner_*.htm`
|
||||
- Plugin base classes: `AllMembers_T_Autodesk_Navisworks_Api_Plugins_*.htm`
|
||||
|
||||
## File Structure Context
|
||||
|
||||
### Source Organization
|
||||
```
|
||||
src/
|
||||
├── Core/ # Main plugin components
|
||||
│ ├── Animation/ # Animation system
|
||||
│ ├── Collision/ # Collision detection
|
||||
│ ├── Properties/ # Attribute management
|
||||
│ └── [Plugin classes] # Main, Tool, Render plugins
|
||||
├── PathPlanning/ # A* pathfinding algorithms
|
||||
├── UI/
|
||||
│ ├── Forms/ # WinForms dialogs
|
||||
│ └── WPF/ # Modern WPF interface
|
||||
└── Utils/ # Utility classes
|
||||
```
|
||||
|
||||
### Documentation Structure
|
||||
- `doc/guide/`: Development guides and troubleshooting
|
||||
- `doc/migration/`: 2017→2026 migration documentation
|
||||
- `doc/requirement/`: Requirements and specifications
|
||||
- `doc/working/`: Active development documentation
|
||||
|
||||
### Build Configuration
|
||||
- **Target Framework**: .NET Framework 4.8 (x64 platform)
|
||||
- **Navisworks Version**: 2026 exclusive with conditional compilation (`NAVISWORKS_2026`)
|
||||
- **Key Dependencies**: RoyT.AStar for pathfinding, WPF for modern UI
|
||||
1458
doc/guide/design_principles.md
Normal file
1458
doc/guide/design_principles.md
Normal file
File diff suppressed because it is too large
Load Diff
463
doc/working/UI架构重构技术设计方案_20250816.md
Normal file
463
doc/working/UI架构重构技术设计方案_20250816.md
Normal file
@ -0,0 +1,463 @@
|
||||
# Navisworks WPF插件UI架构重构技术设计方案
|
||||
|
||||
## 文档信息
|
||||
|
||||
- **创建日期**: 2025-08-16
|
||||
- **版本**: v1.0
|
||||
- **状态**: 技术设计阶段
|
||||
- **作者**: Claude技术架构师 + Claude Code
|
||||
|
||||
## 1. 项目背景
|
||||
|
||||
### 1.1 问题描述
|
||||
|
||||
当前Navisworks 2026 WPF插件在路径规划完成后频繁崩溃,根本原因是将2017年的同步事件驱动架构强行迁移到异步WPF/MVVM模式,导致严重的线程安全问题。
|
||||
|
||||
### 1.2 技术现状
|
||||
|
||||
- **架构**: WPF + MVVM模式
|
||||
- **数据绑定**: ObservableCollection + PropertyChanged
|
||||
- **线程模型**: 后台线程 + Dispatcher异步调用
|
||||
- **状态管理**: 事件驱动 + 多层嵌套调用
|
||||
|
||||
### 1.3 关键问题
|
||||
|
||||
1. **线程竞争**: 后台线程直接修改UI绑定的ObservableCollection
|
||||
2. **事件重入**: PropertyChanged事件链导致循环调用
|
||||
3. **调用链过深**: 多层Dispatcher.BeginInvoke嵌套
|
||||
4. **状态不一致**: UI更新缺乏原子性保证
|
||||
|
||||
## 2. 技术架构设计
|
||||
|
||||
### 2.1 总体架构图
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ UI Layer (WPF) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ LogisticsControlViewModel (重构) │
|
||||
│ ├── ThreadSafeObservableCollection<PathRouteViewModel> │
|
||||
│ ├── UIStateManager.ExecuteUIUpdate() │
|
||||
│ └── Command Pattern (替代事件驱动) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ UI State Management │
|
||||
│ ┌─────────────────┐ ┌─────────────────┐ │
|
||||
│ │ UIStateManager │ │ ViewModelBase │ │
|
||||
│ │ (线程安全) │ │ (防重入机制) │ │
|
||||
│ └─────────────────┘ └─────────────────┘ │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Business Logic Layer │
|
||||
│ PathPlanningManager (解耦UI) │
|
||||
│ ├── IPathPlanningService (接口) │
|
||||
│ ├── PathPlanningResult (数据传输对象) │
|
||||
│ └── StatusUpdateCommand (命令模式) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 2.2 核心组件设计
|
||||
|
||||
#### 2.2.1 UIStateManager - 统一UI状态管理器
|
||||
|
||||
```csharp
|
||||
public class UIStateManager
|
||||
{
|
||||
private readonly SynchronizationContext _uiContext;
|
||||
private readonly object _lock = new object();
|
||||
private readonly Queue<UIUpdateOperation> _updateQueue;
|
||||
private volatile bool _isProcessing = false;
|
||||
|
||||
// 线程安全的UI更新接口
|
||||
public Task ExecuteUIUpdateAsync<T>(Func<T> operation) where T : class
|
||||
{
|
||||
return Task.Run(() =>
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_uiContext.CheckAccess())
|
||||
{
|
||||
return operation();
|
||||
}
|
||||
else
|
||||
{
|
||||
T result = default(T);
|
||||
_uiContext.Send(_ => result = operation(), null);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 批量UI更新(原子性保证)
|
||||
public void ExecuteBatchUIUpdate(params Action[] updates)
|
||||
{
|
||||
if (_uiContext.CheckAccess())
|
||||
{
|
||||
foreach (var update in updates)
|
||||
update();
|
||||
}
|
||||
else
|
||||
{
|
||||
_uiContext.Send(_ =>
|
||||
{
|
||||
foreach (var update in updates)
|
||||
update();
|
||||
}, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2.2 ThreadSafeObservableCollection - 线程安全集合
|
||||
|
||||
```csharp
|
||||
public class ThreadSafeObservableCollection<T> : ObservableCollection<T>
|
||||
{
|
||||
private readonly object _lock = new object();
|
||||
private readonly SynchronizationContext _synchronizationContext;
|
||||
|
||||
public ThreadSafeObservableCollection()
|
||||
{
|
||||
_synchronizationContext = SynchronizationContext.Current;
|
||||
BindingOperations.EnableCollectionSynchronization(this, _lock);
|
||||
}
|
||||
|
||||
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
if (_synchronizationContext != null && _synchronizationContext != SynchronizationContext.Current)
|
||||
{
|
||||
_synchronizationContext.Send(_ => base.OnCollectionChanged(e), null);
|
||||
}
|
||||
else
|
||||
{
|
||||
base.OnCollectionChanged(e);
|
||||
}
|
||||
}
|
||||
|
||||
// 批量操作接口
|
||||
public void AddRange(IEnumerable<T> items)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
foreach (var item in items)
|
||||
Items.Add(item);
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2.3 重构的ViewModelBase - 防重入PropertyChanged
|
||||
|
||||
```csharp
|
||||
public class ViewModelBase : INotifyPropertyChanged
|
||||
{
|
||||
private readonly UIStateManager _uiStateManager;
|
||||
private readonly HashSet<string> _updatingProperties = new HashSet<string>();
|
||||
private readonly object _propertyLock = new object();
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||
{
|
||||
lock (_propertyLock)
|
||||
{
|
||||
// 防重入检查
|
||||
if (_updatingProperties.Contains(propertyName))
|
||||
return;
|
||||
|
||||
_updatingProperties.Add(propertyName);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_uiStateManager.ExecuteUIUpdateAsync(() =>
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
lock (_propertyLock)
|
||||
{
|
||||
_updatingProperties.Remove(propertyName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 批量属性更新
|
||||
protected void SetProperties(params (string name, object value)[] properties)
|
||||
{
|
||||
_uiStateManager.ExecuteBatchUIUpdate(
|
||||
properties.Select(p => new Action(() => OnPropertyChanged(p.name))).ToArray()
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2.4 Command Pattern替代事件驱动
|
||||
|
||||
```csharp
|
||||
public interface IPathPlanningCommand
|
||||
{
|
||||
Task<PathPlanningResult> ExecuteAsync();
|
||||
}
|
||||
|
||||
public class AutoPathPlanningCommand : IPathPlanningCommand
|
||||
{
|
||||
private readonly Point3D _startPoint;
|
||||
private readonly Point3D _endPoint;
|
||||
private readonly PathPlanningManager _manager;
|
||||
|
||||
public async Task<PathPlanningResult> ExecuteAsync()
|
||||
{
|
||||
// 纯业务逻辑,不涉及UI
|
||||
var route = await _manager.AutoPlanPathAsync(_startPoint, _endPoint);
|
||||
return new PathPlanningResult
|
||||
{
|
||||
IsSuccess = route != null,
|
||||
Route = route,
|
||||
Message = route != null ? "规划成功" : "未找到可行路径"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// UI层处理
|
||||
public class LogisticsControlViewModel : ViewModelBase
|
||||
{
|
||||
private async void ExecuteAutoPlanPath()
|
||||
{
|
||||
var command = new AutoPathPlanningCommand(_startPoint, _endPoint, _pathPlanningManager);
|
||||
var result = await command.ExecuteAsync();
|
||||
|
||||
// 使用UIStateManager统一处理UI更新
|
||||
await _uiStateManager.ExecuteUIUpdateAsync(() =>
|
||||
{
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
// 原子性UI更新
|
||||
var viewModel = CreatePathRouteViewModel(result.Route);
|
||||
PathRoutes.Add(viewModel);
|
||||
SelectedPathRoute = viewModel;
|
||||
StatusText = result.Message;
|
||||
}
|
||||
else
|
||||
{
|
||||
StatusText = result.Message;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3 状态机设计
|
||||
|
||||
```csharp
|
||||
public enum UIState
|
||||
{
|
||||
Idle,
|
||||
Planning,
|
||||
Updating,
|
||||
Error
|
||||
}
|
||||
|
||||
public class UIStateMachine
|
||||
{
|
||||
private UIState _currentState = UIState.Idle;
|
||||
private readonly object _stateLock = new object();
|
||||
|
||||
public bool TryTransition(UIState fromState, UIState toState)
|
||||
{
|
||||
lock (_stateLock)
|
||||
{
|
||||
if (_currentState == fromState)
|
||||
{
|
||||
_currentState = toState;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public UIState CurrentState
|
||||
{
|
||||
get { lock (_stateLock) return _currentState; }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 3. 实施计划
|
||||
|
||||
### 3.1 第一阶段:基础设施搭建(1-2周)
|
||||
|
||||
#### 3.1.1 创建核心组件
|
||||
|
||||
- [ ] 实现UIStateManager类
|
||||
- [ ] 实现ThreadSafeObservableCollection类
|
||||
- [ ] 重构ViewModelBase基类
|
||||
- [ ] 创建UIStateMachine状态机
|
||||
|
||||
#### 3.1.2 单元测试
|
||||
|
||||
- [ ] UIStateManager线程安全测试
|
||||
- [ ] ThreadSafeObservableCollection并发测试
|
||||
- [ ] ViewModelBase防重入测试
|
||||
|
||||
### 3.2 第二阶段:核心组件重构(2-3周)
|
||||
|
||||
#### 3.2.1 重构LogisticsControlViewModel
|
||||
|
||||
```csharp
|
||||
public class LogisticsControlViewModel : ViewModelBase
|
||||
{
|
||||
// 使用线程安全集合
|
||||
public ThreadSafeObservableCollection<PathRouteViewModel> PathRoutes { get; }
|
||||
|
||||
// 使用UIStateManager处理所有UI更新
|
||||
private readonly UIStateManager _uiStateManager;
|
||||
|
||||
// 使用状态机管理UI状态
|
||||
private readonly UIStateMachine _stateMachine;
|
||||
|
||||
// 使用Command Pattern替代事件订阅
|
||||
public ICommand AutoPlanPathCommand { get; }
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.2.2 解耦PathPlanningManager
|
||||
|
||||
```csharp
|
||||
public interface IPathPlanningService
|
||||
{
|
||||
Task<PathRoute> AutoPlanPathAsync(Point3D start, Point3D end, double vehicleSize, double safetyMargin);
|
||||
Task<bool> ValidatePathAsync(PathRoute route);
|
||||
}
|
||||
|
||||
public class PathPlanningManager : IPathPlanningService
|
||||
{
|
||||
// 移除所有UI相关事件
|
||||
// 只保留纯业务逻辑
|
||||
|
||||
public async Task<PathRoute> AutoPlanPathAsync(Point3D start, Point3D end, double vehicleSize, double safetyMargin)
|
||||
{
|
||||
// 纯后台线程执行,不触发任何UI事件
|
||||
return await Task.Run(() => {
|
||||
// 原有的路径规划逻辑
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.2.3 重新设计UI更新流程
|
||||
|
||||
```
|
||||
旧流程 (有问题):
|
||||
PathPlanningManager.AutoPlanPath()
|
||||
→ 后台线程触发StatusChanged事件
|
||||
→ UI线程订阅事件处理
|
||||
→ 直接修改ObservableCollection
|
||||
→ 嵌套Dispatcher调用
|
||||
→ 崩溃
|
||||
|
||||
新流程 (安全):
|
||||
UI.ExecuteAutoPlanPath()
|
||||
→ Command.ExecuteAsync()
|
||||
→ PathPlanningService.AutoPlanPathAsync()
|
||||
→ 返回PathPlanningResult
|
||||
→ UIStateManager.ExecuteUIUpdateAsync()
|
||||
→ 原子性UI更新
|
||||
→ 完成
|
||||
```
|
||||
|
||||
### 3.3 第三阶段:测试验证(1周)
|
||||
|
||||
#### 3.3.1 压力测试场景
|
||||
|
||||
1. **重复规划测试**: 连续执行100次"选择起点→选择终点→自动规划"
|
||||
2. **属性修改测试**: 重复执行"通道→门→规划→删除→规划"场景
|
||||
3. **并发操作测试**: 同时进行多个UI操作
|
||||
4. **内存泄漏测试**: 长时间运行检查内存使用
|
||||
|
||||
#### 3.3.2 功能验证清单
|
||||
|
||||
- [ ] 自动路径规划功能正常
|
||||
- [ ] UI响应性能良好
|
||||
- [ ] 无UI线程阻塞
|
||||
- [ ] 无内存泄漏
|
||||
- [ ] 错误处理正确
|
||||
|
||||
### 3.4 第四阶段:性能优化(1周)
|
||||
|
||||
#### 3.4.1 UI性能优化
|
||||
|
||||
- [ ] 减少不必要的PropertyChanged触发
|
||||
- [ ] 优化数据绑定性能
|
||||
- [ ] 实现虚拟化长列表
|
||||
|
||||
#### 3.4.2 内存管理优化
|
||||
|
||||
- [ ] 确保事件订阅正确释放
|
||||
- [ ] 优化大对象内存分配
|
||||
- [ ] 实现对象池模式
|
||||
|
||||
## 4. 风险控制
|
||||
|
||||
### 4.1 技术风险
|
||||
|
||||
1. **兼容性风险**: 新架构可能影响现有功能
|
||||
- **缓解措施**: 分阶段迁移,保持API接口不变
|
||||
|
||||
2. **性能风险**: 线程安全机制可能影响性能
|
||||
- **缓解措施**: 性能基准测试,优化关键路径
|
||||
|
||||
3. **复杂性风险**: 新架构增加代码复杂度
|
||||
- **缓解措施**: 详细文档,代码审查
|
||||
|
||||
### 4.2 进度风险
|
||||
|
||||
1. **开发进度**: 重构工作量可能超出预期
|
||||
- **缓解措施**: 分阶段实施,每阶段可独立交付
|
||||
|
||||
2. **测试时间**: 充分测试需要时间
|
||||
- **缓解措施**: 自动化测试,并行开发和测试
|
||||
|
||||
### 4.3 回滚策略
|
||||
|
||||
- 每个阶段都在独立分支开发
|
||||
- 保持主分支稳定
|
||||
- 每个阶段完成后合并,确保可回滚
|
||||
|
||||
## 5. 预期效果
|
||||
|
||||
### 5.1 稳定性提升
|
||||
|
||||
- **消除UI崩溃**: 通过线程安全机制彻底解决崩溃问题
|
||||
- **提高可靠性**: 原子性UI更新确保状态一致性
|
||||
- **增强健壮性**: 防重入机制避免循环调用
|
||||
|
||||
### 5.2 维护性改善
|
||||
|
||||
- **代码结构清晰**: 分层架构,职责明确
|
||||
- **可测试性强**: 依赖注入,便于单元测试
|
||||
- **可扩展性好**: 接口设计,便于功能扩展
|
||||
|
||||
### 5.3 用户体验优化
|
||||
|
||||
- **响应速度快**: 异步操作,UI不阻塞
|
||||
- **操作流畅**: 无卡顿,无异常
|
||||
- **功能稳定**: 重复操作不崩溃
|
||||
|
||||
## 6. 总结
|
||||
|
||||
本方案通过全面重构UI架构,从根本上解决了WPF版本的稳定性问题。核心思想是:
|
||||
|
||||
1. **分离关注点**: 业务逻辑与UI逻辑完全分离
|
||||
2. **线程安全**: 统一的线程安全管理机制
|
||||
3. **原子操作**: 确保UI更新的原子性
|
||||
4. **防重入**: 避免事件循环调用
|
||||
5. **状态管理**: 清晰的UI状态流转
|
||||
|
||||
通过这个重构方案,将恢复2017版本的稳定性,同时保持WPF现代UI的优势,为后续功能扩展打下坚实基础。
|
||||
|
||||
---
|
||||
|
||||
*本文档将根据实施进度持续更新,确保设计方案与实际开发保持同步。*
|
||||
File diff suppressed because it is too large
Load Diff
@ -857,12 +857,58 @@ namespace NavisworksTransport
|
||||
|
||||
private void OnStatusChanged(string status)
|
||||
{
|
||||
StatusChanged?.Invoke(this, status);
|
||||
try
|
||||
{
|
||||
// 使用异步方式触发事件,避免UI线程死锁
|
||||
if (StatusChanged != null)
|
||||
{
|
||||
System.Threading.Tasks.Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
StatusChanged?.Invoke(this, status);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"[PathManager] StatusChanged事件触发失败: {ex.Message}");
|
||||
LogManager.Error($"[PathManager] 状态消息: {status}");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"[PathManager] OnStatusChanged方法异常: {ex.Message}");
|
||||
LogManager.Error($"[PathManager] 状态消息: {status}");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnErrorOccurred(string error)
|
||||
{
|
||||
ErrorOccurred?.Invoke(this, error);
|
||||
try
|
||||
{
|
||||
// 使用异步方式触发事件,避免UI线程死锁
|
||||
if (ErrorOccurred != null)
|
||||
{
|
||||
System.Threading.Tasks.Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
ErrorOccurred?.Invoke(this, error);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"[PathManager] ErrorOccurred事件触发失败: {ex.Message}");
|
||||
LogManager.Error($"[PathManager] 错误消息: {error}");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"[PathManager] OnErrorOccurred方法异常: {ex.Message}");
|
||||
LogManager.Error($"[PathManager] 错误消息: {error}");
|
||||
}
|
||||
}
|
||||
|
||||
// 日志方法已移动到LogManager类
|
||||
@ -4662,7 +4708,7 @@ namespace NavisworksTransport
|
||||
return channelItems;
|
||||
}
|
||||
|
||||
LogManager.Info("[Search API] 开始使用Navisworks Search API搜索通道类型模型项");
|
||||
LogManager.Info("[Search API] 开始使用Navisworks Search API搜索可通过模型项");
|
||||
|
||||
// 创建搜索对象
|
||||
using (var search = new Search())
|
||||
@ -4700,20 +4746,12 @@ namespace NavisworksTransport
|
||||
return channelItems;
|
||||
}
|
||||
|
||||
// 进一步筛选通道类型的项(包含门、楼梯、电梯等可通行区域)
|
||||
LogManager.Info("[Search API] 开始筛选通道类型的物流项...");
|
||||
// 🔥 关键修改:基于物流属性的通过性筛选,而不是基于类型
|
||||
LogManager.Info("[Search API] 开始筛选具有通过性的物流项...");
|
||||
|
||||
int processedCount = 0;
|
||||
int totalCount = searchResults.Count;
|
||||
|
||||
// 通道类型包括:通道、门、楼梯、电梯等可通行区域
|
||||
var channelTypes = new[] {
|
||||
CategoryAttributeManager.LogisticsElementType.通道,
|
||||
CategoryAttributeManager.LogisticsElementType.门,
|
||||
CategoryAttributeManager.LogisticsElementType.楼梯,
|
||||
CategoryAttributeManager.LogisticsElementType.电梯
|
||||
};
|
||||
|
||||
foreach (ModelItem item in searchResults)
|
||||
{
|
||||
try
|
||||
@ -4723,23 +4761,30 @@ namespace NavisworksTransport
|
||||
// 每处理100个项输出一次进度
|
||||
if (processedCount % 100 == 0)
|
||||
{
|
||||
LogManager.Info($"[Search API] 通道类型筛选进度: {processedCount}/{totalCount} ({(double)processedCount/totalCount*100:F1}%)");
|
||||
LogManager.Info($"[Search API] 通过性筛选进度: {processedCount}/{totalCount} ({(double)processedCount/totalCount*100:F1}%)");
|
||||
}
|
||||
|
||||
var logisticsType = CategoryAttributeManager.GetLogisticsElementType(item);
|
||||
if (channelTypes.Contains(logisticsType))
|
||||
string traversableValue = CategoryAttributeManager.GetLogisticsPropertyValue(item, CategoryAttributeManager.LogisticsProperties.TRAVERSABLE);
|
||||
|
||||
// 检查通过性属性是否为"是"
|
||||
if (traversableValue == "是")
|
||||
{
|
||||
channelItems.Add(item);
|
||||
LogManager.Info($"[Search API] 找到{logisticsType}: {item.DisplayName ?? "unnamed"}");
|
||||
LogManager.Info($"[Search API] 找到可通过{logisticsType}: {item.DisplayName ?? "unnamed"} (通过性=是)");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogManager.Info($"[Search API] 跳过不可通过{logisticsType}: {item.DisplayName ?? "unnamed"} (通过性={traversableValue})");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Warning($"[Search API] 检查项 {processedCount} 的通道类型时出错: {ex.Message}");
|
||||
LogManager.Warning($"[Search API] 检查项 {processedCount} 的通过性时出错: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
LogManager.Info($"[Search API] 通道类型筛选完成: {totalCount} -> {channelItems.Count}");
|
||||
LogManager.Info($"[Search API] 通过性筛选完成: {totalCount} -> {channelItems.Count}");
|
||||
}
|
||||
|
||||
return channelItems;
|
||||
|
||||
@ -76,104 +76,101 @@ namespace NavisworksTransport
|
||||
|
||||
try
|
||||
{
|
||||
// 首先检查文档和模型状态
|
||||
// 快速检查,避免频繁的API调用
|
||||
var activeDoc = Application.ActiveDocument;
|
||||
//LogManager.WriteLog($"[文档状态] ActiveDocument: {activeDoc?.GetType().Name ?? "null"}");
|
||||
|
||||
if (activeDoc == null)
|
||||
if (activeDoc?.Models == null || activeDoc.Models.Count == 0)
|
||||
{
|
||||
LogManager.WriteLog($"[文档状态] ❌ 没有活动文档,跳过渲染");
|
||||
return;
|
||||
return; // 静默返回,避免日志泛滥
|
||||
}
|
||||
|
||||
if (activeDoc.Models == null || activeDoc.Models.Count == 0)
|
||||
// 检查是否有标记需要渲染
|
||||
int markerCount;
|
||||
lock (_lockObject)
|
||||
{
|
||||
LogManager.WriteLog($"[文档状态] ❌ 没有加载的模型,跳过渲染");
|
||||
return;
|
||||
markerCount = _circleMarkers.Count;
|
||||
}
|
||||
|
||||
//LogManager.WriteLog($"[文档状态] ✓ 文档正常,模型数量: {activeDoc.Models.Count}");
|
||||
//LogManager.WriteLog($"[Graphics状态] Graphics对象: {graphics?.GetType().Name ?? "null"}");
|
||||
//LogManager.WriteLog($"[Graphics状态] View对象: {view?.GetType().Name ?? "null"}");
|
||||
if (markerCount == 0) return;
|
||||
|
||||
// 使用BeginModelContext确保正确的渲染上下文
|
||||
graphics.BeginModelContext();
|
||||
//LogManager.WriteLog($"[Graphics状态] BeginModelContext完成");
|
||||
|
||||
// 缓存转换系数,避免重复计算
|
||||
double lineRadiusInMeters = 0.2;
|
||||
double lineRadiusInModelUnits = lineRadiusInMeters * GetMetersToModelUnitsConversionFactor();
|
||||
|
||||
lock (_lockObject)
|
||||
{
|
||||
// 绘制连接线段(作为圆柱体)
|
||||
if (_circleMarkers.Count > 1)
|
||||
// 优化:预过滤和排序,减少重复计算
|
||||
var manualMarkers = _circleMarkers.Where(m => m.SequenceNumber >= 0)
|
||||
.OrderBy(m => m.SequenceNumber)
|
||||
.ToArray(); // 转为数组提高性能
|
||||
|
||||
var autoMarkers = _circleMarkers.Where(m => m.SequenceNumber < -999)
|
||||
.OrderByDescending(m => m.SequenceNumber)
|
||||
.ToArray();
|
||||
|
||||
// 绘制连接线段(仅在有多个标记时)
|
||||
if (markerCount > 1)
|
||||
{
|
||||
// 定义连线的物理半径(例如:20厘米)
|
||||
double lineRadiusInMeters = 0.2;
|
||||
double lineRadiusInModelUnits = lineRadiusInMeters * GetMetersToModelUnitsConversionFactor();
|
||||
|
||||
// 1. 绘制手动路径连线(正序号,黑色)
|
||||
graphics.Color(Color.FromByteRGB(0, 0, 0), 1.0); // 黑色连线
|
||||
var manualMarkers = _circleMarkers.Where(m => m.SequenceNumber >= 0)
|
||||
.OrderBy(m => m.SequenceNumber)
|
||||
.ToList();
|
||||
|
||||
for (int i = 0; i < manualMarkers.Count - 1; i++)
|
||||
if (manualMarkers.Length > 1)
|
||||
{
|
||||
var current = manualMarkers[i];
|
||||
var next = manualMarkers[i + 1];
|
||||
|
||||
// 只为连续序号的点绘制连线
|
||||
if (next.SequenceNumber == current.SequenceNumber + 1)
|
||||
graphics.Color(Color.FromByteRGB(0, 0, 0), 1.0);
|
||||
for (int i = 0; i < manualMarkers.Length - 1; i++)
|
||||
{
|
||||
graphics.Cylinder(current.Center, next.Center, lineRadiusInModelUnits);
|
||||
var current = manualMarkers[i];
|
||||
var next = manualMarkers[i + 1];
|
||||
|
||||
// 只为连续序号的点绘制连线
|
||||
if (next.SequenceNumber == current.SequenceNumber + 1)
|
||||
{
|
||||
graphics.Cylinder(current.Center, next.Center, lineRadiusInModelUnits);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 绘制自动路径连线(负序号-1000系列,橙色)
|
||||
graphics.Color(Color.FromByteRGB(255, 165, 0), 1.0); // 橙色连线,在黑色背景下更明显
|
||||
var autoMarkers = _circleMarkers.Where(m => m.SequenceNumber < -999)
|
||||
.OrderByDescending(m => m.SequenceNumber) // 负数按降序排列
|
||||
.ToList();
|
||||
|
||||
// 只在标记数量变化时输出日志,避免渲染循环造成日志泛滥
|
||||
if (autoMarkers.Count != _lastAutoMarkersCount)
|
||||
if (autoMarkers.Length > 1)
|
||||
{
|
||||
LogManager.WriteLog($"[自动路径连线] 找到 {autoMarkers.Count} 个自动路径标记");
|
||||
_lastAutoMarkersCount = autoMarkers.Count;
|
||||
}
|
||||
|
||||
for (int i = 0; i < autoMarkers.Count - 1; i++)
|
||||
{
|
||||
var current = autoMarkers[i];
|
||||
var next = autoMarkers[i + 1];
|
||||
graphics.Color(Color.FromByteRGB(255, 165, 0), 1.0);
|
||||
|
||||
// 为连续负序号的点绘制连线(-1000, -1001, -1002...)
|
||||
if (current.SequenceNumber - next.SequenceNumber == 1)
|
||||
// 只在标记数量变化时输出日志
|
||||
if (autoMarkers.Length != _lastAutoMarkersCount)
|
||||
{
|
||||
graphics.Cylinder(current.Center, next.Center, lineRadiusInModelUnits);
|
||||
LogManager.WriteLog($"[自动路径连线] 找到 {autoMarkers.Length} 个自动路径标记");
|
||||
_lastAutoMarkersCount = autoMarkers.Length;
|
||||
}
|
||||
|
||||
for (int i = 0; i < autoMarkers.Length - 1; i++)
|
||||
{
|
||||
var current = autoMarkers[i];
|
||||
var next = autoMarkers[i + 1];
|
||||
|
||||
// 为连续负序号的点绘制连线
|
||||
if (current.SequenceNumber - next.SequenceNumber == 1)
|
||||
{
|
||||
graphics.Cylinder(current.Center, next.Center, lineRadiusInModelUnits);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 遍历所有圆形标记并根据其自身属性绘制
|
||||
// 绘制所有球体标记(使用原始集合避免重复过滤)
|
||||
foreach (var marker in _circleMarkers)
|
||||
{
|
||||
// 使用标记自身存储的颜色和不透明度
|
||||
graphics.Color(marker.Color, marker.Alpha);
|
||||
// 使用标记自身存储的半径
|
||||
graphics.Sphere(marker.Center, marker.Radius);
|
||||
}
|
||||
}
|
||||
|
||||
// 结束ModelContext
|
||||
graphics.EndModelContext();
|
||||
//LogManager.WriteLog($"[Graphics状态] EndModelContext完成");
|
||||
|
||||
//LogManager.WriteLog($"[Graphics状态] === 3D渲染完成,如果仍看不到图形,问题可能在Graphics API兼容性 ===");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.WriteLog($"[圆形渲染] 渲染错误: {ex.Message}");
|
||||
LogManager.WriteLog($"[圆形渲染] 异常堆栈: {ex.StackTrace}");
|
||||
// 渲染异常应该静默处理,避免影响Navisworks主程序
|
||||
LogManager.WriteLog($"[渲染异常] {ex.Message}");
|
||||
// 不输出堆栈信息,避免日志过量
|
||||
}
|
||||
}
|
||||
|
||||
@ -432,12 +429,20 @@ namespace NavisworksTransport
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 请求视图刷新
|
||||
/// 请求视图刷新(带防抖机制)
|
||||
/// </summary>
|
||||
private void RequestViewRefresh()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 使用静态变量实现简单的防抖机制
|
||||
var now = DateTime.Now;
|
||||
if ((now - _lastRefreshTime).TotalMilliseconds < 50) // 最小间隔50ms
|
||||
{
|
||||
return; // 忽略过于频繁的刷新请求
|
||||
}
|
||||
_lastRefreshTime = now;
|
||||
|
||||
if (Application.ActiveDocument?.ActiveView != null)
|
||||
{
|
||||
Application.ActiveDocument.ActiveView.RequestDelayedRedraw(ViewRedrawRequests.Render);
|
||||
@ -445,9 +450,11 @@ namespace NavisworksTransport
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.WriteLog($"[球体标记] 视图刷新失败: {ex.Message}");
|
||||
LogManager.WriteLog($"[视图刷新] 失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private static DateTime _lastRefreshTime = DateTime.MinValue;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@ -71,15 +71,35 @@ namespace NavisworksTransport.PathPlanning
|
||||
throw new AutoPathPlanningException($"终点({end.X:F2}, {end.Y:F2})超出网格范围");
|
||||
}
|
||||
|
||||
// 检查起点和终点是否可通行
|
||||
if (!gridMap.IsWalkable(startGrid))
|
||||
// 智能修正起点和终点位置
|
||||
var correctedStartGrid = FindNearestWalkablePosition(gridMap, startGrid, "起点");
|
||||
var correctedEndGrid = FindNearestWalkablePosition(gridMap, endGrid, "终点");
|
||||
|
||||
if (correctedStartGrid == null)
|
||||
{
|
||||
throw new AutoPathPlanningException($"起点({start.X:F2}, {start.Y:F2})位于障碍物上");
|
||||
throw new AutoPathPlanningException($"起点({start.X:F2}, {start.Y:F2})附近没有可通行区域");
|
||||
}
|
||||
|
||||
if (!gridMap.IsWalkable(endGrid))
|
||||
|
||||
if (correctedEndGrid == null)
|
||||
{
|
||||
throw new AutoPathPlanningException($"终点({end.X:F2}, {end.Y:F2})位于障碍物上");
|
||||
throw new AutoPathPlanningException($"终点({end.X:F2}, {end.Y:F2})附近没有可通行区域");
|
||||
}
|
||||
|
||||
// 如果位置被修正,记录日志
|
||||
if (correctedStartGrid.Value != startGrid)
|
||||
{
|
||||
var correctedWorldStart = gridMap.GridToWorld(correctedStartGrid.Value);
|
||||
LogManager.Info($"起点已自动修正: ({start.X:F2}, {start.Y:F2}) -> ({correctedWorldStart.X:F2}, {correctedWorldStart.Y:F2})");
|
||||
start = correctedWorldStart; // 更新起点
|
||||
startGrid = correctedStartGrid.Value;
|
||||
}
|
||||
|
||||
if (correctedEndGrid.Value != endGrid)
|
||||
{
|
||||
var correctedWorldEnd = gridMap.GridToWorld(correctedEndGrid.Value);
|
||||
LogManager.Info($"终点已自动修正: ({end.X:F2}, {end.Y:F2}) -> ({correctedWorldEnd.X:F2}, {correctedWorldEnd.Y:F2})");
|
||||
end = correctedWorldEnd; // 更新终点
|
||||
endGrid = correctedEndGrid.Value;
|
||||
}
|
||||
|
||||
// 转换为RoyT.AStar网格格式并执行A*算法
|
||||
@ -631,5 +651,97 @@ namespace NavisworksTransport.PathPlanning
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找最近的可通行位置
|
||||
/// </summary>
|
||||
/// <param name="gridMap">网格地图</param>
|
||||
/// <param name="originalPos">原始位置</param>
|
||||
/// <param name="positionName">位置名称(用于日志)</param>
|
||||
/// <param name="maxDistance">最大搜索距离(网格单位)</param>
|
||||
/// <returns>最近的可通行位置,如果找不到返回null</returns>
|
||||
private Point2D? FindNearestWalkablePosition(GridMap gridMap, Point2D originalPos, string positionName, int maxDistance = 10)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 如果原始位置已经可通行,直接返回
|
||||
if (gridMap.IsWalkable(originalPos))
|
||||
{
|
||||
return originalPos;
|
||||
}
|
||||
|
||||
LogManager.Info($"[位置修正] {positionName}({originalPos.X}, {originalPos.Y})不可通行,搜索附近可通行区域...");
|
||||
|
||||
// 使用BFS搜索最近的可通行位置
|
||||
var visited = new HashSet<Point2D>();
|
||||
var queue = new Queue<(Point2D pos, int distance)>();
|
||||
|
||||
queue.Enqueue((originalPos, 0));
|
||||
visited.Add(originalPos);
|
||||
|
||||
// 8个方向的偏移量
|
||||
var directions = new[]
|
||||
{
|
||||
new Point2D(0, 1), // 上
|
||||
new Point2D(0, -1), // 下
|
||||
new Point2D(1, 0), // 右
|
||||
new Point2D(-1, 0), // 左
|
||||
new Point2D(1, 1), // 右上
|
||||
new Point2D(1, -1), // 右下
|
||||
new Point2D(-1, 1), // 左上
|
||||
new Point2D(-1, -1) // 左下
|
||||
};
|
||||
|
||||
while (queue.Count > 0)
|
||||
{
|
||||
var (currentPos, distance) = queue.Dequeue();
|
||||
|
||||
// 超出最大搜索距离,停止搜索
|
||||
if (distance > maxDistance)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// 检查8个方向的邻居
|
||||
foreach (var dir in directions)
|
||||
{
|
||||
var neighborPos = new Point2D(currentPos.X + dir.X, currentPos.Y + dir.Y);
|
||||
|
||||
// 跳过已访问的位置
|
||||
if (visited.Contains(neighborPos))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
visited.Add(neighborPos);
|
||||
|
||||
// 检查是否在有效范围内
|
||||
if (!gridMap.IsValidGridPosition(neighborPos))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// 找到可通行位置
|
||||
if (gridMap.IsWalkable(neighborPos))
|
||||
{
|
||||
var worldPos = gridMap.GridToWorld(neighborPos);
|
||||
LogManager.Info($"[位置修正] {positionName}已修正到距离{distance + 1}格的位置: 网格({neighborPos.X}, {neighborPos.Y}) 世界({worldPos.X:F2}, {worldPos.Y:F2})");
|
||||
return neighborPos;
|
||||
}
|
||||
|
||||
// 添加到搜索队列
|
||||
queue.Enqueue((neighborPos, distance + 1));
|
||||
}
|
||||
}
|
||||
|
||||
LogManager.Warning($"[位置修正] {positionName}在{maxDistance}格范围内未找到可通行区域");
|
||||
return null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"[位置修正] {positionName}位置修正失败: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -730,7 +730,7 @@ namespace NavisworksTransport.PathPlanning
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
//LogManager.Error($"[COM几何提取] 提取三角形几何数据失败: {ex.Message}");
|
||||
LogManager.Error($"[COM几何提取] 提取三角形几何数据失败: {ex.Message}");
|
||||
return allTriangles;
|
||||
}
|
||||
}
|
||||
@ -756,7 +756,7 @@ namespace NavisworksTransport.PathPlanning
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
//LogManager.Warning($"[COM几何提取] 处理ModelItem {modelItem.DisplayName} 时出错: {ex.Message}");
|
||||
LogManager.Warning($"[COM几何提取] 处理ModelItem {modelItem.DisplayName} 时出错: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -618,8 +618,8 @@ namespace NavisworksTransport.PathPlanning
|
||||
// 计算要标记的单元格数量
|
||||
int cellsToMark = (maxGrid.X - minGrid.X + 1) * (maxGrid.Y - minGrid.Y + 1);
|
||||
|
||||
// 跳过过大的障碍物,避免标记过多单元格
|
||||
if (cellsToMark > 500000) // 超过50万个单元格的障碍物跳过(更宽松)
|
||||
// 🔥 关键修复:使用中等限制,平衡路径复杂度和性能
|
||||
if (cellsToMark > 500) // 单个障碍物最多标记500个网格单元
|
||||
{
|
||||
LogManager.Warning($"[障碍物标记] 跳过过大障碍物: {obstacle.DisplayName}, 需标记{cellsToMark}个单元格");
|
||||
skippedLargeObstacles++;
|
||||
@ -966,54 +966,80 @@ namespace NavisworksTransport.PathPlanning
|
||||
{
|
||||
try
|
||||
{
|
||||
// 获取通道所在的楼层值
|
||||
string targetFloorValue = GetChannelFloorValue(document);
|
||||
LogManager.Info("[楼层过滤] 开始基于物流属性的复杂楼层过滤");
|
||||
LogManager.Info($"[楼层过滤] 起点: ({startPoint.X:F1}, {startPoint.Y:F1}, {startPoint.Z:F1})");
|
||||
LogManager.Info($"[楼层过滤] 终点: ({endPoint.X:F1}, {endPoint.Y:F1}, {endPoint.Z:F1})");
|
||||
LogManager.Info($"[楼层过滤] 输入模型项数量: {allItems.Count}");
|
||||
|
||||
if (string.IsNullOrEmpty(targetFloorValue))
|
||||
// 步骤1:获取通道楼层属性值
|
||||
string channelFloorValue = GetChannelFloorValue(document);
|
||||
|
||||
if (string.IsNullOrEmpty(channelFloorValue))
|
||||
{
|
||||
LogManager.Info("[楼层过滤] 无法确定目标楼层,使用传统方法");
|
||||
return ApplyFloorAwareFiltering(allItems, startPoint, endPoint);
|
||||
LogManager.Warning("[楼层过滤] 未找到通道楼层属性,应用简化楼层过滤");
|
||||
return ApplySimplifiedFloorFiltering(allItems, startPoint, endPoint);
|
||||
}
|
||||
|
||||
LogManager.Info($"[楼层过滤] 确定目标楼层: '{targetFloorValue}'");
|
||||
LogManager.Info($"[楼层过滤] 检测到通道楼层属性值: '{channelFloorValue}'");
|
||||
|
||||
// 使用Search API直接查找该楼层的所有模型项
|
||||
var targetFloorItems = GetItemsInTargetFloorUsingSearchAPI(document, targetFloorValue);
|
||||
// 步骤2:使用Search API获取该楼层的所有项目
|
||||
var floorItems = GetItemsInTargetFloorUsingSearchAPI(document, channelFloorValue);
|
||||
|
||||
if (targetFloorItems.Count == 0)
|
||||
if (floorItems.Count == 0)
|
||||
{
|
||||
LogManager.Warning($"[楼层过滤] 未找到楼层 '{targetFloorValue}' 的模型项,使用传统方法");
|
||||
return ApplyFloorAwareFiltering(allItems, startPoint, endPoint);
|
||||
LogManager.Warning($"[楼层过滤] 未找到楼层 '{channelFloorValue}' 的项目,应用简化楼层过滤");
|
||||
return ApplySimplifiedFloorFiltering(allItems, startPoint, endPoint);
|
||||
}
|
||||
|
||||
// 直接使用Search API找到的第一层子项目
|
||||
LogManager.Info($"[楼层过滤] 直接使用Search API结果,找到 {targetFloorItems.Count} 个第一层子项目");
|
||||
LogManager.Info($"[楼层过滤] Search API找到 {floorItems.Count} 个属于楼层 '{channelFloorValue}' 的项目");
|
||||
|
||||
// 检查项目边界有效性
|
||||
var validFloorItems = new List<ModelItem>();
|
||||
foreach (ModelItem floorItem in targetFloorItems)
|
||||
// 步骤3:将楼层项目转换为哈希集合以提高查找效率
|
||||
var floorItemSet = new HashSet<ModelItem>(floorItems);
|
||||
|
||||
// 步骤4:从输入的所有项目中筛选出属于目标楼层的项目
|
||||
var filteredItems = new List<ModelItem>();
|
||||
int filteredCount = 0;
|
||||
|
||||
foreach (var item in allItems)
|
||||
{
|
||||
try
|
||||
{
|
||||
var boundingBox = floorItem.BoundingBox();
|
||||
if (boundingBox != null)
|
||||
// 检查项目是否属于目标楼层
|
||||
if (floorItemSet.Contains(item))
|
||||
{
|
||||
validFloorItems.Add(floorItem);
|
||||
filteredItems.Add(item);
|
||||
filteredCount++;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Warning($"[楼层过滤] 检查项目边界失败: {ex.Message}");
|
||||
LogManager.Warning($"[楼层过滤] 检查项目时出错: {ex.Message}");
|
||||
// 出错时保留项目
|
||||
filteredItems.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
LogManager.Info($"[楼层过滤] Search API过滤完成: {allItems.Count} -> {validFloorItems.Count}");
|
||||
return validFloorItems;
|
||||
LogManager.Info($"[楼层过滤] 楼层过滤完成: {allItems.Count} -> {filteredItems.Count} (匹配楼层: {filteredCount})");
|
||||
|
||||
// 步骤5:验证过滤结果的合理性
|
||||
if (filteredItems.Count == 0)
|
||||
{
|
||||
LogManager.Warning("[楼层过滤] 过滤后无剩余项目,使用原始项目列表");
|
||||
return allItems;
|
||||
}
|
||||
|
||||
double filterRatio = (double)filteredItems.Count / allItems.Count;
|
||||
if (filterRatio < 0.1) // 如果过滤掉了90%以上的项目,可能有问题
|
||||
{
|
||||
LogManager.Warning($"[楼层过滤] 过滤比例过低 ({filterRatio:P1}),可能存在问题");
|
||||
}
|
||||
|
||||
return filteredItems;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"[楼层过滤] Search API过滤失败: {ex.Message},回退到传统方法");
|
||||
return ApplyFloorAwareFiltering(allItems, startPoint, endPoint);
|
||||
LogManager.Error($"[楼层过滤] 楼层感知过滤失败: {ex.Message},应用简化楼层过滤");
|
||||
return ApplySimplifiedFloorFiltering(allItems, startPoint, endPoint);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1046,7 +1072,7 @@ namespace NavisworksTransport.PathPlanning
|
||||
ModelItemCollection logisticsResults = search.FindAll(document, false);
|
||||
LogManager.Info($"[通道搜索] Search API找到 {logisticsResults.Count} 个具有物流属性的模型项");
|
||||
|
||||
// 筛选出通道类型的模型项
|
||||
// 筛选出具有通过性的模型项(基于物流属性的通过性判断)
|
||||
foreach (ModelItem item in logisticsResults)
|
||||
{
|
||||
try
|
||||
@ -1054,10 +1080,18 @@ namespace NavisworksTransport.PathPlanning
|
||||
var logisticsType = CategoryAttributeManager.GetLogisticsElementType(item);
|
||||
LogManager.Info($"[通道搜索] 检查模型项: DisplayName='{item.DisplayName}', 物流类型={logisticsType}");
|
||||
|
||||
if (logisticsType == CategoryAttributeManager.LogisticsElementType.通道)
|
||||
// 🔥 关键修改:基于物流属性的通过性判断,而不是只检查类型为"通道"
|
||||
string traversableValue = CategoryAttributeManager.GetLogisticsPropertyValue(item, CategoryAttributeManager.LogisticsProperties.TRAVERSABLE);
|
||||
LogManager.Info($"[通道搜索] 通过性属性值: '{traversableValue}'");
|
||||
|
||||
if (traversableValue == "是")
|
||||
{
|
||||
channelModels.Add(item);
|
||||
LogManager.Info($"[通道搜索] ✅ 找到通道模型: '{item.DisplayName}'");
|
||||
LogManager.Info($"[通道搜索] ✅ 找到可通过模型: '{item.DisplayName}' (类型={logisticsType}, 通过性=是)");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogManager.Info($"[通道搜索] ❌ 跳过不可通过模型: '{item.DisplayName}' (类型={logisticsType}, 通过性={traversableValue})");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -1067,7 +1101,7 @@ namespace NavisworksTransport.PathPlanning
|
||||
}
|
||||
}
|
||||
|
||||
LogManager.Info($"[通道搜索] 搜索完成,共找到 {channelModels.Count} 个通道模型");
|
||||
LogManager.Info($"[通道搜索] 搜索完成,共找到 {channelModels.Count} 个可通过模型");
|
||||
return channelModels;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@ -1363,15 +1363,22 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
{
|
||||
System.Threading.Thread.Sleep(2000); // 模拟检测时间
|
||||
|
||||
// 在UI线程上更新结果
|
||||
System.Windows.Application.Current.Dispatcher.Invoke(() =>
|
||||
// 在UI线程上异步更新结果(避免死锁)
|
||||
System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
CollisionStatus = "碰撞检测完成";
|
||||
CollisionSummary = "发现 3 个潜在碰撞点";
|
||||
HasCollisionResults = true;
|
||||
CanRunCollisionDetection = true;
|
||||
StatusText = "碰撞检测已完成,发现 3 个潜在碰撞点";
|
||||
});
|
||||
try
|
||||
{
|
||||
CollisionStatus = "碰撞检测完成";
|
||||
CollisionSummary = "发现 3 个潜在碰撞点";
|
||||
HasCollisionResults = true;
|
||||
CanRunCollisionDetection = true;
|
||||
StatusText = "碰撞检测已完成,发现 3 个潜在碰撞点";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"碰撞检测UI更新失败: {ex.Message}");
|
||||
}
|
||||
}), System.Windows.Threading.DispatcherPriority.Background);
|
||||
});
|
||||
|
||||
LogManager.Info($"开始碰撞检测: {SelectedPathRoute.Name}");
|
||||
@ -1399,11 +1406,18 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
{
|
||||
System.Threading.Thread.Sleep(3000); // 模拟分析时间
|
||||
|
||||
System.Windows.Application.Current.Dispatcher.Invoke(() =>
|
||||
System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
ModelSplitterStatus = "模型分析完成,可进行拆分操作";
|
||||
StatusText = "模型分层拆分分析完成";
|
||||
});
|
||||
try
|
||||
{
|
||||
ModelSplitterStatus = "模型分析完成,可进行拆分操作";
|
||||
StatusText = "模型分层拆分分析完成";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"模型分层状态UI更新失败: {ex.Message}");
|
||||
}
|
||||
}), System.Windows.Threading.DispatcherPriority.Background);
|
||||
});
|
||||
|
||||
StatusText = "正在分析模型结构...";
|
||||
@ -1486,10 +1500,17 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
{
|
||||
System.Threading.Thread.Sleep(2000);
|
||||
|
||||
System.Windows.Application.Current.Dispatcher.Invoke(() =>
|
||||
System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
StatusText = "当前版本已是最新版本";
|
||||
});
|
||||
try
|
||||
{
|
||||
StatusText = "当前版本已是最新版本";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"更新检查UI更新失败: {ex.Message}");
|
||||
}
|
||||
}), System.Windows.Threading.DispatcherPriority.Background);
|
||||
});
|
||||
|
||||
LogManager.Info("检查更新");
|
||||
@ -1507,11 +1528,18 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
{
|
||||
System.Threading.Thread.Sleep(1500);
|
||||
|
||||
System.Windows.Application.Current.Dispatcher.Invoke(() =>
|
||||
System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
StatusText = "性能报告生成完成";
|
||||
PerformanceInfo = $"报告已生成 - {DateTime.Now:yyyy-MM-dd HH:mm:ss}";
|
||||
});
|
||||
try
|
||||
{
|
||||
StatusText = "性能报告生成完成";
|
||||
PerformanceInfo = $"报告已生成 - {DateTime.Now:yyyy-MM-dd HH:mm:ss}";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"性能报告UI更新失败: {ex.Message}");
|
||||
}
|
||||
}), System.Windows.Threading.DispatcherPriority.Background);
|
||||
});
|
||||
|
||||
LogManager.Info("生成性能报告");
|
||||
@ -1661,68 +1689,116 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
AutoPathVehicleSize,
|
||||
AutoPathSafetyMargin);
|
||||
|
||||
// 在UI线程上更新结果
|
||||
System.Windows.Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
if (pathRoute != null && pathRoute.Points.Count > 0)
|
||||
// 在UI线程上异步更新结果,避免死锁
|
||||
System.Windows.Application.Current.Dispatcher.BeginInvoke(
|
||||
new Action(() =>
|
||||
{
|
||||
// 创建新的路径视图模型
|
||||
var autoPathViewModel = new PathRouteViewModel
|
||||
try
|
||||
{
|
||||
Name = $"自动路径_{DateTime.Now:HHmmss}",
|
||||
Description = "自动生成的最优路径",
|
||||
IsActive = true
|
||||
};
|
||||
|
||||
// 转换路径点
|
||||
foreach (var point in pathRoute.Points)
|
||||
{
|
||||
var pointViewModel = new PathPointViewModel
|
||||
if (pathRoute != null && pathRoute.Points.Count > 0)
|
||||
{
|
||||
Name = point.Name,
|
||||
X = point.X,
|
||||
Y = point.Y,
|
||||
Z = point.Z,
|
||||
Type = point.Type
|
||||
};
|
||||
autoPathViewModel.Points.Add(pointViewModel);
|
||||
// 🔥 关键修复:原子性UI更新,避免竞态条件
|
||||
try
|
||||
{
|
||||
// 创建新的路径视图模型
|
||||
var autoPathViewModel = new PathRouteViewModel
|
||||
{
|
||||
Name = $"自动路径_{DateTime.Now:HHmmss}",
|
||||
Description = "自动生成的最优路径",
|
||||
IsActive = true
|
||||
};
|
||||
|
||||
// 转换路径点
|
||||
foreach (var point in pathRoute.Points)
|
||||
{
|
||||
var pointViewModel = new PathPointViewModel
|
||||
{
|
||||
Name = point.Name,
|
||||
X = point.X,
|
||||
Y = point.Y,
|
||||
Z = point.Z,
|
||||
Type = point.Type
|
||||
};
|
||||
autoPathViewModel.Points.Add(pointViewModel);
|
||||
}
|
||||
|
||||
// 🔥 原子性操作:先添加到集合,再设置选中项,避免中间状态
|
||||
PathRoutes.Add(autoPathViewModel);
|
||||
|
||||
// 稍微延迟选中项设置,确保集合更新完成
|
||||
System.Windows.Application.Current.Dispatcher.BeginInvoke(
|
||||
new Action(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
SelectedPathRoute = autoPathViewModel;
|
||||
LogManager.Info($"✅ UI路径选择已更新: {autoPathViewModel.Name}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"设置选中路径失败: {ex.Message}");
|
||||
}
|
||||
}),
|
||||
System.Windows.Threading.DispatcherPriority.Background
|
||||
);
|
||||
|
||||
AutoPathStatus = $"路径规划完成!共 {pathRoute.Points.Count} 个路径点";
|
||||
StatusText = $"自动路径规划成功: {pathRoute.Points.Count} 个点";
|
||||
LogManager.Info($"✅ UI路径更新完成: {autoPathViewModel.Name}");
|
||||
}
|
||||
catch (Exception uiEx)
|
||||
{
|
||||
LogManager.Error($"UI路径更新失败: {uiEx.Message}");
|
||||
AutoPathStatus = $"路径规划完成,但UI更新失败: {uiEx.Message}";
|
||||
StatusText = $"自动路径规划成功,但显示更新失败";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AutoPathStatus = "路径规划失败,未找到可行路径";
|
||||
StatusText = "自动路径规划失败:未找到可行路径";
|
||||
LogManager.Warning("自动路径规划失败:未找到可行路径");
|
||||
}
|
||||
}
|
||||
|
||||
// 添加到路径列表并选中
|
||||
PathRoutes.Add(autoPathViewModel);
|
||||
SelectedPathRoute = autoPathViewModel;
|
||||
|
||||
AutoPathStatus = $"路径规划完成!共 {pathRoute.Points.Count} 个路径点";
|
||||
StatusText = $"自动路径规划成功: {pathRoute.Points.Count} 个点";
|
||||
|
||||
LogManager.Info($"自动路径规划成功: {autoPathViewModel.Name}, {pathRoute.Points.Count} 个点");
|
||||
}
|
||||
else
|
||||
{
|
||||
AutoPathStatus = "路径规划失败,未找到可行路径";
|
||||
StatusText = "自动路径规划失败:未找到可行路径";
|
||||
LogManager.Warning("自动路径规划失败:未找到可行路径");
|
||||
}
|
||||
});
|
||||
catch (Exception innerEx)
|
||||
{
|
||||
LogManager.Error($"UI线程路径规划结果更新失败: {innerEx.Message}");
|
||||
AutoPathStatus = $"UI更新失败: {innerEx.Message}";
|
||||
StatusText = $"路径规划UI更新失败: {innerEx.Message}";
|
||||
}
|
||||
}),
|
||||
System.Windows.Threading.DispatcherPriority.Background
|
||||
);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Windows.Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
AutoPathStatus = $"路径规划出错: {ex.Message}";
|
||||
StatusText = $"自动路径规划出错: {ex.Message}";
|
||||
|
||||
// 详细记录异常信息
|
||||
LogManager.Error($"自动路径规划出错: {ex.Message}");
|
||||
LogManager.Error($"异常类型: {ex.GetType().FullName}");
|
||||
LogManager.Error($"堆栈跟踪: {ex.StackTrace}");
|
||||
|
||||
if (ex.InnerException != null)
|
||||
// 使用BeginInvoke异步更新异常信息,避免死锁
|
||||
System.Windows.Application.Current.Dispatcher.BeginInvoke(
|
||||
new Action(() =>
|
||||
{
|
||||
LogManager.Error($"内部异常: {ex.InnerException.Message}");
|
||||
LogManager.Error($"内部异常堆栈: {ex.InnerException.StackTrace}");
|
||||
}
|
||||
});
|
||||
try
|
||||
{
|
||||
AutoPathStatus = $"路径规划出错: {ex.Message}";
|
||||
StatusText = $"自动路径规划出错: {ex.Message}";
|
||||
|
||||
// 详细记录异常信息
|
||||
LogManager.Error($"自动路径规划出错: {ex.Message}");
|
||||
LogManager.Error($"异常类型: {ex.GetType().FullName}");
|
||||
LogManager.Error($"堆栈跟踪: {ex.StackTrace}");
|
||||
|
||||
if (ex.InnerException != null)
|
||||
{
|
||||
LogManager.Error($"内部异常: {ex.InnerException.Message}");
|
||||
LogManager.Error($"内部异常堆栈: {ex.InnerException.StackTrace}");
|
||||
}
|
||||
}
|
||||
catch (Exception innerEx)
|
||||
{
|
||||
LogManager.Error($"UI线程异常处理失败: {innerEx.Message}");
|
||||
}
|
||||
}),
|
||||
System.Windows.Threading.DispatcherPriority.Background
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@ -1738,7 +1814,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行清除自动路径命令
|
||||
/// 执行重置自动路径规划命令(只重置起点终点状态,不删除已生成的路径)
|
||||
/// </summary>
|
||||
private void ExecuteClearAutoPath()
|
||||
{
|
||||
@ -1750,73 +1826,54 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
_pathPlanningManager.StopClickTool();
|
||||
PathClickToolPlugin.MouseClicked -= OnAutoPathMouseClicked;
|
||||
|
||||
// 清除PathPlanningManager中的自动路径标记
|
||||
_pathPlanningManager.Clear3DPathMarkers();
|
||||
LogManager.Info("已清除PathPlanningManager中的路径标记");
|
||||
// 只清除临时的点选择标记,不清除已完成的路径
|
||||
LogManager.Info("已停止点选择工具");
|
||||
}
|
||||
|
||||
// 清除可视化的起点、终点球体和自动路径连线
|
||||
// 只清除起点、终点球体,保留已生成的路径
|
||||
if (PathPointRenderPlugin.Instance != null)
|
||||
{
|
||||
// 方法1:精确清除起点和终点球体
|
||||
// 只清除起点和终点球体(用于重新选择)
|
||||
if (_hasStartPoint)
|
||||
{
|
||||
PathPointRenderPlugin.Instance.RemoveMarkerAt(_startPoint3D, 2.0);
|
||||
LogManager.Info("已清除起点标记球体");
|
||||
}
|
||||
if (_hasEndPoint)
|
||||
{
|
||||
PathPointRenderPlugin.Instance.RemoveMarkerAt(_endPoint3D, 2.0);
|
||||
LogManager.Info("已清除终点标记球体");
|
||||
}
|
||||
|
||||
// 方法2:清除所有自动路径相关的标记(负序号-1000系列)
|
||||
var allMarkers = PathPointRenderPlugin.Instance.GetAllMarkers();
|
||||
var autoPathMarkers = allMarkers.Where(m => m.SequenceNumber < -999).ToList();
|
||||
|
||||
foreach (var marker in autoPathMarkers)
|
||||
{
|
||||
PathPointRenderPlugin.Instance.RemoveMarkerAt(marker.Center, 1.0);
|
||||
}
|
||||
|
||||
if (autoPathMarkers.Count > 0)
|
||||
{
|
||||
LogManager.Info($"已清除 {autoPathMarkers.Count} 个自动路径标记和连线");
|
||||
}
|
||||
else if (_hasStartPoint || _hasEndPoint)
|
||||
{
|
||||
LogManager.Info("已清除自动路径规划的起点终点球体");
|
||||
}
|
||||
// 🔥 关键修改:不删除自动路径连线和标记,保留已生成的路径可视化
|
||||
// 已生成的路径应该通过路径列表的删除功能来管理
|
||||
}
|
||||
|
||||
// 清除自动生成的路径(从UI路径列表中移除以"自动路径_"开头的路径)
|
||||
var autoGeneratedPaths = PathRoutes.Where(r => r.Name.StartsWith("自动路径_")).ToList();
|
||||
foreach (var autoPath in autoGeneratedPaths)
|
||||
{
|
||||
PathRoutes.Remove(autoPath);
|
||||
LogManager.Info($"已从路径列表中移除自动生成的路径: {autoPath.Name}");
|
||||
}
|
||||
// 🔥 关键修改:不删除PathRoutes中的自动路径,保留在列表中供用户管理
|
||||
// 路径的删除应该通过每行的删除按钮或统一的删除功能来处理
|
||||
|
||||
// 如果当前选中的是自动生成的路径,清除选择
|
||||
if (SelectedPathRoute != null && SelectedPathRoute.Name.StartsWith("自动路径_"))
|
||||
{
|
||||
SelectedPathRoute = null;
|
||||
}
|
||||
// 如果当前选中的是自动生成的路径,也不清除选择,让用户继续查看
|
||||
// 用户可以通过路径列表来管理选择状态
|
||||
|
||||
// 重置所有自动路径规划状态
|
||||
// 只重置自动路径规划的输入状态,让用户可以重新设置起点终点
|
||||
_hasStartPoint = false;
|
||||
_hasEndPoint = false;
|
||||
_startPoint3D = new Point3D();
|
||||
_endPoint3D = new Point3D();
|
||||
AutoPathStartPoint = "未选择";
|
||||
AutoPathEndPoint = "未选择";
|
||||
AutoPathVehicleSize = 1.0;
|
||||
AutoPathSafetyMargin = 0.5;
|
||||
|
||||
// 保持其他设置不变,用户可能希望使用相同的车辆尺寸和安全间隙
|
||||
// AutoPathVehicleSize = 1.0; // 保持用户设置
|
||||
// AutoPathSafetyMargin = 0.5; // 保持用户设置
|
||||
|
||||
AutoPathStatus = "就绪";
|
||||
IsSelectingStartPoint = false;
|
||||
IsSelectingEndPoint = false;
|
||||
|
||||
StatusText = "已完全清除自动路径规划设置和可视化";
|
||||
LogManager.Info("完全清除自动路径规划:UI状态、3D可视化、自动生成的路径全部清理完毕");
|
||||
}, "清除自动路径");
|
||||
StatusText = "已重置自动路径规划状态,可重新选择起点终点";
|
||||
LogManager.Info("重置自动路径规划:已清除起点终点选择,保留已生成的路径");
|
||||
}, "重置自动路径规划");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -2008,7 +2065,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
{
|
||||
try
|
||||
{
|
||||
// 安全地在UI线程上更新
|
||||
// 安全地在UI线程上更新,使用BeginInvoke避免死锁
|
||||
if (System.Windows.Application.Current?.Dispatcher != null)
|
||||
{
|
||||
if (System.Windows.Application.Current.Dispatcher.CheckAccess())
|
||||
@ -2018,11 +2075,21 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
}
|
||||
else
|
||||
{
|
||||
// 需要切换到UI线程
|
||||
System.Windows.Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
UpdatePathEditState(newState);
|
||||
});
|
||||
// 需要切换到UI线程,使用BeginInvoke异步调用
|
||||
System.Windows.Application.Current.Dispatcher.BeginInvoke(
|
||||
new Action(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
UpdatePathEditState(newState);
|
||||
}
|
||||
catch (Exception innerEx)
|
||||
{
|
||||
LogManager.Error($"UI线程路径编辑状态更新内部错误: {innerEx.Message}");
|
||||
}
|
||||
}),
|
||||
System.Windows.Threading.DispatcherPriority.Background
|
||||
);
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -2061,27 +2128,65 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
/// </summary>
|
||||
private void OnPathPointAddedIn3D(object sender, NavisworksTransport.PathPoint pathPoint)
|
||||
{
|
||||
// 使用 Dispatcher 确保在 UI 线程上更新
|
||||
System.Windows.Application.Current.Dispatcher.Invoke(() =>
|
||||
try
|
||||
{
|
||||
SafeExecute(() =>
|
||||
// 使用BeginInvoke确保在UI线程上更新,避免死锁
|
||||
if (System.Windows.Application.Current?.Dispatcher != null)
|
||||
{
|
||||
if (pathPoint != null && SelectedPathRoute != null)
|
||||
if (System.Windows.Application.Current.Dispatcher.CheckAccess())
|
||||
{
|
||||
// 创建 WPF ViewModel 对应的 PathPoint
|
||||
var newWpfPoint = new PathPointViewModel
|
||||
{
|
||||
Name = pathPoint.Name,
|
||||
X = pathPoint.X,
|
||||
Y = pathPoint.Y,
|
||||
Z = pathPoint.Z,
|
||||
Type = pathPoint.Type // 同步 Type 属性
|
||||
};
|
||||
SelectedPathRoute.Points.Add(newWpfPoint);
|
||||
LogManager.Info($"UI已更新 - 添加路径点: {pathPoint.Name}");
|
||||
// 已在UI线程上
|
||||
AddPathPointToUI(pathPoint);
|
||||
}
|
||||
}, "处理路径点添加事件");
|
||||
});
|
||||
else
|
||||
{
|
||||
// 使用BeginInvoke异步更新
|
||||
System.Windows.Application.Current.Dispatcher.BeginInvoke(
|
||||
new Action(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
AddPathPointToUI(pathPoint);
|
||||
}
|
||||
catch (Exception innerEx)
|
||||
{
|
||||
LogManager.Error($"UI线程路径点添加内部错误: {innerEx.Message}");
|
||||
}
|
||||
}),
|
||||
System.Windows.Threading.DispatcherPriority.Background
|
||||
);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddPathPointToUI(pathPoint);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"处理路径点添加事件失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void AddPathPointToUI(NavisworksTransport.PathPoint pathPoint)
|
||||
{
|
||||
SafeExecute(() =>
|
||||
{
|
||||
if (pathPoint != null && SelectedPathRoute != null)
|
||||
{
|
||||
// 创建 WPF ViewModel 对应的 PathPoint
|
||||
var newWpfPoint = new PathPointViewModel
|
||||
{
|
||||
Name = pathPoint.Name,
|
||||
X = pathPoint.X,
|
||||
Y = pathPoint.Y,
|
||||
Z = pathPoint.Z,
|
||||
Type = pathPoint.Type // 同步 Type 属性
|
||||
};
|
||||
SelectedPathRoute.Points.Add(newWpfPoint);
|
||||
LogManager.Info($"UI已更新 - 添加路径点: {pathPoint.Name}");
|
||||
}
|
||||
}, "处理路径点添加事件");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -2089,23 +2194,61 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
/// </summary>
|
||||
private void OnPathPointRemovedFrom3D(object sender, NavisworksTransport.PathPoint pathPoint)
|
||||
{
|
||||
// 使用 Dispatcher 确保在 UI 线程上更新
|
||||
System.Windows.Application.Current.Dispatcher.Invoke(() =>
|
||||
try
|
||||
{
|
||||
SafeExecute(() =>
|
||||
// 使用BeginInvoke确保在UI线程上更新,避免死锁
|
||||
if (System.Windows.Application.Current?.Dispatcher != null)
|
||||
{
|
||||
if (pathPoint != null && SelectedPathRoute != null)
|
||||
if (System.Windows.Application.Current.Dispatcher.CheckAccess())
|
||||
{
|
||||
// 在 WPF ViewModel 中找到并移除对应的 PathPoint
|
||||
var pointToRemove = SelectedPathRoute.Points.FirstOrDefault(p => p.Name == pathPoint.Name);
|
||||
if (pointToRemove != null)
|
||||
{
|
||||
SelectedPathRoute.Points.Remove(pointToRemove);
|
||||
LogManager.Info($"UI已更新 - 移除路径点: {pathPoint.Name}");
|
||||
}
|
||||
// 已在UI线程上
|
||||
RemovePathPointFromUI(pathPoint);
|
||||
}
|
||||
}, "处理路径点移除事件");
|
||||
});
|
||||
else
|
||||
{
|
||||
// 使用BeginInvoke异步更新
|
||||
System.Windows.Application.Current.Dispatcher.BeginInvoke(
|
||||
new Action(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
RemovePathPointFromUI(pathPoint);
|
||||
}
|
||||
catch (Exception innerEx)
|
||||
{
|
||||
LogManager.Error($"UI线程路径点移除内部错误: {innerEx.Message}");
|
||||
}
|
||||
}),
|
||||
System.Windows.Threading.DispatcherPriority.Background
|
||||
);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
RemovePathPointFromUI(pathPoint);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"处理路径点移除事件失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void RemovePathPointFromUI(NavisworksTransport.PathPoint pathPoint)
|
||||
{
|
||||
SafeExecute(() =>
|
||||
{
|
||||
if (pathPoint != null && SelectedPathRoute != null)
|
||||
{
|
||||
// 在 WPF ViewModel 中找到并移除对应的 PathPoint
|
||||
var pointToRemove = SelectedPathRoute.Points.FirstOrDefault(p => p.Name == pathPoint.Name);
|
||||
if (pointToRemove != null)
|
||||
{
|
||||
SelectedPathRoute.Points.Remove(pointToRemove);
|
||||
LogManager.Info($"UI已更新 - 移除路径点: {pathPoint.Name}");
|
||||
}
|
||||
}
|
||||
}, "处理路径点移除事件");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -2113,45 +2256,83 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
/// </summary>
|
||||
private void OnPathPointsListUpdated(object sender, NavisworksTransport.PathRoute updatedRoute)
|
||||
{
|
||||
// 使用 Dispatcher 确保在 UI 线程上更新
|
||||
System.Windows.Application.Current.Dispatcher.Invoke(() =>
|
||||
try
|
||||
{
|
||||
SafeExecute(() =>
|
||||
// 使用BeginInvoke确保在UI线程上更新,避免死锁
|
||||
if (System.Windows.Application.Current?.Dispatcher != null)
|
||||
{
|
||||
// 查找或创建对应的 WPF ViewModel
|
||||
var routeViewModel = PathRoutes.FirstOrDefault(r => r.Name == updatedRoute?.Name);
|
||||
if (routeViewModel == null && updatedRoute != null)
|
||||
if (System.Windows.Application.Current.Dispatcher.CheckAccess())
|
||||
{
|
||||
// 如果UI列表中没有,则创建一个新的
|
||||
routeViewModel = new PathRouteViewModel
|
||||
{
|
||||
Name = updatedRoute.Name,
|
||||
Description = updatedRoute.Description,
|
||||
IsActive = true // 或根据实际情况设置
|
||||
};
|
||||
PathRoutes.Add(routeViewModel);
|
||||
// 已在UI线程上
|
||||
UpdatePathPointsList(updatedRoute);
|
||||
}
|
||||
|
||||
// 更新 ViewModel 的点列表
|
||||
if (routeViewModel != null && updatedRoute != null)
|
||||
else
|
||||
{
|
||||
routeViewModel.Points.Clear();
|
||||
foreach (var corePoint in updatedRoute.Points)
|
||||
{
|
||||
var wpfPoint = new PathPointViewModel
|
||||
// 使用BeginInvoke异步更新
|
||||
System.Windows.Application.Current.Dispatcher.BeginInvoke(
|
||||
new Action(() =>
|
||||
{
|
||||
Name = corePoint.Name,
|
||||
X = corePoint.X,
|
||||
Y = corePoint.Y,
|
||||
Z = corePoint.Z,
|
||||
Type = corePoint.Type // 同步 Type 属性
|
||||
};
|
||||
routeViewModel.Points.Add(wpfPoint);
|
||||
}
|
||||
LogManager.Info($"UI已更新 - 路径点列表已同步: {updatedRoute.Name}");
|
||||
try
|
||||
{
|
||||
UpdatePathPointsList(updatedRoute);
|
||||
}
|
||||
catch (Exception innerEx)
|
||||
{
|
||||
LogManager.Error($"UI线程路径点列表更新内部错误: {innerEx.Message}");
|
||||
}
|
||||
}),
|
||||
System.Windows.Threading.DispatcherPriority.Background
|
||||
);
|
||||
}
|
||||
}, "处理路径点列表更新事件");
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdatePathPointsList(updatedRoute);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"处理路径点列表更新事件失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdatePathPointsList(NavisworksTransport.PathRoute updatedRoute)
|
||||
{
|
||||
SafeExecute(() =>
|
||||
{
|
||||
// 查找或创建对应的 WPF ViewModel
|
||||
var routeViewModel = PathRoutes.FirstOrDefault(r => r.Name == updatedRoute?.Name);
|
||||
if (routeViewModel == null && updatedRoute != null)
|
||||
{
|
||||
// 如果UI列表中没有,则创建一个新的
|
||||
routeViewModel = new PathRouteViewModel
|
||||
{
|
||||
Name = updatedRoute.Name,
|
||||
Description = updatedRoute.Description,
|
||||
IsActive = true // 或根据实际情况设置
|
||||
};
|
||||
PathRoutes.Add(routeViewModel);
|
||||
}
|
||||
|
||||
// 更新 ViewModel 的点列表
|
||||
if (routeViewModel != null && updatedRoute != null)
|
||||
{
|
||||
routeViewModel.Points.Clear();
|
||||
foreach (var corePoint in updatedRoute.Points)
|
||||
{
|
||||
var wpfPoint = new PathPointViewModel
|
||||
{
|
||||
Name = corePoint.Name,
|
||||
X = corePoint.X,
|
||||
Y = corePoint.Y,
|
||||
Z = corePoint.Z,
|
||||
Type = corePoint.Type // 同步 Type 属性
|
||||
};
|
||||
routeViewModel.Points.Add(wpfPoint);
|
||||
}
|
||||
LogManager.Info($"UI已更新 - 路径点列表已同步: {updatedRoute.Name}");
|
||||
}
|
||||
}, "处理路径点列表更新事件");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -2159,20 +2340,60 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
/// </summary>
|
||||
private void OnCurrentRouteChanged(object sender, NavisworksTransport.PathRoute newRoute)
|
||||
{
|
||||
// 使用 Dispatcher 确保在 UI 线程上更新
|
||||
System.Windows.Application.Current.Dispatcher.Invoke(() =>
|
||||
try
|
||||
{
|
||||
SafeExecute(() =>
|
||||
// 安全地在UI线程上更新,使用BeginInvoke避免死锁
|
||||
if (System.Windows.Application.Current?.Dispatcher != null)
|
||||
{
|
||||
if (newRoute != null)
|
||||
if (System.Windows.Application.Current.Dispatcher.CheckAccess())
|
||||
{
|
||||
// 更新 SelectedPathRoute 为新的当前路径
|
||||
SelectedPathRoute = PathRoutes.FirstOrDefault(r => r.Name == newRoute.Name) ?? SelectedPathRoute;
|
||||
StatusText = $"当前活动路径: {newRoute.Name}";
|
||||
LogManager.Info($"UI已更新 - 当前活动路径变更为: {newRoute.Name}");
|
||||
// 已在UI线程上
|
||||
UpdateCurrentRoute(newRoute);
|
||||
}
|
||||
}, "处理当前路径变更事件");
|
||||
});
|
||||
else
|
||||
{
|
||||
// 需要切换到UI线程,使用BeginInvoke异步调用
|
||||
System.Windows.Application.Current.Dispatcher.BeginInvoke(
|
||||
new Action(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
UpdateCurrentRoute(newRoute);
|
||||
}
|
||||
catch (Exception innerEx)
|
||||
{
|
||||
LogManager.Error($"UI线程当前路径更新内部错误: {innerEx.Message}");
|
||||
}
|
||||
}),
|
||||
System.Windows.Threading.DispatcherPriority.Background
|
||||
);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Dispatcher不可用,直接更新
|
||||
UpdateCurrentRoute(newRoute);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"处理当前路径变更失败: {ex.Message}");
|
||||
LogManager.Error($"堆栈跟踪: {ex.StackTrace}");
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateCurrentRoute(NavisworksTransport.PathRoute newRoute)
|
||||
{
|
||||
SafeExecute(() =>
|
||||
{
|
||||
if (newRoute != null)
|
||||
{
|
||||
// 更新 SelectedPathRoute 为新的当前路径
|
||||
SelectedPathRoute = PathRoutes.FirstOrDefault(r => r.Name == newRoute.Name) ?? SelectedPathRoute;
|
||||
StatusText = $"当前活动路径: {newRoute.Name}";
|
||||
LogManager.Info($"[UI同步] 当前路径已变更: {newRoute.Name}");
|
||||
}
|
||||
}, "处理当前路径变更事件");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -2182,31 +2403,9 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
{
|
||||
try
|
||||
{
|
||||
// 安全地更新状态文本
|
||||
if (System.Windows.Application.Current?.Dispatcher != null)
|
||||
{
|
||||
if (System.Windows.Application.Current.Dispatcher.CheckAccess())
|
||||
{
|
||||
// 已在UI线程上
|
||||
StatusText = statusMessage;
|
||||
LogManager.Info($"PathManager状态更新: {statusMessage}");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 需要切换到UI线程
|
||||
System.Windows.Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
StatusText = statusMessage;
|
||||
LogManager.Info($"PathManager状态更新: {statusMessage}");
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Dispatcher不可用,直接更新(可能在非WPF环境中)
|
||||
StatusText = statusMessage;
|
||||
LogManager.Info($"PathManager状态更新(无Dispatcher): {statusMessage}");
|
||||
}
|
||||
// 🔥 关键修复:简化状态更新逻辑,ViewModelBase已处理线程安全
|
||||
StatusText = statusMessage;
|
||||
LogManager.Info($"PathManager状态更新: {statusMessage}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -2220,14 +2419,21 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
/// </summary>
|
||||
private void OnPathManagerErrorOccurred(object sender, string errorMessage)
|
||||
{
|
||||
// 使用 Dispatcher 确保在 UI 线程上更新
|
||||
System.Windows.Application.Current.Dispatcher.Invoke(() =>
|
||||
// 使用 Dispatcher 异步确保在 UI 线程上更新(避免死锁)
|
||||
System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
StatusText = $"错误: {errorMessage}";
|
||||
LogManager.Error($"PathManager错误: {errorMessage}");
|
||||
// 可以考虑显示一个错误对话框
|
||||
// MessageBox.Show(errorMessage, "PathManager 错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
});
|
||||
try
|
||||
{
|
||||
StatusText = $"错误: {errorMessage}";
|
||||
LogManager.Error($"PathManager错误: {errorMessage}");
|
||||
// 可以考虑显示一个错误对话框
|
||||
// MessageBox.Show(errorMessage, "PathManager 错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"PathManager错误处理UI更新失败: {ex.Message}");
|
||||
}
|
||||
}), System.Windows.Threading.DispatcherPriority.Background);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@ -18,7 +18,45 @@ namespace NavisworksTransport.UI.WPF.ViewModels
|
||||
/// <param name="propertyName">属性名称,自动获取调用者名称</param>
|
||||
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
try
|
||||
{
|
||||
// 🔥 关键修复:确保PropertyChanged事件在UI线程上触发
|
||||
if (System.Windows.Application.Current?.Dispatcher != null)
|
||||
{
|
||||
if (System.Windows.Application.Current.Dispatcher.CheckAccess())
|
||||
{
|
||||
// 已在UI线程上,直接触发
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
else
|
||||
{
|
||||
// 使用BeginInvoke异步调度到UI线程,避免死锁
|
||||
System.Windows.Application.Current.Dispatcher.BeginInvoke(
|
||||
new Action(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
catch (Exception innerEx)
|
||||
{
|
||||
LogManager.Error($"[ViewModel] PropertyChanged内部错误: {innerEx.Message}");
|
||||
}
|
||||
}),
|
||||
System.Windows.Threading.DispatcherPriority.DataBind
|
||||
);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Dispatcher不可用时直接触发(可能在非WPF环境中)
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"[ViewModel] OnPropertyChanged失败: {ex.Message},属性: {propertyName}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
148
线程安全修复报告.md
Normal file
148
线程安全修复报告.md
Normal file
@ -0,0 +1,148 @@
|
||||
# MainPlugin.cs 线程安全修复报告
|
||||
|
||||
## 修复概述
|
||||
|
||||
本次修复系统性地解决了MainPlugin.cs中的所有线程安全问题,确保UI控件操作的线程安全性,避免死锁和崩溃。
|
||||
|
||||
## 修复的问题分类
|
||||
|
||||
### 高风险问题(已修复)
|
||||
|
||||
1. **UpdateAnimationUI方法 (行2829)**
|
||||
- **问题**: 直接操作UI控件,缺少InvokeRequired检查
|
||||
- **修复**: 为所有UI控件(_animationStatusLabel, _startAnimationButton, _stopAnimationButton, _animationProgressBar)添加InvokeRequired检查和BeginInvoke包装
|
||||
|
||||
2. **可见性控制事件处理 (行2915)**
|
||||
- **问题**: 状态标签直接更新
|
||||
- **修复**: 为statusLabel和logisticsOnlyCheckBox添加线程安全的更新机制
|
||||
|
||||
3. **UpdateSelectionDisplay方法 (行3004)**
|
||||
- **问题**: 标签属性直接更新
|
||||
- **修复**: 为_instructionLabel和_selectedModelsLabel添加InvokeRequired检查
|
||||
|
||||
4. **UpdateCurrentPathPointsList方法 (行3516)**
|
||||
- **问题**: ListView直接清空和添加项目
|
||||
- **修复**: 先在后台准备数据,然后线程安全地批量更新ListView
|
||||
|
||||
5. **UpdatePathList方法 (行3574)**
|
||||
- **问题**: ListView直接操作
|
||||
- **修复**: 采用同样的批量更新策略,确保线程安全
|
||||
|
||||
6. **UpdateButtonStates方法 (行3361)**
|
||||
- **问题**: 按钮Enabled属性直接设置
|
||||
- **修复**: 为finishButton和cancelButton添加InvokeRequired检查
|
||||
|
||||
### 中等风险问题(已修复)
|
||||
|
||||
1. **内存标签更新 (行2143)**
|
||||
- **问题**: 系统信息刷新按钮事件中直接更新
|
||||
- **修复**: 添加InvokeRequired检查和异常处理
|
||||
|
||||
2. **路径删除操作 (行1115)**
|
||||
- **问题**: 直接从ListView删除项目
|
||||
- **修复**: 添加线程安全的ListView项目删除
|
||||
|
||||
3. **动画状态标签更新 (行2783, 2801)**
|
||||
- **问题**: 动画播放控制中的状态更新
|
||||
- **修复**: 为所有相关控件添加线程安全更新机制
|
||||
|
||||
## 修复模式
|
||||
|
||||
### 标准线程安全模式
|
||||
```csharp
|
||||
if (control.InvokeRequired)
|
||||
{
|
||||
control.BeginInvoke(new Action(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// UI更新逻辑
|
||||
control.Property = newValue;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"UI更新失败: {ex.Message}");
|
||||
}
|
||||
}));
|
||||
}
|
||||
else
|
||||
{
|
||||
// 直接执行UI更新逻辑
|
||||
control.Property = newValue;
|
||||
}
|
||||
```
|
||||
|
||||
### 批量更新模式(用于ListView)
|
||||
```csharp
|
||||
// 1. 在后台准备数据
|
||||
var items = new List<ListViewItem>();
|
||||
// ... 准备数据 ...
|
||||
|
||||
// 2. 线程安全的批量更新
|
||||
if (listView.InvokeRequired)
|
||||
{
|
||||
listView.BeginInvoke(new Action(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (listView != null && !listView.IsDisposed)
|
||||
{
|
||||
listView.Items.Clear();
|
||||
listView.Items.AddRange(items.ToArray());
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Error($"ListView更新失败: {ex.Message}");
|
||||
}
|
||||
}));
|
||||
}
|
||||
else
|
||||
{
|
||||
listView.Items.Clear();
|
||||
listView.Items.AddRange(items.ToArray());
|
||||
}
|
||||
```
|
||||
|
||||
## 关键技术要点
|
||||
|
||||
### 1. 使用BeginInvoke而非Invoke
|
||||
- **避免死锁**: BeginInvoke是异步调用,不会阻塞调用线程
|
||||
- **性能更好**: 不需要等待UI线程完成操作
|
||||
|
||||
### 2. 空引用检查
|
||||
- 在所有UI更新中都添加了控件是否为null和IsDisposed的检查
|
||||
- 防止在控件销毁后进行操作导致异常
|
||||
|
||||
### 3. 异常处理
|
||||
- 每个UI更新操作都包含在try-catch块中
|
||||
- 记录详细的错误日志以便调试
|
||||
|
||||
### 4. 批量更新策略
|
||||
- 对于ListView等复杂控件,采用先准备数据再批量更新的策略
|
||||
- 减少UI线程的工作量和锁定时间
|
||||
|
||||
## 性能优化
|
||||
|
||||
1. **减少跨线程调用次数**: 将多个UI操作合并为单次BeginInvoke调用
|
||||
2. **数据预处理**: 在后台线程中准备好所有数据,然后一次性更新UI
|
||||
3. **智能判断**: 只在真正需要时才进行跨线程调用
|
||||
|
||||
## 验证结果
|
||||
|
||||
- ✅ 编译成功,无错误
|
||||
- ✅ 所有UI控件操作都已添加线程安全保护
|
||||
- ✅ 保持了原有功能的完整性
|
||||
- ✅ 遵循了项目的现有编码规范
|
||||
|
||||
## 后续建议
|
||||
|
||||
1. **测试验证**: 在实际运行环境中验证修复效果
|
||||
2. **性能监控**: 观察UI响应性是否有改善
|
||||
3. **代码审查**: 确保其他文件中的UI操作也遵循相同的线程安全模式
|
||||
4. **文档更新**: 将线程安全编码规范添加到项目开发指南中
|
||||
|
||||
修复完成时间: 2025-08-16
|
||||
修复文件: MainPlugin.cs
|
||||
影响范围: UI线程安全性
|
||||
风险等级: 无(向后兼容)
|
||||
Loading…
Reference in New Issue
Block a user