Key Words: HLR, Outline Edge, Sihouette Edge
OpenCASCADE中关于隐藏线消除HLR法的描q就是一句话QThese algorithms are based on the principle of comparing each edge of the shape to be visualized with each of its faces, and calculating the visible and the hidden parts of each edge. x据面判断每条边Edge的遮挡关p,计算Edge可见和不可见部分。所以HLR法的输入主要ؓ边和面,计算遮挡关系依赖UK求交法。对于精的HLR法依赖_的线面求交算法,PolyAlgo法依赖多段U与|格求交法。输入的边中除了BREP中的边以外,q有一cLҎ投媄方向计算得来的,卛_轮廓UOutlineQ也UCؓContourUѝACIS的PHLR中称轮廓USihouette Edge?/p>
轮廓U的计算是HLR中比较关键的一步,本文以OpenCASCADE中简单的二次曲面的轮廓线计算入手来理解曲面的轮廓U概念,为理解Q意曲面轮廓线计算打下基础?/p>
OpenCASCADE的HLR中用类HLRTopoBRep_OutLiner来计外轮廓Uѝ轮廓线的计依赖投影方向及投媄方式Q主要计逻辑在函数Fill()中:
投媄方式主要分ؓ透视投媄Perspective和^行投影,工程囄成一般用^行投影方式。实际计类是Contap_ContourQ在cContap_Contour中又Ҏ投媄曲面cd分ؓ两种cd来处理:
其中函数PerformAna()^面、球面、圆柱面、圆锥面的外轮廓U,最l会使用cContap_ContAna。其中Ana为Analytical解析曲面的意思,q里指能用解析表辑ּ表示的二ơ曲面?/p>
cContap_ContAna能计球面、圆柱面和圆锥面的外轮廓UContourQ下面我们主要来看看q三c面的外轮廓U计结果?/p>
对于q投媄球面会生成以投媄方向为法向,以球半径为半径的一个圆Q代码如下所C:
如下图所CZ的绿色的U:
对于q投媄圆柱面会生成两条直线Q若投媄方向与圆柱面法向q时不生成轮廓U,q时是使用圆柱体中的上下两个圆的边。代码如下所C:
生成的两条直U方向ؓ圆柱面的轴方向:
圆锥面的轮廓U生成函数逻辑cMQ留l读者自行分析理解?/p>
lg所qͼBREP的HLR法需要计模型的外轮廓线。如球体的BREPҎ两个退化边Q极点)Q及两个重合边,若来投媄实质上只有重合边中的一条边有用Q而这个边q是个半圆。从理解 单的二次曲面外轮廓线计算函数入手Q再L入理解Q意曲面的外轮廓线计算Ҏ?/p>
理解HLR实现原理Q可以重构HLR代码Q也可以完全自己动手Q开发出满实际需求的自动出图E序Q自动出图是工程c设计Y件中相对核心的功能,目前国内ZPDMS做自动出囄兌Y件开发的有很多家。本着开攄心态分享这些相Ҏ较关键功能的原理Q让国内q些产品能摆脱基于AutoCAD/BricsCAD开发接口或PDMS Draft的限Ӟ开发出更好用、更自由灉|的Y件?/p>
用计机生成三维物体的真实图形,是计机囑Ş学研I的重要内容。真实图形在仿真模拟、几何造型、广告媄视和U学计算可视化等许多领域都有着q泛应用。在用显C备描q物体的囑ŞӞ必须把三l信息经q某U投影变换在二维的显C^面上l制出来。从三维投媄Cl的降维操作Q会D囑Ş的二义性。要消除q类二义性,必dl制时消除被遮挡的不可见的线或面Q习惯上UC为消除隐藏线Hidden Line Removal和隐藏面Hidden Face Removal?/p>
q是渲染昄上对消隐的需求,在根据三l模型自动生成工E图的工E设计Y件中Q对消隐的需求有所不同?/p>
工程设计软g与机械设计Y件不同,工程设计软g一ơ出图消隐的模型量大Q对出图的算法要求主要有Q?/p>
其实最后ȝ成一句话是一键根据模型生成能交付的图U。虽然现在技术上具备三维模型下R间的能力Q但是目前二l图U怾然是设计交付、加工制造主要依据。工E类设计软g主要的功能就是快速徏模,撞和自动囄生成。当模型量大Ӟ消隐速度快及自动生成的标注文字排列整齐(或满_E习惯)成了二维囄自动生成的核心技术,也是E序处理中的隄?/p>
几何内核一般都提供HLR法Q用来根据模型投q成二l工E图。OpenCASCADE的HLR提供了隐藏线消隐法?/p>
https://www.spatial.com/zh/products/cgm-hlr-hidden-line-removal
OpenCASCADE 提供了两U消隐算法:HLRBRep_Algo和HLRBRep_PolyAlgo。这些算法都是基于相同的原理Q比较Ş状每条边相对每个面的可见性,q?计算每条边的可见部分与消隐部分。算法通过计算在指定投影方向上的物体显C特性,去除或标记被面遮挡的辏V这两个法也与一些提取功能配合用,如重构一 个简化的模型{,化后新的模型pl成Q就是在投媄方向上的轮廓Uѝ?/p>
OpenCASCADE的HLR中将边分Z下类型:
从类HLRBRep_HLRToShape和类HLRBRep_PolyHLRToShape中给Zq些边的一些定义。其中Sharp Edge表示C0q箋Q非G1q箋Q的边,是一般EdgeQ?/p>
Smooth Edge表示G1q箋Q非G2 q箋Q的边;
Sewn Edge表示G2q箋的边Q?/p>
Outline Edge表示模型的轮廓边Q这U类型的边不在BREP数据中,需要根据投影方向生成;
Isoparameter Edge表示面的{参U生成的边,q种cd的边不也不在BREP数据中;
其中Sharp Edge、Smooth Edge和Sewn Edge一般都是BREP中的EDGE数据Q而Outline Edge和Isoparameter Edge是根据设|额外生成的辏V理解边的这些定义,方便对HLR法q行理解。HLR法是相对简单的法Q主要是将上述五种cd的边与面q行求交Q判断遮挡关pR?/p>
目前OpenCASCADE中的HLR法代码写得有点乱,上次在深圳ogg的俄|斯开发h员提到要重构HLR部分的代码。深入理?HLR法Qؓ自动生成囄功能打下基础?/p>
eryar@163.com
OpenCASCADE中几何曲U与曲面求交使用cGeomAPI_IntCSQ是对类IntCurveSurface_HInter的简单封装。在IntCurveSurface_HInter中对曲线和曲面求交分Z下几U类型:
本文主要介绍曲线与曲面求交的实现原理?/p>
二次曲线与二ơ曲面求交用IntAna_ConicQuad计算Q主要思\是将曲线用参数方E表C,代入二次曲面的代数方E。二ơ曲面可以用二ơ多式表示Q将二次曲线与二ơ曲面相交表C成一个多式方程Q用math_DirectPolynomialRoots对多式方程q行求解?/p>
二次曲线与自由曲面求交将曲面使用IntCurveSurface_Polyhedron在UQV上采L散得到grid|格。这个类实现与IntPolyh_MaillageAffinagecd能有重复?/p>
IntCurveSurface_ThePolygon多段U与Intf_InterferencePolygonPolyhedron |格求交Q根据多D늺与网格求交情况,扑ֈ初始|使用IntImp_IntCS计算_倹{与曲面求交的Marching法cMQ用P代法去计精交炏VP代方EؓIntImp_ZerCSParFuncQ写个方E的Value()D和Derivatives()微分计算公式?/p>
曲U与曲面求交问题转化为求曲面参数u,v和曲U参数wQ曲线C(w)曲面S(u,v)上的炚w合,建立函数如下Q?/p>
F(u,v,w)=S(u,v) - C(w)
所求的_交点满方程F(u,v,w)=0QFZ含有三个坐标的矢量,对应函数Value()Q?/p>
Fx(u,v,w)=Sx(u,v) - Cx(w) = 0
Fy(u,v,w)=Sy(u,v) - Cy(w) = 0
Fz(u,v,w)=Sz(u,v) - Cz(w) = 0
上面为含有三个方E的以u,v,w为变量的非线性方E组Q精交点就是非U性方E组的解。用类math_FunctionSetRoot应用Newton-Raphsonq代法求解非U性方E组的解。用Newtonq代法有个前提条件是要求非线性方E组一阶可|卌写出Jacobianq代矩阵Q即上述函数Derivatives()的实现原理:
自由曲线与二ơ曲面求交IntCurveSurface_TheQuadCurvExactInter Q通过cIntCurveSurface_TheQuadCurvFuncOfTheQuadCurvExactHInter建立二次曲面与曲U之间的函数Q是求解曲线上参数U的一元函数?/p>
自由曲线与自由曲面求交和二维自由曲线求交cMQ采用的L法。即曲UK过采样L成多D늺PolygonQ将曲面采样生成|格PolyhedronQ通过cIntCurveSurface_TheInterferenceOfHInter来计多D늺与网格的怺?/p>
包Intf主要用来计算二维多段Uѝ三l多D늺及网格的怺。根据离散计的_交点,再根据类IntCurveSurface_TheExactHInter使用q代法求得精交炏V这个思想与曲面和曲面求交相同?/p>
曲线与曲面求交的l果主要也是保存在类IntCurveSurface_Intersection对象中,q个cȝ设计与二l曲U求交类|不够直接?/p>
可以看到IntCurveSurface_Intersectionq个cȝ构造函数是protected的,意思是不能直接使用Q通过zcIntCurveSurface_HInter调用SetValues()函数求交结果保存v来。求交结果ؓ交点IntCurveSurface_IntersectionPoint和交UIntCurveSurface_IntersectionSegment?/p>
其中交点中IntCurveSurface_IntersectionPoint保存了三l坐标点Q交点在曲面上的U,V参数Q交点在曲线上的参数U及相交状态。交U主要是U现面和重合部分的几何奇异情冉|据?/p>
从类图上可以看出Q这个套路同LCHLR法中,理解q个套\对理解HLR法有帮助?/p>
lg所qͼOpenCASCADE中将曲线与曲面求交根据曲U和曲面cd的不同分别处理。二ơ曲U曲面求交依赖IntAna包,自由曲线和自由曲面求交用离散法Q最l实现算法与两个曲面求交的Marching法cMQ通过L得到的精交点Q再代入q代方程求得_解。其中把曲线或曲面离散的采样Ҏ有考虑曲线或曲面的曲率{,采样Ҏ量较大,会媄响性能 。曲面采L散代码与曲面求交中的有重复。从几何求交cM可以看到没有容差的输入,可以思考一下这个问题?/p>
TKGeomAlgo中除了拟合算法外Q大部分代码主要是U线求交、线面求交及面面求交法。理解这些算法的实现原理QؓBoolean法的求交逻辑打下基础?/p>
OpenCASCADE中对二维曲线求交和三l曲U求交是不同的,三维曲线求交l一使用L法,二维曲线求交Ҏ曲线cd的不同分U类型进行处理。二l曲U求交中q提供了计算自交的直接接口。在TKGeomAlgo中,主要内容是拟合、求交算法,理解求交法的实现原理,辑ֈ能阅d修改源码的状态,能够分析和解军_际遇到的问题Q理解OpenCASCADE的能力边界,Ҏ需要选择所需要的功能Q软gl果可控。本文主要介l二l曲U相交的实现原理?/p>
׃OpenCASCADE开发时间相对久q,在二l曲U求交相关代码中大量使用了宏定义的方式来实现C++ 的模板template能力Q宏定义在类的XXX_0.cxx文g中,对应模板实现?.gxx中:
q种实现方式会让代码的可L变差,不利于代码维护。应该用C++的方式对q些*.gxx代码重构Q增Z码可L和可维护性?/p>
二维求交使用cGeom2dAPI_InterCurveCurveQ?q个cL对类Geom2dInt_GInter的封装。在cGeom2dInt_GInter中,如果只输入一条曲U,可以计算自交Q如果输入两条曲U,计算两条曲线的相交?/p>
q些c都是从cIntRes2d_IntersectionzQ?/p>
从上囑֏知,二维求交l果cIntRes2d_Intersection相关zcd知二l求交与HLR法也有关系Q理解二l曲U求交逻辑Q对理解HLR代码也有帮助?/p>
当只输入一条曲U时Q可以对曲线q行自交计算Q主要实现逻辑为:若ؓ普通二ơ曲U,则不会自交;若是其他曲线Q用离散法ҎU进行自交计。代码如下图所C:
二维曲线求交l果保存到类IntRes2d_Intersection中,主要包含两部分:
因ؓcIntRes2d_Interseciton的构造函数protectedQ所以不能直接用这个类Q都是通过其派生类使用函数SetValues()计得到的交点和交U数据保存v来。这里类的设计比较繁琐,代码可读性较差?/p>
OpenCASCADE对于二维曲线求交q行分类处理Q根据曲U类型是二次曲线、参数曲U分成三c:二次曲线与二ơ曲U求交、二ơ曲U与参数曲线求交和参数曲U与参数曲线求交Q不同的求交cd采用不同的策略可以提高求交性能和稳定性。用离散法计算二维曲线自交。从求交l果来看Q也处理了几何奇异问题,xUK叠情c?/p>
对于曲线求交q有很大改进I间Q?/p>
曲线可以用代数方E表C,如圆可以用X^2+Y^2=R^2表示Q也可以用参数方EX(u)=RCos(u), Y(u)=RSin(u)表示。要判断Ҏ不是在线上,用曲U代数方E可以很直接得出l果Q但是用参数方E就没有那么直接。这也是参数曲线上点的反求问题,参数曲线上点的反求问题应用广泛,如前面所q判断点是否在曲U上、点向曲U投影、点与线的求交、点在参数曲U上的参数等Q都与点的反求问题相兟뀂本文主要结合代码介lOpenCASCADE曲线上点的反求实现原理及使用q程中的一些注意事V?/p>
在《The NURBS Book》书中将点的反求问题归结为点向曲U投pL短的问题Q如下图所C:
建立函数f(u)=C’(u).(C(u) - P)表示点到曲线距离Q当f(u)=0时ؓ点到曲线的最短距,不管点P是否在曲U上。几何意义是点到曲线L点的向量与Q意点处的切向量点UؓӞ表示在两个向量垂直的时候求得极值点。注意数学方E中垂直q个几何意义?/p>
OpenCASCADE中实现曲U上点的反求原理与《The NURBS Book》书中一致。点的反求用类GeomLib_Tool::Parameter()函数Q?/p>
输入曲线、点和最大距,计算Ҏ否在曲线上及若在曲线上,点对应参数曲U的参数U?/p>
cExtrema_ExtPC计算点P到线C的极值Extrema。根据代码注释可以看出点的反求数学方E与《The NURBS Book》书中一_
数学方程对应的类的变量ؓmyFQ类名ؓExtrema_FuncExtPCQ从cmath_FunctionWithDerivativezQ所以必dC个关键虚函数Value()和Derivative()。其代码注释说明了这两个函数的实现细节:
其中F(u)对应函数Value():
DF(u)对应函数Derivative()Q最后用Newton法math_FunctionRootsҎE进行求栏V?/p>
OpenCASCADE中点的反求GeomLib_Tool::Parameter()、点向曲U投影GeomAPI_ProjectPointOnCurve、点与曲U的交点IntTools_Context::ComputeVE{算法都是用了Extrema_ExtPCcR?/p>
当用GeomLib_Tool::Parameter()函数来判断点是否在曲U上Ӟ注意端点处点的反求要满垂直的条Ӟ即点与曲线某个端点距离于MaxDistӞ也是q回false。即对于曲线端点处的情况需要自己预先处理,直接点P与曲U端点距MMaxDist比较Q先处理端点?/p>
可以看到q里也处理的端点处的情况Q但是最后没有与MaxDist有关p,最后容差是Precision::SquareConfusion()?/p>
OpenCASCADEZl曲U提供了求交及自交的c?Geom2dAPI_InterCurveCurveQ当传入一个二l几何曲U时可以计算自交self-intersections。但是没有提供直接的三维几何曲线求交的类Q也没有直接的计自交的cR有人同学问OpenCASCADE有没有三l曲U自交的功能Q其实理解两个Edge求交法后,可以自己实现一个自交函数?/p>
因ؓOpenCASCADE中两条三l曲U求交的cLIntTools_EdgeEdgeQ其实现原理是基于包围盒的分割法。基于这个分割递归思想Q实现自交也可以参考这个思\。算法的程为:输入一条要计算自交的边EdgeQ对边进行离散采P采样得到的每段曲线的包围盒生成BVHq行怺,BVH中包围盒怺的两条曲U调用IntTools_EdgeEdge来计相交?/p>
L得到的曲U段会比较多Q如果用两个循环来检两两曲U段的相交情冉|能差,可以引入BVH提高性能?/p>
可以通过插值Interpolate来构造曲U测试,指定几个自交Ҏ构造插值曲Uѝ计结果如下图所C:
与曲U求交原理类|都是使用L的方法,可以思考一下数值算法如何处理?/p>
OpenCASCADE中提供了二维几何曲线的求交类Geom2dAPI_InterCurveCurveQ对应到三维几何只提供了GeomAPI_IntCS, GeomAPI_IntSSQ没有提供几何的GeomAPI_IntCC求交cR这些几何求交一般用的是数值算法,卌方程。对于两条几何曲UP(u1), Q(u2)Q求交就是解P(u1) - Q(u2) = 0q个方程。ؓ什么对于三l几何曲U没有提供数值算法?
对于拓朴Ҏ供了求交法IntTools_EdgeEdgeQ这个类是用类g曲面求交的离散网格法Q用了L包围盒法?/p>
Z包围合盒的算法是个递归法Q算法思\Q?/p>
W一ơ是分别分成2部分Q?/p>
在递归函数FindSolutions()中,只去对第一条边q行参数分割?部分Q?/p>
W一个辅助函数是FindParameters()Q用来更新第二条边在W一条边的的包围盒中的参数范_使用q个参数范围更新包围盒?/p>
W二个辅助函数是CheckCoincidence()Q用来检两D边是否重合。第一步是快速计,对边采样10个点Q若通过初步_检,后面再深入计。这些算法都不太高效?/p>
W三个辅助函数是IsIntersection()用来判断两边条在参数范围内是否相交?/p>
两条边求交q程中的包围盒显C出来,方便查看理解法。先试两个圆之间的怺Q?/p>
其中W一条边是绿色的圆,求交q程中的包围盒也用绿色表C;W二条边是红色的圆,求交q程中的包围盒也用红色表C。因为圆是闭合的Q第一ơ都分割?部分。将上面交点处理攑֤Q?/p>
后面都是第一条边分割?部分Q然后分别用q?部分的包围盒L与第二条边相交的参数范围Q再更新W二条边的包围盒。l放大上面交点处Q?/p>
发现对于两个圆的求交Q执行了100ơ,效率不高。又用两个Bh曲线求交来测试:
发现对于Bh曲线求交速度较快?/p>
曲线求交需要考虑重合部分Qopencascae中没有用数值算法来计算Q而是采用Z包围盒的法来处理。这U算法一般情况下可以快速找到求交解Q有旉归较深Q对于基本曲U可以像曲面求交一样分cd理以提高性能。opencascade中对于三l曲U求交算法性能q有优化I间?/p>
除了在OpenCASCADE入门指南中推荐的书籍之外Q还有一些进阶的书籍Q放在那儿有旉q看,M有些收获。悟性不I只有勤能补拙。对于看不懂的,只能?ldquo;书读NQ其义自?rdquo;安慰一下自己?/p>
王元 数学大辞?nbsp; 工具?nbsp; 方便一些定义,公式Q定理的查找?/p>
《计机辅助几何设计D》比较全面地介绍了计机辅助几何设计的发展历史及其主要内容和最新进展,包括C的Th曲线曲面?/p>
《样条函C计算几何》叙q样条函数和计算几何的基本理论和ҎQ同Ӟȝ了作者几q来在该领域中的研究成果.
《现代数学基丛书165Q散乱数据拟合的模型、方法和理论Q第二版Q》介l了多元散ؕ数据拟合的一般方法,包括多元散ؕ数据多项式插倹{基于三角剖分的插值方法、Boole和与Loons曲面、SibsonҎ或自焉q法、ShepardҎ、KrigingҎ、薄板样条方法、MQ拟插值法、径向基函数Ҏ、运?二乘法、隐函数hҎ、R函数法等。同时还特别介绍了近q来国际上越来越热ƈ在无|格微分方程数D斚w有诸多应用的径向基函数方法及其相关理论?/p>
主要内容包括几何偏微分方E的构造方法、各U微分几何算子的L化方法及其离散格式的收敛性、几何偏微分方程数值求解的有限差分法、有限元法以及水q集ҎQ还包括几何偏微分方E在曲面qx、曲面拼接、NҎ填补、自由曲面设计、曲面重构、曲面恢复、分子曲面构造以及三l实体几何Ş变中的应用?/p>
蒙皮Q?strong>SkinningQ就是将一截面曲U(section curvesQ融合在一L成曲面的q程。蒙皮只?strong>放样Q?strong>LoftingQ的新名词,放样可以q溯到计机没未诞生的时候,从那时到现在Q它一直在造船、汽车和航空工业中被q泛地应用?/p>
扫掠Q?strong>SweepQ研I的是一条截面曲U沿L路径曲线扫掠的问题。根据扫掠曲面的定义Q扫掠曲面未必都能表C成NURBS形式Q所以一般采用拟合算法来D。一U算法是Z蒙皮法,沿着路径曲线变换和采样N个截面,然后它们作为截面曲U进行蒙皮。随着采样数量N的增加,生成的拟合曲面精度也提高?/p>
本文主要介绍OpenCASCADE中扫掠造型法的用,除了上面一般的扫掠曲面Q还有一些高U用法?/p>
在DRaw Test Harness中输入命令setsweep可以看到有指定引?UGuide的选项Q?/p>
q个引导UGuide有什么用呢?下面l出一个示例:
其中Profile是扫掠截面,Spine为扫掠脊U,Guide为扫掠引导线。扫掠结果就是一个螺旋的d模型。在Draw Test Harness的例子中Q给Z个关于引导线扫掠的示例,两个dQ?/p>
把这两个例子理解基本能掌握扫掠算法的使用ҎQ从q两个例子可以看出,OpenCASCADE扫掠造型能力q不错?/p>
扫掠q有一个能力是使扫掠截面垂直于一个支撑面Q这是一个有用的选项。下面还是在Draw Test Harness中测试一下:
OpenCASCADE中扫掠造型法功能q比较强大,除了支持常规的扫掠外Q还支持带引导线的扫掠,及带引导U的多个截面的变形扫掠,q支持截面始l垂直于支持面的扫掠选项。扫掠的关键是确定截面的变换规则Q底层的蒙皮拟合法q是比较E_的。把Draw Test Harness中两个钻头的例子理解后,基本上应该能够掌握OpenCASCADE中扫掠造型的用方法?/p>
OpenCASCADE中对面的怺定义如下图所C:
三维I间中两个带有Geometry Surface的面FaceQ当两个Surface之间的距d于Face中的容差ToleranceQ则认ؓ是相交的。一般两个面之间怺得到的是交线Q还有一些情况得到的是交点,如下图所C:
布尔q算中面的相交是相对复杂的问题,除了考虑上述交线和交点的问题以外Q还要考虑有重叠的情况Q对于新生成的交U,q要考虑生成PCurveQ若面上有开孔,q要穿q开孔区域的交线排除{;最后要考虑如何保存面相交的l果。相交的计算在函敎ͼ
最l是调用IntTools_FaceFace来计两个面的相交。ƈ计结果交U和交点Q是否重叠等信息保存到BOPDS_InterfFF中:
cBOPDS_FaceInfo用来存储以下信息Q?/p>
注意PBo31和PBSc1Q一个状态是OnQ一个状态是Section。在怺处理cBOPAlgo_PaveFiller中通过函数BOPAlgo_PaveFiller::UpdateFaceInfo这些相交的状态更新?/p>
从前面的文章关于边与边、边与面是否有重叠时采用了固定采L来处理的不严谨的逻辑来看Q判断线的重叠是个复杂的问题Q判断面与面的重叠就相对更复杂。本文先从简单入手,先看对于最单的两个q面重叠的检,引出大家对于L两个面重叠区域检的思考。对q种Ҏ的情况处理在IntTools_FaceFace中的函数PerformPlanes()中实现。其中用二ơ曲面的几何求交法进行处理,源码如下Q?/p>
通过源码可以看出Q若两个q面之间的法向夹角小于TolAng及距d于TolӞ则认Z个面是一LIntAna_SameQ当距离大于TolӞ则认为没有相交IntAna_Empty?/p>
对于重叠的^面,theTangentFaces讄成true表示是重叠的。这里留下一个问题大家思考:如何判断自由曲面的重叠情况?
当面上有孔洞Ӟq要对交U进行处理,以排除掉孔洞中的交线。当使用IntTools_FaceFace来计两个面的交U时Q可以看CU的范围不正,没有处理孔洞情况Q甚至也没有处理面的边界。如下图所C红色的交线Z用IntTools_FaceFace计算得到的:
当用BOPAlgo_PaveFiller计算交线q结合PaveBlock得到交线昄如下图所C:
虽然计算两个面之间的怺处理最l是调用的IntTools_FaceFaceQ但是要得到正确的交UK要用类BOPAlgo_PaveFiller。这里也留下问题供大家思考:Z么IntTools_FaceFace计算的交U范围不正确Qؓ什么BOPAlgo_PaveFiller计算的交U正?
lg所qͼ布尔数据中面的相交的l果可能有交U,也可能有交点。将求交l果保存到FaceInfo中。从单的两个q面重叠来看Q将重叠的状态用变量theTangetFaces来保存。那L两个曲面重叠如何判断呢?面的怺虽然提供cIntTools_FaceFace来计,但是没有正确处理交线的范_Z么BOPAlgo_PaveFiller中可以正处理交U呢Q?/p>
大家中U国庆节日快乐!
在OpenCASCADE中对于边的相交分Zc:边与点,边与边,边与面,边与点的怺已经归结为点与边的相交处理了Q边的相交主要处理边与边Q边与面的相交。边与边、边与面的相交会引入一个新的数据结?公共部分Common PartQ用于保存重叠的公共部分数据?/p>
对于两条边的怺是指在两条边的某些地方的距离于边的容差之和Q主要分ZU情况,一U是两条边只有一个交点的情况Q一U是有重叠部分的情况Q先看只有一个交Ҏ况:
我们在DRAW中通过脚本构造最单的情况来测试?/p>
在处理边与边怺的函数BOPAlgo_PaveFiller::PerformEE()中,Ҏ两条边调用BOPAlgo_EdgeEdgeq行求交。从q里可以看到Pave Block的用,相当于对每两条边上的每对Pave Block部分q行求交。这里有一些优化空_目前是用的两个循环处理Q可以尝试用BVH来提升一些性能。当每对Pave Block对应的点的烦引号一致时Q即每对Pave Block的端炚w叠时Q用快速计的法来判断是否有重叠?/p>
对于边的求交l果保存到BOPDS_InterfEE中,都会保存是哪两条边相交及怺的公共部分。对于相交于一点的公共部分的类型ؓTopAbs_VERTEXQ对于有重叠部分的公共部分类型ؓTopAbs_EDGEQ?/p>
当两Ҏ有重叠部分时Q如下图所C:
如何两条边的公共部分呢Q在函数IntTools_EdgeEdge::IsCoincident()中实玎ͼ
//=======================================================================
//function : IsCoincident
//purpose :
//=======================================================================
Standard_Boolean IntTools_EdgeEdge::IsCoincident()
{
Standard_Integer i, iCnt, aNbSeg, aNbP2;
Standard_Real dT, aT1, aCoeff, aTresh, aD;
Standard_Real aT11, aT12, aT21, aT22;
GeomAPI_ProjectPointOnCurve aProjPC;
gp_Pnt aP1;
//
aTresh=0.5;
aNbSeg=23;
myRange1.Range(aT11, aT12);
myRange2.Range(aT21, aT22);
//
aProjPC.Init(myGeom2, aT21, aT22);
//
dT=(aT12-aT11)/aNbSeg;
//
iCnt=0;
for(i=0; i <= aNbSeg; ++i) {
aT1 = aT11+i*dT;
myGeom1->D0(aT1, aP1);
//
aProjPC.Perform(aP1);
aNbP2=aProjPC.NbPoints();
if (!aNbP2) {
continue;
}
//
aD=aProjPC.LowerDistance();
if(aD < myTol) {
++iCnt;
}
}
//
aCoeff=(Standard_Real)iCnt/((Standard_Real)aNbSeg+1);
return aCoeff > aTresh;
}
从上qC码可以看出,对于重叠部分的检是一条边Ҏ范围分?3D采LQ计每个点到另一条边的距,满条g的采L的数量超q?2个,基本认ؓ是重叠的。从q里可以看出q样重叠稍微有点不严}。固定采L数量对于段曲线来说数量q大Q对于很长的曲线来说数量又偏,q里有待提高。如果重叠,则将公共部分的数据保存v来:
对于试的TCL脚本不会走这个通用的判断流E,会直接有IntTools_EdgeEdge::ComputeLineLine()函数来处理这U特D情况:
从保存的数据可以看出Q公共部分的怺cd为TopAbs_VERTEXQ及交点分别在两条边上的参数。关于有重叠部分的两条边怺Q同学们可以自行使用DRAW脚本来测试一下?/p>
边与面的怺会遇到和边与边相交类似的情况Q即会有重叠部分Common Part。也分ؓ两种情况Q一U情冉|边与面只有一个交点的情况Q交点可能会有多个;一U情冉|有重叠部分的情况?/p>
我们可以在用脚本来试一下重叠的情况Q?/p>
从代码中可以看出当边的端点在面上Ӟ则会判断边与面会不会重叠Coincidence。判断逻辑与判断边是否重叠cMQ都是用固?3个采L的方式处理,q加上定位器来判断点是否在面上,因ؓ面上可能会有孔洞Q?/p>
//=======================================================================
//function : IsCoincident
//purpose :
//=======================================================================
Standard_Boolean IntTools_EdgeFace::IsCoincident()
{
Standard_Integer i, iCnt;
Standard_Real dT, aT, aD, aT1, aT2, aU, aV;
gp_Pnt aP;
TopAbs_State aState;
gp_Pnt2d aP2d;
//
GeomAPI_ProjectPointOnSurf& aProjector=myContext->ProjPS(myFace);
Standard_Integer aNbSeg=23;
if (myC.GetType() == GeomAbs_Line &&
myS.GetType() == GeomAbs_Plane)
aNbSeg = 2; // Check only three points for Line/Plane intersection
const Standard_Real aTresh = 0.5;
const Standard_Integer aTreshIdxF = RealToInt((aNbSeg+1)*0.25),
aTreshIdxL = RealToInt((aNbSeg+1)*0.75);
const Handle(Geom_Surface) aSurf = BRep_Tool::Surface(myFace);
aT1=myRange.First();
aT2=myRange.Last();
Standard_Real aBndShift = 0.01 * (aT2 - aT1);
//Shifting first and last curve points in order to avoid projection
//on surface boundary and rejection projection point with minimal distance
aT1 += aBndShift;
aT2 -= aBndShift;
dT=(aT2-aT1)/aNbSeg;
//
Standard_Boolean isClassified = Standard_False;
iCnt=0;
for(i=0; i <= aNbSeg; ++i) {
aT = aT1+i*dT;
aP=myC.Value(aT);
//
aProjector.Perform(aP);
if (!aProjector.IsDone()) {
continue;
}
//
aD=aProjector.LowerDistance();
if (aD > myCriteria) {
if (aD > 100. * myCriteria)
return Standard_False;
else
continue;
}
//
++iCnt;
//We classify only three points: in the begin, in the
//end and in the middle of the edge.
//However, exact middle point (when i == (aNbSeg + 1)/2)
//can be unprojectable. Therefore, it will not be able to
//be classified. Therefore, points with indexes in
//[aTreshIdxF, aTreshIdxL] range are made available
//for classification.
//isClassified == TRUE if MIDDLE point has been chosen and
//classified correctly.
if(((0 < i) && (i < aTreshIdxF)) || ((aTreshIdxL < i ) && (i < aNbSeg)))
continue;
if(isClassified && (i != aNbSeg))
continue;
aProjector.LowerDistanceParameters(aU, aV);
aP2d.SetX(aU);
aP2d.SetY(aV);
IntTools_FClass2d& aClass2d=myContext->FClass2d(myFace);
aState = aClass2d.Perform(aP2d);
if(aState == TopAbs_OUT)
return Standard_False;
if(i != 0)
isClassified = Standard_True;
}
//
const Standard_Real aCoeff=(Standard_Real)iCnt/((Standard_Real)aNbSeg+1);
return (aCoeff > aTresh);
}
求交l果与边与边怺cdQ会保存边与面的索引Q及公共部分的数据。除了保存这些数据以外,q和点与面相交一P更新面上的信息FaceInfoQ即有哪些边在面上?/p>
lg所qͼ边与辏V边与面怺会得到公共部分Common PartQ公共部分可能是点,也可能是重叠的边。在qo怺的边与边、边与面旉有一定的优化I间Q即使用BVH来加速检相交部分。在快速判断边与边是否重叠、边与面是否重叠部分的代码采用固定数量的采样点的处理方式不太严}。将怺的结果及q程数据都保存到BOPDS_DS中作为后面算法用?/p>
ImGui 是一个用于C++的用L面库Q跨q_、无依赖Q支持OpenGL、DirectX{多U渲染APIQ是一U即时UIQImmediate Mode User InterfaceQ库Q保留模式与x模式的区别参?a target="_blank" rel="noopener">保留模式与即时模?/strong>。ImGui渲染非常快,但界面上有大量的数据集需要渲染可能会有一些问题,需要用一些缓存技巧。缓存只是避免数据的更新逻辑耗时太久影响渲染Q实际渲染过E不存在瓉?/p>
IMGUI很轻量,q支持跨q_Q对于小的测试程序IMGUI是理想的GUI?/p>
Zopencascade的glfw sample加入IMGUIQ这样就可以开发一些带有GUI的程序。这些程序小巧且能方便跨q_Q看上去效果也不错?/p>
现在OcctImgui开源,开源地址Qhttps://github.com/eryar/OcctImgui 使用Premake来生成解x案,只需要将premake5.lua中的相关W三方库的\径修改一下,卛_以直接编译运行?/p>
目前occt的视图作为整个背景,下一步可以做成像CADRays中那Pocct的视图作囄一部分Q这样就可以使用IMGUI的Docking功能?/p>
使用IMGUI也可以开发出很Cool的界面,最后放两个ZIMGUI开发的囑Ş界面Q?/p>
https://github.com/adriengivry/Overload https://github.com/sasobadovinac/CADRays https://github.com/MeshInspector/MeshLib2 OcctImgui
3 Next
]]>
在OpenCASCADE中,布尔相关的算子Operator有General Fuse Operator(GFA)QBoolean Operator(BOA)QSection Operator(SA)QSplitter Operator(SPA)Q这些布算子都q一套数据结构BOPDS_DSQ其中存储了输入数据及中间结果数据。布算子包含两部分Q?/p>
由此可见Q布数据BOPDS_DS是布操作中的数据中转站Q将布尔操作的输入数据及中间计算l果数据都保存v来。本文主要介lBOPDS_DS保存的数据?/p>
BOPDS_DS中存储的信息有:
q里的Shapes是模型信息BOPDS_ShapeInfoQ存储模型类型,包围盒等数据Q?/p>
q里应该不需要再另外保存myTypeQ因为在myShape中可以直接获取类型信息。模型信息在初始化函数Init()中来讄Q主要是包围盒等信息Q?/p>
//=======================================================================
//function : Init
//purpose :
//=======================================================================
void BOPDS_DS::Init(const Standard_Real theFuzz)
{
Standard_Integer i1, i2, j, aI, aNb, aNbS, aNbE, aNbSx;
Standard_Integer n1, n2, n3, nV, nW, nE, aNbF;
Standard_Real aTol, aTolAdd;
TopAbs_ShapeEnum aTS;
TopoDS_Iterator aItS;
TColStd_ListIteratorOfListOfInteger aIt1, aIt2, aIt3;
TopTools_ListIteratorOfListOfShape aIt;
BOPDS_IndexRange aR;
Handle(NCollection_BaseAllocator) aAllocator;
TopTools_MapOfShape aMS;
//
// 1 Append Source Shapes
aNb=myArguments.Extent();
if (!aNb) {
return;
}
//
myRanges.SetIncrement(aNb);
//
aNbS=0;
aIt.Initialize(myArguments);
for (; aIt.More(); aIt.Next()) {
const TopoDS_Shape& aSx=aIt.Value();
//
aNbSx=0;
TotalShapes(aSx, aNbSx, aMS);
//
aNbS=aNbS+aNbSx;
}
aMS.Clear();
//
myLines.SetIncrement(2*aNbS);
//-----------------------------------------------------scope_1 f
aAllocator=
NCollection_BaseAllocator::CommonBaseAllocator();
//
//
i1=0;
i2=0;
aIt.Initialize(myArguments);
for (; aIt.More(); aIt.Next()) {
const TopoDS_Shape& aS=aIt.Value();
if (myMapShapeIndex.IsBound(aS)) {
continue;
}
aI=Append(aS);
//
InitShape(aI, aS);
//
i2=NbShapes()-1;
aR.SetIndices(i1, i2);
myRanges.Append(aR);
i1=i2+1;
}
//
aTolAdd = Max(theFuzz, Precision::Confusion()) * 0.5;
myNbSourceShapes = NbShapes();
//
// 2 Bounding Boxes
//
// 2.1 Vertex
for (j=0; j<myNbSourceShapes; ++j) {
BOPDS_ShapeInfo& aSI=ChangeShapeInfo(j);
//
const TopoDS_Shape& aS=aSI.Shape();
//
aTS=aSI.ShapeType();
//
if (aTS==TopAbs_VERTEX) {
Bnd_Box& aBox=aSI.ChangeBox();
const TopoDS_Vertex& aV=*((TopoDS_Vertex*)&aS);
const gp_Pnt& aP=BRep_Tool::Pnt(aV);
aTol = BRep_Tool::Tolerance(aV);
aBox.SetGap(aTol + aTolAdd);
aBox.Add(aP);
}
}
在初始化函数中通过两个递归函数TotalShapes()和InitShape()来收集所有模型数据,然后再分别计点、边、面的包围盒。这些包围盒数据为后面用BVH怺做准备?/p>
怺数据Interferences主要用来保存求交l果数据Q用了单的z关系Q不同的怺cd得到不同的相交结果?/p>
保存的数据有Q?/p>
其中Index1和Index2为相交的两个模型在BOPDS_DS中的索引受对于点Vertex和边Edge的相交结果,保存了相交点在边上的参数myParamQ?/p>
在DRAW中输入相关的命o可以方便地对q些数据l构q行Debug?/p>
从源码可以看出,在做求交的初始函C准备了三部分数据Q一个是BOPDS_DSQ一个是BOPDS_IteratorQ还有一部分是缓存的求交工具的数据IntTools_Context。后面将l合DRAW代码对C++源码调试Q分析布操作中求交数据BOPDS_DS保存的具体数据?/p>
OpenCASCADE中新的布工具TKBO相对已经废弃的TKBool代码更规范,更易于理解。与ModelingData和ModelingAlgorithms大的模块l织一P主要也是数据l构Data Structure+法Algorithm的组lŞ式?/p>
其中BOPDS为布中的数据结构部分,BOPAlgo为布中的算法部分。理解算法的前提是先理解数据l构DS(Data Structure)Q所以先从数据结构入手,来深入理解布操作。本文先从简单的数据l构BOPDS_Iterator开始对源码q行分析?/p>
从类的注释可以看出,q代器BOPDS_Iterator有以下两个功能:
- 扑և包围盒相交的ShapeQ?/p>
- 遍历怺的一对ShapeQ?/p>
//! The class BOPDS_Iterator is
//! 1.to compute intersections between BRep sub-shapes
//! of arguments of an operation (see the class BOPDS_DS)
//! in terms of theirs bounding boxes
//! 2.provides interface to iterate the pairs of
//! intersected sub-shapes of given type
class BOPDS_Iterator
{
public:
其中核心的算法在函数Intersect()中,代码如下所C:
//=======================================================================
// function: Intersect
// purpose:
//=======================================================================
void BOPDS_Iterator::Intersect(const Handle(IntTools_Context)& theCtx,
const Standard_Boolean theCheckOBB,
const Standard_Real theFuzzyValue)
{
const Standard_Integer aNb = myDS->NbSourceShapes();
// Prepare BVH
BOPTools_BoxTree aBoxTree;
aBoxTree.SetSize (aNb);
for (Standard_Integer i = 0; i < aNb; ++i)
{
const BOPDS_ShapeInfo& aSI = myDS->ShapeInfo(i);
if (!aSI.HasBRep())
continue;
const Bnd_Box& aBox = aSI.Box();
aBoxTree.Add (i, Bnd_Tools::Bnd2BVH (aBox));
}
// Build BVH
aBoxTree.Build();
// Select pairs of shapes with interfering bounding boxes
BOPTools_BoxPairSelector aPairSelector;
aPairSelector.SetBVHSets (&aBoxTree, &aBoxTree);
aPairSelector.SetSame (Standard_True);
aPairSelector.Select();
aPairSelector.Sort();
// Treat the selected pairs
const std::vector<BOPTools_BoxPairSelector::PairIDs>& aPairs = aPairSelector.Pairs();
const Standard_Integer aNbPairs = static_cast<Standard_Integer> (aPairs.size());
Standard_Integer iPair = 0;
const Standard_Integer aNbR = myDS->NbRanges();
for (Standard_Integer iR = 0; iR < aNbR; ++iR)
{
const BOPDS_IndexRange& aRange = myDS->Range(iR);
for (; iPair < aNbPairs; ++iPair)
{
const BOPTools_BoxPairSelector::PairIDs& aPair = aPairs[iPair];
if (!aRange.Contains (aPair.ID1))
// Go to the next range
break;
if (aRange.Contains (aPair.ID2))
// Go to the next pair
continue;
const BOPDS_ShapeInfo& aSI1 = myDS->ShapeInfo (aPair.ID1);
const BOPDS_ShapeInfo& aSI2 = myDS->ShapeInfo (aPair.ID2);
const TopAbs_ShapeEnum aType1 = aSI1.ShapeType();
const TopAbs_ShapeEnum aType2 = aSI2.ShapeType();
Standard_Integer iType1 = BOPDS_Tools::TypeToInteger (aType1);
Standard_Integer iType2 = BOPDS_Tools::TypeToInteger (aType2);
// avoid interfering of the shape with its sub-shapes
if (((iType1 < iType2) && aSI1.HasSubShape (aPair.ID2)) ||
((iType1 > iType2) && aSI2.HasSubShape (aPair.ID1)))
continue;
if (theCheckOBB)
{
// Check intersection of Oriented bounding boxes of the shapes
const Bnd_OBB& anOBB1 = theCtx->OBB (aSI1.Shape(), theFuzzyValue);
const Bnd_OBB& anOBB2 = theCtx->OBB (aSI2.Shape(), theFuzzyValue);
if (anOBB1.IsOut (anOBB2))
continue;
}
Standard_Integer iX = BOPDS_Tools::TypeToInteger (aType1, aType2);
myLists(iX).Append (BOPDS_Pair (Min (aPair.ID1, aPair.ID2),
Max (aPair.ID1, aPair.ID2)));
}
}
}
在求交函数Intersect中用BVH快速找出包围盒有相交的每对ShapeQƈ以烦引的形式记录下来。从q个函数中可以看出布操作是否用OBB的选项的作用:当不使用OBBӞ只以AABB包围盒来相交的ShapeQ当使用OBBӞ在AABB的基上进一步用包围更紧密的OBB来检相交,可以排除部分。当怺的模型中以AABB就能检出来的Q再打开OBB选项Q不会提高性能Q反而会有所降低。ؓ了减这个媄响,在IntTools_Context中缓存Cachingq些OBBQ避免构造OBB带来的性能损失?/p>
布尔q代器BOPDS_Iterator通过BVH扑և求交的模型中每对包围盒有怺的模型ƈ提供遍历每对包围盒相交的模型的功能,为后面求交作准备。从其代码实现可以看出布选项使用OBBҎ能提高是有限的Q当使用AABB能检出来的Q再使用OBB会降低性能。当使用AABB出来相交,但OBB不相交的场景Ҏ能提升明显?/p>
今日?#8220;九一八事?#8221;92周年Q落后就要挨打,吾辈仍需努力?/p>
l常用Visual Studio写一些小E序来验证OpenCASCADE的功能,每次创徏目后都配置头文Ӟ库\径,E序q行时还要配|Debug的环境变量,比较ȝ。也试qCMake和QMakeQ都不太理想。CMake学习曲线陡峭一点,q会生成一堆文件。QMake单些Q但是有的选项不支持。直到看C个开源的游戏E序OverloadQ看其编译说明用了Premake来构建?/p>
使用IMGUI生成的Y件界面比较酷炫,使用Premake生成Visual Studio解决Ҏ?/p>
构徏pȝQBuildSystemQ是用来从源码生成用户可以用的目标QTargetsQ的自动化工兗目标可以包括库Q可执行文gQ或者生成的脚本{等?/p>
目模块依赖关系l护 Q?/p>
目标的可配置化(不同pȝQWindowsQMac…Q不同^収ͼWin32QWin64QAmd64…Q?/p>
目标生成的自动化
主要用于提高开发h员的效率与稳定,试与发布的效率
-减少开发h员的知识成本Q比如对同一程Q但多种q_Q多U开发环境差异化的了解)
-减少目目变动的维护成?/p>
– VisualStudio 的编译选项规则Q配|方?/p>
– Xcode 的编译选项规则Q配|方?/p>
– 其他。。?/p>
-减少模块的维护成?/p>
– 协同人员对工E文Ӟ目录的修改冲H?/p>
减少q_Q系l生成的差异化成本(可以z地配置不同的^CpȝQ?/p>
– 通过单的配置Q可以灵z,快速地dQ修改,更新模块
– 可以方便的处理依赖关p,提高E_性和~译速度
使用脚本和配|文Ӟ实现自动清理Q生成所需q_Q系l,调试环境Q测试流E,然后发布版本?/p>
ȝ来说Q就是生成q程更加z,灉|Q高效,自动化?/p>
L的可以跨q_Q支持C++的构建系l?/p>
- CMake
- Scons
- Premake
其他q有 GNU MakeQGNU autotoolsQApache AntQ主要用于JavaQ,GradleQ主要用于JavaQ?/p>
Premake 是一U命令工P通过d目脚本Q来生成各种开发环境的目文g。主要用于:
生成开发h员喜Ƣ的q_Q工具集Q协同开发的人员Q可以用不同的q_和开发工P
通过脚本保持不同q_Q工具集下的目配置同步Q比如新建文件夹Q引入新的库文gQ?/p>
通过脚本来快速更新许多不同的大型代码库,q新生成项目(比如对依赖的多种著名库的版本更新Q?/p>
快速升U工具集的版本(比如无缝CVisualStudio 2010升到VisualStudio 2019Q?/p>
目前支持Q?/p>
Microsoft Visual Studio 2005-2019
GNU MakeQ包?Cygwin ?MinGW
XCode
Codelite
Premake 5.0 目前支持Q?/p>
32 ?64 位^?/p>
Xbox 360Q仅支持Visual StudioQ?/p>
插g模块可以支持其他语言Q框Ӟ和工具集
Premake 是存_的旧版C应用E序Q发布ؓ一个单个的Q非常小的exe文g。支持完整的Lua脚本环境
开源地址Qhttps://github.com/premake/premake-core
下蝲地址Qhttps://premake.github.io/
实例地址Q?a >https://github.com/wuguyannian/tutorial_premake
使用premake来构Z个用了glfw, occt和imgui的程序。从配置文g可以看出Q配|比CMake要简单:
workspace "OcctImgui"
configurations {"Debug", "Release"}
system "Windows"
platforms {"Win64"}
architecture "X64"
language "C++"
project "OcctImgui"
kind "ConsoleApp"
language "C++"
targetdir "build/bin/%{cfg.buildcfg}"
objdir "build/obj/%{cfg.buildcfg}"
files { "**.h", "**.cpp"}
-- Header files.
includedirs
{
"C:/OpenCASCADE-7.6.0/opencascade-7.6.0/inc",
"C:/glfw-3.3.8/include"
}
-- Library files.
links
{
"TKernel", "TKMath", "TKG2d", "TKG3d", "TKGeomBase", "TKGeomAlgo", "TKBRep", "TKTopAlgo", "TKPrim", "TKMesh", "TKService", "TKOpenGl", "TKV3d",
"glfw3"
}
filter "configurations:Debug"
defines { "DEBUG" }
symbols "On"
libdirs
{
"C:/OpenCASCADE-7.6.0/opencascade-7.6.0/win64/vc14/libd",
"C:/glfw-3.3.8/lib"
}
debugenvs
{
"path=%path%;C:/OpenCASCADE-7.6.0/opencascade-7.6.0/win64/vc14/bind"
}
filter "configurations:Release"
defines { "NDEBUG" }
symbols "Off"
optimize "On"
libdirs
{
"C:/OpenCASCADE-7.6.0/opencascade-7.6.0/win64/vc14/lib",
"C:/glfw-3.3.8/lib"
}
debugenvs
{
"path=%path%;C:/OpenCASCADE-7.6.0/opencascade-7.6.0/win64/vc14/bin"
}
通过premake生成的Visual Studio解决Ҏ很干净Q没有多余的文g。后面再要写一个小的验证程序时Q只需要复制premake5.lua修改一下即可,很方ѝ对于小的验证程序来_使用premake是理想的构徏工具?/p>