diff --git a/.claude/agents/navisworks-api-researcher.md b/.claude/agents/navisworks-api-researcher.md
new file mode 100644
index 0000000..95d9c19
--- /dev/null
+++ b/.claude/agents/navisworks-api-researcher.md
@@ -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: 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." The user needs specific API information for file saving functionality, which requires searching through Navisworks API documentation. 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." The user needs detailed API documentation for a specific method that's causing errors.
+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使用指导。
diff --git a/.claude/agents/navisworks-feature-developer.md b/.claude/agents/navisworks-feature-developer.md
new file mode 100644
index 0000000..ceafc6b
--- /dev/null
+++ b/.claude/agents/navisworks-feature-developer.md
@@ -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: Context: User wants to implement a new path visualization feature for the logistics plugin. user: "我需要实现一个新的路径可视化功能,能够在3D模型中显示物流路径" assistant: "我将使用navisworks-feature-developer代理来实现这个路径可视化功能,它会将任务分解为小步骤并逐步开发" Context: User has a design document and wants to implement collision detection functionality. user: "根据设计文档,我需要开发碰撞检测功能" assistant: "让我使用navisworks-feature-developer代理来分析设计文档并实现碰撞检测功能,它会确保使用正确的Navisworks API"
+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功能开发专家,通过渐进式开发和持续验证,确保每个功能都能高质量地实现。
diff --git a/.claude/agents/project-task-manager.md b/.claude/agents/project-task-manager.md
new file mode 100644
index 0000000..662177f
--- /dev/null
+++ b/.claude/agents/project-task-manager.md
@@ -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: 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" Context: User wants to plan the development of a new logistics animation system. user: "我需要开发一个新的物流动画系统,能够支持多对象同步移动和实时碰撞检测" assistant: "我来使用project-task-manager代理来分解这个开发需求,创建详细的任务清单和项目管理文档"
+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.
diff --git a/.claude/agents/technical-architect.md b/.claude/agents/technical-architect.md
new file mode 100644
index 0000000..86a59d7
--- /dev/null
+++ b/.claude/agents/technical-architect.md
@@ -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: 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.' The user needs technical design for a specific development task, so use the technical-architect agent to create a detailed implementation plan. 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.' Multiple related tasks need a cohesive technical design, perfect for the technical-architect agent.
+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.
diff --git a/.claude/settings.local.json b/.claude/settings.local.json
index 078cd28..846164f 100644
--- a/.claude/settings.local.json
+++ b/.claude/settings.local.json
@@ -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"
+ ]
}
}
\ No newline at end of file
diff --git a/.serena/cache/csharp/document_symbols_cache_v23-06-25.pkl b/.serena/cache/csharp/document_symbols_cache_v23-06-25.pkl
new file mode 100644
index 0000000..510a8b1
Binary files /dev/null and b/.serena/cache/csharp/document_symbols_cache_v23-06-25.pkl differ
diff --git a/.serena/project.yml b/.serena/project.yml
new file mode 100644
index 0000000..6affd3b
--- /dev/null
+++ b/.serena/project.yml
@@ -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"
diff --git a/WARP.md b/WARP.md
new file mode 100644
index 0000000..8804129
--- /dev/null
+++ b/WARP.md
@@ -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 `` 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
diff --git a/doc/guide/design_principles.md b/doc/guide/design_principles.md
new file mode 100644
index 0000000..da44219
--- /dev/null
+++ b/doc/guide/design_principles.md
@@ -0,0 +1,1458 @@
+# NavisworksTransport 设计指导原则
+
+本文档记录了NavisworksTransport项目中积累的设计原则和最佳实践,为后续开发提供指导。
+
+## 1. 线程安全与UI更新
+
+### 问题描述
+
+Navisworks插件开发中经常遇到UI线程死锁和跨线程操作异常,特别是在后台任务需要更新UI状态时。
+
+### 设计原则
+
+#### 1.1 异步事件触发
+
+```csharp
+// ❌ 错误:同步事件触发可能导致死锁
+private void OnStatusChanged(string status)
+{
+ StatusChanged?.Invoke(this, status);
+}
+
+// ✅ 正确:异步事件触发避免死锁
+private void OnStatusChanged(string status)
+{
+ try
+ {
+ if (StatusChanged != null)
+ {
+ System.Threading.Tasks.Task.Run(() =>
+ {
+ try
+ {
+ StatusChanged?.Invoke(this, status);
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"StatusChanged事件触发失败: {ex.Message}");
+ }
+ });
+ }
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"OnStatusChanged方法异常: {ex.Message}");
+ }
+}
+```
+
+#### 1.2 UI线程更新策略
+
+```csharp
+// ❌ 错误:使用Invoke可能导致死锁
+if (!Dispatcher.CheckAccess())
+{
+ Dispatcher.Invoke(() => UpdateUI());
+}
+
+// ✅ 正确:使用BeginInvoke异步更新
+if (!Dispatcher.CheckAccess())
+{
+ Dispatcher.BeginInvoke(
+ new Action(() => {
+ try
+ {
+ UpdateUI();
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"UI更新失败: {ex.Message}");
+ }
+ }),
+ DispatcherPriority.Background
+ );
+}
+```
+
+#### 1.3 WinForms控件线程安全模式
+
+```csharp
+// ✅ WinForms控件的标准线程安全模式
+private void UpdateWinFormsControl()
+{
+ if (someControl.InvokeRequired)
+ {
+ someControl.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ // 具体的UI更新逻辑
+ someControl.Text = "更新的文本";
+ someControl.Enabled = true;
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"控件更新失败: {ex.Message}");
+ }
+ }));
+ }
+ else
+ {
+ // 已在UI线程上,直接更新
+ someControl.Text = "更新的文本";
+ someControl.Enabled = true;
+ }
+}
+
+// ✅ ListView等复杂控件的批量更新策略
+private void UpdateListViewSafely(ListView listView, List newItems)
+{
+ if (listView.InvokeRequired)
+ {
+ listView.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ // 批量更新,减少重绘次数
+ listView.BeginUpdate();
+ listView.Items.Clear();
+ listView.Items.AddRange(newItems.ToArray());
+ listView.EndUpdate();
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"ListView更新失败: {ex.Message}");
+ }
+ }));
+ }
+ else
+ {
+ listView.BeginUpdate();
+ listView.Items.Clear();
+ listView.Items.AddRange(newItems.ToArray());
+ listView.EndUpdate();
+ }
+}
+```
+
+#### 1.4 事件处理器中的线程安全
+
+```csharp
+// ✅ 事件处理器应该总是检查线程安全
+private static void OnCurrentRouteChanged(object sender, PathRoute newRoute)
+{
+ GlobalExceptionHandler.SafeExecute(() =>
+ {
+ LogManager.Info($"[UI同步] 当前路径已变更: {newRoute?.Name ?? "无路径"}");
+
+ // 确保在UI线程上执行
+ if (_controlPanelForm != null && _controlPanelForm.InvokeRequired)
+ {
+ _controlPanelForm.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ OnCurrentRouteChanged(sender, newRoute);
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"路径变更UI更新失败: {ex.Message}");
+ }
+ }));
+ return;
+ }
+
+ // 实际的UI更新逻辑
+ UpdateCurrentPathPointsList();
+ UpdatePathList();
+
+ }, "处理当前路径变更");
+}
+```
+
+### 适用场景
+
+- 后台任务状态更新
+- 路径规划进度报告
+- 动画播放状态同步
+- 错误信息显示
+
+## 2. 异常处理架构
+
+### 问题描述
+
+插件崩溃通常由未捕获异常导致,需要建立多层异常处理机制确保系统稳定性。
+
+### 设计原则
+
+#### 2.1 多层异常处理
+
+```csharp
+// 1. 全局异常处理器
+public static class GlobalExceptionHandler
+{
+ public static void Initialize()
+ {
+ AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
+ TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
+ Application.ThreadException += OnThreadException;
+ }
+}
+
+// 2. 业务层安全执行
+public static void SafeExecute(Action action, string operationName = "操作")
+{
+ try
+ {
+ action();
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"{operationName}失败: {ex.Message}");
+ // 显示用户友好错误信息
+ ShowErrorToast($"{operationName}失败: {ex.Message}");
+ }
+}
+
+// 3. 关键操作的精确异常处理
+try
+{
+ // 关键业务逻辑
+ var result = SomeImportantOperation();
+}
+catch (SpecificException ex)
+{
+ // 特定异常的处理逻辑
+ HandleSpecificError(ex);
+}
+catch (Exception ex)
+{
+ // 通用异常处理
+ LogManager.Error($"操作失败: {ex.Message}");
+ throw new BusinessException("业务操作失败", ex);
+}
+```
+
+#### 2.2 异常恢复机制
+
+```csharp
+private static void TryRecoverComponents()
+{
+ try
+ {
+ // 重置路径编辑状态
+ var activeManager = PathPlanningManager.GetActivePathManager();
+ activeManager?.ResetPathEditState();
+
+ // 清除临时高亮
+ NavisApplication.ActiveDocument?.Models?.ResetAllTemporaryMaterials();
+
+ // 清理资源
+ CleanupResources();
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"组件恢复失败: {ex.Message}");
+ }
+}
+```
+
+### 适用场景
+
+- 所有对外接口方法
+- 事件处理器
+- 后台任务执行
+- Navisworks API调用
+
+## 3. 内存管理与性能优化
+
+### 问题描述
+
+Navisworks插件长时间运行可能出现内存泄漏,特别是在大模型操作和复杂算法执行时。
+
+### 设计原则
+
+#### 3.1 及时资源释放
+
+```csharp
+// ✅ 使用using语句自动释放资源
+using (var disposableResource = new SomeDisposableClass())
+{
+ // 使用资源
+}
+
+// ✅ 手动清理大对象
+public void Cleanup()
+{
+ try
+ {
+ _largeDataStructure?.Clear();
+ _largeDataStructure = null;
+
+ // 强制垃圾回收(谨慎使用)
+ if (memoryPressure > threshold)
+ {
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+ GC.Collect();
+ }
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"资源清理失败: {ex.Message}");
+ }
+}
+```
+
+#### 3.2 大数据处理策略
+
+```csharp
+// ✅ 分批处理大数据集
+public void ProcessLargeDataSet(IEnumerable items)
+{
+ const int batchSize = 1000;
+ var batch = new List(batchSize);
+
+ foreach (var item in items)
+ {
+ batch.Add(item);
+
+ if (batch.Count >= batchSize)
+ {
+ ProcessBatch(batch);
+ batch.Clear();
+
+ // 检查内存压力
+ if (NeedMemoryRelief())
+ {
+ GC.Collect();
+ }
+ }
+ }
+
+ // 处理剩余项目
+ if (batch.Count > 0)
+ {
+ ProcessBatch(batch);
+ }
+}
+```
+
+### 适用场景
+
+- 大模型数据处理
+- A*路径规划算法
+- 批量属性设置
+- 3D渲染操作
+
+## 4. 事件驱动架构
+
+### 问题描述
+
+插件各组件间需要松耦合通信,避免直接依赖导致的紧耦合问题。
+
+### 设计原则
+
+#### 4.1 事件定义规范
+
+```csharp
+// ✅ 标准事件定义
+public class PathPlanningManager
+{
+ // 状态变更事件
+ public event EventHandler StatusChanged;
+ public event EventHandler ErrorOccurred;
+
+ // 业务事件
+ public event EventHandler RouteGenerated;
+ public event EventHandler PathEditStateChanged;
+
+ // 安全触发事件
+ protected virtual void OnStatusChanged(string status)
+ {
+ // 使用异步触发避免死锁
+ Task.Run(() => {
+ try
+ {
+ StatusChanged?.Invoke(this, status);
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"事件触发失败: {ex.Message}");
+ }
+ });
+ }
+}
+```
+
+#### 4.2 事件订阅管理
+
+```csharp
+public class EventSubscriptionManager : IDisposable
+{
+ private readonly List _unsubscribeActions = new List();
+
+ public void Subscribe(EventHandler handler,
+ Action> subscribe,
+ Action> unsubscribe)
+ {
+ subscribe(handler);
+ _unsubscribeActions.Add(() => unsubscribe(handler));
+ }
+
+ public void Dispose()
+ {
+ foreach (var unsubscribe in _unsubscribeActions)
+ {
+ try
+ {
+ unsubscribe();
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"事件取消订阅失败: {ex.Message}");
+ }
+ }
+ _unsubscribeActions.Clear();
+ }
+}
+```
+
+### 适用场景
+
+- UI状态同步
+- 插件间通信
+- 进度报告
+- 错误传播
+
+## 5. Navisworks API使用规范
+
+### 问题描述
+
+Navisworks API调用可能失败,需要正确的错误处理和重试机制。
+
+### 设计原则
+
+#### 5.1 API调用封装
+
+```csharp
+public static class NavisApiHelper
+{
+ public static T SafeApiCall(Func apiCall, T defaultValue = default(T),
+ string operationName = "API调用")
+ {
+ try
+ {
+ // 检查API可用性
+ if (NavisApplication.ActiveDocument == null)
+ {
+ throw new InvalidOperationException("Navisworks文档未加载");
+ }
+
+ return apiCall();
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"{operationName}失败: {ex.Message}");
+ return defaultValue;
+ }
+ }
+
+ public static void SafeApiCall(Action apiCall, string operationName = "API调用")
+ {
+ SafeApiCall(() => { apiCall(); return true; }, false, operationName);
+ }
+}
+```
+
+#### 5.2 COM API与.NET API协调使用
+
+```csharp
+// ✅ 正确的双API使用模式
+public class CategoryAttributeManager
+{
+ // 使用.NET API读取
+ public static List GetItemsWithCategory(LogisticsElementType category)
+ {
+ return NavisApiHelper.SafeApiCall(() =>
+ {
+ var search = new Search();
+ search.SearchConditions.Add(new SearchCondition()
+ {
+ PropertyName = "Logistics.Category",
+ Condition = SearchConditionType.Equal,
+ Value = category.ToString()
+ });
+ return search.FindAll(NavisApplication.ActiveDocument).ToList();
+ }, new List(), "查询分类属性");
+ }
+
+ // 使用COM API持久化属性
+ public static bool SetLogisticsAttribute(ModelItem item, LogisticsElementType category)
+ {
+ return NavisApiHelper.SafeApiCall(() =>
+ {
+ var comApi = ComApiBridge.ToInwOaPath(item);
+ comApi.SetUserAttribute("Logistics", "Category", category.ToString());
+ return true;
+ }, false, "设置物流属性");
+ }
+}
+```
+
+### 适用场景
+
+- 模型数据访问
+- 属性读写操作
+- 3D场景操作
+- 文件导入导出
+
+## 6. 状态管理模式
+
+### 问题描述
+
+插件需要维护复杂的状态信息,确保状态一致性和可预测性。
+
+### 设计原则
+
+#### 6.1 状态枚举定义
+
+```csharp
+// ✅ 清晰的状态定义
+public enum PathEditState
+{
+ None, // 无编辑状态
+ Creating, // 创建新路径
+ Editing, // 编辑现有路径
+ Selecting // 选择路径点
+}
+
+public enum AnimationState
+{
+ Stopped, // 停止
+ Playing, // 播放中
+ Paused, // 暂停
+ Recording // 录制中
+}
+```
+
+#### 6.2 状态机模式
+
+```csharp
+public class PathEditStateMachine
+{
+ private PathEditState _currentState = PathEditState.None;
+
+ public PathEditState CurrentState
+ {
+ get => _currentState;
+ private set
+ {
+ if (_currentState != value)
+ {
+ var oldState = _currentState;
+ _currentState = value;
+ OnStateChanged(oldState, value);
+ }
+ }
+ }
+
+ public bool CanTransitionTo(PathEditState newState)
+ {
+ return ValidTransitions[_currentState].Contains(newState);
+ }
+
+ public void TransitionTo(PathEditState newState)
+ {
+ if (!CanTransitionTo(newState))
+ {
+ throw new InvalidOperationException(
+ $"不能从{_currentState}转换到{newState}");
+ }
+
+ CurrentState = newState;
+ }
+
+ private static readonly Dictionary> ValidTransitions =
+ new Dictionary>
+ {
+ [PathEditState.None] = new HashSet { PathEditState.Creating, PathEditState.Editing },
+ [PathEditState.Creating] = new HashSet { PathEditState.None, PathEditState.Selecting },
+ [PathEditState.Editing] = new HashSet { PathEditState.None, PathEditState.Selecting },
+ [PathEditState.Selecting] = new HashSet { PathEditState.Creating, PathEditState.Editing }
+ };
+}
+```
+
+### 适用场景
+
+- 路径编辑流程
+- 动画播放控制
+- UI模式切换
+- 工具状态管理
+
+## 7. 配置与数据持久化
+
+### 问题描述
+
+插件配置和用户数据需要可靠的持久化机制,支持版本升级和迁移。
+
+### 设计原则
+
+#### 7.1 配置文件结构
+
+```csharp
+[Serializable]
+public class PluginConfig
+{
+ public string Version { get; set; } = "1.0.0";
+ public DateTime LastModified { get; set; } = DateTime.Now;
+
+ // 用户设置
+ public UserSettings User { get; set; } = new UserSettings();
+
+ // 路径数据
+ public List SavedPaths { get; set; } = new List();
+
+ // 验证配置有效性
+ public ValidationResult Validate()
+ {
+ var result = new ValidationResult();
+
+ if (string.IsNullOrEmpty(Version))
+ result.Errors.Add("版本号不能为空");
+
+ if (SavedPaths?.Any(p => string.IsNullOrEmpty(p.Name)) == true)
+ result.Errors.Add("路径名称不能为空");
+
+ return result;
+ }
+}
+```
+
+#### 7.2 数据迁移机制
+
+```csharp
+public class ConfigMigrationManager
+{
+ private static readonly Dictionary> Migrations =
+ new Dictionary>
+ {
+ ["1.0.0"] = MigrateFrom100To101,
+ ["1.0.1"] = MigrateFrom101To102
+ };
+
+ public static PluginConfig MigrateConfig(string configJson, string targetVersion)
+ {
+ var config = JObject.Parse(configJson);
+ var currentVersion = config["Version"]?.ToString() ?? "1.0.0";
+
+ while (currentVersion != targetVersion && Migrations.ContainsKey(currentVersion))
+ {
+ config = Migrations[currentVersion](config);
+ currentVersion = config["Version"].ToString();
+ }
+
+ return config.ToObject();
+ }
+}
+```
+
+### 适用场景
+
+- 用户偏好设置
+- 路径数据保存
+- 项目配置管理
+- 插件状态恢复
+
+## 8. 日志记录规范
+
+### 问题描述
+
+有效的日志记录对于问题诊断和系统维护至关重要。
+
+### 设计原则
+
+#### 8.1 日志级别使用
+
+```csharp
+public static class LogManager
+{
+ // Info: 正常业务流程
+ public static void Info(string message)
+ {
+ WriteLog(LogLevel.Info, message);
+ }
+
+ // Warning: 可恢复的问题
+ public static void Warning(string message)
+ {
+ WriteLog(LogLevel.Warning, message);
+ }
+
+ // Error: 需要关注的错误
+ public static void Error(string message)
+ {
+ WriteLog(LogLevel.Error, message);
+ }
+
+ // Debug: 开发调试信息
+ public static void Debug(string message)
+ {
+ if (IsDebugEnabled)
+ WriteLog(LogLevel.Debug, message);
+ }
+}
+
+// 使用示例
+LogManager.Info("开始自动路径规划");
+LogManager.Warning($"找到{conflictCount}个潜在冲突点");
+LogManager.Error($"路径规划失败: {ex.Message}");
+LogManager.Debug($"网格地图尺寸: {width}x{height}");
+```
+
+#### 8.2 结构化日志
+
+```csharp
+public static class StructuredLogger
+{
+ public static void LogOperation(string operation, object parameters,
+ TimeSpan duration, bool success, string error = null)
+ {
+ var logEntry = new
+ {
+ Timestamp = DateTime.Now,
+ Operation = operation,
+ Parameters = parameters,
+ Duration = duration.TotalMilliseconds,
+ Success = success,
+ Error = error,
+ ThreadId = Thread.CurrentThread.ManagedThreadId
+ };
+
+ LogManager.Info(JsonConvert.SerializeObject(logEntry, Formatting.None));
+ }
+}
+
+// 使用示例
+var stopwatch = Stopwatch.StartNew();
+try
+{
+ var result = AutoPlanPath(start, end, vehicleSize);
+ StructuredLogger.LogOperation("AutoPlanPath",
+ new { start, end, vehicleSize },
+ stopwatch.Elapsed, true);
+}
+catch (Exception ex)
+{
+ StructuredLogger.LogOperation("AutoPlanPath",
+ new { start, end, vehicleSize },
+ stopwatch.Elapsed, false, ex.Message);
+}
+```
+
+### 适用场景
+
+- 操作流程跟踪
+- 性能监控
+- 错误诊断
+- 用户行为分析
+
+## 9. 测试策略
+
+### 问题描述
+
+Navisworks插件的测试需要特殊考虑,包括UI测试、API模拟等。
+
+### 设计原则
+
+#### 9.1 可测试性设计
+
+```csharp
+// ✅ 依赖注入提高可测试性
+public interface INavisworksApiWrapper
+{
+ Document ActiveDocument { get; }
+ ModelItemCollection FindItems(Search search);
+ void SetUserAttribute(ModelItem item, string category, string name, string value);
+}
+
+public class PathPlanningManager
+{
+ private readonly INavisworksApiWrapper _apiWrapper;
+
+ public PathPlanningManager(INavisworksApiWrapper apiWrapper = null)
+ {
+ _apiWrapper = apiWrapper ?? new DefaultNavisworksApiWrapper();
+ }
+
+ // 业务逻辑与API分离,便于测试
+ public PathRoute PlanPath(Point3D start, Point3D end)
+ {
+ var items = _apiWrapper.FindItems(CreateChannelSearch());
+ return ExecutePathPlanning(start, end, items);
+ }
+}
+```
+
+#### 9.2 单元测试结构
+
+```csharp
+[TestClass]
+public class PathPlanningManagerTests
+{
+ private Mock _mockApi;
+ private PathPlanningManager _manager;
+
+ [TestInitialize]
+ public void Setup()
+ {
+ _mockApi = new Mock();
+ _manager = new PathPlanningManager(_mockApi.Object);
+ }
+
+ [TestMethod]
+ public void PlanPath_ValidInput_ReturnsPath()
+ {
+ // Arrange
+ var start = new Point3D(0, 0, 0);
+ var end = new Point3D(10, 10, 0);
+ _mockApi.Setup(x => x.FindItems(It.IsAny()))
+ .Returns(CreateMockChannels());
+
+ // Act
+ var result = _manager.PlanPath(start, end);
+
+ // Assert
+ Assert.IsNotNull(result);
+ Assert.IsTrue(result.Points.Count > 0);
+ }
+}
+```
+
+### 适用场景
+
+- 核心算法验证
+- API调用测试
+- 业务逻辑验证
+- 回归测试
+
+## 10. 性能监控与优化
+
+### 问题描述
+
+插件性能问题需要及时发现和定位,特别是在大模型处理时。
+
+### 设计原则
+
+#### 10.1 性能监控代码
+
+```csharp
+public class PerformanceMonitor : IDisposable
+{
+ private readonly string _operationName;
+ private readonly Stopwatch _stopwatch;
+ private readonly long _initialMemory;
+
+ public PerformanceMonitor(string operationName)
+ {
+ _operationName = operationName;
+ _stopwatch = Stopwatch.StartNew();
+ _initialMemory = GC.GetTotalMemory(false);
+
+ LogManager.Debug($"[性能] 开始监控: {operationName}");
+ }
+
+ public void Dispose()
+ {
+ _stopwatch.Stop();
+ var finalMemory = GC.GetTotalMemory(false);
+ var memoryDelta = finalMemory - _initialMemory;
+
+ LogManager.Info($"[性能] {_operationName} 完成: " +
+ $"耗时{_stopwatch.ElapsedMilliseconds}ms, " +
+ $"内存变化{memoryDelta / 1024}KB");
+
+ if (_stopwatch.ElapsedMilliseconds > 5000) // 超过5秒警告
+ {
+ LogManager.Warning($"[性能] {_operationName} 执行时间过长");
+ }
+ }
+}
+
+// 使用方式
+using (new PerformanceMonitor("自动路径规划"))
+{
+ var result = AutoPlanPath(start, end, vehicleSize);
+}
+```
+
+#### 10.2 缓存策略
+
+```csharp
+public class ModelDataCache
+{
+ private static readonly Dictionary _cache =
+ new Dictionary();
+ private static readonly TimeSpan DefaultTtl = TimeSpan.FromMinutes(10);
+
+ public static T GetOrCreate(string key, Func factory, TimeSpan? ttl = null)
+ {
+ CleanExpiredEntries();
+
+ if (_cache.TryGetValue(key, out var entry) && !entry.IsExpired)
+ {
+ LogManager.Debug($"[缓存] 命中: {key}");
+ return (T)entry.Value;
+ }
+
+ LogManager.Debug($"[缓存] 未命中,创建: {key}");
+ var value = factory();
+ _cache[key] = new CacheEntry(value, DateTime.Now.Add(ttl ?? DefaultTtl));
+
+ return value;
+ }
+}
+```
+
+### 适用场景
+
+- 算法性能监控
+- 内存使用跟踪
+- 缓存效果评估
+- 瓶颈识别
+
+## 11. 3D渲染性能优化
+
+### 问题描述
+Navisworks RenderPlugin在用户进行视图操作时会频繁调用,如果渲染逻辑复杂或存在性能问题,会导致界面卡顿甚至挂起。
+
+### 设计原则
+
+#### 11.1 高效的渲染循环
+```csharp
+public override void Render(View view, Graphics graphics)
+{
+ if (!_isEnabled) return;
+
+ try
+ {
+ // ✅ 快速检查,避免频繁的API调用
+ var activeDoc = Application.ActiveDocument;
+ if (activeDoc?.Models == null || activeDoc.Models.Count == 0)
+ {
+ return; // 静默返回,避免日志泛滥
+ }
+
+ // ✅ 早期退出,避免无意义的渲染
+ int markerCount;
+ lock (_lockObject)
+ {
+ markerCount = _circleMarkers.Count;
+ }
+ if (markerCount == 0) return;
+
+ graphics.BeginModelContext();
+
+ // ✅ 缓存计算结果,减少重复计算
+ double lineRadiusInModelUnits = 0.2 * GetMetersToModelUnitsConversionFactor();
+
+ lock (_lockObject)
+ {
+ // ✅ 使用数组而非List,提高迭代性能
+ var markers = _circleMarkers.ToArray();
+
+ // 高效的渲染逻辑
+ foreach (var marker in markers)
+ {
+ graphics.Color(marker.Color, marker.Alpha);
+ graphics.Sphere(marker.Center, marker.Radius);
+ }
+ }
+
+ graphics.EndModelContext();
+ }
+ catch (Exception ex)
+ {
+ // ✅ 渲染异常应静默处理,避免影响主程序
+ LogManager.WriteLog($"[渲染异常] {ex.Message}");
+ }
+}
+```
+
+#### 11.2 防抖机制避免频繁刷新
+```csharp
+// ❌ 错误:每次操作都刷新视图
+public void AddMarker(Point3D position)
+{
+ _markers.Add(new Marker(position));
+ Application.ActiveDocument.ActiveView.RequestDelayedRedraw(ViewRedrawRequests.Render);
+}
+
+// ✅ 正确:使用防抖机制
+private static DateTime _lastRefreshTime = DateTime.MinValue;
+
+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);
+ }
+ }
+ catch (Exception ex)
+ {
+ LogManager.WriteLog($"[视图刷新] 失败: {ex.Message}");
+ }
+}
+```
+
+#### 11.3 数据结构优化
+```csharp
+// ✅ 使用高效的数据结构
+public class OptimizedRenderPlugin : RenderPlugin
+{
+ // 使用数组存储频繁访问的数据
+ private CircleMarker[] _cachedMarkers = new CircleMarker[0];
+ private bool _cacheInvalid = true;
+
+ // 批量更新机制
+ public void BatchUpdateMarkers(IEnumerable newMarkers)
+ {
+ lock (_lockObject)
+ {
+ _circleMarkers.Clear();
+ _circleMarkers.AddRange(newMarkers);
+ _cacheInvalid = true;
+ }
+
+ RequestViewRefresh();
+ }
+
+ public override void Render(View view, Graphics graphics)
+ {
+ // 延迟更新缓存
+ if (_cacheInvalid)
+ {
+ lock (_lockObject)
+ {
+ _cachedMarkers = _circleMarkers.ToArray();
+ _cacheInvalid = false;
+ }
+ }
+
+ // 使用缓存的数组进行渲染
+ foreach (var marker in _cachedMarkers)
+ {
+ graphics.Color(marker.Color, marker.Alpha);
+ graphics.Sphere(marker.Center, marker.Radius);
+ }
+ }
+}
+```
+
+### 适用场景
+- 3D标记和路径可视化
+- 实时渲染更新
+- 大量图形元素渲染
+- 用户交互响应优化
+
+## 12. 路径规划智能容错
+
+### 问题描述
+用户在复杂楼层环境中选择起点和终点时,经常会无意中点击到障碍物(墙体、柱子等)位置,导致路径规划失败。
+
+### 设计原则
+
+#### 12.1 智能位置修正
+```csharp
+// ❌ 错误:严格验证,用户体验差
+if (!gridMap.IsWalkable(startGrid))
+{
+ throw new AutoPathPlanningException($"起点位于障碍物上");
+}
+
+// ✅ 正确:智能修正,提升用户体验
+var correctedStartGrid = FindNearestWalkablePosition(gridMap, startGrid, "起点");
+if (correctedStartGrid == null)
+{
+ throw new AutoPathPlanningException($"起点附近没有可通行区域");
+}
+
+// 记录修正信息
+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;
+}
+```
+
+#### 12.2 BFS最近邻搜索算法
+```csharp
+private Point2D? FindNearestWalkablePosition(GridMap gridMap, Point2D originalPos,
+ string positionName, int maxDistance = 10)
+{
+ // 如果原始位置已经可通行,直接返回
+ if (gridMap.IsWalkable(originalPos))
+ {
+ return originalPos;
+ }
+
+ // 使用BFS搜索最近的可通行位置
+ var visited = new HashSet();
+ 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;
+
+ foreach (var dir in directions)
+ {
+ var neighborPos = new Point2D(currentPos.X + dir.X, currentPos.Y + dir.Y);
+
+ if (visited.Contains(neighborPos) ||
+ !gridMap.IsValidGridPosition(neighborPos))
+ {
+ continue;
+ }
+
+ visited.Add(neighborPos);
+
+ if (gridMap.IsWalkable(neighborPos))
+ {
+ LogManager.Info($"位置已修正到距离{distance + 1}格的位置");
+ return neighborPos;
+ }
+
+ queue.Enqueue((neighborPos, distance + 1));
+ }
+ }
+
+ return null; // 未找到可通行位置
+}
+```
+
+#### 12.3 渐进式错误处理
+```csharp
+public PathRoute AutoPlanPath(Point3D startPoint, Point3D endPoint,
+ double vehicleSize = 1.0, double safetyMargin = 0.5)
+{
+ try
+ {
+ // 第一次尝试:使用原始参数
+ return PlanPathInternal(startPoint, endPoint, vehicleSize, safetyMargin);
+ }
+ catch (AutoPathPlanningException ex) when (ex.Message.Contains("位于障碍物上"))
+ {
+ LogManager.Info("使用智能修正重试路径规划...");
+
+ try
+ {
+ // 第二次尝试:启用智能修正
+ return PlanPathWithCorrection(startPoint, endPoint, vehicleSize, safetyMargin);
+ }
+ catch (Exception innerEx)
+ {
+ LogManager.Warning($"智能修正也失败: {innerEx.Message}");
+
+ // 第三次尝试:降低精度重试
+ var reducedMargin = safetyMargin * 0.5;
+ var reducedSize = vehicleSize * 0.8;
+
+ LogManager.Info($"降低参数重试: 车辆尺寸{reducedSize:F1}m, 安全边距{reducedMargin:F1}m");
+ return PlanPathInternal(startPoint, endPoint, reducedSize, reducedMargin);
+ }
+ }
+}
+```
+
+#### 12.4 用户反馈机制
+```csharp
+// 在UI层提供清晰的反馈
+private void OnPathPlanningResult(PathRoute result, bool wasPositionCorrected)
+{
+ if (result != null)
+ {
+ if (wasPositionCorrected)
+ {
+ StatusText = "路径规划成功(起点/终点已自动调整到最近可通行位置)";
+ // 可选:高亮显示修正后的位置
+ HighlightCorrectedPositions();
+ }
+ else
+ {
+ StatusText = "路径规划成功";
+ }
+ }
+}
+
+// 提供手动精确定位的选项
+private void ShowPositionCorrectionDialog(Point3D originalPos, Point3D correctedPos)
+{
+ var message = $"所选位置位于障碍物上,已自动调整到最近可通行位置:\n" +
+ $"原位置: ({originalPos.X:F2}, {originalPos.Y:F2})\n" +
+ $"调整后: ({correctedPos.X:F2}, {correctedPos.Y:F2})\n\n" +
+ $"是否接受此调整?";
+
+ var result = MessageBox.Show(message, "位置自动调整",
+ MessageBoxButtons.YesNo, MessageBoxIcon.Question);
+
+ if (result == DialogResult.No)
+ {
+ // 允许用户重新选择
+ RequestPositionReselection();
+ }
+}
+```
+
+### 适用场景
+- 复杂建筑环境的路径规划
+- 用户交互式点选位置
+- 精度要求高的导航系统
+- 自动化路径生成工具
+
+## 13. 线程安全实践经验总结
+
+### 问题描述
+
+在实际开发中发现,UI线程死锁是导致插件崩溃的主要原因之一。特别是在自动路径规划等后台任务完成后更新UI时,经常出现界面冻结和崩溃问题。
+
+### 关键经验
+
+#### 13.1 死锁问题的根本原因
+
+```csharp
+// ❌ 导致死锁的典型模式
+public void UpdateUI()
+{
+ // 后台线程试图更新UI
+ Application.Current.Dispatcher.Invoke(() => {
+ // UI线程被阻塞,等待后台任务完成
+ // 后台任务又在等待UI线程响应 -> 死锁
+ someLabel.Text = "更新文本";
+ });
+}
+
+// ✅ 避免死锁的正确做法
+public void UpdateUI()
+{
+ Application.Current.Dispatcher.BeginInvoke(
+ new Action(() => {
+ try {
+ someLabel.Text = "更新文本";
+ } catch (Exception ex) {
+ LogManager.Error($"UI更新失败: {ex.Message}");
+ }
+ }),
+ DispatcherPriority.Background
+ );
+}
+```
+
+#### 13.2 系统性检查清单
+
+基于实际修复经验,以下是需要重点检查的场景:
+
+1. **事件处理器** - 所有UI事件处理都需要线程安全检查
+2. **ListView/ComboBox操作** - Items.Clear()、Items.Add()等操作
+3. **Label/TextBox更新** - Text属性、ForeColor属性更新
+4. **Button状态控制** - Enabled、Visible属性更新
+5. **ProgressBar更新** - Value属性在动画过程中的更新
+6. **状态标签更新** - 在异步操作完成后的状态显示
+
+#### 13.3 修复模式标准化
+
+```csharp
+// 标准修复模式 - 适用于简单控件
+private void SafeUpdateControl(T control, Action updateAction)
+ where T : Control
+{
+ if (control.InvokeRequired)
+ {
+ control.BeginInvoke(new Action(() => {
+ try {
+ updateAction(control);
+ } catch (Exception ex) {
+ LogManager.Error($"控件更新失败: {ex.Message}");
+ }
+ }));
+ }
+ else
+ {
+ updateAction(control);
+ }
+}
+
+// 使用示例
+SafeUpdateControl(statusLabel, label => {
+ label.Text = "操作完成";
+ label.ForeColor = Color.Green;
+});
+
+// 复杂控件的批量更新模式
+private void SafeUpdateListView(ListView listView, Action updateAction)
+{
+ if (listView.InvokeRequired)
+ {
+ listView.BeginInvoke(new Action(() => {
+ try {
+ listView.BeginUpdate();
+ updateAction(listView);
+ listView.EndUpdate();
+ } catch (Exception ex) {
+ LogManager.Error($"ListView更新失败: {ex.Message}");
+ }
+ }));
+ }
+ else
+ {
+ listView.BeginUpdate();
+ updateAction(listView);
+ listView.EndUpdate();
+ }
+}
+```
+
+#### 13.4 性能优化考虑
+
+```csharp
+// ✅ 减少跨线程调用的开销
+private void UpdateMultipleControls()
+{
+ // 准备所有数据
+ var statusText = "操作完成";
+ var itemCount = pathList.Count;
+ var progressValue = 100;
+
+ // 一次性跨线程调用,而不是多次
+ if (InvokeRequired)
+ {
+ BeginInvoke(new Action(() => {
+ try {
+ statusLabel.Text = statusText;
+ countLabel.Text = $"共{itemCount}项";
+ progressBar.Value = progressValue;
+ } catch (Exception ex) {
+ LogManager.Error($"批量UI更新失败: {ex.Message}");
+ }
+ }));
+ }
+}
+
+// ❌ 效率低下的多次跨线程调用
+private void UpdateMultipleControlsWrongWay()
+{
+ SafeUpdateControl(statusLabel, l => l.Text = "操作完成");
+ SafeUpdateControl(countLabel, l => l.Text = $"共{pathList.Count}项");
+ SafeUpdateControl(progressBar, p => p.Value = 100);
+ // 每个控件都单独调用BeginInvoke,开销大
+}
+```
+
+#### 13.5 调试和监控
+
+```csharp
+// 添加线程安全检查的调试代码
+public static class ThreadSafetyChecker
+{
+ public static void CheckUIThread(string operationName)
+ {
+ if (!Application.Current.Dispatcher.CheckAccess())
+ {
+ LogManager.Warning($"[线程警告] {operationName} 在非UI线程上调用");
+ // 在调试模式下抛出异常,强制修复
+ #if DEBUG
+ throw new InvalidOperationException($"{operationName} 必须在UI线程上调用");
+ #endif
+ }
+ }
+}
+
+// 在关键UI更新点添加检查
+private void UpdateCurrentPathStatus(string status)
+{
+ ThreadSafetyChecker.CheckUIThread("UpdateCurrentPathStatus");
+ _currentPathStatusLabel.Text = status;
+}
+```
+
+### 实施建议
+
+1. **预防性修复** - 在新功能开发时主动应用线程安全模式
+2. **代码审查重点** - 重点检查UI更新相关的代码
+3. **测试策略** - 在高负载场景下测试多线程行为
+4. **监控机制** - 添加线程安全违规的监控和报警
+
+### 常见陷阱
+
+1. **ListView.SelectedItems访问** - 在事件处理器中直接访问可能引发跨线程异常
+2. **Control.Text属性** - 看似简单但必须在UI线程上执行
+3. **事件链式调用** - 一个事件触发另一个事件,可能导致递归的线程问题
+4. **Dispose检查遗漏** - 在BeginInvoke回调中访问已释放的控件
+
+## 总结
+
+这些设计原则基于NavisworksTransport项目的实际开发经验总结,涵盖了插件开发中的关键技术挑战。遵循这些原则可以:
+
+1. **提高系统稳定性** - 通过多层异常处理和安全的线程操作
+2. **增强代码可维护性** - 通过清晰的架构设计和规范的编码实践
+3. **优化性能表现** - 通过有效的资源管理和性能监控
+4. **简化问题诊断** - 通过完善的日志记录和错误跟踪
+5. **消除UI死锁** - 通过系统性的线程安全实践
+
+### 重点实践建议
+
+基于实际修复经验,以下实践最为关键:
+
+#### 线程安全优先级
+1. **最高优先级** - 所有UI更新操作必须使用BeginInvoke模式
+2. **高优先级** - 事件处理器必须包含线程安全检查
+3. **中优先级** - 复杂控件操作使用批量更新策略
+
+#### 代码审查检查点
+- [ ] 是否所有控件更新都有InvokeRequired检查?
+- [ ] 是否使用BeginInvoke而不是Invoke?
+- [ ] 是否在UI更新中添加了异常处理?
+- [ ] 是否避免了嵌套的Invoke调用?
+
+#### 测试验证要点
+- 在多路径规划场景下测试UI响应性
+- 验证后台任务完成后的UI更新是否正常
+- 检查动画播放过程中的界面稳定性
+- 确认异常情况下的优雅降级
+
+在后续开发中,应该:
+
+- **优先应用线程安全原则** - 在所有UI相关代码中
+- **持续更新和完善指导文档** - 记录新发现的问题和解决方案
+- **在代码审查中重点检查线程安全** - 特别关注UI更新相关代码
+- **定期进行压力测试** - 验证多线程环境下的稳定性
+- **建立监控机制** - 及时发现和修复线程安全问题
+
+---
+
+*本文档将随着项目发展持续更新,确保设计指导的有效性和实用性。*
diff --git a/doc/working/UI架构重构技术设计方案_20250816.md b/doc/working/UI架构重构技术设计方案_20250816.md
new file mode 100644
index 0000000..c94343d
--- /dev/null
+++ b/doc/working/UI架构重构技术设计方案_20250816.md
@@ -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 │
+│ ├── 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 _updateQueue;
+ private volatile bool _isProcessing = false;
+
+ // 线程安全的UI更新接口
+ public Task ExecuteUIUpdateAsync(Func 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 : ObservableCollection
+{
+ 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 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 _updatingProperties = new HashSet();
+ 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 ExecuteAsync();
+}
+
+public class AutoPathPlanningCommand : IPathPlanningCommand
+{
+ private readonly Point3D _startPoint;
+ private readonly Point3D _endPoint;
+ private readonly PathPlanningManager _manager;
+
+ public async Task 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 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 AutoPlanPathAsync(Point3D start, Point3D end, double vehicleSize, double safetyMargin);
+ Task ValidatePathAsync(PathRoute route);
+}
+
+public class PathPlanningManager : IPathPlanningService
+{
+ // 移除所有UI相关事件
+ // 只保留纯业务逻辑
+
+ public async Task 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的优势,为后续功能扩展打下坚实基础。
+
+---
+
+*本文档将根据实施进度持续更新,确保设计方案与实际开发保持同步。*
diff --git a/src/Core/MainPlugin.cs b/src/Core/MainPlugin.cs
index f310c73..ce3db6c 100644
--- a/src/Core/MainPlugin.cs
+++ b/src/Core/MainPlugin.cs
@@ -1110,9 +1110,47 @@ namespace NavisworksTransport
if (result == DialogResult.Yes)
{
- // TODO: 实现删除路径的逻辑
- LogManager.Info($"删除路径: {_pathListView.SelectedItems[0].Text}");
- _pathListView.SelectedItems[0].Remove();
+ // 线程安全的路径删除操作
+ string pathName = _pathListView.SelectedItems[0].Text;
+ LogManager.Info($"删除路径: {pathName}");
+
+ // 实际删除路径数据(在PathPlanningManager中)
+ var pathManager = PathPlanningManager.GetActivePathManager();
+ if (pathManager != null)
+ {
+ var routeToDelete = pathManager.Routes?.FirstOrDefault(r => r.Name == pathName);
+ if (routeToDelete != null)
+ {
+ pathManager.Routes.Remove(routeToDelete);
+ LogManager.Info($"已从路径管理器中删除路径: {pathName}");
+ }
+ }
+
+ // 线程安全的UI更新
+ if (_pathListView.InvokeRequired)
+ {
+ _pathListView.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ if (_pathListView != null && !_pathListView.IsDisposed && _pathListView.SelectedItems.Count > 0)
+ {
+ _pathListView.SelectedItems[0].Remove();
+ }
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"路径删除UI更新失败: {ex.Message}");
+ }
+ }));
+ }
+ else
+ {
+ if (_pathListView != null && !_pathListView.IsDisposed && _pathListView.SelectedItems.Count > 0)
+ {
+ _pathListView.SelectedItems[0].Remove();
+ }
+ }
MessageBox.Show("路径已删除", "操作完成",
MessageBoxButtons.OK, MessageBoxIcon.Information);
}
@@ -2140,7 +2178,36 @@ namespace NavisworksTransport
refreshInfoButton.Click += (sender, e) =>
{
- memoryLabel.Text = $"内存使用: {GC.GetTotalMemory(false) / 1024 / 1024} MB";
+ GlobalExceptionHandler.SafeExecute(() =>
+ {
+ // 线程安全的内存标签更新
+ string memoryText = $"内存使用: {GC.GetTotalMemory(false) / 1024 / 1024} MB";
+
+ if (memoryLabel.InvokeRequired)
+ {
+ memoryLabel.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ if (memoryLabel != null && !memoryLabel.IsDisposed)
+ {
+ memoryLabel.Text = memoryText;
+ }
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"内存标签更新失败: {ex.Message}");
+ }
+ }));
+ }
+ else
+ {
+ if (memoryLabel != null && !memoryLabel.IsDisposed)
+ {
+ memoryLabel.Text = memoryText;
+ }
+ }
+ }, "刷新系统信息");
};
}
@@ -2521,13 +2588,63 @@ namespace NavisworksTransport
}
selectedComponent = selectedItems.First;
- vehicleStatusLabel.Text = $"状态: 已选择 {selectedComponent.DisplayName}";
- vehicleStatusLabel.ForeColor = System.Drawing.Color.Green;
+
+ // 线程安全的vehicleStatusLabel更新
+ string statusText = $"状态: 已选择 {selectedComponent.DisplayName}";
+ if (vehicleStatusLabel.InvokeRequired)
+ {
+ vehicleStatusLabel.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ if (vehicleStatusLabel != null && !vehicleStatusLabel.IsDisposed)
+ {
+ vehicleStatusLabel.Text = statusText;
+ vehicleStatusLabel.ForeColor = System.Drawing.Color.Green;
+ }
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"车辆状态标签更新失败: {ex.Message}");
+ }
+ }));
+ }
+ else
+ {
+ if (vehicleStatusLabel != null && !vehicleStatusLabel.IsDisposed)
+ {
+ vehicleStatusLabel.Text = statusText;
+ vehicleStatusLabel.ForeColor = System.Drawing.Color.Green;
+ }
+ }
- // 检查是否可以启用生成动画按钮
+ // 线程安全的动画按钮启用检查
if (pathComboBox.SelectedItem != null)
{
- createAnimationButton.Enabled = true;
+ if (createAnimationButton.InvokeRequired)
+ {
+ createAnimationButton.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ if (createAnimationButton != null && !createAnimationButton.IsDisposed)
+ {
+ createAnimationButton.Enabled = true;
+ }
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"动画按钮状态更新失败: {ex.Message}");
+ }
+ }));
+ }
+ else
+ {
+ if (createAnimationButton != null && !createAnimationButton.IsDisposed)
+ {
+ createAnimationButton.Enabled = true;
+ }
+ }
}
}, "获取选中部件");
@@ -2538,12 +2655,31 @@ namespace NavisworksTransport
{
GlobalExceptionHandler.SafeExecute(() =>
{
+ // 线程安全地更新UI控件
if (pathComboBox.SelectedItem != null)
{
var selectedPath = pathComboBox.SelectedItem as PathRoute;
if (selectedPath != null)
{
- pathInfoLabel.Text = $"点数: {selectedPath.Points?.Count ?? 0}";
+ // 线程安全地更新pathInfoLabel
+ if (pathInfoLabel.InvokeRequired)
+ {
+ pathInfoLabel.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ pathInfoLabel.Text = $"点数: {selectedPath.Points?.Count ?? 0}";
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"路径信息标签更新失败: {ex.Message}");
+ }
+ }));
+ }
+ else
+ {
+ pathInfoLabel.Text = $"点数: {selectedPath.Points?.Count ?? 0}";
+ }
// 添加路径可视化功能
try
@@ -2575,17 +2711,71 @@ namespace NavisworksTransport
LogManager.Error($"路径可视化失败: {ex.Message}");
}
- // 检查是否可以启用生成动画按钮
+ // 线程安全地检查是否可以启用生成动画按钮
if (selectedComponent != null)
{
- createAnimationButton.Enabled = true;
+ if (createAnimationButton.InvokeRequired)
+ {
+ createAnimationButton.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ createAnimationButton.Enabled = true;
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"动画按钮状态更新失败: {ex.Message}");
+ }
+ }));
+ }
+ else
+ {
+ createAnimationButton.Enabled = true;
+ }
}
}
}
else
{
- pathInfoLabel.Text = "点数: 0";
- createAnimationButton.Enabled = false;
+ // 线程安全地更新pathInfoLabel
+ if (pathInfoLabel.InvokeRequired)
+ {
+ pathInfoLabel.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ pathInfoLabel.Text = "点数: 0";
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"路径信息标签重置失败: {ex.Message}");
+ }
+ }));
+ }
+ else
+ {
+ pathInfoLabel.Text = "点数: 0";
+ }
+
+ // 线程安全地更新createAnimationButton
+ if (createAnimationButton.InvokeRequired)
+ {
+ createAnimationButton.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ createAnimationButton.Enabled = false;
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"动画按钮禁用失败: {ex.Message}");
+ }
+ }));
+ }
+ else
+ {
+ createAnimationButton.Enabled = false;
+ }
// 清空路径可视化
try
@@ -2605,18 +2795,18 @@ namespace NavisworksTransport
}, "路径选择变化");
};
- // 初始化路径下拉框
- var refreshPathList = new Action(() =>
+ // 路径列表UI更新的辅助方法
+ Action refreshPathListUI = (comboBox) =>
{
GlobalExceptionHandler.SafeExecute(() =>
{
- pathComboBox.Items.Clear();
+ comboBox.Items.Clear();
var pathManager = PathPlanningManager.GetActivePathManager();
if (pathManager?.Routes != null)
{
foreach (var route in pathManager.Routes)
{
- pathComboBox.Items.Add(route);
+ comboBox.Items.Add(route);
}
LogManager.Info($"动画控制面板:已刷新路径列表,共{pathManager.Routes.Count}条路径");
}
@@ -2625,6 +2815,37 @@ namespace NavisworksTransport
LogManager.Info("动画控制面板:未找到路径管理器或路径列表为空");
}
}, "刷新路径列表");
+ };
+
+ // 初始化路径下拉框
+ var refreshPathList = new Action(() =>
+ {
+ // 确保在UI线程上执行WinForms控件操作
+ if (pathComboBox.InvokeRequired)
+ {
+ pathComboBox.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ refreshPathListUI(pathComboBox);
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"路径列表UI更新失败: {ex.Message}");
+ }
+ }));
+ }
+ else
+ {
+ try
+ {
+ refreshPathListUI(pathComboBox);
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"路径列表UI更新失败: {ex.Message}");
+ }
+ }
});
refreshPathList(); // 初始加载
@@ -2671,15 +2892,66 @@ namespace NavisworksTransport
// 订阅动画管理器的事件
_animationManager.StateChanged += (s, newState) =>
{
- // 在UI线程上更新状态
- _controlPanelForm.Invoke(new Action(() => UpdateAnimationUI(newState)));
+ // 在UI线程上异步更新状态(避免死锁)
+ if (_controlPanelForm != null && _controlPanelForm.InvokeRequired)
+ {
+ _controlPanelForm.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ UpdateAnimationUI(newState);
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"动画状态UI更新失败: {ex.Message}");
+ }
+ }));
+ }
+ else if (_controlPanelForm != null)
+ {
+ try
+ {
+ UpdateAnimationUI(newState);
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"动画状态UI更新失败: {ex.Message}");
+ }
+ }
};
_animationManager.ProgressChanged += (s, progress) =>
{
- // 在UI线程上更新进度条
+ // 在UI线程上异步更新进度条(避免死锁)
if (_animationProgressBar != null && !_animationProgressBar.IsDisposed)
{
- _controlPanelForm.Invoke(new Action(() => _animationProgressBar.Value = progress));
+ if (_controlPanelForm != null && _controlPanelForm.InvokeRequired)
+ {
+ _controlPanelForm.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ if (_animationProgressBar != null && !_animationProgressBar.IsDisposed)
+ {
+ _animationProgressBar.Value = progress;
+ }
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"动画进度UI更新失败: {ex.Message}");
+ }
+ }));
+ }
+ else if (_controlPanelForm != null)
+ {
+ try
+ {
+ _animationProgressBar.Value = progress;
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"动画进度UI更新失败: {ex.Message}");
+ }
+ }
}
};
@@ -2698,12 +2970,108 @@ namespace NavisworksTransport
{
_animationManager.StartAnimation();
- _animationStatusLabel.Text = "状态: 动画播放中";
- _animationStatusLabel.ForeColor = System.Drawing.Color.Blue;
+ // 线程安全的动画状态更新
+ if (_animationStatusLabel.InvokeRequired)
+ {
+ _animationStatusLabel.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ if (_animationStatusLabel != null && !_animationStatusLabel.IsDisposed)
+ {
+ _animationStatusLabel.Text = "状态: 动画播放中";
+ _animationStatusLabel.ForeColor = System.Drawing.Color.Blue;
+ }
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"动画状态标签更新失败: {ex.Message}");
+ }
+ }));
+ }
+ else
+ {
+ if (_animationStatusLabel != null && !_animationStatusLabel.IsDisposed)
+ {
+ _animationStatusLabel.Text = "状态: 动画播放中";
+ _animationStatusLabel.ForeColor = System.Drawing.Color.Blue;
+ }
+ }
- _startAnimationButton.Enabled = false;
- _stopAnimationButton.Enabled = true;
- createAnimationButton.Enabled = false;
+ if (_startAnimationButton.InvokeRequired)
+ {
+ _startAnimationButton.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ if (_startAnimationButton != null && !_startAnimationButton.IsDisposed)
+ {
+ _startAnimationButton.Enabled = false;
+ }
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"开始动画按钮更新失败: {ex.Message}");
+ }
+ }));
+ }
+ else
+ {
+ if (_startAnimationButton != null && !_startAnimationButton.IsDisposed)
+ {
+ _startAnimationButton.Enabled = false;
+ }
+ }
+
+ if (_stopAnimationButton.InvokeRequired)
+ {
+ _stopAnimationButton.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ if (_stopAnimationButton != null && !_stopAnimationButton.IsDisposed)
+ {
+ _stopAnimationButton.Enabled = true;
+ }
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"停止动画按钮更新失败: {ex.Message}");
+ }
+ }));
+ }
+ else
+ {
+ if (_stopAnimationButton != null && !_stopAnimationButton.IsDisposed)
+ {
+ _stopAnimationButton.Enabled = true;
+ }
+ }
+
+ if (createAnimationButton.InvokeRequired)
+ {
+ createAnimationButton.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ if (createAnimationButton != null && !createAnimationButton.IsDisposed)
+ {
+ createAnimationButton.Enabled = false;
+ }
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"生成动画按钮更新失败: {ex.Message}");
+ }
+ }));
+ }
+ else
+ {
+ if (createAnimationButton != null && !createAnimationButton.IsDisposed)
+ {
+ createAnimationButton.Enabled = false;
+ }
+ }
}
}, "播放动画");
};
@@ -2716,12 +3084,108 @@ namespace NavisworksTransport
{
_animationManager.StopAnimation();
- _animationStatusLabel.Text = "状态: 动画已停止";
- _animationStatusLabel.ForeColor = System.Drawing.Color.Orange;
+ // 线程安全的动画状态更新
+ if (_animationStatusLabel.InvokeRequired)
+ {
+ _animationStatusLabel.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ if (_animationStatusLabel != null && !_animationStatusLabel.IsDisposed)
+ {
+ _animationStatusLabel.Text = "状态: 动画已停止";
+ _animationStatusLabel.ForeColor = System.Drawing.Color.Orange;
+ }
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"动画状态标签更新失败: {ex.Message}");
+ }
+ }));
+ }
+ else
+ {
+ if (_animationStatusLabel != null && !_animationStatusLabel.IsDisposed)
+ {
+ _animationStatusLabel.Text = "状态: 动画已停止";
+ _animationStatusLabel.ForeColor = System.Drawing.Color.Orange;
+ }
+ }
- _startAnimationButton.Enabled = true;
- _stopAnimationButton.Enabled = false;
- createAnimationButton.Enabled = true;
+ if (_startAnimationButton.InvokeRequired)
+ {
+ _startAnimationButton.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ if (_startAnimationButton != null && !_startAnimationButton.IsDisposed)
+ {
+ _startAnimationButton.Enabled = true;
+ }
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"开始动画按钮更新失败: {ex.Message}");
+ }
+ }));
+ }
+ else
+ {
+ if (_startAnimationButton != null && !_startAnimationButton.IsDisposed)
+ {
+ _startAnimationButton.Enabled = true;
+ }
+ }
+
+ if (_stopAnimationButton.InvokeRequired)
+ {
+ _stopAnimationButton.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ if (_stopAnimationButton != null && !_stopAnimationButton.IsDisposed)
+ {
+ _stopAnimationButton.Enabled = false;
+ }
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"停止动画按钮更新失败: {ex.Message}");
+ }
+ }));
+ }
+ else
+ {
+ if (_stopAnimationButton != null && !_stopAnimationButton.IsDisposed)
+ {
+ _stopAnimationButton.Enabled = false;
+ }
+ }
+
+ if (createAnimationButton.InvokeRequired)
+ {
+ createAnimationButton.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ if (createAnimationButton != null && !createAnimationButton.IsDisposed)
+ {
+ createAnimationButton.Enabled = true;
+ }
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"生成动画按钮更新失败: {ex.Message}");
+ }
+ }));
+ }
+ else
+ {
+ if (createAnimationButton != null && !createAnimationButton.IsDisposed)
+ {
+ createAnimationButton.Enabled = true;
+ }
+ }
// 清理 Clash Detective 集成
GlobalExceptionHandler.CleanupClashDetectiveIntegration();
@@ -2783,22 +3247,119 @@ namespace NavisworksTransport
break;
}
+ // 线程安全的UI更新
if (_animationStatusLabel != null && !_animationStatusLabel.IsDisposed)
{
- _animationStatusLabel.Text = statusText;
- _animationStatusLabel.ForeColor = statusColor;
+ if (_animationStatusLabel.InvokeRequired)
+ {
+ _animationStatusLabel.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ if (_animationStatusLabel != null && !_animationStatusLabel.IsDisposed)
+ {
+ _animationStatusLabel.Text = statusText;
+ _animationStatusLabel.ForeColor = statusColor;
+ }
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"动画状态标签更新失败: {ex.Message}");
+ }
+ }));
+ }
+ else
+ {
+ if (_animationStatusLabel != null && !_animationStatusLabel.IsDisposed)
+ {
+ _animationStatusLabel.Text = statusText;
+ _animationStatusLabel.ForeColor = statusColor;
+ }
+ }
}
+
if (_startAnimationButton != null && !_startAnimationButton.IsDisposed)
{
- _startAnimationButton.Enabled = startEnabled;
+ if (_startAnimationButton.InvokeRequired)
+ {
+ _startAnimationButton.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ if (_startAnimationButton != null && !_startAnimationButton.IsDisposed)
+ {
+ _startAnimationButton.Enabled = startEnabled;
+ }
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"开始动画按钮更新失败: {ex.Message}");
+ }
+ }));
+ }
+ else
+ {
+ if (_startAnimationButton != null && !_startAnimationButton.IsDisposed)
+ {
+ _startAnimationButton.Enabled = startEnabled;
+ }
+ }
}
+
if (_stopAnimationButton != null && !_stopAnimationButton.IsDisposed)
{
- _stopAnimationButton.Enabled = stopEnabled;
+ if (_stopAnimationButton.InvokeRequired)
+ {
+ _stopAnimationButton.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ if (_stopAnimationButton != null && !_stopAnimationButton.IsDisposed)
+ {
+ _stopAnimationButton.Enabled = stopEnabled;
+ }
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"停止动画按钮更新失败: {ex.Message}");
+ }
+ }));
+ }
+ else
+ {
+ if (_stopAnimationButton != null && !_stopAnimationButton.IsDisposed)
+ {
+ _stopAnimationButton.Enabled = stopEnabled;
+ }
+ }
}
+
if (_animationProgressBar != null && !_animationProgressBar.IsDisposed)
{
- _animationProgressBar.Value = progressValue;
+ if (_animationProgressBar.InvokeRequired)
+ {
+ _animationProgressBar.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ if (_animationProgressBar != null && !_animationProgressBar.IsDisposed)
+ {
+ _animationProgressBar.Value = progressValue;
+ }
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"动画进度条更新失败: {ex.Message}");
+ }
+ }));
+ }
+ else
+ {
+ if (_animationProgressBar != null && !_animationProgressBar.IsDisposed)
+ {
+ _animationProgressBar.Value = progressValue;
+ }
+ }
}
}
@@ -2834,9 +3395,47 @@ namespace NavisworksTransport
{
try
{
- logisticsOnlyCheckBox.Enabled = false;
- statusLabel.Text = "状态: 正在处理...";
- statusLabel.ForeColor = System.Drawing.Color.Orange;
+ // 线程安全的复选框禁用
+ if (logisticsOnlyCheckBox.InvokeRequired)
+ {
+ logisticsOnlyCheckBox.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ logisticsOnlyCheckBox.Enabled = false;
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"复选框禁用失败: {ex.Message}");
+ }
+ }));
+ }
+ else
+ {
+ logisticsOnlyCheckBox.Enabled = false;
+ }
+
+ // 线程安全的状态标签更新
+ if (statusLabel.InvokeRequired)
+ {
+ statusLabel.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ statusLabel.Text = "状态: 正在处理...";
+ statusLabel.ForeColor = System.Drawing.Color.Orange;
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"状态标签更新失败: {ex.Message}");
+ }
+ }));
+ }
+ else
+ {
+ statusLabel.Text = "状态: 正在处理...";
+ statusLabel.ForeColor = System.Drawing.Color.Orange;
+ }
VisibilityOperationResult result;
@@ -2846,14 +3445,70 @@ namespace NavisworksTransport
result = VisibilityManager.HideNonLogisticsItems();
if (result.Success)
{
- statusLabel.Text = $"状态: 已隐藏 {result.HiddenCount} 个非物流项目";
- statusLabel.ForeColor = System.Drawing.Color.DarkGreen;
+ // 线程安全的UI更新
+ if (statusLabel.InvokeRequired)
+ {
+ statusLabel.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ statusLabel.Text = $"状态: 已隐藏 {result.HiddenCount} 个非物流项目";
+ statusLabel.ForeColor = System.Drawing.Color.DarkGreen;
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"状态标签更新失败: {ex.Message}");
+ }
+ }));
+ }
+ else
+ {
+ statusLabel.Text = $"状态: 已隐藏 {result.HiddenCount} 个非物流项目";
+ statusLabel.ForeColor = System.Drawing.Color.DarkGreen;
+ }
}
else
{
- statusLabel.Text = "状态: 隐藏操作失败";
- statusLabel.ForeColor = System.Drawing.Color.Red;
- logisticsOnlyCheckBox.Checked = false; // 回滚状态
+ // 线程安全的UI更新
+ if (statusLabel.InvokeRequired)
+ {
+ statusLabel.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ statusLabel.Text = "状态: 隐藏操作失败";
+ statusLabel.ForeColor = System.Drawing.Color.Red;
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"状态标签更新失败: {ex.Message}");
+ }
+ }));
+ }
+ else
+ {
+ statusLabel.Text = "状态: 隐藏操作失败";
+ statusLabel.ForeColor = System.Drawing.Color.Red;
+ }
+
+ if (logisticsOnlyCheckBox.InvokeRequired)
+ {
+ logisticsOnlyCheckBox.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ logisticsOnlyCheckBox.Checked = false; // 回滚状态
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"复选框状态回滚失败: {ex.Message}");
+ }
+ }));
+ }
+ else
+ {
+ logisticsOnlyCheckBox.Checked = false; // 回滚状态
+ }
}
}
else
@@ -2862,14 +3517,70 @@ namespace NavisworksTransport
result = VisibilityManager.ShowAllItems();
if (result.Success)
{
- statusLabel.Text = "状态: 显示所有项目";
- statusLabel.ForeColor = System.Drawing.Color.DarkGreen;
+ // 线程安全的UI更新
+ if (statusLabel.InvokeRequired)
+ {
+ statusLabel.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ statusLabel.Text = "状态: 显示所有项目";
+ statusLabel.ForeColor = System.Drawing.Color.DarkGreen;
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"状态标签更新失败: {ex.Message}");
+ }
+ }));
+ }
+ else
+ {
+ statusLabel.Text = "状态: 显示所有项目";
+ statusLabel.ForeColor = System.Drawing.Color.DarkGreen;
+ }
}
else
{
- statusLabel.Text = "状态: 显示操作失败";
- statusLabel.ForeColor = System.Drawing.Color.Red;
- logisticsOnlyCheckBox.Checked = true; // 回滚状态
+ // 线程安全的UI更新
+ if (statusLabel.InvokeRequired)
+ {
+ statusLabel.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ statusLabel.Text = "状态: 显示操作失败";
+ statusLabel.ForeColor = System.Drawing.Color.Red;
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"状态标签更新失败: {ex.Message}");
+ }
+ }));
+ }
+ else
+ {
+ statusLabel.Text = "状态: 显示操作失败";
+ statusLabel.ForeColor = System.Drawing.Color.Red;
+ }
+
+ if (logisticsOnlyCheckBox.InvokeRequired)
+ {
+ logisticsOnlyCheckBox.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ logisticsOnlyCheckBox.Checked = true; // 回滚状态
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"复选框状态回滚失败: {ex.Message}");
+ }
+ }));
+ }
+ else
+ {
+ logisticsOnlyCheckBox.Checked = true; // 回滚状态
+ }
}
}
@@ -2881,14 +3592,70 @@ namespace NavisworksTransport
}
catch (Exception ex)
{
- statusLabel.Text = "状态: 发生错误";
- statusLabel.ForeColor = System.Drawing.Color.Red;
- logisticsOnlyCheckBox.Checked = !logisticsOnlyCheckBox.Checked; // 回滚状态
+ // 线程安全的错误状态更新
+ if (statusLabel.InvokeRequired)
+ {
+ statusLabel.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ statusLabel.Text = "状态: 发生错误";
+ statusLabel.ForeColor = System.Drawing.Color.Red;
+ }
+ catch (Exception ex2)
+ {
+ LogManager.Error($"错误状态标签更新失败: {ex2.Message}");
+ }
+ }));
+ }
+ else
+ {
+ statusLabel.Text = "状态: 发生错误";
+ statusLabel.ForeColor = System.Drawing.Color.Red;
+ }
+
+ if (logisticsOnlyCheckBox.InvokeRequired)
+ {
+ logisticsOnlyCheckBox.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ logisticsOnlyCheckBox.Checked = !logisticsOnlyCheckBox.Checked; // 回滚状态
+ }
+ catch (Exception ex2)
+ {
+ LogManager.Error($"复选框回滚失败: {ex2.Message}");
+ }
+ }));
+ }
+ else
+ {
+ logisticsOnlyCheckBox.Checked = !logisticsOnlyCheckBox.Checked; // 回滚状态
+ }
+
MessageBox.Show($"操作出错: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally
{
- logisticsOnlyCheckBox.Enabled = true;
+ // 线程安全的复选框启用
+ if (logisticsOnlyCheckBox.InvokeRequired)
+ {
+ logisticsOnlyCheckBox.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ logisticsOnlyCheckBox.Enabled = true;
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"复选框启用失败: {ex.Message}");
+ }
+ }));
+ }
+ else
+ {
+ logisticsOnlyCheckBox.Enabled = true;
+ }
}
};
@@ -2929,28 +3696,85 @@ namespace NavisworksTransport
var selectedItems = NavisApplication.ActiveDocument.CurrentSelection.SelectedItems;
int selectedCount = selectedItems.Count;
- // 更新指令标签
+ // 线程安全的更新指令标签
if (_instructionLabel != null && !_instructionLabel.IsDisposed)
{
- _instructionLabel.Text = selectedCount == 0 ? "请在主界面中点击选择模型" : $"已选中 {selectedCount} 个模型";
- _instructionLabel.ForeColor = selectedCount > 0 ? System.Drawing.Color.Blue : System.Drawing.Color.Orange;
+ string instructionText = selectedCount == 0 ? "请在主界面中点击选择模型" : $"已选中 {selectedCount} 个模型";
+ System.Drawing.Color instructionColor = selectedCount > 0 ? System.Drawing.Color.Blue : System.Drawing.Color.Orange;
+
+ if (_instructionLabel.InvokeRequired)
+ {
+ _instructionLabel.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ if (_instructionLabel != null && !_instructionLabel.IsDisposed)
+ {
+ _instructionLabel.Text = instructionText;
+ _instructionLabel.ForeColor = instructionColor;
+ }
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"指令标签更新失败: {ex.Message}");
+ }
+ }));
+ }
+ else
+ {
+ if (_instructionLabel != null && !_instructionLabel.IsDisposed)
+ {
+ _instructionLabel.Text = instructionText;
+ _instructionLabel.ForeColor = instructionColor;
+ }
+ }
}
- // 更新选中模型标签
+ // 线程安全的更新选中模型标签
if (_selectedModelsLabel != null && !_selectedModelsLabel.IsDisposed)
{
+ string modelText = "";
+ bool isVisible = false;
+
if (selectedCount > 0)
{
string modelNames = string.Join(", ", selectedItems.Take(3).Select(item =>
item.DisplayName.Length > 15 ? item.DisplayName.Substring(0, 15) + "..." : item.DisplayName));
if (selectedCount > 3) modelNames += $" 等{selectedCount}项";
- _selectedModelsLabel.Text = $"选中模型: {modelNames}";
- _selectedModelsLabel.Visible = true;
+ modelText = $"选中模型: {modelNames}";
+ isVisible = true;
}
else
{
- _selectedModelsLabel.Visible = false;
+ isVisible = false;
+ }
+
+ if (_selectedModelsLabel.InvokeRequired)
+ {
+ _selectedModelsLabel.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ if (_selectedModelsLabel != null && !_selectedModelsLabel.IsDisposed)
+ {
+ _selectedModelsLabel.Text = modelText;
+ _selectedModelsLabel.Visible = isVisible;
+ }
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"选中模型标签更新失败: {ex.Message}");
+ }
+ }));
+ }
+ else
+ {
+ if (_selectedModelsLabel != null && !_selectedModelsLabel.IsDisposed)
+ {
+ _selectedModelsLabel.Text = modelText;
+ _selectedModelsLabel.Visible = isVisible;
+ }
}
}
@@ -3110,11 +3934,21 @@ namespace NavisworksTransport
LogManager.Info($"[UI同步] 受影响模型数: {e.AffectedCount}");
LogManager.Info($"[UI同步] 当前线程: {System.Threading.Thread.CurrentThread.ManagedThreadId}");
- // 确保在UI线程上执行
+ // 确保在UI线程上执行(使用异步调用避免死锁)
if (_controlPanelForm != null && _controlPanelForm.InvokeRequired)
{
- LogManager.Info("[UI同步] 需要跨线程调用,使用Invoke");
- _controlPanelForm.Invoke(new Action(() => OnLogisticsAttributeChanged(sender, e)));
+ LogManager.Info("[UI同步] 需要跨线程调用,使用BeginInvoke");
+ _controlPanelForm.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ OnLogisticsAttributeChanged(sender, e);
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"物流属性变更UI更新失败: {ex.Message}");
+ }
+ }));
return;
}
@@ -3187,11 +4021,21 @@ namespace NavisworksTransport
LogManager.Info($"[UI同步] 新状态: {newState}");
LogManager.Info($"[UI同步] 当前线程: {System.Threading.Thread.CurrentThread.ManagedThreadId}");
- // 确保在UI线程上执行
+ // 确保在UI线程上执行(使用异步调用避免死锁)
if (_controlPanelForm != null && _controlPanelForm.InvokeRequired)
{
- LogManager.Info("[UI同步] 需要跨线程调用,使用Invoke");
- _controlPanelForm.Invoke(new Action(() => OnPathEditStateChanged(sender, newState)));
+ LogManager.Info("[UI同步] 需要跨线程调用,使用BeginInvoke");
+ _controlPanelForm.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ OnPathEditStateChanged(sender, newState);
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"路径编辑状态UI更新失败: {ex.Message}");
+ }
+ }));
return;
}
@@ -3268,14 +4112,61 @@ namespace NavisworksTransport
bool isEditable = editState == PathEditState.Creating || editState == PathEditState.Editing;
+ // 线程安全的按钮状态更新
if (finishButton != null)
{
- finishButton.Enabled = isEditable;
+ if (finishButton.InvokeRequired)
+ {
+ finishButton.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ if (finishButton != null && !finishButton.IsDisposed)
+ {
+ finishButton.Enabled = isEditable;
+ }
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"完成编辑按钮更新失败: {ex.Message}");
+ }
+ }));
+ }
+ else
+ {
+ if (finishButton != null && !finishButton.IsDisposed)
+ {
+ finishButton.Enabled = isEditable;
+ }
+ }
}
if (cancelButton != null)
{
- cancelButton.Enabled = isEditable;
+ if (cancelButton.InvokeRequired)
+ {
+ cancelButton.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ if (cancelButton != null && !cancelButton.IsDisposed)
+ {
+ cancelButton.Enabled = isEditable;
+ }
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"取消编辑按钮更新失败: {ex.Message}");
+ }
+ }));
+ }
+ else
+ {
+ if (cancelButton != null && !cancelButton.IsDisposed)
+ {
+ cancelButton.Enabled = isEditable;
+ }
+ }
}
LogManager.Info($"[UI同步] 按钮状态已更新,编辑状态: {editState}, 按钮启用: {isEditable}");
@@ -3419,8 +4310,7 @@ namespace NavisworksTransport
return;
var currentRoute = _pathPlanningManager.CurrentRoute;
-
- _currentPathPointsListView.Items.Clear();
+ var items = new List();
if (currentRoute?.Points != null && currentRoute.Points.Count > 0)
{
@@ -3450,7 +4340,7 @@ namespace NavisworksTransport
item.SubItems.Add(typeText); // 类型
item.SubItems.Add($"({point.Position.X:F1}, {point.Position.Y:F1}, {point.Position.Z:F1})"); // 坐标
- _currentPathPointsListView.Items.Add(item);
+ items.Add(item);
}
}
else
@@ -3458,7 +4348,35 @@ namespace NavisworksTransport
// 可选:添加一个提示项
var placeholder = new ListViewItem("暂无路径点");
placeholder.ForeColor = System.Drawing.Color.Gray;
- _currentPathPointsListView.Items.Add(placeholder);
+ items.Add(placeholder);
+ }
+
+ // 线程安全的ListView更新
+ if (_currentPathPointsListView.InvokeRequired)
+ {
+ _currentPathPointsListView.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ if (_currentPathPointsListView != null && !_currentPathPointsListView.IsDisposed)
+ {
+ _currentPathPointsListView.Items.Clear();
+ _currentPathPointsListView.Items.AddRange(items.ToArray());
+ }
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"当前路径点列表UI更新失败: {ex.Message}");
+ }
+ }));
+ }
+ else
+ {
+ if (_currentPathPointsListView != null && !_currentPathPointsListView.IsDisposed)
+ {
+ _currentPathPointsListView.Items.Clear();
+ _currentPathPointsListView.Items.AddRange(items.ToArray());
+ }
}
LogManager.Info($"[UI同步] 当前路径点列表已更新,共{currentRoute?.Points?.Count ?? 0}个点");
@@ -3476,9 +4394,9 @@ namespace NavisworksTransport
if (_pathListView == null || _pathPlanningManager == null)
return;
- _pathListView.Items.Clear();
-
var routes = _pathPlanningManager.Routes;
+ var items = new List();
+
if (routes != null && routes.Count > 0)
{
foreach (var route in routes)
@@ -3488,7 +4406,35 @@ namespace NavisworksTransport
item.SubItems.Add(route.Points?.Count.ToString() ?? "0");
item.Tag = route; // 保存路径对象引用
- _pathListView.Items.Add(item);
+ items.Add(item);
+ }
+ }
+
+ // 线程安全的ListView更新
+ if (_pathListView.InvokeRequired)
+ {
+ _pathListView.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ if (_pathListView != null && !_pathListView.IsDisposed)
+ {
+ _pathListView.Items.Clear();
+ _pathListView.Items.AddRange(items.ToArray());
+ }
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"路径列表UI更新失败: {ex.Message}");
+ }
+ }));
+ }
+ else
+ {
+ if (_pathListView != null && !_pathListView.IsDisposed)
+ {
+ _pathListView.Items.Clear();
+ _pathListView.Items.AddRange(items.ToArray());
}
}
@@ -3594,7 +4540,31 @@ namespace NavisworksTransport
}
}
- historyListView.Items.Clear();
+ // 线程安全的ListView清空操作
+ if (historyListView.InvokeRequired)
+ {
+ historyListView.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ if (historyListView != null && !historyListView.IsDisposed)
+ {
+ historyListView.Items.Clear();
+ }
+ }
+ catch (Exception ex)
+ {
+ LogManager.Error($"清空历史记录列表失败: {ex.Message}");
+ }
+ }));
+ }
+ else
+ {
+ if (historyListView != null && !historyListView.IsDisposed)
+ {
+ historyListView.Items.Clear();
+ }
+ }
MessageBox.Show("历史记录已清空", "操作完成",
MessageBoxButtons.OK, MessageBoxIcon.Information);
LogManager.Info("用户清空了路径历史记录");
diff --git a/src/Core/PathPlanningManager.cs b/src/Core/PathPlanningManager.cs
index 77687df..576eea3 100644
--- a/src/Core/PathPlanningManager.cs
+++ b/src/Core/PathPlanningManager.cs
@@ -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;
diff --git a/src/Core/PathPointRenderPlugin.cs b/src/Core/PathPointRenderPlugin.cs
index 0aaa9bb..666a503 100644
--- a/src/Core/PathPointRenderPlugin.cs
+++ b/src/Core/PathPointRenderPlugin.cs
@@ -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
}
///
- /// 请求视图刷新
+ /// 请求视图刷新(带防抖机制)
///
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
diff --git a/src/PathPlanning/AutoPathFinder.cs b/src/PathPlanning/AutoPathFinder.cs
index ad9a45d..5c6e76e 100644
--- a/src/PathPlanning/AutoPathFinder.cs
+++ b/src/PathPlanning/AutoPathFinder.cs
@@ -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;
}
}
+
+ ///
+ /// 查找最近的可通行位置
+ ///
+ /// 网格地图
+ /// 原始位置
+ /// 位置名称(用于日志)
+ /// 最大搜索距离(网格单位)
+ /// 最近的可通行位置,如果找不到返回null
+ 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();
+ 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;
+ }
+ }
}
}
\ No newline at end of file
diff --git a/src/PathPlanning/ChannelHeightDetector.cs b/src/PathPlanning/ChannelHeightDetector.cs
index f3b0e12..4a6a341 100644
--- a/src/PathPlanning/ChannelHeightDetector.cs
+++ b/src/PathPlanning/ChannelHeightDetector.cs
@@ -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}");
}
}
diff --git a/src/PathPlanning/GridMapGenerator.cs b/src/PathPlanning/GridMapGenerator.cs
index 89a0192..6916210 100644
--- a/src/PathPlanning/GridMapGenerator.cs
+++ b/src/PathPlanning/GridMapGenerator.cs
@@ -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();
- foreach (ModelItem floorItem in targetFloorItems)
+ // 步骤3:将楼层项目转换为哈希集合以提高查找效率
+ var floorItemSet = new HashSet(floorItems);
+
+ // 步骤4:从输入的所有项目中筛选出属于目标楼层的项目
+ var filteredItems = new List();
+ 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)
diff --git a/src/UI/WPF/ViewModels/LogisticsControlViewModel.cs b/src/UI/WPF/ViewModels/LogisticsControlViewModel.cs
index 16c7107..2afae59 100644
--- a/src/UI/WPF/ViewModels/LogisticsControlViewModel.cs
+++ b/src/UI/WPF/ViewModels/LogisticsControlViewModel.cs
@@ -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
}
///
- /// 执行清除自动路径命令
+ /// 执行重置自动路径规划命令(只重置起点终点状态,不删除已生成的路径)
///
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("重置自动路径规划:已清除起点终点选择,保留已生成的路径");
+ }, "重置自动路径规划");
}
///
@@ -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
///
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}");
+ }
+ }, "处理路径点添加事件");
}
///
@@ -2089,23 +2194,61 @@ namespace NavisworksTransport.UI.WPF.ViewModels
///
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}");
+ }
+ }
+ }, "处理路径点移除事件");
}
///
@@ -2113,45 +2256,83 @@ namespace NavisworksTransport.UI.WPF.ViewModels
///
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}");
+ }
+ }, "处理路径点列表更新事件");
}
///
@@ -2159,20 +2340,60 @@ namespace NavisworksTransport.UI.WPF.ViewModels
///
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}");
+ }
+ }, "处理当前路径变更事件");
}
///
@@ -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
///
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
diff --git a/src/UI/WPF/ViewModels/ViewModelBase.cs b/src/UI/WPF/ViewModels/ViewModelBase.cs
index 889e423..889b710 100644
--- a/src/UI/WPF/ViewModels/ViewModelBase.cs
+++ b/src/UI/WPF/ViewModels/ViewModelBase.cs
@@ -18,7 +18,45 @@ namespace NavisworksTransport.UI.WPF.ViewModels
/// 属性名称,自动获取调用者名称
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}");
+ }
}
///
diff --git a/线程安全修复报告.md b/线程安全修复报告.md
new file mode 100644
index 0000000..49068c0
--- /dev/null
+++ b/线程安全修复报告.md
@@ -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();
+// ... 准备数据 ...
+
+// 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线程安全性
+风险等级: 无(向后兼容)
\ No newline at end of file