

在 Navisworks 2017 中进行插件开发，若想在 3D 空间中的特定模型（如一个通道）上方绘制彩色圆和数字标记，即便日志提示已绘制但屏幕上却看不到，这通常涉及几个关键概念：**正确的插件类型、图形绘制接口、坐标转换和场景刷新**。

本文将深入探讨实现这一功能的具体方法，提供详细的 C# 代码示例，并着重解决“绘制后不可见”这一常见问题。

### 核心组件解析

要成功在 Navisworks 3D 视图中添加自定义图形，您需要了解并使用以下几个核心 API 组件：

1. **插件类型 (Plugin Types)**:
    
    - `AddinPlugin`: 这是最基础的插件类型，通常用于执行一次性操作，例如通过点击按钮来触发。对于动态、持续的图形绘制，`AddinPlugin` 可以用来“启动”或“关闭”一个绘制过程，但它本身不负责每一帧的渲染。
    - `RenderPlugin`: 这是专门用于在 3D 视图中进行自定义绘制的插件类型。它会随着视角的改变而不断重绘。`RenderPlugin` 提供了 `Render()` 和 `OverlayRender()` 两个核心方法，分别用于绘制 3D 场景中的几何体和 2D 屏幕覆盖层。**这是解决您问题的关键所在**。
2. **图形绘制接口 (`Autodesk.Navisworks.Api.Graphics`)**:
    
    - 这个类提供了所有绘制操作所需的方法，例如 `Color()`、`Circle()`、`Line()` 和 `Text3D()` 等。
    - 绘制 3D 图形时，您需要提供 `Point3D`（三维空间点）作为坐标。
    - 绘制 2D 覆盖图形（如HUD）时，您会使用 `Point2D`（二维屏幕点），并且通常需要将绘制代码包裹在 `graphics.BeginWindowContext()` 和 `graphics.EndWindowContext()` 之间。
3. **坐标系统与转换**:
    
    - **世界坐标 (World Coordinates)**: Navisworks 模型所在的 3D 空间坐标。`ModelItem.BoundingBox` 返回的就是这种坐标。
    - **屏幕坐标 (Screen Coordinates)**: 2D 屏幕上的像素坐标，原点通常在左上角或左下角。
    - 要将 3D 世界坐标点转换为 2D 屏幕坐标，可以使用 `Autodesk.Navisworks.Api.View.ProjectPoint()` 方法。这对于在 3D 对象的屏幕位置上绘制 2D 文本非常有用。
4. **模型信息获取 (`Autodesk.Navisworks.Api.ModelItem`)**:
    
    - 您需要获取要标记的模型对象。这通常通过用户的当前选择 (`Application.ActiveDocument.CurrentSelection.SelectedItems`) 来实现。
    - `ModelItem.BoundingBox` 属性至关重要，它提供了一个包围盒 (`BoundingBox3D`)，您可以从中获取模型的中心点 (`.Center`) 或最高点 (`.Max.Z`)，作为绘制图形的基准位置。
### 解决“绘制后不可见”的常见原因

您提到日志显示已绘制，但屏幕上看不到，这通常是以下一个或多个原因造成的：

1. **未使用 `RenderPlugin`**: 如果您在 `AddinPlugin` 的 `Execute` 方法中直接绘制，代码确实会执行一次，但 Navisworks 不会在下一次屏幕刷新时重绘您的图形。当您移动视角时，您绘制的内容会立即消失。正确的做法是创建一个 `RenderPlugin`，Navisworks 会在每次渲染场景时自动调用它的 `Render` 方法。
    
2. **未激活 `RenderPlugin`**: 仅仅定义一个 `RenderPlugin` 是不够的。您需要通过代码激活它。通常的做法是，在 `AddinPlugin` 中提供一个按钮，点击后将您的 `RenderPlugin` 实例添加到 `Application.Plugins.RenderPlugins` 集合中。
    
3. **坐标问题**:
    
    - **3D 绘制**: 确保您提供的 `Point3D` 坐标位于当前相机的可见范围内。如果坐标过大或过小，或者被其他模型遮挡，图形将不可见。
    - **2D 绘制**: 如果使用 `Text2D`，其坐标是屏幕像素坐标。一个常见的错误是直接使用模型的 3D 世界坐标，这会导致文本被绘制在屏幕范围之外。您必须先用 `View.ProjectPoint()` 将 3D 点转换为 2D 点。
4. **未强制刷新视图**: 在某些情况下，尤其是在 `AddinPlugin` 中进行一次性绘制操作后，您可能需要通知 Navisworks 刷新视图。虽然对于 `RenderPlugin` 这不是必需的，但在其他场景下，`Application.ActiveDocument.ActiveView.RequestDelayedRedraw(View.RedrawHint.All)` 可能会有所帮助。
    
5. **Z-Fighting (Z冲突)**: 如果您绘制的图形与模型表面过于接近，可能会发生Z冲突，导致图形闪烁或被模型表面覆盖。解决方案是将图形的Z坐标略微抬高一点。
    

### 详细代码实现

以下是一个完整的解决方案，包含两个部分：

1. 一个 `AddinPlugin`，用于提供一个按钮来开启和关闭标记显示。
2. 一个 `RenderPlugin`，负责实际的绘制工作。

#### 第1步: 创建 `RenderPlugin`

这个插件将负责绘制一个彩色的圆和一个数字。
using Autodesk.Navisworks.Api;
using Autodesk.Navisworks.Api.Plugins;

namespace NavisworksDrawingPlugin
{
    // RenderPlugin 负责在3D场景中进行持续绘制
    [Plugin("ColorCircleRenderPlugin", "YourCompany", DisplayName = "Color Circle Renderer")]
    public class ColorCircleRenderPlugin : RenderPlugin
    {
        public override void Render(View view, Graphics graphics)
        {
            // 如果当前没有选择任何模型，则不进行绘制
            if (Application.ActiveDocument.CurrentSelection.SelectedItems.Count == 0)
            {
                return;
            }

            // 获取当前选中的第一个模型
            ModelItem selectedItem = Application.ActiveDocument.CurrentSelection.SelectedItems.First;
            
            // 1. 计算绘制位置
            // 获取模型的包围盒
            BoundingBox3D bbox = selectedItem.BoundingBox;
            if (bbox == null) return;
            
            // 计算模型顶部中心点
            Point3D centerTop = new Point3D(
                bbox.Center.X,
                bbox.Center.Y,
                bbox.Max.Z 
            );

            // 稍微抬高一点，防止Z冲突
            double offset = (bbox.Max.Z - bbox.Min.Z) * 0.1; // 向上偏移包围盒高度的10%
            Point3D drawPosition = centerTop.Add(new Vector3D(0, 0, offset));
            
            // 2. 绘制彩色的圆 (3D)
            // 设置颜色 (例如：红色) 和透明度
            graphics.Color(Autodesk.Navisworks.Api.Color.Red, 0.8);

            // 绘制一个面向屏幕的3D圆
            // Vector3D.CrossProduct 计算两个向量的叉积，得到一个垂直于当前视线方向的向量，用于定义圆平面
            Vector3D viewDirection = view.GetCamera().GetDirection().Negate();
            Vector3D upVector = view.GetCamera().GetUp().Normalize();
            Vector3D rightVector = viewDirection.CrossProduct(upVector).Normalize();
            
            double radius = (bbox.Max.X - bbox.Min.X) / 4; // 圆的半径为包围盒宽度的1/4
            if (radius <= 0) radius = 1.0; // 防止半径为0

            // 绘制一个填充的3D圆，通过构建多边形实现
            int segments = 32; // 圆的平滑度
            Point3D[] circlePoints = new Point3D[segments];
            for (int i = 0; i < segments; i++)
            {
                double angle = 2 * System.Math.PI * i / segments;
                Vector3D r_vec = rightVector.Multiply(System.Math.Cos(angle) * radius);
                Vector3D u_vec = upVector.Multiply(System.Math.Sin(angle) * radius);
                circlePoints[i] = drawPosition.Add(r_vec).Add(u_vec);
            }
            graphics.BeginPolygon();
            foreach(Point3D p in circlePoints)
            {
                graphics.Vertex(p);
            }
            graphics.EndPolygon();

            // 3. 绘制数字标记 (3D)
            // 设置文本颜色 (例如：白色)
            graphics.Color(Autodesk.Navisworks.Api.Color.White);
            
            // 定义3D文本
            // 为了让文本始终面向相机，我们需要计算文本的基准向量
            graphics.Text3D(drawPosition, "1", rightVector, upVector);
        }

        // OverlayRender 用于绘制2D覆盖层，如果需要也可以在这里绘制
        public override void OverlayRender(View view, Graphics graphics)
        {
            // 如果您希望文本是2D的，并且大小固定，可以使用 OverlayRender
            // 这种方式更适合显示不随缩放变化的UI信息
            /*
            if (Application.ActiveDocument.CurrentSelection.SelectedItems.Count == 0) return;

            ModelItem selectedItem = Application.ActiveDocument.CurrentSelection.SelectedItems.First;
            BoundingBox3D bbox = selectedItem.BoundingBox;
            if (bbox == null) return;

            Point3D centerTop = new Point3D(bbox.Center.X, bbox.Center.Y, bbox.Max.Z);
            
            // 将3D世界坐标转换为2D屏幕坐标
            ProjectionResult projection = view.ProjectPoint(centerTop, false, true);
            
            if (projection != null && !projection.IsBehind)
            {
                Point2D screenPoint = new Point2D(projection.X, projection.Y);
                
                // 开始2D绘制
                graphics.BeginWindowContext();

                // 绘制2D文本
                graphics.Color(Autodesk.Navisworks.Api.Color.Blue);
                graphics.Text2D(screenPoint, "ID: 123");

                // 结束2D绘制
                graphics.EndWindowContext();
            }
            */
        }
    }
}

#### 第2步: 创建 `AddinPlugin` 来控制 `RenderPlugin`

这个插件会创建一个按钮，点击后可以启动或停止 `RenderPlugin` 的渲染。

using Autodesk.Navisworks.Api;
using Autodesk.Navisworks.Api.Plugins;
using System.Linq;

namespace NavisworksDrawingPlugin
{
    [Plugin("ToggleDrawingPlugin", "YourCompany", ToolTip = "Toggle the color circle drawing", DisplayName = "Toggle Circle")]
    [AddInPlugin(AddInLocation.AddIn, CanToggle = true)]
    public class ToggleDrawingPlugin : AddInPlugin
    {
        private static RenderPlugin _renderPluginInstance;
        private static bool _isDrawingActive = false;

        public override int Execute(params string[] parameters)
        {
            // 切换绘制状态
            _isDrawingActive = !_isDrawingActive;

            if (_isDrawingActive)
            {
                // 如果是首次激活，则创建RenderPlugin实例
                if (_renderPluginInstance == null)
                {
                    _renderPluginInstance = new ColorCircleRenderPlugin();
                }

                // 将RenderPlugin实例添加到活动插件中，使其生效
                // 检查确保不重复添加
                if (!Application.Plugins.RenderPlugins.Contains(_renderPluginInstance))
                {
                    Application.Plugins.RenderPlugins.Add(_renderPluginInstance);
                }
            }
            else
            {
                // 如果存在实例，则从活动插件中移除，停止绘制
                if (_renderPluginInstance != null && Application.Plugins.RenderPlugins.Contains(_renderPluginInstance))
                {
                    Application.Plugins.RenderPlugins.Remove(_renderPluginInstance);
                }
            }

            // 强制重绘所有视图以立即看到变化
            Application.ActiveDocument?.ActiveView?.RequestDelayedRedraw(View.RedrawHint.All);

            return 0;
        }
    }
}

### 部署和使用

1. **编译项目**: 在 Visual Studio 中编译您的项目，生成 DLL 文件。
2. **放置插件**: 将生成的 DLL 文件复制到 Navisworks 2017 的 `Plugins` 目录下。通常路径为 `C:\Program Files\Autodesk\Navisworks Manage 2017\Plugins`。
3. **启动 Navisworks**: 启动 Navisworks 并打开一个模型。
4. **使用**:
    - 在 3D 视图中选择一个模型（例如，一个通道）。
    - 转到 "Add-ins" (或“附加模块”) 选项卡，您会看到一个名为 "Toggle Circle" 的按钮。
    - 点击此按钮，您应该能看到在所选模型上方的指定位置出现了红色的圆和白色的数字 "1"。
    - 再次点击该按钮，图形将会消失。
    - 移动或旋转视角，您会发现圆和数字始终面向您。

通过这种 `AddinPlugin` + `RenderPlugin` 的组合模式，您可以有效地控制自定义图形的生命周期，并确保它们在正确的上下文中被正确、持续地渲染，从而解决“只执行不显示”的难题。
