在浩辰CAD中实现3D转2D并支持2D消隐的可行方案
引言
在AutoCAD生态里做.NET二次开发是成熟套路——API文档齐全,StackOverflow上什么坑都有人趟过。但换到浩辰CAD(GStarCAD),同样的 CommandMethod、Transaction、Editor,下面踩出来的就不是坑,是矿洞。
这个项目表面是一个浩辰CAD插件Demo,入门命令、绘图演示一应俱全。但真正的主线只有一条:选中3D实体,自动生成多视图2D工程图,直接输出DWG。
为了这个目标,项目走过了五条完整的路线。每一条都是一次"假设-尝试-碰壁-绕行"的循环。
一、进化全貌
FLATEXPORT → VIEWEXPORT → HLREXPORT → MESHVIEWEXPORT → SOLPROFEXPORT
(对话框堵死) (OCCT依赖) (简化OCCT) (自研纯托管引擎) (★ 最终方案)
五条路线背后是对同一个问题的五次不同回答:"当你不能信任平台的任何一个子系统,你该怎么做?"
二、第一站:FLATEXPORT — 被一个对话框终结
最直觉的方案:程序切正交视图 → 调用 FLATSHOT 命令 → 拿2D投影。
AutoCAD下这条路是通的。但 GStarCAD 的 FLATSHOT 永远弹对话框——系统变量 CMDDIA=0 对它无效。SetCurrentView() 编程调用不生效,COM层的 VPOINT 是异步的,时序无法保证。每一种组合都走进了死胡同。
教训:在一个兼容平台上,最直觉的方案往往最先撞墙。不是因为思路错,而是因为那些"不言自明"的API行为不再自明。
三、第二站:VIEWEXPORT — 外部工具链噩梦
既然平台原生命令靠不住,那就把工作外包出去。
思路变成了一个跨进程流水线:导出SAT格式 → 启动第二个GStarCAD实例转成STEP → 调用 Open CASCADE 的 HLR 引擎做消隐投影 → 输出2D STEP文件,用户手动另存DWG。
为了让这条流水线跑通,团队撞上了一整面墙的API缺陷:COM层的 Section API 方法声明了但运行时返回 E_NOTIMPL;COM互操作整个不兼容AutoCAD的标准行为;_.SCRIPT 命令不工作;不能同时跑两个GStarCAD实例(DDE/SDI冲突)。
最终靠 SendStringToExecute 加 DoEvents 消息泵硬把各环节串了起来,但这是一条能跑但完全不可交付的路线——依赖外部C++运行时库,任何一个环节出问题都会断链。
教训:在API受限的平台上,"外包给外部工具"听起来很好,但跨进程边界的每一步都是脆弱点。
四、第三站:HLREXPORT — 简化了依赖,没简化问题
砍掉VIEWEXPORT的多余环节:直接从插件导出STL → 调用外部OCCT做HLR消隐 → 输出单视图DWG。
依赖链短了,但根因没变——还是那个外部OCCT工具链,还是那些时序脆弱问题。这条路很快被判定为死胡同并弃用。
五、第四站:MESHVIEWEXPORT — 自己造轮子
到此为止,结论已经很清楚:放弃依赖GStarCAD原生命令、COM互操作、外部工具链的一切路线,在插件进程内纯托管实现整个消隐管线。
于是有了 MESHVIEWEXPORT——一个完全用C#手写的3D→2D消隐引擎。整个管线按图形学标准流程组织成四个模块:
5.1 STL解析器
从文件头5字节自动判定ASCII或Binary格式,分流解析。不做多余的事,干净利落。
5.2 网格拓扑构建
STL本质是"三角面片汤"——每个三角形独立存储,共享顶点被重复记录。这个模块做三件事:
- 顶点焊接:用3D量化哈希把空间重合的顶点合并(容差百万分之一),构建出真正的共享顶点表
- 无向边邻接表:遍历所有三角形,为每条边记录共享它的两个相邻面(或单个,表示边界边)
- 锐边分类:计算每条共享边两侧三角形面的二面角——大于25°判定为真正的特征边(盒体棱、倒角边),小于25°标记为曲面镶嵌噪声(圆柱面上那些密密麻麻却毫无结构意义的线)
25°这个阈值是整个管线最重要的参数。它决定了最终工程图是干净的结构线,还是带着几千条镶嵌噪声的垃圾。
5.3 特征边提取 + Z-buffer消隐
每个正交视图执行三层筛选:
- 轮廓边:相邻两个三角面一个朝相机、一个背相机——这是物体在该方向上的真实外轮廓
- 锐边:二面角>25°且两面同时可见——特征棱线
- 边界边:只被一个三角形引用——网格边界
直接淘汰的是非轮廓平滑边——曲面上那些看不见结构特征的密集镶嵌线。
然后进入消隐:所有的正对相机三角面被纯CPU光栅化到一个900×900的正交深度缓冲中。每条候选边沿线采样(约每像素一个点),逐采样比较深度。在近表面之前的线段判为可见,被缓冲遮挡的判为隐藏。最后按0.1%深度范围的bias消除Z-fighting自遮挡。
输出前做共线合并:把焊点网格沿一条直线产生的成百上千个小段合并为少数几条长线段。可见边→实线层,隐藏边→虚线层。最后按国标第一角投影排版——俯视图在主视图正下方,左视图在正右方——直出DWG。
5.4 这个方案的得与失
MESHVIEWEXPORT 跑通了。它证明了在零外部依赖、单进程的条件下,从网格到消隐三视图的完全自动化是可行的。
但它有一个天生的天花板:STL是三角面片格式。无论 FACETRES 调到多高,圆柱面永远是折线近似,精度本质上受限于三角面片数。对于高精度机械零件,这不够。
六、第五站:SOLPROFEXPORT — 与平台和解
MESHVIEWEXPORT 证明了一件事:插件内全流程自动化是做得到的。那下一步的问题就变了——能不能把GStarCAD原生真正好用的能力利用起来?
答案藏在 SOLPROF 命令里。这是GStarCAD内置的剖面轮廓命令,工作在BREP(精确边界表示)层面,精度远超任何网格投影,而且不弹对话框——之前几站对原生命令的"创伤后应激"让人没有第一时间认真尝试它。
SOLPROFEXPORT 的思路:
- 创建临时布局和视口:程序化在模型空间和图纸空间之间切换
- 遍历六个正交方向:俯/前/仰/左/后/右,每次用
-VIEW命令切换视角后调用 SOLPROF 生成该方向的2D轮廓 - 收集+旋转+排版:SOLPROF生成的轮廓位于各自的3D平面中,需要用矩阵旋转拼平到XY平面,再按3×2网格平移到各自的位置
- 分层+清理:自动将可见轮廓和隐藏线分到不同图层,擦除原始3D实体,删除临时视口和布局
最关键的技术突破是矩阵乘法顺序的修复:平移 × 旋转 而非 旋转 × 平移。这个顺序错误导致了各视图不在同一个XY平面上、相互重叠——而纠正这一行代码就让六个视图整齐归位。
七、意外收获:CSG数据交换
在开发过程中,项目还顺带产出了一对有趣的命令:
导出:遍历选中3D实体的包围盒信息,以CSG(构造实体几何)JSON格式序列化。当前从包围盒推断长方体基元,架构已为圆柱、球体、锥体及布尔运算(并/交/差)预留了完整的递归CSG树支持。
导入:解析JSON中的CSG树,递归构建对应的 Solid3d 对象——box、sphere、cylinder、cone、以及三组布尔运算(union/subtract/intersect),每个节点独立支持位移和旋转Transform。
这是一个轻量级的、人类可读的3D实体数据交换格式。
八、平台限制全景图
这个项目最有档案价值的部分,不是实现了什么功能,而是记录了一张经过实战验证的"浩辰CAD二次开发地雷分布图":
| 限制 | 严重程度 |
|---|---|
| COM Section API声明了但未实现 | ★★★ 阻断 |
| COM IDispatch不兼容AutoCAD标准 | ★★★ 阻断 |
| SECTIONPLANETOBLOCK不生成几何体 | ★★★ 阻断 |
| SCRIPT命令不工作 | ★★ 阻断 |
| FLATSHOT对话框无法抑制 | ★★ 阻断 |
| SetCurrentView编程不生效 | ★★ 阻断 |
| Editor.Command不支持_ALL关键字 | ★★ 变通 |
| Editor.Command无法打开非DWG | ★★ 变通 |
| SendStringToExecute不可靠 | ★ 变通 |
| 不能同时运行两个实例 | ★ 变通 |
这张清单的价值在于:每一项都不是文档里写的,而是在代码里撞出来的。后来者可以直接跳过这些已验证的死胡同。
九、启示
五条路线勾勒出的真正故事不是"最后成功了",而是一种在受限平台上做开发的策略演化:
- 信任平台 → 撞墙(对话框、API未实现)
- 外包给外部 → 脆弱(跨进程、多依赖、时序不可控)
- 自己造轮子 → 可行但天花板低(STL精度上限)
- 选择性利用平台 → 精准找到那少数几个真正可用的API,最大化利用
MESHVIEWEXPORT 证明了"什么都靠不住就自己造"的骨气,SOLPROFEXPORT 证明了"不是所有原生命令都不可靠"的智慧。两者缺哪一个,都到不了最后。
在国产CAD平台的二次开发生态中,这种"踩雷→记录→绕行→成功"的完整轨迹,比一个完美的最终实现更有价值。因为下一个做GStarCAD插件的人,不需要再踩一遍。

浙公网安备 33010602011771号