1. 先说清楚:球标到底是个什么东西,为什么值得单独拿出来写
1.1 一张装配工程图里,球标是那个绕不开的圆
做CREO二次开发久了你会发现,图纸上能让出图工程师最烦躁的,往往不是尺寸标注,而是那一圈一圈的球标。球标就是装配图里带圆圈的数字序号,它和BOM表一一对应,告诉你图上这个圈里的件在明细表里是哪一行。零件一多,比如上百个零件的装配体,光靠手一个个点球标、调引线、对位置,一天的时间就耗进去了。
我最早接触球标函数是在给一家做非标设备的客户做图纸自动化工具的时候。他们的总装图每次改版都要重新核对BOM、重新补球标,手工操作两三个小时是常态,改一版错一版。后来我把球标这块用CREO Toolkit的API做了批处理,两分钟就能跑完全部图纸,从那以后我才真正意识到,球标这个看起来不起眼的“小圆圈”,在二次开发里的价值被严重低估了。
这篇内容就是围绕CREO二次开发里和球标相关的函数展开,讲清楚球标是怎么创建的、位置怎么控制、文本怎么改、批量操作怎么排布,以及我实际开发中踩过哪些坑。适合正在做图纸自动化、BOM自动标注、或者想把Creo出图流程提速的工程师参考。
1.2 开发前先理清图纸对象的关系链
在动代码之前,得先理解CREO工程图里的对象层次。一张工程图(Drawing)下面有若干视图(View),视图里可以显示模型,模型可以是装配体(Assembly)也可以是零件(Part)。球标并不是单独悬浮在图纸上的,它本质上是一种带引线的注释,挂在某一个视图上,并且通常和装配里的某个元件或者BOM表条目关联。
这个关系链决定了你调用API的路径。比如你用ProBomBalloon系列函数,背后的逻辑就是从装配BOM表折返回视图,再生成球标。如果你用ProDwgBalloon这类函数,重点则在“图纸注释”这个维度上。早期我写代码时没分清这两条路,结果一个球标创建接口折腾了好几天,后来才明白需要看你到底是想从BOM侧生成,还是从视图侧生成。
搞开发的人容易犯一个毛病,上来就翻函数库。我的建议是先花十分钟理清楚:你手里的数据入口是装配模型、BOM表,还是图纸视图?你要生成的球标是挂在哪个视图上?这三个问题定了,API函数怎么选自然就清楚了。
2. 球标相关的核心API与数据模型拆解
2.1 Pro/TOOLKIT里球标函数怎么分组
CREO二次开发最常用的环境是Pro/TOOLKIT,也就是C语言接口。球标相关的函数分布不算散,我按用途给你分个组,这样查起来有方向。
第一类是创建与删除。创建球标的核心函数围绕ProBomBalloon展开,比如ProBomBalloonCreate,删除用ProBomBalloonDelete,批量收集图纸里所有球标用ProBomBalloonCollect。这三个函数是高频使用的,其他大部分操作都是在拿到球标对象句柄之后做的。
第二类是查询与修改属性。拿到一个球标对象后,你经常需要知道它挂在哪个视图、引线指向哪个元件、球标文本内容是什么。相关的有ProBomBalloonReferenceGet、ProBomBalloonTextGet、ProBomBalloonTypeGet等。反过来,修改文本内容和球标类型就用TextSet、TypeSet这一类。
第三类是位置控制。球标在图纸上的位置,包括坐标点位置和引线折点,对应的是ProBomBalloonLocationGet和ProBomBalloonLocationSet。这一组函数值得重点研究,因为批量排布球标能不能做好看,全靠它。
第四类是参数相关。有些公司会给球标挂自定义参数,比如“是否外购件”“是否必标件”,然后用参数值控制哪些零件要显示球标。这部分会用到ProBomBalloonParameterGet和ProBomBalloonParameterSet。
我整理个简表方便你对照查文档:
| 用途 | 典型函数 | 说明 |
|---|---|---|
| 创建球标 | ProBomBalloonCreate | 在图纸上创建BOM球标 |
| 收集球标 | ProBomBalloonCollect | 取当前图纸所有球标数组 |
| 删除球标 | ProBomBalloonDelete | 删除指定球标 |
| 获取位置 | ProBomBalloonLocationGet | 拿球标坐标和引线折点 |
| 设置位置 | ProBomBalloonLocationSet | 移动球标到指定坐标 |
| 获取引用 | ProBomBalloonReferenceGet | 拿球标关联的元件或视图 |
| 文本读写 | ProBomBalloonTextGet/Set | 改球标里的数字或文字 |
| 类型读写 | ProBomBalloonTypeGet/Set | 切换球标样式 |
注意一点,具体函数签名和常量定义以你安装的Creo版本头文件为准,不同版本之间会有细微差异,但整体思路没变过。
2.2 球标的“三要素”:位置、引线、文本,缺一个都不行
球标在界面上一看就是个圆加数字,但在API的对象模型里,它是由三个核心要素组成的。
位置要素。球标圆心的位置是图纸空间坐标,不是模型空间坐标。这就意味着一旦你把球标放到某个位置,它不会跟着模型转动而自动改变,模型视图变了位置可能就飘了。理解这一点很重要,很多初学者以为球标挂在某个零件上,零件移动球标就跟着走,实际不是这样的。球标的位置是独立存储在图纸上的。
引线要素。球标和零件之间靠引线连接。引线可能有一条折线,也可能有多个折点。API里拿到的方向点就是折线经过的关键点。批量创建球标时,引线折点如果不处理,默认会生成一条从球标到视图中心的直引线,多个球标重叠在一起时极其难看。
文本要素。球标里的内容不一定是纯数字,也可能是“3-1”这种带后缀的格式,甚至是多行文本。文本和BOM表的行号关联逻辑需要在创建时确定,有的公司习惯按BOM顺序编号,有的公司习惯按零件在视图中的位置编号,这些都是通过文本处理和BOM排序来控制的。
我见过很多二次开发工程师把精力全花在创建球标本身,忽略了引线和位置的处理,结果球标是生成出来了,但全部挤成一团,还不如手工点。实话说,球标创建的一半工作量,在位置排布上。
3. 实操:从零开始写一个批量球标工具
3.1 开发环境怎么搭,注册文件怎么配
CREO二次开发的常规组合是Visual Studio加Pro/TOOLKIT。我用的是VS 2019搭配Creo 8.0,安装时选上Toolkit模块,然后新建一个DLL工程,包含目录指向Toolkit的include文件夹,库文件链接对应版本的库。
这里有个容易踩的坑:Creo的Toolkit DLL注册方式有两种,一种是随Creo启动自动加载的异步模式,一种是用户点菜单触发的同步模式。球标操作一般放在异步模式下做,因为要一次性处理多张图纸,异步模式不会阻塞Creo主界面,处理完还能弹个结果框。
在protk.dat注册文件里,最少要包含这些信息:
name BalloonAutoTool exec_file $ORIGIN/balloon_tool.dll text_dir $ORIGIN startup dll allow_stop true delay_start false revision creo_8把这个文件放到Creo的启动目录下,或者在配置选项里指定toolkit_registry_file路径。配置完以后,启动Creo时如果日志里能看到你的DLL被加载,那环境就通了。
3.2 第一步:遍历图纸和装配,收集需要打球标的零件
批量球标工具的第一步,是把“要处理哪些图纸、每张图纸对应哪个装配”这两个信息拿到手。我的做法是先定义一个结构体,保存图纸名、模型名、视图名,然后从外部配置文件读入这个列表。也可以直接遍历当前进程打开的图纸窗口,但那样太受限于用户当前会话,自动化程度不够。
装配里的零件收集,用ProBomTable或者ProAsmMdlCollect这类函数可以拿到装配下的元件列表。注意,这里要处理一个问题:子装配要不要展开?如果装配里套了子装配,而BOM表是展开到零件级别的,那你收集元件时也要递归到最底层零件。反之,如果BOM表只到子装配级别,就不需要递归。
这一步还牵扯到过滤。实际项目中并不是所有零件都要打球标,标准件、共享件经常被排除。我的做法是序列化收集到的每个元件,再逐个检查它的自定义参数,用一个“是否出球标”的参数控制。
3.3 第二步:创建球标并指定视图和引线
搭好零件列表之后,核心环节是创建球标。代码逻辑大致长这样:
ProError CreateBalloonsForAssembly(ProDrawing drawing, ProAsmcomppath comp_path, ProView view, ProBomBalloon* balloon_array, int* count) { ProError err; ProBomBalloon balloon; ProPoint3d location; ProLine3d leader_line; /* 位置初始化为视图中心偏移 */ location[0] = view_center_x; location[1] = view_center_y; location[2] = 0.0; /* 创建球标 */ err = ProBomBalloonCreate(drawing, &view, &comp_path, &location, &balloon); if (err == PRO_TK_NO_ERROR) { /* 设置引线折点 */ leader_line.point1[0] = location[0] - 5.0; leader_line.point1[1] = location[1] - 5.0; leader_line.point2[0] = comp_x; leader_line.point2[1] = comp_y; err = ProBomBalloonLeaderLineSet(&balloon, &leader_line); balloon_array[*count] = balloon; (*count)++; } return err; }这段代码我不能保证在你的版本里原样编译通过,毕竟每个项目的头文件和约束不同,但整体调用流程是一致的:先准备球标位置点,再创建球标对象,再设置引线。位置点如果不想传,某些接口也有默认生成逻辑,但默认生成的位置经常不好看,我建议还是自己计算。
视图的选择也要注意,一张图纸可能有多个视图,同一个零件也可能出现在多个视图里。通常球标只标在主视图或者明细视图上,不能每个视图都打。我用一个视图过滤规则:优先选图纸里比例最大的那个视图,或者选带BOM表注释的那个视图。
3.4 第三步:批量位置排布和避让
球标创建完后,最花功夫的是排布。所有球标初始位置都在视图中心附近,不处理的话就是一团乱麻。我的排布思路分成三步走。
先统计球标数量,确定大致布局形状。数量少时按单列排,数量多时按多列蛇形排。比如50个球标,我会算出来用两列,每列25个,列间距大概是球标直径的三倍。
再计算每个球标的目标坐标。按照BOM顺序从左上角开始往下排,每个球标间隔相等。这里有一个关键点:球标位置必须相对于视图位置计算,不能相对于图纸原点计算,否则视图平移后球标位置就会错乱。我当初因为忽略这个问题,被现场工程师截图骂了一顿。
最后做引线交叉避让。这一步最费时间,我用的是一种比较笨但稳定的贪心算法:先排完所有球标,再对每条引线做交叉检测,如果发现交叉,就把其中一个球标往旁边偏移,反复迭代几次。这个算法虽然不是最优解,但工程上够用,而且跑得快。
位置写回用ProBomBalloonLocationSet,传入新的坐标数组,一次性把所有球标的位置更新掉,这样Creo界面上的刷新操作也只发生一次,效率高很多。
4. 常见问题与排查技巧实录
4.1 球标位置乱跳,或者干脆跑到视图外头
这个问题出现频率最高,而且多在“图纸打开后被脚本修改”的场景下发生。球标位置的坐标是图纸空间坐标,如果你用ProBomBalloonLocationGet拿到位置后,直接拿这个值去写另一个视图的球标,位置大概率是错的。
我的排查经验是:先把视图的变换矩阵取出来,把图纸坐标转成视图坐标系再做偏移。Creo的API里视图坐标转换相关的函数有好几个,我最常用的是按视图原点偏移换算,处理简单场景足够了。
球标跑到视图外面,还有一个原因是创建时没有传有效的引线参考。某些版本里,如果你创建球标时引线指向的零件不在当前视图中,球标会被默认丢到图纸左上角。解决方法是创建之前先判断零件在当前视图中是否可见,不可见的直接跳过。
4.2 BOM表一改,球标序号就对不上了
这个坑我也踩过,不是球标的错,是BOM排序和球标文本没有同步。球标里的数字通常对应BOM表的行号,但BOM表的行号会随着你添加、删除零件而变化。如果你只批量生成了球标,后期BOM变动了,球标不会自动重新编号。
我的办法是写一个“重新编号”功能,每次图纸变更后调用。先把所有球标收集起来,按引线指向的元件在BOM表中的实际位置排序,然后把从1开始的序号重新写进球标文本。这样即使BOM变了,只要球标关联关系还在,一键就能刷新编号。
刷新编号还要注意一个细节:BOM表可能存在多级折叠,行号是显示出来的行号,不是内部序号。我用的做法是拿到BOM表每一行的实际索引,然后跳过折叠隐藏的行,按显示顺序编号。
4.3 大批量创建球标,运行速度像蜗牛
几百个零件的装配体,逐个创建球标和设置位置,如果每次调用API都触发界面刷新,时间会非常可怕。我第一次写这个工具时没做任何优化,400个球标跑了将近十分钟,现场差点摔鼠标。
优化方向有两个。一是尽量批量操作。Creo的Toolkit里很多函数支持数组参数,比如一次传入多个球标对象统一设置位置,比一个一个设置快很多。二是延迟重绘。在脚本处理期间,利用某种后台方式将UI刷新暂时挂起,等所有球标处理完再统一刷新。具体做法不同版本不一样,但思路都是“最后集中重绘一次”。
实测下来,优化后同样是400个球标,处理时间从十分钟降到了三十秒以内,体感差距巨大。
4.4 C的API搞不定的场景,换J-Link或者Python试试
虽然Pro/TOOLKIT功能最全,但有些操作在C接口里做起来很别扭,比如复杂的字符串处理、正则匹配、和外部系统对接。这时候我习惯把球标工具拆成两层:核心的创建和位置操作用C DLL完成,业务逻辑和流程控制在J-Link或者Python里做。
J-Link对球标的封装思路和C类似,但对象模型更面向对象一些,代码维护起来清爽。Python的话可以用Creo的二次开发库间接调用Toolkit接口,适合快速验证想法。我的习惯是:正式工具用C写,做实验和调试用Python,两边互补。
5. 把球标工具做成稳定可交付的成品
5.1 封装成独立DLL,配合菜单和配置文件使用
写一次性的脚本容易,做成给同事和客户稳定使用的工具难。我的做法是把球标功能封装成独立DLL,暴露一个入口函数,然后用菜单注册文件挂到Creo的“工具”菜单里。这样用户不用碰代码,点一下菜单,选好图纸,工具自动跑完。
配置方面,我习惯用一个ini文件存参数,比如球标直径、列间距、起始编号、过滤参数名。配置文件的好处是现场调整不用改代码重编译,改一行ini就能适配不同公司的出图习惯。这看起来是个小细节,但在实际交付时非常加分。
5.2 积累了这么久,几个特别想提醒你的习惯
球标相关的函数,细碎参数非常多,头文件和文档经常不够直观。我个人的经验是每接触一个新函数,先写一个最小测试用例,单独验证这个函数的行为,确认没问题再往主工具里集成。千万不要一上来就写大流程,出了问题很难定位。
另一个习惯是把典型的报错信息收集起来做成日志。球标创建失败的原因千奇百怪,有的是因为视图比例不对,有的是因为元件被隐藏,有的是因为BOM表为空。有了日志,现场反馈问题的时候你能快速知道是哪一类,不用远程猜半天。
最后,给球标工具留一个“撤销式”的清理功能。批量创建之后,用户大概率还想微调几个球标,如果工具没有提供一键删除所有自动生成球标的能力,用户只能手动一个个删,那体验就非常糟糕了。我后来都会在工具里加一个“清除本视图自动球标”的按钮,看似功能小,但实际使用率非常高。
这些经验都是我在真实项目里一点点攒出来的。球标函数本身不难,难的是把生成、排布、同步、交付这一整条链路捋顺。现在每当我看到几百个球标在几秒钟内自动整齐排好,还是会觉得当初在这上面死磕的时间和精力是值得的。