昨天在某个技术群里看到有个小伙伴在问hypermesh二开的问题,题大概如下所示:
问题:如何通过二开的方法找到图中几何模型的四个外表面(不包含圆角)?
首先对题目解读一下
已知条件:
①Comp的名称
②全局坐标系的朝向
其实,这两个条件也就相当于没有已知条件,毕竟只要打开这个模型,这两个条件在任何情况下都是存在的
我看这个问题的第一反应其实是抽取这个几何的外表面,因为前段时间刚好写了这个需求,所以对这个需求印象比较深刻,但后来仔细一想,确实搞复杂了,那就从不同的角度和逻辑写一下这个需求吧
梳理一下需求,方向有很多,我这边只写了2个作为示例:第一个是不单独抽取这个几何外表面,直接在这个Solid几何上处理;第二个就是单独抽取几何外表面,然后对新的几何外表面进行处理
方向一:不单独抽取几何外表面
逻辑:
step1:找到几何的最大最小边界坐标
step2:通过坐标计算这四个外表面的中心点
step3:通过点到面的距离得到最小距离及对应的面ID
step4:索引面的ID
Ps:一般情况下,谈到最短距离及ID,我首先想到的是hm_measureshortestdistance2或者hm_measureshortestdistance,但是我这次没有用它,因为之前写代码时候经过了多次测试,这两个API存在BUG,在2021.1的版本下,这个BUG目前还没有得到修复,最短距离数据没问题,但是返回的ID并不是距离最短的,有兴趣的小伙伴可以下去测试一下
相关代码如下:
namespace eval ::Geom::Find {
}
proc ::Geom::Find::surfaceGetMethod3 {CompName} {
# ----------------------------------------------
#最大最小坐标计算
*createmark surfs 1 "$CompName"
set allSurf [hm_getmark surfs 1]
set compCoord [hm_getboundingbox surfs 1 2 0 0]
foreach {x_min y_min z_min x_max y_max z_max} $compCoord {}
# ----------------------------------------------
#计算四个外表面的中心坐标
set Point1 "[expr (abs($x_max)- abs($x_min))/2] $y_max [expr (abs($z_max)- abs($z_min))/2]"
set Point2 "[expr (abs($x_max)- abs($x_min))/2] $y_min [expr (abs($z_max)- abs($z_min))/2]"
set Point3 "[expr (abs($x_max)- abs($x_min))/2] [expr (abs($y_max)- abs($y_min))/2] $z_max"
set Point4 "[expr (abs($x_max)- abs($x_min))/2] [expr (abs($y_max)- abs($y_min))/2] $z_min"
foreach {x1 y1 z1} $Point1 {}
foreach {x2 y2 z2} $Point2 {}
foreach {x3 y3 z3} $Point3 {}
foreach {x4 y4 z4} $Point4 {}
# ----------------------------------------------
#得出四个外表面的ID
set surf1 [lindex [hm_getdistancefromnearestsurface [list $x1 $y1 $z1] [list {*}$allSurf]] 1]
set surf2 [lindex [hm_getdistancefromnearestsurface [list $x2 $y2 $z2] [list {*}$allSurf]] 1]
set surf3 [lindex [hm_getdistancefromnearestsurface [list $x3 $y3 $z3] [list {*}$allSurf]] 1]
set surf4 [lindex [hm_getdistancefromnearestsurface [list $x4 $y4 $z4] [list {*}$allSurf]] 1]
# *createmark surfs 1 $surf1 $surf2 $surf3 $surf4
# hm_highlightmark surfs 1 "h"
}
::Geom::Find::surfaceGetMethod3 $CompName
方向二:单独抽取几何外表面作为新的Comp
在方向二这边又可以衍生出2种方法:
①进行法向量判断想要的面
②观察几何特征,通过面积比较法进行排除不需要的面
先来说法向量的方法吧
逻辑:
step1:通过抽中面的命令抽中几何的外表面,
这里要注意的一点是抽中面的功能并不是只能抽取到中面,
其实它还可以抽取到内表面,外表面以及指定偏移距离的任何面,但是需要一些设置
step2:遍历comp名称为"Middle Surface"的所有面,通过面的法向量进行判断
step3:留下法向指向Z和Y的面即可
Ps:当时判断法向量的时候有点小插曲,这确实也是经常会遇到的问题,就是法向量应该是"0 0 1"的面,它的法向量在hypermesh中计算出来是
"1.85e-25 5.48e-16 1"
这是由于精度误差所致的,所以说没法直接判断,即便它的值很小,无限接近于0,但是它不是0
后面我就设置了一个容差0.00001,然后让得出来无限接近于0的数减去0,如果小于这个容差,那就当做0进行处理,于是原本简洁的代码又新增了3个If判断
相关代码如下:
namespace eval ::Geom::Find {
}
# --------------------------------------------------------------------------------
# 提取实体外表面
# --------------------------------------------------------------------------------
proc ::Geom::Find::Exterior_surface {CompName} {
*createmark solids 1 "by comp" "$CompName"
*createmark surfaces 2 "all"
*midsurface_edit_base_surfaces 2 1 0 0
*midsurface_extract_10 solids 1 3 0 5 1 9 0 20 0 0 10 0 10 0 undefined 0 0 1
*createmark components 1 "^Base surfaces"
*deletemark components 1
}
proc ::Geom::Find::surfaceGet {CompName} {
# ----------------------------------------------
*createmark surfs 1 "$CompName"
set surfId [hm_getmark surfs 1]
set trueSurf ""
set tol 0.00001
foreach surf $surfId {
*createmark surfs 1 $surf
# ----------------------------------------------
#得到面的中心
set surfCenter [hm_getcentroid surfs 1]
hm_markclearall 1
foreach {x y z} $surfCenter {}
# ----------------------------------------------
#求面的法向量
set surfNormal [lrange [hm_getsurfacenormalatcoordinate $surf $x $y $z] 3 5]
foreach {i j k} $surfNormal {}
# ----------------------------------------------
#消除计算精度误差导致的判断失效问题
if {[expr abs($i - 0) < $tol]} {
set i 0
}
if {[expr abs($j - 0) < $tol]} {
set j 0
}
if {[expr abs($k - 0) < $tol]} {
set k 0
}
# ----------------------------------------------
#判断法向量是否满足
if {"$i $j $k" == "0 0 1" || "$i $j $k" == "0 0 -1" || "$i $j $k" == "0 1 0" || "$i $j $k" == "0 -1 0"} {
lappend trueSurf $surf
}
}
puts "The Surf you need is $trueSurf"
# *createmark surfs 1 {*}$trueSurf
# hm_highlightmark surfs 1 "h"
}
::Geom::Find::Exterior_surface $CompName
::Geom::Find::surfaceGet "Middle Surface"
再来说面积比较法,缺点就是会选取所有的surf进行判断,不一定能得到你想要的,所以需要删除其他无关的comp
逻辑:
step1:同样是先使用抽中面功能抽取几何外表面
step2:遍历每个面的面积放入列表,进行从小到大的排序,
因为此时一共有8个面,其中4个面是圆角面,所以它的面积肯定是最小的,
也就是说8个面积中,索引位置分别为0 1 2 3,然后把位置3对应的面积得到
step3:使用hm_getsurfacesbyarea进行筛选,
这个API可以把小于或等于你指定的面积对应的面ID得到
step4:从8个面中通过列表操作移除4个圆角面
相关代码如下:
namespace eval ::Geom::Find {
}
# --------------------------------------------------------------------------------
# 提取实体外表面
# --------------------------------------------------------------------------------
proc ::Geom::Find::Exterior_surface {CompName} {
*createmark solids 1 "by comp" "$CompName"
*createmark surfaces 2 "all"
*midsurface_edit_base_surfaces 2 1 0 0
*midsurface_extract_10 solids 1 3 0 5 1 9 0 20 0 0 10 0 10 0 undefined 0 0 1
*createmark components 1 "^Base surfaces"
*deletemark components 1
}
::Geom::Find::Exterior_surface $CompName
proc ::Geom::Find::surfaceGetMethod2 {CompName} {
#通过面积对比法,缺点就是会选取所有的surf进行判断,不一定能得到你想要的
*createmark surfs 1 "$CompName"
set allSurf [hm_getmark surfs 1]
set surfAreaList ""
foreach surf $allSurf {
set currentArea [hm_getareaofsurface surfaces $surf]
lappend surfAreaList $currentArea
}
# ----------------------------------------------
# 面积大小排序
set newAreaList [lsort -increasing $surfAreaList]
set areaValue [lindex $newAreaList 3]
set smallArea [hm_getsurfacesbyarea [expr double($areaValue)]]
hm_markremove surfs 1 {*}$smallArea
set trueSurf [hm_getmark surfs 1]
# *createmark surfs 1 {*}$trueSurf
# hm_highlightmark surfs 1 "h"
}
::Geom::Find::surfaceGetMethod2 "Middle Surface"
对比完这些方法的感受就是,要实现某一个功能,逻辑方法很多,但是简洁通用的还需要进行打磨才可以,这个文章写出来只是为了记录一下我关于这个问题的一些思路及涉及的一些代码,算是给在学二开的人一些参考,代码格式可别参考我的,哈哈,要是自评的话,我只能说:"毫无章法"
最后说一下我写了一段时间的hm二次开发脚本的感受吧,首先就是要对需求了解清楚,梳理好可能存在的问题,到时候输出一些log文件方便调试bug,其次就是对整个数据的传输与接收,数据的类型一定要有清晰的认识,最后分清楚主函数与方法调用,还有就是一定要写注释,一定!一定!
最近学到了驼峰式命名,挺有意思~