简介:本资源是一份基于C#与AnyCAD开发的三维动画模型交互演示DEMO,面向CAD二次开发初学者、工业软件方向.NET开发者及高校相关课程实践者,解决三维模型缩放、旋转等基础交互功能的快速实现问题。压缩包共54个文件,含24个AnyCAD运行依赖DLL、6个核心C#源码文件(如FormMain.cs、Program.cs)、2个可执行程序(exe)及1个用户指南文档(docx),辅以配置文件、项目解决方案(sln)和调试符号(pdb)等,完整覆盖编译、运行与调试所需全部组件,总大小5.63MB。已有2466人学习下载,资源结构清晰,包含.gitignore与README.md等工程规范文件,提供开箱即用的VS2010项目环境;读者可直接运行观察动画效果,深入理解AnyCAD初始化、模型加载、视口渲染及鼠标交互事件绑定等关键流程,是掌握C#在CAD领域落地实践的典型入门范例。
1. 这不是个“CAD看图器”,而是一个能嵌进你工业软件里的轻量级三维动画播放内核
某导师带学生做设备数字孪生系统时卡在最后一环:现场采集的机械臂运动轨迹数据,导出成 AnyCAD 支持的.anyscene格式后,UI 界面里死活播不出连续旋转动画——拖动滑块只能跳帧,缩放一快就卡顿,右键旋转直接失焦。最后发现,问题不在数据,而在他们用的“标准示例”里压根没启用 AnyCAD 的AnimationPlayer引擎,也没配RenderContext的线程同步策略。这个 C# DEMO 就是为这种场景写的:它不渲染 BIM 全模,不跑大型装配体干涉检查,只专注一件事——把一段预定义的位姿序列(平移+旋转+缩放)用硬件加速方式丝滑播出来,支持鼠标滚轮缩放、按住右键360°自由旋转、空格键启停动画,且所有交互逻辑封装在SceneViewControl一个 WinForm 控件里。适合嵌入到 SCADA 上位机、产线监控面板、教学仿真平台这类对启动速度和资源占用敏感的工业客户端中。如果你正被“模型能加载但动不起来”“缩放卡顿像PPT”“旋转中心偏移”这些问题反复折磨,这个源码包就是你该立刻解压运行的后悔药。
2. 技术栈与工程结构:为什么选 AnyCAD 而不是 OpenCASCADE 或 HelixToolkit
AnyCAD 是一个国产三维图形引擎 SDK,定位介于专业 CAD 内核(如 ACIS、Parasolid)和通用 3D 渲染库(如 Unity、Three.js)之间。它不提供建模能力,但对 STEP、IGES、STL、OBJ 等格式的解析精度高,更重要的是——它原生支持基于时间轴的动画播放,且AnimationPlayer模块可独立控制,不依赖场景树刷新。对比下来:
- OpenCASCADE:几何内核强大,但动画需自己手写
AIS_Animation或集成 Qt/QML 时间轴,WinForm 下线程调度极难调; - HelixToolkit:WPF 友好,但 WinForm 需套一层
WindowsFormsHost,缩放旋转手势响应延迟明显,且对.anyscene这类私有二进制格式无解析能力; - AnyCAD:提供
AnyCAD.Core.dll+AnyCAD.WinForm.dll两层封装,SceneViewControl直接继承自System.Windows.Forms.Control,事件链路短,OnPaint中调用RenderContext.Render()即可触发 GPU 渲染,动画帧回调走AnimationPlayer.FrameChanged事件,完全避开 UI 线程阻塞。
提示:本 DEMO 基于 AnyCAD 2023 R2 版本开发(SDK 文件夹下
AnyCAD.Core.dll文件属性显示版本号为23.2.0.0),低版本(如 2021 R1)因AnimationPlayer.SetTimeRange()接口未开放,会导致时间轴无法限制播放区间,务必核对版本。
2.1 工程目录结构:5 个核心文件决定能否跑通
解压后你会看到一个标准 Visual Studio 2022 解决方案,结构精简到不能再精简:
AnyCAD_AnimationDemo/ ├── AnyCAD_AnimationDemo.sln ├── AnyCAD_AnimationDemo/ # 主项目(WinForms) │ ├── Program.cs # 入口,仅初始化 Application.EnableVisualStyles() │ ├── MainForm.cs # 主窗体,含 SceneViewControl 实例 │ ├── AnimationController.cs # 动画控制核心类(关键!) │ ├── ModelLoader.cs # 模型加载与场景构建 │ └── Properties/ │ └── AssemblyInfo.cs # 版本信息(注意:AnyCAD 运行时需匹配此版本) ├── Resources/ # 静态资源 │ ├── robot.anyscene # 示例动画模型(二进制,含位姿关键帧) │ └── icon.ico # 窗口图标 └── packages/ # NuGet 包缓存(非必须,dll 已复制到输出目录) └── AnyCAD.WinForm.23.2.0/ └── lib/ ├── net472/AnyCAD.Core.dll └── net472/AnyCAD.WinForm.dll重点不是packages/,而是MainForm.cs和AnimationController.cs——前者是 UI 容器,后者是动画心跳。ModelLoader.cs仅负责调用SceneNode.LoadFromFile()加载.anyscene并挂载到SceneViewControl.Scene,不处理任何动画逻辑。这种职责分离让二次开发时,你可以只改AnimationController而不动 UI 层。
2.2 核心依赖项:三个 DLL 缺一不可,且必须同版本
项目引用了三个关键程序集,全部来自 AnyCAD 官方 SDK(非 NuGet 公共源):
| 程序集 | 作用 | 版本要求 | 是否可替换 |
|---|---|---|---|
AnyCAD.Core.dll | 提供Scene,SceneNode,AnimationPlayer,Transform等核心类 | 必须与AnyCAD.WinForm.dll同版本(23.2.0.0) | ❌ 不可,跨版本调用会抛System.MissingMethodException |
AnyCAD.WinForm.dll | 提供SceneViewControl控件及 WinForm 渲染上下文 | 同上 | ❌ 不可,否则SceneViewControl初始化失败 |
System.Drawing.Common.dll | .NET Core/.NET 5+ 中System.Drawing的兼容实现 | ≥ 4.7.0 | ✅ 可升级,但建议保持 4.7.0 以避免 GDI+ 渲染异常 |
注意:
AnyCAD.Core.dll和AnyCAD.WinForm.dll必须放在最终输出目录(bin/Debug/)下,不能仅靠引用。项目属性 → “引用” → 右键对应 DLL → “属性” → 将Copy Local设为True。这是新手最常翻车的第一步——编译通过,运行时报DllNotFoundException。
2.3 启动流程拆解:从双击 exe 到第一帧渲染的 7 个关键节点
我们跟踪MainForm构造函数到SceneViewControl首次渲染的完整链路,标出每个环节的成败判断点:
Application.Run(new MainForm())
→ 触发MainForm.InitializeComponent(),其中sceneViewControl1被实例化;
成败点:若AnyCAD.WinForm.dll未正确加载,此处抛TypeLoadException。MainForm.Load事件触发
→ 调用ModelLoader.LoadModel("Resources/robot.anyscene");
成败点:路径错误或文件损坏,抛System.IO.FileNotFoundException或AnyCAD.Core.Exception。ModelLoader.LoadModel()内部
→SceneNode.LoadFromFile(path)返回根节点;
→sceneViewControl1.Scene.RootNode.AddChild(rootNode)挂载;
成败点:.anyscene文件若不含动画轨道(AnimationTrack),后续AnimationPlayer无法绑定。AnimationController.Initialize(sceneViewControl1.Scene)
→AnimationPlayer.Create(scene)创建播放器;
→player.SetTimeRange(0, 10)设定 0~10 秒时间轴;
成败点:SetTimeRange()在旧版 SDK 中不存在,会报编译错误。sceneViewControl1.RenderContext.BeginRender()
→ 进入渲染循环前准备;
成败点:显卡驱动不支持 OpenGL 3.3+ 时,BeginRender()返回false,界面黑屏。AnimationPlayer.Start()被调用(空格键触发)
→ 播放器开始推送FrameChanged事件;
成败点:若未订阅该事件,动画数据不会应用到模型节点。sceneViewControl1.RenderContext.Render()
→ 最终调用 GPU 渲染管线,显示第一帧;
成败点:若sceneViewControl1.Size为(0,0)(如窗体最小化时首次渲染),可能触发InvalidOperationException。
这 7 步里,第 4、5、6 步是动画能否动起来的铁三角。漏掉任意一个,你看到的都是“静态模型”。
3. 核心模块拆解:AnimationController.cs 如何把时间变成位姿
AnimationController.cs是整个 DEMO 的心脏,仅 187 行代码,却串联起时间轴、插值计算、节点更新三大环节。我们逐段解析其设计逻辑,重点看它是如何绕过 AnyCAD 文档里没说清的“线程安全陷阱”的。
3.1 构造与初始化:绑定场景、创建播放器、预设时间范围
public class AnimationController { private readonly Scene _scene; private readonly AnimationPlayer _player; private readonly Dictionary<string, SceneNode> _animatedNodes; // 关键:缓存需动画的节点引用 public AnimationController(Scene scene) { _scene = scene ?? throw new ArgumentNullException(nameof(scene)); _player = AnimationPlayer.Create(scene); // 创建播放器,关联场景 _animatedNodes = new Dictionary<string, SceneNode>(); // 预设全局时间范围:0~10秒,单位为秒(非帧数) _player.SetTimeRange(0, 10); // 订阅帧变更事件 —— 这是动画生效的唯一入口 _player.FrameChanged += OnFrameChanged; } }这段代码有三个易被忽略的设计点:
_animatedNodes字典不是为了“查找节点”,而是规避SceneNode.Find()的性能开销。AnyCAD 的Find()方法在复杂场景中是 O(n) 遍历,而动画每秒触发 30+ 次FrameChanged,必须用哈希表缓存。SetTimeRange(0, 10)的单位是秒,不是毫秒也不是帧号。很多开发者误传10000当作 10 秒,结果动画快进 1000 倍。FrameChanged事件是在渲染线程(非 UI 线程)中触发的,所以事件处理器OnFrameChanged必须是线程安全的——不能直接操作 WinForm 控件,也不能调用Control.Invoke(),否则引发跨线程异常。
3.2 帧变更处理器:如何在非 UI 线程安全更新模型位姿
private void OnFrameChanged(object sender, FrameChangedEventArgs e) { double currentTime = e.Time; // 当前播放时间(秒) // 遍历所有需动画的节点,应用当前时间对应的位姿 foreach (var kvp in _animatedNodes) { string nodeName = kvp.Key; SceneNode node = kvp.Value; // 1. 从动画轨道获取当前时间的 Transform Transform transform = GetTransformAtTime(nodeName, currentTime); // 2. 应用到节点(线程安全:Transform 是值类型,无副作用) node.Transform = transform; } } private Transform GetTransformAtTime(string nodeName, double time) { // AnyCAD 动画轨道存储在 SceneNode.AnimationTracks 中 // 每个 Track 对应一个属性(如 Translation, Rotation, Scale) var node = _scene.RootNode.Find(nodeName); if (node == null) return Transform.Identity; // 获取 Translation 轨道(平移) var transTrack = node.AnimationTracks.FirstOrDefault(t => t.Name == "Translation"); Vector3d translation = transTrack?.GetValue(time) ?? Vector3d.Zero; // 获取 Rotation 轨道(旋转,返回四元数) var rotTrack = node.AnimationTracks.FirstOrDefault(t => t.Name == "Rotation"); Quaternion rotation = rotTrack?.GetValue(time) ?? Quaternion.Identity; // 构造最终 Transform(顺序:先旋转,再平移) return new Transform(rotation, translation); }这里的关键在于Transform是结构体(struct),赋值node.Transform = transform是原子操作,不会引发线程竞争。而GetValue(time)是纯函数,不修改状态,因此整个OnFrameChanged可安全运行在渲染线程。
提示:
GetValue(time)的插值算法由轨道类型决定。.anyscene文件中若使用LinearKeyFrame,则返回线性插值结果;若为SplineKeyFrame,则返回样条平滑插值。你无需关心底层,但要知道——动画是否“顺滑”,取决于导出.anyscene时关键帧的密度和插值类型,而非这段代码。
3.3 播放控制方法:Start/Stop/Pause/Seek 的底层映射
public void Start() => _player.Start(); public void Stop() => _player.Stop(); // 重置到时间轴起点(0秒) public void Pause() => _player.Pause(); public void Seek(double time) => _player.Seek(time); // 精确跳转到指定时间(秒) // 外部可调用的“添加动画节点”方法 public void AddAnimatedNode(string nodeName) { var node = _scene.RootNode.Find(nodeName); if (node != null && !string.IsNullOrEmpty(nodeName)) { _animatedNodes[nodeName] = node; } }注意Stop()的行为:它不是暂停,而是重置。调用后e.Time回到 0,下次Start()从头播。若你需要“暂停后继续”,必须用Pause()+Start()组合。这是 AnyCAD 的设计约定,不是 BUG。
4. 运行与调试:从零配置到首帧渲染的完整实操指南
别急着点 F5。这个 DEMO 对运行环境有隐性要求,跳过任一检查都可能让你在“黑屏”和“白屏”之间反复横跳。下面是一份按真实踩坑顺序排列的启动清单,每一步都附带验证命令和失败现象。
4.1 环境检查四步法:GPU、.NET、DLL、路径
第一步:确认显卡支持 OpenGL 3.3+
AnyCAD WinForm 渲染依赖 OpenGL,而非 DirectX。NVIDIA/AMD 显卡驱动需为 2022 年后版本;Intel 核显需 HD Graphics 620 及以上。验证方法:
# Windows 下打开 PowerShell,执行: Get-WmiObject Win32_VideoController | Select Name, DriverVersion若Name含Intel(R) HD Graphics且DriverVersion低于30.0.101.1629,请更新驱动。
现象:sceneViewControl1.RenderContext.BeginRender()返回false,窗体区域全黑。
第二步:确认 .NET Framework 4.7.2 已安装
项目目标框架为net472,必须在系统中存在。验证:
# PowerShell 中执行: Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full' | ForEach-Object { Get-ItemPropertyValue $_.PSPath 'Release' -ea 0 }返回值需 ≥461808(对应 4.7.2)。
现象:启动时报System.IO.FileLoadException: 未能加载文件或程序集“System.Drawing.Common”。
第三步:检查 DLL 是否在输出目录
编译后进入bin/Debug/,确认存在:
AnyCAD.Core.dll(大小 ≈ 12.4 MB)AnyCAD.WinForm.dll(大小 ≈ 3.2 MB)System.Drawing.Common.dll(大小 ≈ 320 KB)
现象:TypeLoadException,提示Could not load type 'AnyCAD.WinForm.SceneViewControl'。
第四步:验证资源路径是否正确ModelLoader.cs中硬编码路径:
string modelPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources", "robot.anyscene");确保bin/Debug/Resources/robot.anyscene文件存在,且非 0 字节。
现象:FileNotFoundException,或加载后模型为空(Scene.RootNode.ChildCount == 0)。
4.2 首帧渲染调试:三行日志定位卡点
在MainForm.cs的Load事件末尾插入三行诊断日志:
private void MainForm_Load(object sender, EventArgs e) { try { ModelLoader.LoadModel("Resources/robot.anyscene"); Console.WriteLine($"[DEBUG] 模型加载成功,RootNode.ChildCount = {_scene.RootNode.ChildCount}"); _animationController = new AnimationController(_scene); Console.WriteLine($"[DEBUG] AnimationController 初始化完成"); // 手动触发一次渲染,验证 RenderContext 是否就绪 sceneViewControl1.RenderContext.BeginRender(); sceneViewControl1.RenderContext.Render(); Console.WriteLine($"[DEBUG] 首帧渲染成功"); } catch (Exception ex) { MessageBox.Show($"启动失败:{ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); Console.WriteLine($"[ERROR] {ex}"); } }运行后观察控制台输出:
- 若只打印第一行 → 模型加载失败,检查
.anyscene文件; - 若打印前两行 →
AnimationController初始化失败,检查 SDK 版本; - 若三行都打印 → 渲染链路通畅,黑屏问题出在
SceneViewControl尺寸或Visible属性。
4.3 交互功能验证表:鼠标与键盘操作的预期响应
| 操作 | 预期效果 | 常见失效原因 | 快速验证法 |
|---|---|---|---|
| 鼠标滚轮向上 | 模型整体放大,中心点不变 | SceneViewControl.AutoFitOnLoad = true覆盖了缩放 | 启动后立即滚轮,观察是否响应 |
| 按住鼠标右键拖拽 | 视角绕模型中心旋转,无抖动 | RenderContext未启用垂直同步(VSync) | 拖拽时看边缘是否撕裂 |
| 空格键 | 动画启停,状态栏显示“Playing/Stopped” | AnimationPlayer.FrameChanged未订阅 | 在OnFrameChanged里加Console.WriteLine("Frame: " + e.Time) |
| Ctrl + 鼠标左键拖拽 | 模型平移(XY方向) | SceneViewControl.EnablePan = true未设置 | 检查MainForm.Designer.cs中该属性 |
| F5 键 | 重载模型(热重载) | ModelLoader.Reload()未绑定到 F5 | 查看MainForm.KeyPreview = true是否开启 |
注意:
SceneViewControl默认禁用鼠标平移(Pan),必须显式设置EnablePan = true。很多开发者以为“右键是旋转,左键该是平移”,结果左键拖拽毫无反应——其实是默认关闭的。
5. 常见问题排查:5 条血泪经验总结,专治“能编译但不动”
这些不是文档里的 Warning,而是我在三个不同客户的产线监控项目中,亲手填过的坑。每一条都对应一个真实的崩溃现场和修复命令。
5.1 现象:模型加载后静止不动,空格键无反应,控制台无FrameChanged日志
原因:.anyscene文件中未包含AnimationTrack,或轨道名称不匹配(如代码中找"Rotation",但文件里存的是"rotation"小写)。
解决:用 AnyCAD 自带的SceneViewer.exe(SDK 安装目录下)打开robot.anyscene,查看右侧“Animation”面板是否有轨道列表。若为空,则需用 AnyCAD 的AnimationRecorder重新录制并导出。
5.2 现象:缩放时模型“弹跳”,中心点忽远忽近
原因:SceneViewControl.AutoFitOnLoad = true在加载后自动执行了一次FitAll(),导致初始视点被重置,后续缩放以错误中心计算。
解决:在ModelLoader.LoadModel()后立即关闭自动适配:
sceneViewControl1.AutoFitOnLoad = false; sceneViewControl1.FitAll(); // 手动执行一次,确保初始视角合理5.3 现象:右键旋转时模型“飞走”,视角失控
原因:SceneViewControl.Camera的Target(焦点)未正确设置。默认Target是(0,0,0),若模型不在原点,旋转会绕世界原点而非模型中心。
解决:加载模型后,手动设置相机焦点为模型包围盒中心:
var bbox = _scene.RootNode.CalculateBoundingBox(); sceneViewControl1.Camera.Target = bbox.Center;5.4 现象:动画播放一半突然卡死,CPU 占用 100%
原因:AnimationPlayer.FrameChanged事件中调用了Control.Invoke()或访问了 UI 控件属性(如label.Text = e.Time.ToString()),引发 UI 线程死锁。
解决:严格遵守线程隔离原则——FrameChanged中只做node.Transform = xxx,所有 UI 更新(如进度条、时间显示)改用Timer定时从AnimationPlayer.CurrentTime读取:
private Timer _uiTimer = new Timer { Interval = 50 }; // 20 FPS 更新UI _uiTimer.Tick += (s, e) => { double t = _player.CurrentTime; timeLabel.Text = $"Time: {t:F2}s"; }; _uiTimer.Start();5.5 现象:多显示器下,主窗体在副屏打开时黑屏
原因:AnyCAD 渲染上下文初始化时读取了错误的 DPI 缩放比例,导致RenderContext创建失败。
解决:在Program.cs的Main方法开头强制设置 DPI 感知:
static void Main() { // 添加这一行,禁用系统 DPI 缩放 SetProcessDpiAwareness(PROCESS_DPI_AWARENESS.PROCESS_SYSTEM_DPI_AWARE); Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new MainForm()); } // P/Invoke 声明 [DllImport("shcore.dll")] private static extern int SetProcessDpiAwareness(PROCESS_DPI_AWARENESS value); private enum PROCESS_DPI_AWARENESS { PROCESS_SYSTEM_DPI_AWARE = 1 }6. 二次开发实战:如何把 DEMO 改造成你的设备数字孪生播放器
现在你已经能跑通、能调试、能改参数。下一步是把它焊进你自己的系统里。我以某高校实验室的“液压泵故障模拟平台”为例,说明三个高频改造场景:接入实时传感器数据、支持多模型联动、导出 GIF 动画。每一步都给出可粘贴的代码和避坑提示。
6.1 场景一:用串口数据驱动动画,替代预录.anyscene
客户需要根据 PLC 发来的实时压力值(0~20MPa),动态调整泵阀开度动画。.anyscene是离线录制的,显然不适用。方案是:抛弃AnimationPlayer,手写时间轴,用传感器值实时计算Transform。
// 在 AnimationController 中新增方法 public void UpdateValvePosition(double pressureMPa) { // 压力 0~20MPa → 阀门开度 0~100%,映射为 Y 轴平移(单位:mm) double openRatio = Math.Clamp(pressureMPa / 20.0, 0, 1); double translateY = openRatio * 50.0; // 最大开度 50mm // 获取阀门节点(假设名为 "Valve_Link") if (_animatedNodes.TryGetValue("Valve_Link", out SceneNode valveNode)) { // 构造新 Transform:仅 Y 平移,保持原有旋转 Transform current = valveNode.Transform; Vector3d newTrans = new Vector3d(current.Translation.X, translateY, current.Translation.Z); valveNode.Transform = new Transform(current.Rotation, newTrans); } } // 在串口数据接收事件中调用 private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e) { string data = serialPort.ReadExisting(); if (double.TryParse(data.Trim(), out double pressure)) { // 关键:必须在 UI 线程调用,因为串口事件在后台线程 this.Invoke((MethodInvoker)delegate { _animationController.UpdateValvePosition(pressure); }); } }注意:
Invoke是必须的,因为串口事件在ThreadPool线程触发,而SceneNode.Transform虽然线程安全,但 AnyCAD 的Scene对象本身不是线程安全的。Invoke保证更新发生在 UI 线程,避免ObjectDisposedException。
6.2 场景二:同时播放两个模型,实现“主泵+辅泵”联动
需求:主泵旋转带动辅泵齿轮啮合转动,两模型需保持固定传动比(如 3:1)。.anyscene只支持单模型动画,必须手动同步。
// 在 AnimationController 中维护两个播放器 private readonly AnimationPlayer _mainPumpPlayer; private readonly AnimationPlayer _auxPumpPlayer; public void SyncPumps(double mainSpeedRPM) { // 主泵转速(RPM)→ 时间轴播放速度(倍速) double speedFactor = mainSpeedRPM / 1000.0; // 假设 1000RPM 对应 1x 播放速度 _mainPumpPlayer.Speed = speedFactor; // 辅泵按 3:1 传动比,反向旋转 _auxPumpPlayer.Speed = speedFactor * 3.0; _auxPumpPlayer.Reverse = true; // 反向播放 } // 启动时分别绑定 _mainPumpPlayer = AnimationPlayer.Create(mainScene); _auxPumpPlayer = AnimationPlayer.Create(auxScene);提示:
AnimationPlayer.Speed可动态调整,无需 Stop/Start。Reverse = true会让动画倒播,完美模拟齿轮反向啮合。但注意.anyscene文件中的旋转轨道必须是“全周期”设计(如 0°→360°),否则倒播会突变。
6.3 场景三:导出当前动画为 GIF,用于报告生成
客户需要点击按钮,将当前播放的 5 秒动画导出为report_animation.gif。AnyCAD 不提供截图 API,需借力Graphics.CopyFromScreen。
public void ExportGif(string outputPath, int durationMs = 5000, int fps = 10) { int frameCount = (int)(durationMs / 1000.0 * fps); var frames = new Bitmap[frameCount]; // 暂停播放器,逐帧截图 _player.Pause(); double startTime = _player.CurrentTime; for (int i = 0; i < frameCount; i++) { double time = startTime + (i * 1000.0 / fps) / 1000.0; // 转为秒 _player.Seek(time); _player.Render(); // 强制渲染当前帧 // 截取 SceneViewControl 区域 var rect = sceneViewControl1.Bounds; var bmp = new Bitmap(rect.Width, rect.Height); using (var g = Graphics.FromImage(bmp)) { g.CopyFromScreen( sceneViewControl1.PointToScreen(Point.Empty), Point.Empty, rect.Size ); } frames[i] = bmp; } // 合成 GIF(使用ImageSharp,NuGet 安装 SixLabors.ImageSharp) using var image = frames[0].Clone() as Image<Rgba32>; using var gifEncoder = new GifEncoder(); image.Save(outputPath, gifEncoder); }血泪经验:
CopyFromScreen在多显示器、高 DPI 下易截错区域。务必用sceneViewControl1.PointToScreen(Point.Empty)获取绝对坐标,而非Bounds。且导出前必须Pause(),否则Seek()和Render()会与自动播放冲突。
从那以后我每次接到“三维动画嵌入”需求,第一件事就是检查客户给的模型格式是否支持.anyscene导出,第二件事是确认他们的上位机是否已部署 .NET 4.7.2 和 OpenGL 驱动——这两步省下的调试时间,够我喝三杯咖啡。希望帮到你。
本文还有配套的精品资源,点击获取