把动画改成步进式,在动画生成阶段进行快速碰撞计算。

This commit is contained in:
tian 2025-09-12 22:44:49 +08:00
parent 0b0028c19c
commit cc8842dcd8
8 changed files with 846 additions and 414 deletions

View File

@ -0,0 +1,368 @@
步进式动画系统完整方案 │
│ │
│ 核心架构 │
│ │
│ - 步进式播放:基于帧索引而非时间 │
│ - 预计算碰撞:提前计算所有碰撞点 │
│ - 逐帧控制:支持单帧前进/后退 │
│ - 双向导航:可以在任意帧之间跳转 │
│ │
│ 实现方案 │
│ │
│ 1. 步进式动画数据结构 │
│ │
│ // 动画帧数据 │
│ public class AnimationFrame │
│ { │
│ public int FrameIndex { get; set; } │
│ public double Progress { get; set; } // 0-1之间的进度 │
│ public Point3D Position { get; set; } // 该帧的位置 │
│ public List<CollisionInfo> Collisions { get; set; } // 该帧的碰撞信息 │
│ public DateTime Timestamp { get; set; } // 帧时间戳(用于性能分析) │
│ } │
│ │
│ public class CollisionInfo │
│ { │
│ public ModelItem CollidingObject { get; set; } │
│ public Point3D CollidingPosition { get; set; } │
│ public double Distance { get; set; } │
│ } │
│ │
│ // 在PathAnimationManager中添加 │
│ private List<AnimationFrame> _animationFrames; // 所有动画帧 │
│ private int _currentFrameIndex = 0; // 当前帧索引 │
│ private int _totalFrames = 0; // 总帧数 │
│ private double _frameStepSize = 0.01; // 帧步进大小(1%) │
│ private bool _isSteppingMode = true; // 是否步进模式 │
│ │
│ 2. 预计算所有帧SetupAnimation
│ │
│ public void SetupAnimation(ModelItem animatedObject, List<Point3D> pathPoints, double durationSeconds = 10.0) │
│ { │
│ // ... 现有验证代码 ... │
│ │
│ // 计算总帧数 │
│ _totalFrames = (int)(1.0 / _frameStepSize); // 如0.01步进=100帧 │
│ _animationFrames = new List<AnimationFrame>(); │
│ │
│ LogManager.Info($"=== 开始预计算动画帧 ==="); │
│ LogManager.Info($"总帧数: {_totalFrames}, 步进大小: {_frameStepSize:F3}"); │
│ │
│ PrecomputeAllFrames(); │
│ │
│ LogManager.Info($"=== 预计算完成 ==="); │
│ LogManager.Info($"总帧数: {_animationFrames.Count}"); │
│ LogManager.Info($"包含碰撞的帧: {_animationFrames.Count(f => f.Collisions.Count > 0)}"); │
│ │
│ // 移动到起点 │
│ MoveToFrame(0); │
│ } │
│ │
│ private void PrecomputeAllFrames() │
│ { │
│ var modelItems = new ModelItemCollection { _animatedObject }; │
│ var originalPosition = _currentPosition; │
│ │
│ // 初始化碰撞检测 │
│ ClashDetectiveIntegration.Instance.Initialize(); │
│ │
│ for (int i = 0; i <= _totalFrames; i++) │
│ { │
│ double progress = (double)i / _totalFrames; │
│ var framePosition = InterpolatePosition(progress); │
│ │
│ // 临时移动到该帧位置 │
│ MoveObjectToPosition(framePosition); │
│ │
│ // 执行碰撞检测 │
│ var collisions = ClashDetectiveIntegration.Instance.DetectCollisions( │
│ _animatedObject, null, _detectionGap); │
│ │
│ // 创建帧数据 │
│ var frame = new AnimationFrame │
│ { │
│ FrameIndex = i, │
│ Progress = progress, │
│ Position = framePosition, │
│ Collisions = new List<CollisionInfo>(), │
│ Timestamp = DateTime.Now │
│ }; │
│ │
│ // 记录碰撞信息 │
│ foreach (var collision in collisions) │
│ { │
│ frame.Collisions.Add(new CollisionInfo │
│ { │
│ CollidingObject = collision.Item2, │
│ CollidingPosition = GetObjectPosition(collision.Item2), │
│ Distance = collision.Distance │
│ }); │
│ } │
│ │
│ _animationFrames.Add(frame); │
│ │
│ // 进度报告 │
│ if (i % 10 == 0) │
│ { │
│ LogManager.Info($"预计算进度: {i}/{_totalFrames} ({progress*100:F1}%)"); │
│ } │
│ } │
│ │
│ // 恢复原始位置 │
│ MoveObjectToPosition(originalPosition); │
│ } │
│ │
│ 3. 步进式播放控制 │
│ │
│ // 播放模式枚举 │
│ public enum PlaybackMode │
│ { │
│ Continuous, // 连续播放 │
│ StepByStep, // 逐帧步进 │
│ Manual // 手动控制 │
│ } │
│ │
│ private PlaybackMode _playbackMode = PlaybackMode.StepByStep; │
│ │
│ // 修改OnApplicationIdle - 支持步进模式 │
│ private void OnApplicationIdle(object sender, EventArgs e) │
│ { │
│ if (_currentState != AnimationState.Playing) │
│ return; │
│ │
│ // 帧率控制 │
│ var now = DateTime.Now; │
│ var elapsed = (now - _lastFrameTime).TotalMilliseconds; │
│ │
│ if (elapsed < _frameInterval)
│ return; │
│ │
│ // 根据播放模式执行 │
│ switch (_playbackMode) │
│ { │
│ case PlaybackMode.Continuous: │
│ // 自动步进到下一帧 │
│ if (_currentFrameIndex < _totalFrames)
│ { │
│ MoveToFrame(_currentFrameIndex + 1); │
│ _lastFrameTime = now; │
│ } │
│ else │
│ { │
│ FinishAnimation(); │
│ } │
│ break; │
│ │
│ case PlaybackMode.StepByStep: │
│ // 等待用户触发下一帧 │
│ // 不自动前进 │
│ break; │
│ │
│ case PlaybackMode.Manual: │
│ // 完全手动控制 │
│ break; │
│ } │
│ } │
│ │
│ 4. 帧导航方法 │
│ │
│ // 移动到指定帧 │
│ public void MoveToFrame(int frameIndex) │
│ { │
│ if (frameIndex < 0 || frameIndex >= _animationFrames.Count) │
│ { │
│ LogManager.Warning($"帧索引 {frameIndex} 超出范围 [0, {_animationFrames.Count-1}]"); │
│ return; │
│ } │
│ │
│ var frame = _animationFrames[frameIndex]; │
│ _currentFrameIndex = frameIndex; │
│ │
│ // 更新位置 │
│ UpdateObjectPosition(frame.Position); │
│ _currentPosition = frame.Position; │
│ │
│ // 更新碰撞高亮 │
│ HighlightFrameCollisions(frame); │
│ │
│ // 触发进度事件 │
│ ProgressChanged?.Invoke(this, frame.Progress * 100); │
│ │
│ // 更新TimeLiner │
│ if (_timeLinerManager != null && !string.IsNullOrEmpty(_currentTaskId)) │
│ { │
│ _timeLinerManager.UpdateTaskProgress(_currentTaskId, frame.Progress, _currentState); │
│ } │
│ │
│ LogManager.Debug($"移动到帧 {frameIndex}/{_totalFrames} (进度: {frame.Progress*100:F1}%)"); │
│ } │
│ │
│ // 前进一帧 │
│ public void StepForward() │
│ { │
│ if (_currentFrameIndex < _totalFrames - 1)
│ { │
│ MoveToFrame(_currentFrameIndex + 1); │
│ } │
│ else │
│ { │
│ LogManager.Info("已到达最后一帧"); │
│ } │
│ } │
│ │
│ // 后退一帧 │
│ public void StepBackward() │
│ { │
│ if (_currentFrameIndex > 0) │
│ { │
│ MoveToFrame(_currentFrameIndex - 1); │
│ } │
│ else │
│ { │
│ LogManager.Info("已到达第一帧"); │
│ } │
│ } │
│ │
│ // 跳转到指定进度 │
│ public void JumpToProgress(double progress) │
│ { │
│ int targetFrame = (int)(progress * _totalFrames); │
│ MoveToFrame(targetFrame); │
│ } │
│ │
│ // 快进/快退 │
│ public void FastForward(int frames = 10) │
│ { │
│ int targetFrame = Math.Min(_currentFrameIndex + frames, _totalFrames - 1); │
│ MoveToFrame(targetFrame); │
│ } │
│ │
│ public void FastBackward(int frames = 10) │
│ { │
│ int targetFrame = Math.Max(_currentFrameIndex - frames, 0); │
│ MoveToFrame(targetFrame); │
│ } │
│ │
│ 5. 碰撞高亮管理 │
│ │
│ private HashSet<ModelItem> _currentHighlightedItems = new HashSet<ModelItem>(); │
│ │
│ private void HighlightFrameCollisions(AnimationFrame frame) │
│ { │
│ var newHighlights = new HashSet<ModelItem>(frame.Collisions.Select(c => c.CollidingObject)); │
│ │
│ // 只在高亮集合变化时更新 │
│ if (!newHighlights.SetEquals(_currentHighlightedItems)) │
│ { │
│ // 清除旧高亮 │
│ if (_currentHighlightedItems.Count > 0) │
│ { │
│ ClashDetectiveIntegration.Instance.ClearHighlight(); │
│ } │
│ │
│ // 应用新高亮 │
│ if (newHighlights.Count > 0) │
│ { │
│ var collisionResults = frame.Collisions.Select(c => new CollisionResult │
│ { │
│ Item1 = _animatedObject, │
│ Item2 = c.CollidingObject, │
│ Distance = c.Distance │
│ }).ToList(); │
│ │
│ ClashDetectiveIntegration.Instance.HighlightCollisions(collisionResults); │
│ } │
│ │
│ _currentHighlightedItems = newHighlights; │
│ │
│ if (frame.Collisions.Count > 0) │
│ { │
│ LogManager.Info($"帧 {frame.FrameIndex}: 高亮 {frame.Collisions.Count} 个碰撞"); │
│ } │
│ } │
│ } │
│ │
│ 6. 播放控制API │
│ │
│ // 开始连续播放 │
│ public void Play() │
│ { │
│ _playbackMode = PlaybackMode.Continuous; │
│ SetState(AnimationState.Playing); │
│ NavisApplication.Idle += OnApplicationIdle; │
│ } │
│ │
│ // 暂停(保持当前帧) │
│ public void Pause() │
│ { │
│ _playbackMode = PlaybackMode.StepByStep; │
│ SetState(AnimationState.Paused); │
│ } │
│ │
│ // 设置播放速度(通过调整帧间隔) │
│ public void SetPlaybackSpeed(double speed) │
│ { │
│ _frameInterval = (1000.0 / _animationFrameRate) / speed; │
│ LogManager.Info($"播放速度设置为 {speed}x"); │
│ } │
│ │
│ // 获取当前帧信息 │
│ public AnimationFrame GetCurrentFrame() │
│ { │
│ if (_currentFrameIndex >= 0 && _currentFrameIndex < _animationFrames.Count)
│ return _animationFrames[_currentFrameIndex]; │
│ return null; │
│ } │
│ │
│ // 获取帧统计信息 │
│ public string GetFrameStatistics() │
│ { │
│ var current = GetCurrentFrame(); │
│ if (current != null) │
│ { │
│ return $"帧 {_currentFrameIndex}/{_totalFrames} | " + │
│ $"进度 {current.Progress*100:F1}% | " + │
│ $"碰撞 {current.Collisions.Count}"; │
│ } │
│ return "无帧数据"; │
│ } │
│ │
│ 7. UI集成建议 │
│ │
│ // 动画控制面板应添加的控件 │
│ public class AnimationControlPanel │
│ { │
│ // 播放控制 │
│ Button PlayButton; // 连续播放 │
│ Button PauseButton; // 暂停 │
│ Button StepForwardButton; // 前进一帧 │
│ Button StepBackButton; // 后退一帧 │
│ Button FastForwardButton; // 快进10帧 │
│ Button FastRewindButton; // 快退10帧 │
│ │
│ // 进度控制 │
│ Slider ProgressSlider; // 进度条(可拖动跳转) │
│ Label FrameLabel; // 当前帧信息 │
│ Label CollisionLabel; // 碰撞信息 │
│ │
│ // 速度控制 │
│ Slider SpeedSlider; // 播放速度(0.1x - 5x) │
│ ComboBox PlayModeCombo; // 播放模式选择 │
│ } │
│ │
│ 优势 │
│ │
│ 1. 完全可控:每一帧都可以精确控制 │
│ 2. 双向导航:支持前进和后退 │
│ 3. 性能优秀:预计算避免实时检测 │
│ 4. 调试友好:可以逐帧分析碰撞 │
│ 5. 灵活播放:支持多种播放模式 │
│ │
│ 实施步骤 │
│ │
│ 1. 添加AnimationFrame数据结构 │
│ 2. 实现预计算所有帧逻辑 │
│ 3. 改造播放控制为步进式 │
│ 4. 实现帧导航方法 │
│ 5. 更新UI添加步进控制 │
│ 6. 测试各种播放模式

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Autodesk.Navisworks.Api;
using NavisApplication = Autodesk.Navisworks.Api.Application;
@ -24,16 +25,37 @@ namespace NavisworksTransport.Core.Animation
/// 注意由于Navisworks API限制无法直接使用Animator API因此使用OverridePermanentTransform实现动画
/// 已集成 TimeLiner 功能,支持在 TimeLiner 中显示和管理动画任务
/// </summary>
/// <summary>
/// 动画帧数据(包含位置和碰撞信息)
/// </summary>
public class AnimationFrame
{
public int Index { get; set; } // 帧索引
public double Progress { get; set; } // 进度(0-1)
public Point3D Position { get; set; } // 该帧的位置
public List<CollisionResult> Collisions { get; set; } // 该帧的碰撞结果
public bool HasCollision => Collisions?.Count > 0;
public AnimationFrame()
{
Collisions = new List<CollisionResult>();
}
}
public class PathAnimationManager
{
private static PathAnimationManager _instance;
private ModelItem _animatedObject;
private List<Point3D> _pathPoints;
// === 预计算动画系统 ===
private List<AnimationFrame> _animationFrames; // 所有帧数据
private int _currentFrameIndex = 0; // 当前帧索引
private HashSet<ModelItem> _currentHighlightedItems; // 当前高亮的对象
// === Idle事件动画系统 ===
private double _frameInterval; // 帧间隔(毫秒)
private DateTime _lastFrameTime = DateTime.MinValue; // 上一帧时间
private int _collisionCheckCounter = 0; // 碰撞检测计数器(用于降频)
// === 动画参数 ===
private double _animationDuration = 10.0; // 动画总时长(秒)
@ -81,6 +103,8 @@ namespace NavisworksTransport.Core.Animation
public PathAnimationManager()
{
_pathPoints = new List<Point3D>();
_animationFrames = new List<AnimationFrame>();
_currentHighlightedItems = new HashSet<ModelItem>();
// 初始化动画模式
_frameInterval = 1000.0 / _animationFrameRate; // 计算帧间隔
@ -173,6 +197,9 @@ namespace NavisworksTransport.Core.Animation
var originalBoundingBox = animatedObject.BoundingBox();
_originalCenter = originalBoundingBox.Center;
// 预计算动画帧和碰撞
PrecomputeAnimationFrames();
// 关键修复:将车辆立即移动到路径起点
// 这样动画就从路径起点开始,而不是从车辆当前位置开始
MoveVehicleToPathStart();
@ -247,6 +274,240 @@ namespace NavisworksTransport.Core.Animation
}
}
/// <summary>
/// 预计算所有动画帧和碰撞信息
/// </summary>
private void PrecomputeAnimationFrames()
{
try
{
// 基于动画时长和帧率计算总帧数
int totalFrames = (int)(_animationDuration * _animationFrameRate);
// 反向计算检测精度(确保每帧都检测)
var totalDistance = CalculateTotalPathDistance();
_collisionDetectionAccuracy = totalDistance / totalFrames;
LogManager.Info($"=== 预计算动画帧 ===");
LogManager.Info($"动画时长: {_animationDuration}秒");
LogManager.Info($"动画帧率: {_animationFrameRate} FPS");
LogManager.Info($"总帧数: {totalFrames}");
LogManager.Info($"路径总长: {totalDistance:F2}米");
LogManager.Info($"检测精度: {_collisionDetectionAccuracy:F3}米/帧");
// 初始化帧列表
_animationFrames = new List<AnimationFrame>();
_currentHighlightedItems = new HashSet<ModelItem>();
_currentFrameIndex = 0;
// 初始化碰撞检测系统
ClashDetectiveIntegration.Instance.Initialize();
// 获取动画对象的包围盒信息
var originalBoundingBox = _animatedObject.BoundingBox();
var boundingBoxSize = new Vector3D(
originalBoundingBox.Max.X - originalBoundingBox.Min.X,
originalBoundingBox.Max.Y - originalBoundingBox.Min.Y,
originalBoundingBox.Max.Z - originalBoundingBox.Min.Z
);
// 获取场景中所有可能碰撞的对象
var potentialColliders = GetPotentialColliders();
LogManager.Info($"潜在碰撞对象数: {potentialColliders.Count}");
// 预计算每一帧
for (int i = 0; i <= totalFrames; i++)
{
double progress = (double)i / totalFrames;
var framePosition = InterpolatePosition(progress);
// 创建帧数据
var frame = new AnimationFrame
{
Index = i,
Progress = progress,
Position = framePosition,
Collisions = new List<CollisionResult>()
};
// 虚拟碰撞检测(不移动实际物体)
var virtualBoundingBox = CreateVirtualBoundingBox(framePosition, boundingBoxSize);
// 检测与所有潜在对象的碰撞
foreach (var collider in potentialColliders)
{
var colliderBox = collider.BoundingBox();
// 使用包围盒检测
if (BoundingBoxesIntersectWithGap(virtualBoundingBox, colliderBox, _detectionGap))
{
frame.Collisions.Add(new CollisionResult
{
Item1 = _animatedObject,
Item2 = collider,
Distance = CalculateBoundingBoxDistance(virtualBoundingBox, colliderBox)
});
}
}
_animationFrames.Add(frame);
// 进度报告
if (i % 30 == 0) // 每秒报告一次30fps
{
LogManager.Info($"预计算进度: 帧 {i}/{totalFrames} ({progress*100:F1}%)");
if (frame.HasCollision)
{
LogManager.Debug($" 帧 {i} 检测到 {frame.Collisions.Count} 个碰撞");
}
}
}
// 统计碰撞信息
var framesWithCollision = _animationFrames.Count(f => f.HasCollision);
var totalCollisions = _animationFrames.Sum(f => f.Collisions.Count);
LogManager.Info($"=== 预计算完成 ===");
LogManager.Info($"总帧数: {_animationFrames.Count}");
LogManager.Info($"包含碰撞的帧: {framesWithCollision}");
LogManager.Info($"总碰撞次数: {totalCollisions}");
}
catch (Exception ex)
{
LogManager.Error($"预计算动画帧失败: {ex.Message}");
// 如果预计算失败,创建空帧列表以避免崩溃
_animationFrames = new List<AnimationFrame>();
}
}
/// <summary>
/// 获取潜在碰撞对象列表
/// </summary>
private List<ModelItem> GetPotentialColliders()
{
try
{
LogManager.Info("开始获取潜在碰撞对象");
// 确保ClashDetectiveIntegration已初始化并构建了通道缓存
ClashDetectiveIntegration.Instance.Initialize();
ClashDetectiveIntegration.Instance.BuildChannelObjectsCache();
ClashDetectiveIntegration.BuildAllGeometryItemsCache();
// 获取所有有几何体的对象
var allItems = Application.ActiveDocument.Models.RootItemDescendantsAndSelf
.Where(item => item.HasGeometry)
.ToList();
// 构建排除列表:动画对象本身及其子对象
var excludeList = new ModelItemCollection();
excludeList.AddRange(_animatedObject.DescendantsAndSelf);
// 使用ClashDetectiveIntegration的碰撞检测方法它会自动排除通道对象
// 创建一个较大的检测间隙来获取路径范围内所有潜在碰撞对象
var detectionGap = 100.0; // 100米范围内的对象都视为潜在碰撞对象
// 调用ClashDetectiveIntegration的DetectCollisions方法
// 该方法内部会自动排除通道对象通过IsChannelObject方法
var collisionResults = ClashDetectiveIntegration.Instance.DetectCollisions(
_animatedObject,
excludeList,
detectionGap
);
// 从碰撞结果中提取唯一的碰撞对象
var potentialColliders = new HashSet<ModelItem>();
foreach (var result in collisionResults)
{
if (result.Item2 != null && !potentialColliders.Contains(result.Item2))
{
potentialColliders.Add(result.Item2);
}
}
var finalList = potentialColliders.ToList();
LogManager.Info($"获取潜在碰撞对象完成: 总对象={allItems.Count}, " +
$"排除自身及子对象={excludeList.Count}, " +
$"检测范围内潜在碰撞对象={finalList.Count}(已自动排除通道)");
return finalList;
}
catch (Exception ex)
{
LogManager.Error($"获取潜在碰撞对象失败: {ex.Message}");
return new List<ModelItem>();
}
}
/// <summary>
/// 计算路径的包围盒
/// </summary>
private BoundingBox3D CalculatePathBounds()
{
if (_pathPoints == null || _pathPoints.Count == 0)
return new BoundingBox3D();
var minX = _pathPoints.Min(p => p.X);
var minY = _pathPoints.Min(p => p.Y);
var minZ = _pathPoints.Min(p => p.Z);
var maxX = _pathPoints.Max(p => p.X);
var maxY = _pathPoints.Max(p => p.Y);
var maxZ = _pathPoints.Max(p => p.Z);
return new BoundingBox3D(
new Point3D(minX, minY, minZ),
new Point3D(maxX, maxY, maxZ)
);
}
/// <summary>
/// 创建虚拟包围盒(用于碰撞检测)
/// </summary>
private BoundingBox3D CreateVirtualBoundingBox(Point3D position, Vector3D size)
{
return new BoundingBox3D(
new Point3D(
position.X - size.X / 2,
position.Y - size.Y / 2,
position.Z
),
new Point3D(
position.X + size.X / 2,
position.Y + size.Y / 2,
position.Z + size.Z
)
);
}
/// <summary>
/// 包围盒碰撞检测(带间隙)
/// </summary>
private bool BoundingBoxesIntersectWithGap(BoundingBox3D box1, BoundingBox3D box2, double gap)
{
return !(box1.Max.X + gap < box2.Min.X || box2.Max.X + gap < box1.Min.X ||
box1.Max.Y + gap < box2.Min.Y || box2.Max.Y + gap < box1.Min.Y ||
box1.Max.Z + gap < box2.Min.Z || box2.Max.Z + gap < box1.Min.Z);
}
/// <summary>
/// 计算包围盒中心距离
/// </summary>
private double CalculateBoundingBoxDistance(BoundingBox3D box1, BoundingBox3D box2)
{
var center1 = new Point3D(
(box1.Min.X + box1.Max.X) / 2,
(box1.Min.Y + box1.Max.Y) / 2,
(box1.Min.Z + box1.Max.Z) / 2
);
var center2 = new Point3D(
(box2.Min.X + box2.Max.X) / 2,
(box2.Min.Y + box2.Max.Y) / 2,
(box2.Min.Z + box2.Max.Z) / 2
);
return CalculateDistance(center1, center2);
}
/// <summary>
/// 开始播放动画
/// </summary>
@ -309,7 +570,6 @@ namespace NavisworksTransport.Core.Animation
// 重置Idle模式状态
_lastFrameTime = DateTime.MinValue;
_collisionCheckCounter = 0;
_fpsFrameCount = 0;
_fpsCounterStart = DateTime.Now;
_frameInterval = 1000.0 / _animationFrameRate;
@ -345,8 +605,41 @@ namespace NavisworksTransport.Core.Animation
NavisApplication.Idle -= OnApplicationIdle;
LogManager.Debug("[Idle模式] Idle事件已注销");
// 收集整个路径上的所有碰撞对象
var allCollisionResults = new List<CollisionResult>();
var uniqueCollidedItems = new HashSet<ModelItem>();
foreach (var frame in _animationFrames)
{
if (frame.HasCollision)
{
foreach (var collision in frame.Collisions)
{
// 收集唯一的碰撞对象
if (!uniqueCollidedItems.Contains(collision.Item2))
{
uniqueCollidedItems.Add(collision.Item2);
allCollisionResults.Add(collision);
}
}
}
}
// 高亮显示所有碰撞过的对象
if (allCollisionResults.Count > 0)
{
ClashDetectiveIntegration.Instance.HighlightCollisions(allCollisionResults);
LogManager.Info($"动画停止,高亮显示路径上的 {uniqueCollidedItems.Count} 个碰撞对象");
}
else
{
ClashDetectiveIntegration.Instance.ClearHighlights();
LogManager.Info("动画停止,路径上无碰撞对象");
}
SetState(AnimationState.Stopped);
_pausedProgress = 0.0; // 重置暂停进度
_currentFrameIndex = 0; // 重置帧索引
LogManager.Info("动画已停止");
// 动画停止时不创建碰撞测试汇总,由动画完成事件统一处理
@ -377,6 +670,38 @@ namespace NavisworksTransport.Core.Animation
// 重置暂停进度
_pausedProgress = 0.0;
// 收集整个路径上的所有碰撞对象
var allCollisionResults = new List<CollisionResult>();
var uniqueCollidedItems = new HashSet<ModelItem>();
foreach (var frame in _animationFrames)
{
if (frame.HasCollision)
{
foreach (var collision in frame.Collisions)
{
// 收集唯一的碰撞对象
if (!uniqueCollidedItems.Contains(collision.Item2))
{
uniqueCollidedItems.Add(collision.Item2);
allCollisionResults.Add(collision);
}
}
}
}
// 高亮显示所有碰撞过的对象
if (allCollisionResults.Count > 0)
{
ClashDetectiveIntegration.Instance.HighlightCollisions(allCollisionResults);
LogManager.Info($"动画完成,高亮显示路径上的 {uniqueCollidedItems.Count} 个碰撞对象");
}
else
{
ClashDetectiveIntegration.Instance.ClearHighlights();
LogManager.Info("动画完成,路径上无碰撞对象");
}
// 直接设置为完成状态,避免中间状态切换
SetState(AnimationState.Finished);
@ -1274,6 +1599,60 @@ namespace NavisworksTransport.Core.Animation
/// <summary>
/// Idle事件处理器 - 新的动画核心
/// </summary>
/// <summary>
/// 根据当前帧索引更新碰撞高亮
/// </summary>
private void UpdateCollisionHighlightFromFrame()
{
try
{
// 确保帧索引有效
if (_currentFrameIndex < 0 || _currentFrameIndex >= _animationFrames.Count)
{
return;
}
// 获取当前帧数据
var currentFrame = _animationFrames[_currentFrameIndex];
// 使用ClashDetectiveIntegration的高亮方法
if (currentFrame.HasCollision)
{
// 高亮当前帧的碰撞对象
ClashDetectiveIntegration.Instance.HighlightCollisions(currentFrame.Collisions);
// 缓存碰撞结果用于动画结束后的处理
var animatedObjectPosition = currentFrame.Position;
foreach (var collision in currentFrame.Collisions)
{
var collisionObjectPosition = GetObjectPosition(collision.Item2);
ClashDetectiveIntegration.Instance.CacheCollisionDuringAnimation(
_animatedObject,
animatedObjectPosition,
collision.Item2,
collisionObjectPosition
);
}
LogManager.Debug($"帧 {_currentFrameIndex}: 检测到 {currentFrame.Collisions.Count} 个碰撞");
// 触发碰撞事件
var eventArgs = new CollisionDetectedEventArgs(currentFrame.Collisions);
OnCollisionDetected(eventArgs);
}
else
{
// 清除高亮(没有碰撞时)
ClashDetectiveIntegration.Instance.ClearHighlights();
LogManager.Debug($"帧 {_currentFrameIndex}: 无碰撞");
}
}
catch (Exception ex)
{
LogManager.Error($"更新碰撞高亮失败: {ex.Message}");
}
}
private void OnApplicationIdle(object sender, EventArgs e)
{
try
@ -1305,9 +1684,27 @@ namespace NavisworksTransport.Core.Animation
var totalElapsed = (now - _animationStartTime).TotalSeconds;
var progress = Math.Min(totalElapsed / _animationDuration, 1.0);
// 更新动画位置(使用原有逻辑)
Point3D newPosition = InterpolatePosition(progress);
UpdateObjectPosition(newPosition);
// 根据进度计算当前帧索引
int targetFrameIndex = (int)(progress * (_animationFrames.Count - 1));
targetFrameIndex = Math.Min(targetFrameIndex, _animationFrames.Count - 1);
// 如果帧索引发生变化,更新位置和碰撞高亮
if (targetFrameIndex != _currentFrameIndex || _currentFrameIndex == 0)
{
_currentFrameIndex = targetFrameIndex;
// 使用预计算的帧位置
if (_currentFrameIndex < _animationFrames.Count)
{
var frameData = _animationFrames[_currentFrameIndex];
UpdateObjectPosition(frameData.Position);
// 更新碰撞高亮(基于预计算结果)
UpdateCollisionHighlightFromFrame();
LogManager.Debug($"帧 {_currentFrameIndex}/{_animationFrames.Count-1}, 进度: {progress*100:F1}%, 位置: ({frameData.Position.X:F2},{frameData.Position.Y:F2},{frameData.Position.Z:F2})");
}
}
// 触发进度事件
var progressPercent = progress * 100;
@ -1319,17 +1716,6 @@ namespace NavisworksTransport.Core.Animation
_timeLinerManager.UpdateTaskProgress(_currentTaskId, progress, _currentState);
}
// 碰撞检测降频执行每3帧检测一次
_collisionCheckCounter++;
if (_collisionCheckCounter >= 3)
{
_collisionCheckCounter = 0;
if (_currentState == AnimationState.Playing)
{
CheckAndHighlightCollisionsWithClashDetective();
}
}
// 记录帧时间
_lastFrameTime = now;
_animationFrameCount++;

View File

@ -106,40 +106,6 @@ namespace NavisworksTransport
}
/// <summary>
/// 执行动态碰撞检测
/// </summary>
/// <param name="animatedObject">动画对象</param>
/// <param name="excludeObjects">排除对象</param>
/// <param name="detectionGap">检测间隙(米)</param>
/// <returns>碰撞结果</returns>
public List<CollisionResult> DetectCollisions(ModelItem animatedObject,
ModelItemCollection excludeObjects = null, double detectionGap = 0.2)
{
try
{
// 使用简化检测模式
LogManager.Debug($"使用简化检测模式进行碰撞检测,检测间隙: {detectionGap}米");
var results = DetectCollisionsSimple(animatedObject, excludeObjects, detectionGap);
// 在动画过程中不创建Clash Detective测试避免影响性能
// 只有在动画结束后手动测试时才创建完整的测试
// 触发事件
OnCollisionDetected(new CollisionDetectedEventArgs(results));
LogManager.Debug($"碰撞检测完成,发现 {results.Count} 个碰撞");
return results;
}
catch (Exception ex)
{
LogManager.Error($"碰撞检测失败: {ex.Message}");
// 回退到简化检测
return DetectCollisionsSimple(animatedObject, excludeObjects, detectionGap);
}
}
/// <summary>
/// 创建碰撞快照(动画结束后一次性更新结果)
/// </summary>
@ -187,34 +153,6 @@ namespace NavisworksTransport
{
try
{
// 🔍 添加碰撞缓存开始的详细日志
LogManager.Debug($"=== [碰撞缓存-开始] ===");
LogManager.Debug($"[碰撞缓存-开始] 动画对象: {animatedObject?.DisplayName ?? "NULL"}");
LogManager.Debug($"[碰撞缓存-开始] 碰撞对象: {collisionObject?.DisplayName ?? "NULL"}");
// 🔍 对象验证详情
if (animatedObject == null)
{
LogManager.Error($"[碰撞缓存-错误] 动画对象为NULL");
}
if (collisionObject == null)
{
LogManager.Error($"[碰撞缓存-错误] 碰撞对象为NULL");
}
// 🔍 位置信息详情
LogManager.Debug($"[碰撞缓存-位置] 动画对象位置: ({animatedObjectPosition.X:F3},{animatedObjectPosition.Y:F3},{animatedObjectPosition.Z:F3})");
if (collisionObjectPosition != null)
{
LogManager.Debug($"[碰撞缓存-位置] 碰撞对象位置: ({collisionObjectPosition.X:F3},{collisionObjectPosition.Y:F3},{collisionObjectPosition.Z:F3})");
}
else
{
LogManager.Debug($"[碰撞缓存-位置] 碰撞对象位置: NULL (将自动计算)");
}
if (!IsModelItemValid(animatedObject) || !IsModelItemValid(collisionObject))
{
LogManager.Warning($"[诊断-无效对象] 对象验证失败,退出缓存过程");
@ -229,43 +167,19 @@ namespace NavisworksTransport
bool hasMapping1 = !mappedAnimatedObject.Equals(animatedObject);
bool hasMapping2 = !mappedCollisionObject.Equals(collisionObject);
LogManager.Info($"[容器映射] 动画对象: '{animatedObject.DisplayName}' -> '{mappedAnimatedObject.DisplayName}' (映射: {hasMapping1})");
LogManager.Info($"[容器映射] 碰撞对象: '{collisionObject.DisplayName}' -> '{mappedCollisionObject.DisplayName}' (映射: {hasMapping2})");
// 使用包围盒碰撞检测算法
var animatedBoundingBox = animatedObject.BoundingBox();
var collisionBoundingBox = collisionObject.BoundingBox();
LogManager.Debug($"[碰撞缓存-包围盒] 动画对象包围盒: Min({animatedBoundingBox.Min.X:F3},{animatedBoundingBox.Min.Y:F3},{animatedBoundingBox.Min.Z:F3}) Max({animatedBoundingBox.Max.X:F3},{animatedBoundingBox.Max.Y:F3},{animatedBoundingBox.Max.Z:F3})");
LogManager.Debug($"[碰撞缓存-包围盒] 碰撞对象包围盒: Min({collisionBoundingBox.Min.X:F3},{collisionBoundingBox.Min.Y:F3},{collisionBoundingBox.Min.Z:F3}) Max({collisionBoundingBox.Max.X:F3},{collisionBoundingBox.Max.Y:F3},{collisionBoundingBox.Max.Z:F3})");
if (BoundingBoxGeometryUtils.BoundingBoxesIntersect(animatedBoundingBox, collisionBoundingBox))
{
// 🔍 计算并验证位置信息
var finalCollisionPosition = collisionObjectPosition ?? GetObjectPosition(collisionObject);
LogManager.Debug($"[碰撞缓存-位置计算] 最终使用的碰撞对象位置: ({finalCollisionPosition.X:F3},{finalCollisionPosition.Y:F3},{finalCollisionPosition.Z:F3})");
// 🔍 检查位置是否相同(可能的自碰撞标志)
var positionDistance = Math.Sqrt(
Math.Pow(animatedObjectPosition.X - finalCollisionPosition.X, 2) +
Math.Pow(animatedObjectPosition.Y - finalCollisionPosition.Y, 2) +
Math.Pow(animatedObjectPosition.Z - finalCollisionPosition.Z, 2));
LogManager.Debug($"[碰撞缓存-距离计算] 动画对象与碰撞对象的位置距离: {positionDistance:F4}");
if (positionDistance < 0.001) // 位置基本相同
{
LogManager.Warning($"[诊断-位置警告] 两个对象位置几乎相同 (距离: {positionDistance:F6}),这可能是自碰撞的标志!");
}
// 创建碰撞结果
var collisionDistance = BoundingBoxGeometryUtils.CalculateDistance(animatedBoundingBox, collisionBoundingBox);
var collisionCenter = BoundingBoxGeometryUtils.CalculateCenter(animatedBoundingBox, collisionBoundingBox);
LogManager.Debug($"[碰撞缓存-距离] 包围盒碰撞距离: {collisionDistance:F4}");
LogManager.Debug($"[碰撞缓存-中心点] 碰撞中心: ({collisionCenter.X:F3},{collisionCenter.Y:F3},{collisionCenter.Z:F3})");
var collision = new CollisionResult
{
ClashGuid = Guid.NewGuid(),
@ -293,10 +207,6 @@ namespace NavisworksTransport
_cachedResults.Add(collision);
}
}
else
{
LogManager.Debug($"[碰撞缓存-包围盒] 包围盒不相交,跳过缓存");
}
}
catch (Exception ex)
{
@ -341,7 +251,7 @@ namespace NavisworksTransport
{
LogManager.Info($"=== 动画结束开始创建ClashDetective碰撞测试容差: {detectionGap}米) ===");
// 🔧 修复:在处理前记录动画过程中的碰撞数量
// 在处理前记录动画过程中的碰撞数量
_animationCollisionCount = _cachedResults.Count;
LogManager.Info($"记录动画过程碰撞数量: {_animationCollisionCount}");
@ -352,34 +262,6 @@ namespace NavisworksTransport
}
LogManager.Info($"[缓存分析-统计] 共有 {_cachedResults.Count} 个原始缓存结果");
// 🔍 详细分析每个缓存项
LogManager.Info($"[缓存分析-原始] 开始分析所有原始缓存结果:");
for (int i = 0; i < _cachedResults.Count; i++)
{
var item = _cachedResults[i];
LogManager.Info($"[缓存分析-{i+1:00}] 对象: {item.Item1?.DisplayName ?? "NULL"} <-> {item.Item2?.DisplayName ?? "NULL"}");
LogManager.Info($"[缓存分析-{i+1:00}] 时间: {item.CreatedTime:HH:mm:ss.fff}");
LogManager.Info($"[缓存分析-{i+1:00}] 动画位置: ({item.Item1Position.X:F3},{item.Item1Position.Y:F3},{item.Item1Position.Z:F3})");
LogManager.Info($"[缓存分析-{i+1:00}] 碰撞位置: ({item.Item2Position.X:F3},{item.Item2Position.Y:F3},{item.Item2Position.Z:F3})");
LogManager.Info($"[缓存分析-{i+1:00}] 距离: {item.Distance:F4}");
LogManager.Info($"[缓存分析-{i+1:00}] GUID: {item.ClashGuid}");
LogManager.Info($"[缓存分析-{i+1:00}] HasPositionInfo: {item.HasPositionInfo}");
// 验证对象有效性
var item1Valid = IsModelItemValid(item.Item1);
var item2Valid = IsModelItemValid(item.Item2);
LogManager.Info($"[缓存分析-{i+1:00}] 对象有效性: Item1={item1Valid}, Item2={item2Valid}");
if (!item1Valid)
{
LogManager.Warning($"[缓存分析-{i+1:00}] Item1无效: {item.Item1?.DisplayName ?? "NULL"}");
}
if (!item2Valid)
{
LogManager.Warning($"[缓存分析-{i+1:00}] Item2无效: {item.Item2?.DisplayName ?? "NULL"}");
}
}
// 获取动画对象和当前动画终点位置(用于后续恢复)
ModelItem animatedObject = null;
@ -419,38 +301,8 @@ namespace NavisworksTransport
LogManager.Info($"[去重分析-结果] 去重后得到 {uniqueCollisions.Count} 个唯一碰撞对");
LogManager.Info($"[去重分析-效率] 去重前: {_cachedResults.Count}, 去重后: {uniqueCollisions.Count}, 压缩率: {(1.0 - (double)uniqueCollisions.Count / _cachedResults.Count) * 100:F1}%");
// 🔍 分析每个唯一碰撞对的详情
for (int i = 0; i < uniqueCollisions.Count; i++)
{
var group = uniqueCollisions[i];
LogManager.Info($"[去重分析-唯一{i+1:00}] 碰撞对: {group.Collision.Item1?.DisplayName ?? "NULL"} <-> {group.Collision.Item2?.DisplayName ?? "NULL"}");
LogManager.Info($"[去重分析-唯一{i+1:00}] 重复次数: {group.Count}");
LogManager.Info($"[去重分析-唯一{i+1:00}] 时间范围: {group.FirstTime:HH:mm:ss.fff} ~ {group.LastTime:HH:mm:ss.fff}");
LogManager.Info($"[去重分析-唯一{i+1:00}] 选择记录: 时间={group.Collision.CreatedTime:HH:mm:ss.fff}, 距离={group.Collision.Distance:F4}");
// 如果有重复,分析所有重复项
if (group.Count > 1)
{
LogManager.Info($"[去重分析-唯一{i+1:00}] 重复项详情:");
for (int j = 0; j < group.AllItems.Count; j++)
{
var dupItem = group.AllItems[j];
LogManager.Info($" [{j+1}] 时间: {dupItem.CreatedTime:HH:mm:ss.fff}, 距离: {dupItem.Distance:F4}, GUID: {dupItem.ClashGuid}");
}
}
// 验证去重键的具体值
var key1Name = group.Collision.Item1?.DisplayName ?? "NULL";
var key2Name = group.Collision.Item2?.DisplayName ?? "NULL";
var key1Guid = group.Collision.Item1?.InstanceGuid.ToString() ?? "NULL";
var key2Guid = group.Collision.Item2?.InstanceGuid.ToString() ?? "NULL";
LogManager.Debug($"[去重分析-唯一{i+1:00}] 去重键: Item1Name='{key1Name}', Item2Name='{key2Name}'");
LogManager.Debug($"[去重分析-唯一{i+1:00}] 去重键: Item1GUID='{key1Guid}', Item2GUID='{key2Guid}'");
}
var doc = Autodesk.Navisworks.Api.Application.ActiveDocument;
var doc = Application.ActiveDocument;
// 使用分组方案:创建一个主测试和一个包含所有碰撞结果的组
LogManager.Info("=== 开始分组方案:创建主测试和碰撞分组 ===");
@ -882,8 +734,6 @@ namespace NavisworksTransport
}
}
private DateTime _lastTestCreationTime = DateTime.MinValue;
/// <summary>
/// 高亮显示碰撞对象
/// </summary>
@ -926,12 +776,6 @@ namespace NavisworksTransport
// 高亮碰撞对象(使用指定颜色)
doc.Models.OverrideTemporaryColor(collidingItems, highlightColor);
LogManager.Info($"高亮显示 {collidingItems.Count} 个碰撞对象,颜色: {highlightColor}");
}
else
{
LogManager.Debug("没有碰撞结果需要高亮");
}
}
catch (Exception ex)
@ -1017,12 +861,6 @@ namespace NavisworksTransport
// 记录活跃高亮
_activeHighlights[category] = collidingItems;
LogManager.Info($"类别 '{category}' 高亮显示 {collidingItems.Count} 个碰撞对象,颜色: {highlightColor}");
}
else
{
LogManager.Debug($"类别 '{category}' 没有碰撞结果需要高亮");
}
}
}
@ -1096,17 +934,14 @@ namespace NavisworksTransport
/// <summary>
/// 基于包围盒的快速碰撞检测不用Clash Detective
/// </summary>
private List<CollisionResult> DetectCollisionsSimple(ModelItem animatedObject,
public List<CollisionResult> DetectCollisions(ModelItem animatedObject,
ModelItemCollection excludeObjects, double detectionGap)
{
var results = new List<CollisionResult>();
try
{
LogManager.Debug($"开始快速碰撞检测,动画对象: {animatedObject.DisplayName}");
{
var animatedBoundingBox = animatedObject.BoundingBox();
LogManager.Debug($"动画对象包围盒: Min({animatedBoundingBox.Min.X:F2}, {animatedBoundingBox.Min.Y:F2}, {animatedBoundingBox.Min.Z:F2}) Max({animatedBoundingBox.Max.X:F2}, {animatedBoundingBox.Max.Y:F2}, {animatedBoundingBox.Max.Z:F2})");
// 性能分析计时器
var exclusionStopwatch = new System.Diagnostics.Stopwatch();
@ -1135,7 +970,6 @@ namespace NavisworksTransport
}
}
exclusionStopwatch.Stop();
LogManager.Debug($"构建排除列表完成,耗时: {exclusionStopwatch.ElapsedMilliseconds}ms排除对象: {exclusionList.Count} 个");
// 🔥 使用预构建的缓存获取对象列表
getAllItemsStopwatch.Start();
@ -1147,7 +981,6 @@ namespace NavisworksTransport
{
// 从缓存中过滤掉动画对象本身
itemList = _allGeometryItemsCache.Where(item => !item.Equals(animatedObject)).ToList();
LogManager.Debug($"使用缓存获取对象列表,缓存对象总数: {_allGeometryItemsCache.Count},过滤后: {itemList.Count}");
}
else
{
@ -1160,7 +993,6 @@ namespace NavisworksTransport
}
getAllItemsStopwatch.Stop();
LogManager.Debug($"获取对象列表完成,耗时: {getAllItemsStopwatch.ElapsedMilliseconds}ms对象总数: {itemList.Count}");
int checkedCount = 0;
int excludedCount = 0;
@ -1217,10 +1049,7 @@ namespace NavisworksTransport
// 检查是否进行了容器映射
bool hasMapping1 = !mappedAnimatedObject.Equals(animatedObject);
bool hasMapping2 = !mappedCollisionObject.Equals(item);
LogManager.Info($"[容器映射] 原始对象: {animatedObject.DisplayName} -> 容器对象: {mappedAnimatedObject.DisplayName} (映射: {hasMapping1})");
LogManager.Info($"[容器映射] 原始对象: {item.DisplayName} -> 容器对象: {mappedCollisionObject.DisplayName} (映射: {hasMapping2})");
var result = new CollisionResult
{
ClashGuid = Guid.NewGuid(),
@ -1237,7 +1066,6 @@ namespace NavisworksTransport
};
results.Add(result);
LogManager.Info($"检测到碰撞: {mappedAnimatedObject.DisplayName} <-> {mappedCollisionObject.DisplayName},距离: {result.Distance:F2}");
}
}
@ -1340,11 +1168,9 @@ namespace NavisworksTransport
try
{
var document = Application.ActiveDocument;
LogManager.Debug("[通道缓存] 开始使用SearchAPI构建通道对象缓存");
// 🔥 一行调用:直接获取所有可通行的物流模型项使用优化后的SearchAPI
// 获取所有可通行的物流模型项
var allChannelItems = CategoryAttributeManager.GetAllTraversableLogisticsItems(document);
LogManager.Info($"[通道缓存] 使用SearchAPI直接获取到 {allChannelItems.Count} 个可通行物流元素");
if (allChannelItems.Count == 0)
{
@ -1357,19 +1183,14 @@ namespace NavisworksTransport
foreach (var channelItem in allChannelItems)
{
try
{
LogManager.Debug($"[通道收集] 开始处理通道节点: '{channelItem.DisplayName}'");
// 🔥 直接使用 ModelItemAnalysisHelper 的方法收集相关节点
{
var itemRelatedNodes = ModelItemAnalysisHelper.CollectRelatedNodes(channelItem);
// 将结果添加到缓存中
foreach (var node in itemRelatedNodes)
{
_channelObjectsCache.Add(node);
}
LogManager.Debug($"[通道收集] 从 '{channelItem.DisplayName}' 收集到 {itemRelatedNodes.Count} 个相关节点");
}
}
catch (Exception ex)
{
@ -1383,21 +1204,6 @@ namespace NavisworksTransport
LogManager.Info($"通道对象缓存构建完成,耗时: {cacheStopwatch.ElapsedMilliseconds}ms");
LogManager.Info($" - 可通行物流根对象: {allChannelItems.Count} 个");
LogManager.Info($" - 缓存总对象数: {_channelObjectsCache.Count} 个");
// 列出找到的通道根对象
if (allChannelItems.Count > 0)
{
LogManager.Info("[通道缓存] 找到的可通行物流对象列表:");
for (int i = 0; i < Math.Min(allChannelItems.Count, 10); i++) // 最多显示前10个
{
var item = allChannelItems[i];
LogManager.Info($" {i + 1}. {item.DisplayName} (HasGeometry: {item.HasGeometry})");
}
if (allChannelItems.Count > 10)
{
LogManager.Info($" ... 还有 {allChannelItems.Count - 10} 个对象(省略显示)");
}
}
}
catch (Exception ex)
{
@ -1520,9 +1326,7 @@ namespace NavisworksTransport
// 否则查找有名称的父级容器
var containerObject = ModelItemAnalysisHelper.FindNamedParentContainer(originalItem);
LogManager.Info($"[碰撞对象映射] 原对象: '{originalItem.DisplayName}' -> 容器对象: '{containerObject?.DisplayName}'");
return containerObject ?? originalItem;
}

View File

@ -25,7 +25,7 @@ namespace NavisworksTransport
private CoordinateConverter _coordinateConverter;
private PathPointRenderPlugin _renderPlugin;
private List<ModelItem> _selectedChannels;
private List<ModelItem> _walkableAreas;
private List<PathRoute> _routes;
private PathRoute _currentRoute;
private ChannelBounds _combinedChannelBounds;
@ -131,7 +131,7 @@ namespace NavisworksTransport
_renderPlugin = null;
}
_selectedChannels = new List<ModelItem>();
_walkableAreas = new List<ModelItem>();
_routes = new List<PathRoute>();
_currentRoute = new PathRoute("默认路径");
_historyManager = new PathHistoryManager(50); // 最多保存50个历史记录
@ -195,7 +195,7 @@ namespace NavisworksTransport
/// </summary>
internal List<PathRoute> ModifiableRoutes => _routes;
public IReadOnlyList<ModelItem> SelectedChannels => _selectedChannels.AsReadOnly();
public IReadOnlyList<ModelItem> SelectedChannels => _walkableAreas.AsReadOnly();
public PathPointType CurrentPointType
{
@ -1082,7 +1082,7 @@ namespace NavisworksTransport
{
try
{
_selectedChannels.Clear();
_walkableAreas.Clear();
if (useCurrentSelection)
{
@ -1090,8 +1090,8 @@ namespace NavisworksTransport
var currentSelection = Application.ActiveDocument.CurrentSelection.SelectedItems;
if (currentSelection.Any())
{
_selectedChannels.AddRange(currentSelection);
RaiseStatusChanged($"已选择 {_selectedChannels.Count} 个模型作为通道", PathPlanningStatusType.Success);
_walkableAreas.AddRange(currentSelection);
RaiseStatusChanged($"已选择 {_walkableAreas.Count} 个模型作为通道", PathPlanningStatusType.Success);
}
else
{
@ -1107,23 +1107,23 @@ namespace NavisworksTransport
{
var allLogisticsItems = CategoryAttributeManager.GetAllLogisticsItems(document);
var channelItems = CategoryAttributeManager.FilterByLogisticsType(allLogisticsItems, CategoryAttributeManager.LogisticsElementType.);
_selectedChannels.AddRange(channelItems);
_walkableAreas.AddRange(channelItems);
LogManager.Info($"[SelectChannels] 通过CategoryAttributeManager直接筛选到 {channelItems.Count} 个通道");
}
RaiseStatusChanged($"通过类别筛选到 {_selectedChannels.Count} 个通道", PathPlanningStatusType.Success);
RaiseStatusChanged($"通过类别筛选到 {_walkableAreas.Count} 个通道", PathPlanningStatusType.Success);
}
if (_selectedChannels.Any())
if (_walkableAreas.Any())
{
// 计算组合边界
CalculateCombinedBounds();
// 触发通道选择变更事件
RaiseChannelSelectionChanged(_selectedChannels, useCurrentSelection ? "手动选择" : "类别筛选");
RaiseChannelSelectionChanged(_walkableAreas, useCurrentSelection ? "手动选择" : "类别筛选");
}
return _selectedChannels.Count;
return _walkableAreas.Count;
}
catch (Exception ex)
{
@ -1254,7 +1254,7 @@ namespace NavisworksTransport
// 更新路径关联的通道ID
route.AssociatedChannelIds.Clear();
foreach (var channel in _selectedChannels)
foreach (var channel in _walkableAreas)
{
route.AssociatedChannelIds.Add(channel.InstanceGuid.ToString());
}
@ -1294,7 +1294,7 @@ namespace NavisworksTransport
AutoSelectLogisticsChannels();
// 检查是否有可通行的物流模型
if (_selectedChannels == null || _selectedChannels.Count == 0)
if (_walkableAreas == null || _walkableAreas.Count == 0)
{
RaiseErrorOccurred("没有找到任何可通行的物流模型,请先为模型设置可通行的物流属性");
// 重置状态
@ -1974,7 +1974,7 @@ namespace NavisworksTransport
/// <returns>统计信息字符串</returns>
public string GetStatistics()
{
var stats = $"通道数量: {_selectedChannels.Count}\n";
var stats = $"通道数量: {_walkableAreas.Count}\n";
stats += $"路径数量: {_routes.Count}\n";
if (_currentRoute != null)
@ -2021,7 +2021,7 @@ namespace NavisworksTransport
errors.Add("当前文档中没有加载的模型");
// 检查通道选择状态
if (_selectedChannels.Count == 0)
if (_walkableAreas.Count == 0)
errors.Add("没有选择任何通道模型");
if (errors.Count > 0)
@ -2116,7 +2116,7 @@ namespace NavisworksTransport
private void CalculateCombinedBounds()
{
// 保留原有实现但移除UI相关调用
if (!_selectedChannels.Any())
if (!_walkableAreas.Any())
{
_combinedChannelBounds = null;
return;
@ -2129,7 +2129,7 @@ namespace NavisworksTransport
var allBounds = new List<BoundingBox3D>();
foreach (var channel in _selectedChannels)
foreach (var channel in _walkableAreas)
{
try
{
@ -2214,7 +2214,7 @@ namespace NavisworksTransport
var document = Application.ActiveDocument;
if (document?.Models == null) return;
_selectedChannels.Clear();
_walkableAreas.Clear();
LogManager.Info("[通道自动选择] 开始搜索可通行的物流模型");
@ -2229,19 +2229,19 @@ namespace NavisworksTransport
// 将可通行的物流模型添加到_selectedChannels
foreach (ModelItem item in traversableItems)
{
_selectedChannels.Add(item);
_walkableAreas.Add(item);
LogManager.Info($"[通道自动选择] 添加可通行模型: '{item.DisplayName}'");
}
if (_selectedChannels.Count > 0)
if (_walkableAreas.Count > 0)
{
// 计算组合边界
CalculateCombinedBounds();
// 触发通道选择变更事件
RaiseChannelSelectionChanged(_selectedChannels, "自动选择");
RaiseChannelSelectionChanged(_walkableAreas, "自动选择");
LogManager.Info($"[通道自动选择] ✅ 自动选择了 {_selectedChannels.Count} 个可通行的物流模型");
LogManager.Info($"[通道自动选择] ✅ 自动选择了 {_walkableAreas.Count} 个可通行的物流模型");
}
else
{
@ -2373,8 +2373,8 @@ namespace NavisworksTransport
}
catch (Exception ex)
{
LogManager.WriteLog($"[ToolPlugin] 激活异常: {ex.Message}");
LogManager.WriteLog($"[ToolPlugin] 堆栈: {ex.StackTrace}");
LogManager.Error($"[ToolPlugin] 激活异常: {ex.Message}");
LogManager.Error($"[ToolPlugin] 堆栈: {ex.StackTrace}");
return false;
}
}
@ -2425,7 +2425,7 @@ namespace NavisworksTransport
}
catch (Exception ex)
{
LogManager.WriteLog($"[ReactivateToolPlugin] 重新激活失败: {ex.Message}");
LogManager.Error($"[ReactivateToolPlugin] 重新激活失败: {ex.Message}");
RaiseErrorOccurred($"重新激活工具失败: {ex.Message}", ex);
}
}
@ -2437,11 +2437,6 @@ namespace NavisworksTransport
{
try
{
LogManager.WriteLog("[ToolPlugin事件-V2] ===== 收到精确点击坐标 =====");
LogManager.WriteLog($"[ToolPlugin事件-V2] 精确坐标: ({pickResult.Point.X:F3}, {pickResult.Point.Y:F3}, {pickResult.Point.Z:F3})");
LogManager.WriteLog($"[ToolPlugin事件-V2] 选中对象: {pickResult.ModelItem?.DisplayName ?? "NULL"}");
LogManager.WriteLog($"[ToolPlugin事件-V2] 当前点类型: {CurrentPointType}");
// 如果在自动路径规划模式则跳过处理应该由PathEditingViewModel处理
if (IsInAutoPathMode)
{
@ -2454,8 +2449,8 @@ namespace NavisworksTransport
}
catch (Exception ex)
{
LogManager.WriteLog($"[ToolPlugin事件] 处理异常: {ex.Message}");
LogManager.WriteLog($"[ToolPlugin事件] 堆栈: {ex.StackTrace}");
LogManager.Error($"[ToolPlugin事件] 处理异常: {ex.Message}");
LogManager.Error($"[ToolPlugin事件] 堆栈: {ex.StackTrace}");
}
}
@ -2465,17 +2460,17 @@ namespace NavisworksTransport
private void ProcessManualPathEditing(PickItemResult pickResult)
{
// 检查当前选中的通道状态
LogManager.WriteLog($"[手动编辑] 当前_selectedChannels状态: {(_selectedChannels == null ? "NULL" : $"{_selectedChannels.Count}")}");
LogManager.WriteLog($"[手动编辑] 当前_selectedChannels状态: {(_walkableAreas == null ? "NULL" : $"{_walkableAreas.Count}")}");
// 如果没有选中的通道,尝试实时搜索
if (_selectedChannels == null || _selectedChannels.Count == 0)
if (_walkableAreas == null || _walkableAreas.Count == 0)
{
LogManager.WriteLog("[手动编辑] 没有预选通道,开始实时搜索可通行的物流模型");
SearchAndSetTraversableChannels();
}
// 检查是否在可通行的物流模型内并处理点击
if (_selectedChannels != null && _selectedChannels.Any())
if (_walkableAreas != null && _walkableAreas.Any())
{
bool isInTraversableLogisticsModel = IsItemInSelectedChannels(pickResult.ModelItem) ||
IsItemChildOfSelectedChannels(pickResult.ModelItem);
@ -2557,12 +2552,12 @@ namespace NavisworksTransport
LogManager.WriteLog($"[搜索通道] 筛选出 {traversableItems.Count} 个可通行的物流模型");
// 临时设置为选中通道
if (_selectedChannels == null) _selectedChannels = new List<ModelItem>();
_selectedChannels.Clear();
if (_walkableAreas == null) _walkableAreas = new List<ModelItem>();
_walkableAreas.Clear();
foreach (ModelItem item in traversableItems)
{
_selectedChannels.Add(item);
_walkableAreas.Add(item);
LogManager.WriteLog($"[搜索通道] 添加可通行模型: '{item.DisplayName}'");
}
}
@ -2647,19 +2642,14 @@ namespace NavisworksTransport
try
{
// 清理资源
if (_renderPlugin != null)
{
_renderPlugin.ClearAllPaths();
_renderPlugin = null;
}
_renderPlugin?.ClearAllPaths();
_renderPlugin = null;
// 停用ToolPlugin
if (_isToolPluginActive)
{
DeactivateToolPlugin();
}
LogManager.Info($"PathPlanningManager已释放ManagerId: {_managerId}");
}
catch (Exception ex)
{
@ -2677,20 +2667,14 @@ namespace NavisworksTransport
try
{
LogManager.WriteLog("[ToolPlugin] ===== 开始停用ToolPlugin =====");
// 1. 取消事件订阅
LogManager.WriteLog("[ToolPlugin] 步骤1: 取消事件订阅");
PathClickToolPlugin.MouseClicked -= OnToolPluginMouseClicked;
LogManager.WriteLog("[ToolPlugin] ✓ 事件订阅已取消");
// 2. 重置为无活动工具状态
LogManager.WriteLog("[ToolPlugin] 步骤2: 重置为默认工具");
try
{
// 使用Tool.None重置到无活动工具状态让Navisworks处理默认导航
Application.MainDocument.Tool.Value = Tool.None;
LogManager.WriteLog("[ToolPlugin] ✓ 已重置工具为None状态");
}
catch (Exception ex)
{
@ -2703,8 +2687,8 @@ namespace NavisworksTransport
}
catch (Exception ex)
{
LogManager.WriteLog($"[ToolPlugin] 停用异常: {ex.Message}");
LogManager.WriteLog($"[ToolPlugin] 堆栈: {ex.StackTrace}");
LogManager.Error($"[ToolPlugin] 停用异常: {ex.Message}");
LogManager.Error($"[ToolPlugin] 堆栈: {ex.StackTrace}");
return false;
}
}
@ -2762,59 +2746,27 @@ namespace NavisworksTransport
{
try
{
var document = Application.ActiveDocument;
if (document?.Models == null || !document.Models.Any())
// 如果没有选中的可通行区域,自动搜索
if (_walkableAreas == null || _walkableAreas.Count == 0)
{
LogManager.Warning("文档中没有模型");
return null;
LogManager.Info("未找到预定义的可通行区域,尝试自动搜索...");
AutoSelectLogisticsChannels();
}
// 如果有选中的通道,使用通道边界
if (_selectedChannels != null && _selectedChannels.Count > 0)
// 检查是否找到可通行区域
if (_walkableAreas == null || _walkableAreas.Count == 0)
{
LogManager.Info($"使用选中通道边界,通道数量: {_selectedChannels.Count}");
return CalculateChannelsBounds(_selectedChannels);
LogManager.Error("没有找到任何可通行区域(通道、门、电梯、楼梯等)");
throw new InvalidOperationException("无法进行路径规划:模型中没有定义可通行的物流区域。请先设置物流属性。");
}
// 否则使用限制范围的模型边界
LogManager.Info("使用限制范围的模型边界");
var allBounds = new List<BoundingBox3D>();
foreach (var model in document.Models)
{
if (model.RootItem != null)
{
var modelBounds = model.RootItem.BoundingBox();
if (modelBounds != null)
{
allBounds.Add(modelBounds);
}
}
}
if (!allBounds.Any())
{
LogManager.Warning("无法获取任何模型的边界框");
return null;
}
// 计算组合边界
var minX = allBounds.Min(b => b.Min.X);
var minY = allBounds.Min(b => b.Min.Y);
var minZ = allBounds.Min(b => b.Min.Z);
var maxX = allBounds.Max(b => b.Max.X);
var maxY = allBounds.Max(b => b.Max.Y);
var maxZ = allBounds.Max(b => b.Max.Z);
return new BoundingBox3D(
new Point3D(minX, minY, minZ),
new Point3D(maxX, maxY, maxZ)
);
// 使用可通行区域的边界作为路径规划范围
return CalculateChannelsBounds(_walkableAreas);
}
catch (Exception ex)
{
LogManager.Error($"获取模型边界失败: {ex.Message}");
return null;
throw;
}
}
@ -2889,10 +2841,10 @@ namespace NavisworksTransport
}
// 如果用户已选择通道,优先使用用户选择的通道
if (_selectedChannels != null && _selectedChannels.Count > 0)
if (_walkableAreas != null && _walkableAreas.Count > 0)
{
LogManager.Info($"[通道数据] 使用用户选择的通道,数量: {_selectedChannels.Count}");
channelItems.AddRange(_selectedChannels);
LogManager.Info($"[通道数据] 使用用户选择的通道,数量: {_walkableAreas.Count}");
channelItems.AddRange(_walkableAreas);
return channelItems;
}
@ -3018,7 +2970,7 @@ namespace NavisworksTransport
/// <returns>是否在选定通道中</returns>
private bool IsItemInSelectedChannels(ModelItem item)
{
return _selectedChannels.Contains(item) || IsItemChildOfSelectedChannels(item);
return _walkableAreas.Contains(item) || IsItemChildOfSelectedChannels(item);
}
/// <summary>
@ -3028,7 +2980,7 @@ namespace NavisworksTransport
/// <returns>是否为子项</returns>
private bool IsItemChildOfSelectedChannels(ModelItem item)
{
foreach (var channel in _selectedChannels)
foreach (var channel in _walkableAreas)
{
if (IsChildOf(item, channel))
{

View File

@ -703,8 +703,6 @@ namespace NavisworksTransport.Core
_flushInterval, // 最小间隔50ms
10 // 高优先级UI更新很重要
);
LogManager.Debug($"已注册UI更新队列处理到IdleEventManager最小间隔: {_flushInterval}ms");
}
catch (Exception ex)
{

View File

@ -758,17 +758,17 @@ namespace NavisworksTransport.PathPlanning
{
// 可以直线连接,更新最远索引
farthestIndex = testIndex;
LogManager.Debug($"[斜线优化] ✓ 成功连接:点{currentIndex}→点{testIndex},距离={distance:F2}m");
//LogManager.Debug($"[斜线优化] ✓ 成功连接:点{currentIndex}→点{testIndex},距离={distance:F2}m");
}
else
{
// 新增:记录失败详情
var startGrid = gridMap.WorldToGrid(startPoint);
var endGrid = gridMap.WorldToGrid(endPoint);
LogManager.Debug($"[斜线优化] ✗ 连接失败:" +
$"点{currentIndex}[网格({startGrid.X},{startGrid.Y})]→点{testIndex}[网格({endGrid.X},{endGrid.Y})]" +
$"世界坐标:({startPoint.X:F2},{startPoint.Y:F2})→({endPoint.X:F2},{endPoint.Y:F2})" +
$"距离={distance:F2}m跨越{Math.Abs(endGrid.X-startGrid.X)+Math.Abs(endGrid.Y-startGrid.Y)}个网格");
// LogManager.Debug($"[斜线优化] ✗ 连接失败:" +
// $"点{currentIndex}[网格({startGrid.X},{startGrid.Y})]→点{testIndex}[网格({endGrid.X},{endGrid.Y})]" +
// $"世界坐标:({startPoint.X:F2},{startPoint.Y:F2})→({endPoint.X:F2},{endPoint.Y:F2})" +
// $"距离={distance:F2}m跨越{Math.Abs(endGrid.X-startGrid.X)+Math.Abs(endGrid.Y-startGrid.Y)}个网格");
}
// 🔧 关键改进:不像现有算法那样遇到失败就停止,而是继续尝试更远的点
// 这样可以发现更多斜线连接机会
@ -778,7 +778,7 @@ namespace NavisworksTransport.PathPlanning
if (farthestIndex > currentIndex + 1)
{
int skippedPoints = farthestIndex - currentIndex - 1;
LogManager.Info($"[斜线优化] 从点{currentIndex}直连到点{farthestIndex},跳过{skippedPoints}个中间点");
//LogManager.Info($"[斜线优化] 从点{currentIndex}直连到点{farthestIndex},跳过{skippedPoints}个中间点");
}
currentIndex = farthestIndex;

View File

@ -104,13 +104,11 @@ namespace NavisworksTransport.PathPlanning
{
if (gridMap != null)
{
LogManager.Info("[路径优化] 执行基于网格的路径简化");
optimizedPoints = SimplifyGridBasedPath(optimizedPoints, gridMap);
LogManager.Info($"[路径优化] 简化完成,点数:{originalCount} -> {optimizedPoints.Count}");
}
else
{
LogManager.Info("[路径优化] 执行传统共线点简化");
optimizedPoints = SimplifyCollinearPoints(optimizedPoints);
LogManager.Info($"[路径优化] 简化完成,点数:{originalCount} -> {optimizedPoints.Count}");
}
@ -123,13 +121,6 @@ namespace NavisworksTransport.PathPlanning
// optimizedPoints = SmoothPath(optimizedPoints);
}
// 3. 未来扩展:碰撞检测验证
if (_config.EnableCollisionCheck)
{
LogManager.Info("[路径优化] 碰撞检测功能尚未实现");
// ValidatePathCollision(optimizedPoints);
}
// 创建优化后的路径
var optimizedRoute = CreateOptimizedRoute(originalPath, optimizedPoints);
@ -160,26 +151,6 @@ namespace NavisworksTransport.PathPlanning
var worldPath = points.Select(p => p.Position).ToList();
var gridPath = worldPath.Select(p => gridMap.WorldToGrid(p)).ToList();
// 检查重复点(调试用)
int duplicateCount = 0;
for (int i = 1; i < gridPath.Count; i++)
{
if (gridPath[i].Equals(gridPath[i-1]))
{
duplicateCount++;
LogManager.Warning($"[路径优化] 发现重复网格点:索引 {i-1} 和 {i} 都是 ({gridPath[i].X}, {gridPath[i].Y})");
}
}
if (duplicateCount > 0)
{
LogManager.Warning($"[路径优化] 总共发现 {duplicateCount} 个重复网格点,开始去重处理");
}
else
{
LogManager.Info("[路径优化] 未发现重复网格点A*算法输出正常");
}
// 步骤1先去除重复点
var dedupedGridPath = new List<GridPoint2D>();
var dedupedPoints = new List<PathPoint>();
@ -191,15 +162,7 @@ namespace NavisworksTransport.PathPlanning
dedupedPoints.Add(points[i]);
}
}
LogManager.Info($"[路径优化] 去重完成:{gridPath.Count} -> {dedupedGridPath.Count} 个点");
if (dedupedGridPath.Count < 3)
{
LogManager.Info("[路径优化] 去重后点数不足3个直接返回");
return dedupedPoints;
}
// 步骤2基于去重后的网格路径进行方向优化
var simplified = new List<GridPoint2D> { dedupedGridPath[0] };
@ -264,7 +227,6 @@ namespace NavisworksTransport.PathPlanning
var simplified = new List<PathPoint>();
simplified.Add(points[0]); // 添加起点
LogManager.Info($"[共线简化] 开始简化,原始点数:{points.Count}");
int removedCount = 0;
// 遍历中间点,只保留转折点
@ -281,13 +243,11 @@ namespace NavisworksTransport.PathPlanning
{
// 这是一个转折点,需要保留
simplified.Add(currPoint);
LogManager.Debug($"[共线简化] 保留转折点 {i}{currPoint.Name}");
}
else
{
// 这是直线上的冗余点,跳过
removedCount++;
LogManager.Debug($"[共线简化] 移除冗余点 {i}{currPoint.Name}");
}
}
@ -332,8 +292,7 @@ namespace NavisworksTransport.PathPlanning
if (isSegment1Zero || isSegment2Zero)
{
// 🔥 关键修复:只有在真正重复点时才认为共线
LogManager.Debug($"[共线检测] ✅ 包含真正重复点,视为共线:({p1.X:F3},{p1.Y:F3}) -> ({p2.X:F3},{p2.Y:F3}) -> ({p3.X:F3},{p3.Y:F3})");
// 🔥 在真正重复点时才认为共线
return true;
}
@ -348,39 +307,18 @@ namespace NavisworksTransport.PathPlanning
if (isSegment1Horizontal && isSegment2Horizontal)
{
// 检查水平方向是否一致(同向或反向都可以)
bool sameDirection = (dx12 * dx23 > 0) || Math.Abs(dx12) < tolerance || Math.Abs(dx23) < tolerance;
if (sameDirection)
{
LogManager.Debug($"[共线检测] ✅ 水平共线:({p1.X:F3},{p1.Y:F3}) -> ({p2.X:F3},{p2.Y:F3}) -> ({p3.X:F3},{p3.Y:F3})");
return true;
}
else
{
LogManager.Debug($"[共线检测] ❌ 水平线段方向相反dx12={dx12:F3}, dx23={dx23:F3}");
return false;
}
return (dx12 * dx23 > 0) || Math.Abs(dx12) < tolerance || Math.Abs(dx23) < tolerance;
}
else if (isSegment1Vertical && isSegment2Vertical)
{
// 检查垂直方向是否一致(同向或反向都可以)
bool sameDirection = (dy12 * dy23 > 0) || Math.Abs(dy12) < tolerance || Math.Abs(dy23) < tolerance;
if (sameDirection)
{
LogManager.Debug($"[共线检测] ✅ 垂直共线:({p1.X:F3},{p1.Y:F3}) -> ({p2.X:F3},{p2.Y:F3}) -> ({p3.X:F3},{p3.Y:F3})");
return true;
}
else
{
LogManager.Debug($"[共线检测] ❌ 垂直线段方向相反dy12={dy12:F3}, dy23={dy23:F3}");
return false;
}
return (dy12 * dy23 > 0) || Math.Abs(dy12) < tolerance || Math.Abs(dy23) < tolerance;
}
else
{
// 🔥 关键:不同类型的线段(一个水平一个垂直,或包含斜线)一律拒绝
string seg1Type = isSegment1Horizontal ? "水平" : (isSegment1Vertical ? "垂直" : "斜线");
string seg2Type = isSegment2Horizontal ? "水平" : (isSegment2Vertical ? "垂直" : "斜线");
LogManager.Debug($"[共线检测] ❌ 线段类型不匹配线段1={seg1Type}({dx12:F3},{dy12:F3}), 线段2={seg2Type}({dx23:F3},{dy23:F3})");
return false;
}
}

View File

@ -82,9 +82,7 @@ namespace NavisworksTransport.Utils
{
var nodeType = GetModelItemType(current);
var displayName = GetSafeDisplayName(current);
LogManager.Debug($"[容器查找] 检查节点: '{displayName}' (类型:{nodeType}, 层级:{levels})");
// 根据节点类型决策
switch (nodeType)
{
@ -93,19 +91,11 @@ namespace NavisworksTransport.Utils
// 集合节点或混合节点:有意义的容器,停止查找
if (!string.IsNullOrEmpty(displayName))
{
LogManager.Debug($"[智能容器映射] 找到有意义容器: '{GetSafeDisplayName(geometryItem)}' -> '{displayName}' (向上{levels}层, 类型:{nodeType})");
return current;
}
else
{
LogManager.Debug($"[容器查找] {nodeType}节点无名称,继续向上查找");
}
break;
case ModelItemType.PureGeometry:
case ModelItemType.EmptyNode:
// 纯几何体或空节点:通常无意义,需要继续向上查找父节点
LogManager.Debug($"[容器查找] {nodeType}节点,向上查找父节点");
break;
}
@ -230,22 +220,18 @@ namespace NavisworksTransport.Utils
if (hasGeometry && childCount == 0)
{
LogManager.Debug($"[节点类型判断] '{item.DisplayName}' -> 纯几何体节点");
return ModelItemType.PureGeometry;
}
else if (!hasGeometry && childCount > 0)
{
LogManager.Debug($"[节点类型判断] '{item.DisplayName}' -> 集合节点 (子节点数: {childCount})");
return ModelItemType.GroupNode;
}
else if (hasGeometry && childCount > 0)
{
LogManager.Debug($"[节点类型判断] '{item.DisplayName}' -> 混合节点 (子节点数: {childCount})");
return ModelItemType.HybridNode;
}
else
{
LogManager.Debug($"[节点类型判断] '{item.DisplayName}' -> 空节点");
return ModelItemType.EmptyNode;
}
}