Archive for the ‘3-开发 Develop’ Category

OSG项目实践之多轴数控(2)-通用图形仿真平台开发设计方案

数控指令翻译及驱动

数控指令用于驱动机械执行特定的动作或设置某种状态。虽然每种机床的数控指令都有所不同,不同种类的机床的数控指令集差别很大,但可以根据指令分类为:驱动指令、状态指令。

驱动指令驱动机械进行一些特定的动作,例如常见的数控铣床的G01代表直线插补指令,用于驱动一个或几个轴进行直线插补动作。状态指令设置当前模式,例如常见的G41、G42命令设置当前的刀具插补方式。

在驱动虚拟机床时,必须要将数控指令进行翻译。传统的指令翻译方法将指令翻译为一个命令ID的链表,然后根据ID的不同进行指令区分。在本节中将运用设计模式来更好的解决指令翻译中的软件设计问题。

设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。常见的设计模式有23种[15]。根据数控指令的特点,可以应用软件设计模式中的命令设计模式进行动作驱动,有限状态机的方式进行状态的存储和跳转。

命令设计模式

图 3-3使用UML图描述了整个命令设计模式。其中:

  1. Command接口声明了执行命令的接口。
  2. ConcreteCommand实现了Command接口,具体定义了命令的执行方法。其中Execute()函数实现真正的命令执行,他调用Receiver(接受者)的响应操作来实现。
  3. Client通常是应用程序,它创建一个具体的命令对象并设置它的接受者。
  4. Invoker对应于一个触发者,这个触发者可能是特定的按钮或者特定的条件,它要求命令执行这个请求。
    1. Receiver作为接受者,知道如何实施与执行一个请求相关的操作。

    图 3-3 命令设计模式UML图

    在仿真平台中,每一种G代码都对应于一个ConcreteCommand类。对G代码进行分解后可得到一系列的ConcreteCommand类的实例。例如以下代码:

    G01X0087Y2234Z234

    G02X2345Y86234I2312

    G42

    G01X-987234Y09723Z234

    分析以上代码可以得到图 3-4的结构。类图中有G01、G02、G42四个类,其功能相似,都继承于AbstractCommand,每个类单独的Execute()函数执行各自相关的动作。CommandSequence类较为特殊,它存储一个链表,此链表存储各种命令的实例的指针,可以模拟一批命令进行不可分割的顺序执行的过程。

    图 3-4 命令及命令链表

    通常,将这些命令对应的类的实例指针按照执行的顺序放入链表中,主程序并不需要直接知道这是什么指令,就能进行相关的动作。例如以上的命令可以有如下伪代码:

    List<AbstractCommand> cmdList;

    cmdList.Add(new G01_Command);

    cmdList.Add(new G02_Command);

    cmdList.Add(new G42_Command);

    cmdList.Add(new G01_Command);

    foreach cmd in cmdList

    {

    int rst=cmd->Execute(); //通过AbstractCommand接口进行统一调用。

    //检查返回值并执行另外的动作

    }

    如果采用传统的应用链表存储指令ID的方式有以下的伪代码:

    List<int> cmdList;

    cmdList.Add(G01_ID);

    cmdList.Add(G02_ID);

    cmdList.Add(G42_ID);

    cmdList.Add(G01_ID);

    foreach cmd in cmdList{

    switch(cmd){

    case G01_ID:

    do_G01_Command();break;

    case G02_ID:

    do_G02_Command();break;

    case …

    }

    }

    传统的思路和命令模式会的对比如表3-1。可见应用命令模式会减少设计的难度并且减少软件的维护成本。

    表3-1 传统思路和命令模式对比

     

    传统思路

    命令模式

    接口设计

    无统一接口,较为混乱

    统一的接口,通过AbstractCommand定义

    添加指令的灵活度

    增加ID和switch/case,增加指令会改变调用命令的逻辑。

    增加相关类,调用逻辑不变。

    高级应用

    较难实现,并且维护起来较为麻烦。

    可以通过操作链表来实现命令的增添、删除、撤销(Undo)操作等。

  5. 有限状态机
  6. 图 3-5 数控系统典型状态机

    有限状态机(也称"有限状态自动机")提供了一个简单、优雅的方法揭示和定义复杂系统的行为。它们同样也提供了一个易于理解、易于修改的有效实现策略。在仿真系统中,从控制高层逻辑的GUI到最底层的通讯协议,都可以应用它[16]

    有限状态机提供了一种途径,使得执行了某种动作后会将处从一种状态跳转为另一种状态。图 3-5描述了一个数控系统典型的状态机。每一个圆圈表示一个"状态"(State),箭头代表了状态之间的"转化"(Transaction),箭头上标明了状态转化所需要的"动作"(Action)。一个相同的动作在不同的状态会产生不同的效果。例如在"初始化"状态时,执行"运行",则状态依然保持在运行状态;当处于"文件已载入"的情况下,执行"运行"则会进入到"运行中"的状态,在此状态下执行"运行"则停留在"运行中";在"报警中"的状态下,执行"运行"还是回到"报警中"的状态,仅有执行"解除报警"才能回到"运行中"的状态。

    为了实现有限状态机,有以下几种典型方案:嵌套switch/case语句;迁移表;状态模式。其中状态模式具有嵌套switch/case语句的效率又有迁移表的灵活性[16]

    状态模式

    图 3-6状态模式UML

    图 3-6是状态模式的UML图,其中:

  7. Context定义了用户感兴趣的接口并维护了一个ConcreteState子类的实例,定义了当前的状态。
  8. State接口定义一个接口封装特性状态相关的行为。
  9. ConcreteState实现了Context的一个状态相关行为。

    在应用中,具体的State状态实例会被ConcreateState代替,Handle()函数的调用也会根据ConcreateState的不同而调用不同的Handle()。

    图 3-5所示状态机可用图 3-7的UML图解释表示。应用程序初始化时Application类初始化了四个状态,并将初始化状态设置为Init状态(1)。此时State实质是Init类的实例。Init类拥有五个继承的函数。执行LoadFile()动作会执行loadFile()函数,并且将this指针指向新的FileLoaded类的实例。此时State指向了FileLoaded类的实例。执行其他四个函数并不会执行有效的过程,内存中State依然指向Init。

    在FileLoaded状态下,执行Run()动作便会激发Run()函数,并且将当前this指针指向新的Running类的实例,这就实现了从FileLoaded到Running状态的转换。以此类推,Running类执行Stop()函数后将会将this指针指向FileLoaded类的实例。实现了从Running到FileLoaded的转换。

    图 3-7状态转化

    状态机编译器

    在具体的实践中,状态机的代码量随着状态的增加而变得几乎不可能用人工完成,必须要有适当的工具辅助人工进行代码的编写。State Machine Compiler(见:http://smc.sourceforge.net/)就是这样一种工具,它使用简便的语法实现状态机的编写,提供多语言支持,支持生成图形,大大简化了状态机的编写。

    真实感图形显示

    真实感图形技术是指在使用显示设备绘制物体图形时,必须把三维信息经过某种投影变换,在二维的显示表面上绘制出来,使其具有真实地立体感觉。三维图形的表现形式有三种,第一种是线框图,它是通过物体的棱边和轮廓线表示物体。用线框图表示的三维图形具有二义性。第二种形式是消隐图,图中只保留形体上能看见的部分,看不见或被遮挡的部分不画出来或用虚线表示。第三种形式是用光照效果、图案纹理和颜色,使图形具有立体真实地感觉。在实际应用中,第三种形式应用最多。

    在实际应用中,通常为了增加真实感,还需要包含以下图形技术:

    1. 提供精细的多边形模型及消隐技术。

    场景的模型的多边形数量直接决定了仿真系统的精细度,对于要求较高的场合,通过导出机床的设计图纸可以得到极为真实的机床模型。但由于应用如此精细的机床模型可能会降低场景的整体性能,并且隐藏不可见零件或合并过于细小零件对场景的视觉感官差异并不大,所以在实际应用时力求精简几何模型,达到速度和画质的平衡。

    另一方面,精细的多边形模型会为干涉检查带来更多的优势。对于某些较为特殊的机床,例如多轴联动的弯管机,可能需要进行某些极限情况下的仿真。这时机床零件之间的相互距离减小到一个临界值,如果多边形模型过于粗糙,则可能不能反映出正确的加工要求。

    多边形模型如果用线框模型进行显示,则会显示出大量的线段,没有层次感、不便于进行观察。通过面片模型进行渲染,进行隐面消除会渲染出较为真实的图形。传统的OpenGL或DirectX提供了基于Z-buffer的隐面消除方案。OpenSceneGraph则直接在物体空间进行剔除和裁剪,完全不绘制不出现的物体,为更复杂场景提供了速度更快,质量更好的隐面消除。

    1. 光照模型、明暗处理

    如果场景仅仅是消隐过的多边形模型,则渲染得到的多边形图形将会是缺乏立体感,看起来像是平面的对象。之所以得到这样的结果,是由于默认的光照方式使得观察者看到的只是单一的颜色。例如一个球体,则会看上去是一个均匀着色的圆。

    OpenGL提供了明暗处理的模型,将光线与对象之间的相互作用分镜面反射、漫反射和半透明表面三种。为了提高渲染的速度,OpenGL引入并实现了Phong反射模型,通过设置环境光、漫反射光和镜面反射光即可得到较为真实的三维图像。实际应用中,大多数建模软件导出的三维模型的选项中都提供了导出光照的选项,结合仿真系统自身的光照系统,能够获得最佳的图像质量。

    1. 应用材质和贴图

    材质的应用在提升整体视觉效果上占有较高的比例。人眼通过不同的材料的视觉效果能够区分材料的种类,而贴图则能提供一般无法用多边形表达的图形。OpenGL提供了设置多边形颜色、材质和进行多边形贴图的函数。通常,材质和贴图应用在比较仿真级别较高的场合,用于更为真实的交互环境。

OSG项目实践之多轴数控(1)-通用图形仿真平台开发设计方案

一个完善的仿真平台必须要提供行业仿真的基本功能以及相关的接口。图形仿真平台最为关键的功能包括机床及环境的显示、加工刀具的显示、加工工件的显示。这些部件之间存在相互的关联,必须要建立相关的数学模型进行位置、速度、加速度的计算。为了能够驱动仿真平台的几何图形,数控平台还将导入相关的驱动指令,包括平移、旋转、换刀等。得到驱动指令后,经过离线插值转换,生成相关部件的移动指令、刀具的路径以及刀具姿态。通过刀具和工件之间的相互关系,得到工件的切削、变形的形状,最终通过显示接口反映到几何图形进行显示。为了完善用户体验,还需要建立一套适合的用户操作界面,便于用户进行指令的载入、进行加工验证、修改指令、输出仿真结果等。

数控机床几何建模及几何关系的确立

仿真系统中的虚拟数控机床要实现指令驱动,必须要建立起各种几何关系。几何关系来源于数控机床的几何模型。通常一个仿真系统中应用的几何模型都是真实机床的简化。一个典型的数控系统包括以下几个部分:

主机,它是数控机床的主体。包括机床身、立柱、主轴、进给机构等机械部件。是用于完成各种切削加工的机械部件。

数控装置,是数控机床的核心,包括硬件(印刷电路板、显示器、键盘、纸带阅读机等)以及相应的软件,用于输入NC代码,并完成输入信息的存储、数据的变换、插补运算以及实现各种控制功能。

辅助装置,指数控机床的一些必要的配套部件,用以保证数控机床的运行,如冷却、排屑、润滑、照明、监测等。它包括液压和气动装置、排屑装置、交换工作台、数控转台和数控分度头,还包括刀具及监控检测装置等。

驱动装置,它是数控机床执行机构的驱动部件,包括主轴驱动单元、进给单元、主轴电机及进给电机等。它在数控装置的控制下通过电气或电液伺服系统实现主轴和进给驱动。当几个进给联动时,可以完成定位、直线、平面曲线和空间曲线的加工。

编程及其他附属设备,可用来在机外进行零件的程序编制、存储等。

在仿真系统中,主要针对主机进行开发,其他的部件可以根据需求的不同,进行不同程度的简化。

几何建模

机床的几何模型一般可以由制造商进行提供简化的图纸进行设计。常用的设计软件包括SolidWorks、UG、AutoCAD、3DSMax等。为了能够在仿真平台中应用,这些设计软件设计的模型必须要导出为一种通用的格式。绝大数软件都支持业界标准的VRML(Virtual Reality Modeling Language,虚拟现实建模语言)格式。

对于某些特殊的部件,例如最常见的模具,需要进行参数化建模。此时需要根据模具的几何特征,编写相应的代码来实现。通常应用CSG进行参数化建模。

导入到仿真平台后,各个部件将具有一定的几何形状、材质及其他属性。必须确立几何关系,将其组装起来才能组成一个完整的机床。

几何关系确立

由软件导出的各种零件由于没有任何约束,可以在三维空间中自由的移动,具有6个自由度。为了能够让各种零件能够按照真实机床运动,必须将零件的几何关系进行设置。零件之间的几何关系分为以下几类:

父子关系:父零件能够带动子零件一起运动,而子零件能够在父零件上单独运动。

约束关系:一个零件约束另一个零件的运动,例如常见的键用于固定两个零件。

比例关系:一个零件的运动是另一个零件的运动的比例放大。

所有的零件都使用矩阵在三维空间中进行定位,以上这些关系也可以通过矩阵的形式进行表达。

图 3-1是五轴数控铣床结构的一种。工作台定义了整个机床在空间中的位置,立架X和上转台B均为工作台的直接接触体。它们与工作台属于"父子关系"。其中立架X和工作台之间是刚性结合,也可将它们看成"约束关系"。刀架Y位于立架X之上,属于"父子关系"。其中刀架Y仅能在立架X的竖直方向运动,属于"约束关系"。上转台A位于上转台B之上,属于"父子关系"。但转台A仅能做旋转运动,属于"约束关系"。

图 3-1五轴数控铣床结构

4X4矩阵表达转换

在图形学中应用最多矩阵是4X4的矩阵,其具有以下特点:

将空间中的向量通过扩展维度转换为向量,最后一位通常称之为w,这种4D向量称为齐次坐标。

用于变换的4X4矩阵由3X3变换矩阵发展而来:

(3-1)

4X4矩阵和3X3矩阵同样可以描述旋转变换:

(3-2)

(3-3)

 

但4X4矩阵可以用矩阵乘法来表达平移,而3X3矩阵中是不可能的:

(3-4)

6个自由度的移动可以分解为平移和旋转,令R表示旋转,T表示平移,则有:

(3-5)

由于使用行向量表示向量v,所以变换的顺序必须要和矩阵乘法的顺序相吻合(从左往右),先旋转再平移。所以变换后的向量v'可以表示为:

(3-6)

观察RT的乘积,令M=RT,可以计算得到

(3-7)

M左上角的3X3称为旋转部分,最下一行是平移部分,此时,通过一个单独的4X4矩阵就能够同时表达旋转和平移。

矩阵表达零件相互关系

在三维空间中,通过从场景根原点开始计算场景树每个分支的矩阵计算可以得到物体在场景根中的绝对位置。根场景是用户自定义的一个三维空间,也可以称为世界坐标。图 3-2是图 3-1的简化的场景图,场景图通过矩阵的转换来布置场景。每个物体的空间绝对位置的矩阵通过遍历并累计父矩阵得到。

图 3-2 五轴数控铣床结构场景图

工作台和立架属于"父子关系",也属于完全固定的"约束关系"。工作台相对于场景根的转换矩阵为Mt,立架X相对于场景根的转换矩阵为Mx。如果仅更新Mt而不更新Mx,则在场景中,工作台的移动将不会带动立架X的移动。为了能够让立架X能在工作台Mt运动的时候一起运动,并且立架X自身的运动并不影响工作台的运动,需要在Mx和Mt之间引入一个矩阵Mxt,这个矩阵就确定了两者之间的关系:

(3-8)

对于立架X上每一个向量,都有如下的转换:

(3-9)

由于立架X被约束在工作台上,所以的乘积为一个常量矩阵,Mx和Mt之间的坐标关系是固定的。通过计算公式(3-8)可以得到Mxt的值。

立架X和刀架Y既属于"父子关系",也属于"约束关系"。引入矩阵Myx确定"父子关系",确定"约束关系":

(3-10)

矩阵Myx由于有Y方向的运动的"约束关系",根据4X4矩阵的特点,所以可得约束后的矩阵:

(3-11)

考虑v到v'的无约束转换有:

(3-12)

代表了从My空间到Mx空间无约束的转换,此转换在模型导出时已经计算好。设此时,可得

(3-13)

这便实现了Y方向上的约束。

更一般的,可得各种条件下的约束:



(3-14)

比例关系相对简单,它的实质是运动的增量放大,可以直接的表达为以下约束:

(3-15)

 

OSG项目实践之弯管仿真(6)-干涉检查

6.1  干涉检查基本原理

干涉检查(Collision Detection)就是检测虚拟场景中不同对象之间是否发生了干涉。从几何上讲,干涉检查表现为两个多面体的求交测试问题;按对象所处的空间可分为二维平面干涉检查和三维空间干涉检查[20]。

本课题中所研究的物体都是三维空间的干涉检查,且所有的物体都离散为多面体,所以本课题的干涉检查是三维空间的多面体的干涉检查。而多面体可以离散为很多三角形,这就简化为对三角形的求交测试。

干涉检查及其相关问题有很长的研究历史,特别是刚体间干涉检查的研究,已形成了一些比较成熟的技术。如层次包围盒方法,其基本思想是通过建立对象的包围盒树状层次结构来逐渐逼近对象的几何模型,从而用体积略大而形状简单的包围盒代替复杂的几何对象参加干涉检查,通过包围盒间的相交测试快速地排除不相交的基本几何元素对,以减少相交测试的次数[9]。其中最为著名的是RAPID 系统,它是1996 年由Gottschalk 等人实现的一个开放的软件包。本系统中的干涉检查算法就是使用的RAPID系统。

6.2  RAPID干涉检查算法

RAPID干涉检查算法基于OBB包围盒。一个给定对象的OBB 被定义为包含该对象且相对于坐标轴方向任意的最小的正六面体。OBB 的最大特点是其方向的任意性,这使得它可以根据被包围对象的形状特点尽可能紧密地包围对象。因此,计算OBB 的关键是寻找最佳方向,并确定在该方向上包围对象的包围盒的最小尺寸。通过计算协方差矩阵C可以得到三个特征向量。假设中的基本集合元素为三角形,第i个三角形的顶点用的均值和协方差矩阵C计算如下:

 

 

公式(61)

  

公式(62)

其中

协方差矩阵C 的三个特征向量是正交的,正规化后可作为一个基底,它确定了OBB 的方向。分别计算中各元素顶点在该基底三个轴上的最大值和最小值,以确定该OBB 的大小。存储一个OBB 需要15 个浮点数(表示方向的3 个基底向量共9 个浮点数和表示范围的6 个浮点数)。

OBB 间的相交测试基于分离轴理论。若两个OBB 在一条轴线上(不一定是坐标轴) 上的投影不重叠,则该轴称为分离轴。若一对OBB 间存在一条分离轴,则可以判定这两个OBB 不相交。对任何两个不相交的三维凸多面体,其分离轴要么垂直于某个多面体的某一个面,要么同时垂直于每个多面体的某一条边。

一个复杂的虚拟环境不仅包含大量静态的环境对象,而且还可能包含可自由运动的活动对象,甚至会包含可改变形状和拓朴结构的软体对象。应用于虚拟环境中的干涉检查算法应能适应各种复杂的情况。我们将从用于活动对象和软体对象两个方面来讨论干涉检查方法和适应性。一个对象的OBB 完全是由它自身的几何属性决定的,不受任何外界因素的影响。因此,当活动对象运动后,如果对它的OBB 进行同样的运动,则得到的仍然是包围它的最小的OBB 包围盒。通常活动对象在虚拟环境中的运动为六个自由度的运动,故而总可以把它分解旋转和平移。因此,基于OBB 的干涉检查算法用于活动对象时的处理十分简单,只需要对OBB 的基底乘以一旋转矩阵,并加一平移向量即可,OBB 在各个基底方向上的大小则保持不变。也正是因为一个对象的OBB 完全是由它自身的几何属性决定的,所以父结点的OBB 与子结点的OBB 没有任何内在的联系。对于软体对象,当它发生变形或拓朴结构改变时,如果仅重新计算变化了的部分叶结点的OBB,仍无法得到它的所有前代结点的OBB。因此,只有重新构造OBB树。这样的代价是相当大的,通常无法满足实时计算的要求,故基于OBB 的干涉检查算法很难适用于软体对象的干涉检查。

在弯管机仿真模拟中,机床等的模型都是不变的,只有管形的几何数据在不断变化,所以在干涉检查中,必须要重建管形的OBB,造成一定的性能开销。但此性能开销在允许的范围之内,基本不影响仿真模拟。

6.3  导出物件几何信息

对于每一个物件来说,我们可以有很多种表示方法,但由于我们的干涉检查算法的需要,需要将所有的物件的多边形数据导出为三角形数据。在OpenSceneGraph中为我们提供了osg::TriangleFunctor类来简化三角形的转换。

在我们的系统中,通过TriangleConvertor类进行三角形转换工作。这个类继承自osg::NodeVisitor,通过它来遍历一个osg::Group实例,然后对其下所包含的osg::Geode所包含的几何信息进行转换,然后将转换的结果保存到一个std::Vector中。

这里使用了设计模式中的Visitor模式,它可以是你可以在不改变各元素的类的前提下定义作用于这些元素的新操作[3]。这里,我们在VertexVisitor类里重载了NodeVisitor的apply操作:

virtual void apply(osg::Geode& geode)

在这个函数里面,

osg::NodePathList odePathList=geode.getParentalNodePaths(stopAt);

得到这个几何节点到stopAt节点的所有父对象的一个路径表示。其中stopAt在TriangleConvertor::Convert(osg::Group* group )函数中,被设置为等于group。这就使得在获取子对象的父对象的路径表示的时候能够在group处停止。

osg::NodePath firstNodePath=*(nodePathList.begin());

firstNodePath.erase(firstNodePath.begin());      //First node is the parent,do not include this!

osg::Matrix matrix=osg::computeLocalToWorld(firstNodePath);

调用computeLocalToWorld函数计算除了firstNodePath第一个节点以外的矩阵转换。这就把子对象的坐标系转换到了以group为世界坐标系的表示。

osg::TriangleFunctor<GetVertex> tf;

tf.vertexList=vertexList;

tf.matrix=&matrix;

这三行语句首先建立了TriangleFunctor模板类的一个对象,这个模板类将对几何信息进行转换,为每个三角形的三个顶点调用GetVertex函数,进行点的坐标转换。然后将转换所得的顶点值记录到vertexList中,这就完成了整个转换过程。

例如,一个group实例的树形结构如图 61

图 61 一个Group实例的结构

Osg::Geode Body和Osg::Geode Doors是物件的几何信息,通过osg::MatrixTransform进行"装配",这就组成了一个osg::Group Car,在通过osg::MatrixTransform的变换,就得到了在世界坐标系中的WorldCar。

Osg::Geode Body和Osg::Geode Doors所描述的几何信息需要转换成在WorldCar为世界坐标系中的一系列三角形,这样Car就可以看成一个三角形描述的整体以便进行干涉检查。设置WorldCar为stopAt值,Osg::Geode Body的父对象路径为:osg::Group Car->osg::MatrixTransform Body->osg::Geode Body。这条路径上所得到的矩阵值为osg::MatrixTransform的值,再将osg::Geode的三角形经过坐标转换就得到了WorldCar为世界坐标系中的三角形。

6.4  计算物件的矩阵

得到了物件的几何信息以后,还需要得到平移矩阵才能计算干涉。对于几何形状不发生变化的物件,创建一次几何信息以后,只需要重新传递当前平移矩阵即可进行干涉检查。

在我们的系统里,osg::Matrix CalcuateMatrix(osg::Group* group)函数计算group的世界坐标系的平移矩阵。OpenSceneGraph提供了computeLocalToWorld函数计算group的世界坐标系。此函数需要传递一个NodePath,通过group的getParentalNodePaths函数即可得到:

   osg::NodePathList nodePathList=group->getParentalNodePaths();

   osg::NodePath firstNodePath=*(nodePathList.begin());

   osg::Matrix matrix=osg::computeLocalToWorld(firstNodePath);

6.5  进行干涉检查

RAPID系统需要我们提供一个RAPID_model(几何信息)和一个矩阵来进行计算。为了能够使用这个系统,CollisionDetector类提供了BuildRapidModel函数用于创建RAPID_model,6.4节提供的CalcuateMatrix函数用于计算平移矩阵。

BuildRapidModel函数利用6.3节所得到的三角形片信息创建RAPID_Model。其代码片段如下:

osg::ref_ptr<osg::Vec3Array> vertices=TriangleConvertor::Convert(group);

RAPID_model* rapidModel=new RAPID_model;

rapidModel->BeginModel();{

       for(osg::Vec3Array::iterator itr=vertices->begin();

          itr<vertices->end();

          itr+=3){

          osg::Vec3 vertex1=*itr;  osg::Vec3 vertex2=*(itr+1);

          osg::Vec3 vertex3=*(itr+2);

rapidModel->AddTri(vertex1.ptr(),vertex2.ptr(),vertex3.ptr());}

}

rapidModel->EndModel();

创建RAPID_model时,在BeginModel()和EndModel()函数之间传递几何信息。顶点信息是osg::Vec3类型,只需要简单的传递它的指针即可。

RAPID系统所使用的矩阵和OpenGL通常使用的列序优先矩阵相反,它使用行序优先。而且RAPID系统需要将旋转矩阵和平移分量分开传递,并且将osg::Matrix的列序优先转换成行序优先:

  

公式(63)

然后再提取osg::Matrix中的最后一列的前三个元素作为平移分量即可。

6.6  与系统融合

PartBase类及其继承类实现了对机床各部件进行了分离设计的思想。我们给PartBase增加virtual RAPID_model* GetRapidModel()函数得到部件的几何信息。由于机床各部件在整个模拟流程中只发生位置的变化而没有形状的变化,所以只需要在调用这个函数的第一次建立RAPID_model并将指针储存在类的rapidModel成员中即可:

if(rapidModel==NULL){rapidModel=CollisionDetector::BuildRapidModel(this->transform);}

return rapidModel;

当需要进行干涉检查的时候,只需要将部件的RAPID_model和矩阵传递给CollisionDetector::Detect函数即可,此函数有三个版本,用于根据需要选择干涉检查的方式:

static bool Detect( RAPID_model* object1,  osg::Matrix matrix1,  RAPID_model* object2,  osg::Matrix matrix2);

static bool Detect( RAPID_model* object1,  osg::Group* group1 ,  RAPID_model* object2,  osg::Group* group2);

static bool Detect( PartBase* part1, PartBase* part2);

干涉如果发生,就返回True否则返回False。

在我们的系统中,需要在每一次动作执行完成时进行干涉检查,所以我们在只需要在CommandSequence::Execute()函数执行完成之后进行干涉检查即可。

OSG项目实践之弯管仿真(5)-弯管工艺流程的模拟

5.1  机床模型的建立

通常,复杂的机床模型应该使用专业的建模软件进行建模,而不是由程序直接生成[12]。在我们的仿真系统中,机床的模型由弯管机生产厂商提供Solidworks模型,这就产生了格式转换和转换后模型重心定位、重新命名的问题。

为了仿真的需要,Solidworks建立的机床模型需要提供简化的机床模型。简化的机床模型是真实机床部件的一个适当的简化,以最小的资源消耗达到仿真和干涉检查的需要。

5.1.1            几何建模

机床的主要简化模型和部件名称见表 51,其中英文名称作为程序中检索用。

表 51 机床简化模型及部件名称

序号

模型

英文名称

对应类名

1

床身

MACHINE

Machine

2

小车

CARRIER

Carrier

3

主轴

MAIN_AXIS

MainAxis

4

主夹具

MAIN_CLAMP

MainClamp

5

辅助夹具

AUX_CLAMP

AuxClamp

6

机头

HEAD

Head

对于各个基本的部件,简化的要求是:

1. 保留:体积较大外露部件和移动部件。包含床身、油缸、夹具、小车(以及夹头)、电机、油缸、轨道、转轴、伸缩件。

2. 忽略:

油管:机头可能和管子发生干涉的、不会弯曲的固定油管除外。

可移动、柔性的连接线:包括各种电缆。

细小部件:包括各种螺丝、铆钉。

3. 尺寸要求:

按照机床实际尺寸提供

根据以上要求,弯管机厂商提供了简化的Solidworks机床模型如图 51

图 51 机床简化模型

5.1.2            格式的转换和模型的二次修改

Solidworks提供的模型无法直接在OpenSceneGraph系统中使用,为了能够顺利的使用其模型,需要进行模型格式的转换。

我们决定在3DSMAX中对模型进行二次处理,利用OpenSceneGraph的OSG、IVE导出插件进行导出。3DSMAX支持很多种文件格式的导入,最为常用的是3DS格式。但很遗憾,Solidworks无法导出成为3DS格式。

经过尝试,决定使用VRML格式作为中介,将Solidworks装配好的机床导出到*.WRL文件(VRML格式),然后导入到3DSMAX中。

导入到3DSMAX后,需要对坐标系进行更正。因为导入的所有的模型元件都以世界坐标系原点作为原点。对于需要绕轴进行旋转的部分,必须要进行坐标系的重定义。方法是在3DSMAX中选择模型后,选择工具栏中的Hierarchy,Affect Pivot Only,然后重新定义其坐标系到转轴即可。

对于所得的模型,按照表 51进行命名,再导出为OSG、IVE格式就得到了程序所支持的机床模型。

5.2  建立模型部件和C++类的关系

根据面向对象的思路,将每一个模型都建立成单独的C++类,每一个类和一个机床部件进行关联,这样可以操控单独的类,也方便以后的扩展[17]。

在这个系统中,由表 51所列的五大部件分别单独建立C++类,这些类都继承自PartBase类。PartBase类提供了操控机床部件的一些常用函数:

Move:移动

Rotate:绕定义的轴旋转

SetMatrixTransform,GetMatrixTransform:设置、获取对应的模型部件的osg::Group,也可通过直接改变变换矩阵进行定位等操作。

SetName,GetName:设置或获取名字

这些函数都定义为虚函数,继承类可根据实际需要改动这些函数的实现。

Machine类定义了机床所有的部件的一个列表:

struct PartList

{

   AuxClamp* auxClamp;

   Carrier* carrier;

   Head* head;

   MainAxis* mainAxis;

   MainClamp* mainClamp;

   InstalledPipe* installedPipe;

};

这个列表包含了机床的所有部分,还包括一根安装在机床上的管子InstalledPipe。InstalledPipe类也继承自PartBase,但还有一些辅助函数:

SetPipe,GetPipe:用于设置一个管子的实例

RebuildPipe:加工后管子形状会发生变化,调用这个函数重新生产管子的几何信息。

disableRebuild,ManualRebuild:由于重放操作的需要而设置。disableRebuild设置为True的时候可以跳过RebuildPipe步骤,在回放完成需要绘制管形的时候再执行ManualRebuild进行重画,提升处理速度。(参见5.4.4节)

Machine类还定义了一个参数列表,用于设置机床的各个参数:

struct ParameterList

{

   float HoldMainClampVelocity;    //主夹紧的速度

   float HoldMainClampMovement;    //主夹紧的移动量

   float ReleaseMainClampVelocity; //主夹松的速度

   float ReleaseMainClampMovement; //主夹松的移动量

  
 

   float HoldAuxClampVelocity;     //辅夹紧的速度

   float HoldAuxClampMovement;     //辅夹紧的移动量

   float ReleaseAuxClampVelocity;  //辅夹松的速度

   float ReleaseAuxClampMovement;  //辅夹松的移动量

 
 

   float ResetVelocity;        //转臂回转速度

 
 

   float zMoveVelocity;        //c轴转速

 
 

   std::vector<float> DiePosition; //模具的Z轴位置

};

Machine类还定义了一个状态列表:

struct StatusList

{

   bool IsBending;

};

IsBending状态指示现在机床的状态。在IsBending为True的时候,机床各个部件可以根据预先定义的流程执行弯管操作,而为False的时候,机床部件不进行弯管。

5.3  调入机床模型并初始化

OpenSceneGraph提供了很简洁的方式调用一个模型文件:

osg::Node* machineModel=osgDB::readNodeFile("machine.ive");

osgDB::readNodeFile首先查找相应的插件,然后通过插件解析模型文件,最后返回一个osg::Node的指针,这个指针就包含了机床部件之间的层次关系。只需要将这个osg::Node作场景根root的子Node即可:root->addChild(machineModel);

管子作为场景的一部分也需要进行初始化和定位,在这里专门设置CreatePipe函数进行管子的初始化工作:

void CreatePipe()

{

   if(pipe!=NULL) delete pipe;

   pipe=new Pipe();

   pipe->Create(2.519,366.5); //建立一个r=2.519,l=366.5的管子

   installedPipe->SetPipe(pipe);   //设置机床安装的管子为这根管子

   pipeMt=pipe->GetPipe();         //得到管子的几何模型

   osg::Matrix matrix=pipeMt->getMatrix();  

   matrix.setTrans(osg::Vec3(21.079,-135.938,46.918));   //设置管子的安装点

   pipeMt->setMatrix(matrix);

   installedPipe->SetMatrixTransform(pipeMt.get()); //设置机床安装的管子的几何模型

}

同样,作为场景的一部分,管子的几何模型也需要加入到场景中:root->addChild(pipeMt.get());

接下来对machine->parameters的各个参数进行详细设置:

   float moveMent=3;

   machine->parameters.HoldAuxClampMovement=moveMent;

……

   machine->parameters.zMoveVelocity=20;

   machine->parameters.DiePosition.push_back(0);

   machine->parameters.DiePosition.push_back(-8.992);

DiePosition定义了模具的Z坐标位置,需要从上到下进行设置。

接下来需要对机床的各个部件的模型按照到虚拟机床上:

第一步需要找到机床的模型并为部件设置具体的模型,例如:auxClamp->SetMatrixTransform(FindMatrixTransform(root,"AUX_CLAMP"));

FindMatrixTransform从 root中检索出名字为AUX_CLAMP的模型,然后将AuxClamp类的实例auxClamp的模型设置为此模型即可。其他部件类似。对于类提供的额外的参数,需要进行进一步详细的设置:

auxClamp->IsAuxClampHold=true; //辅夹夹紧

carrier->installedPipe=installedPipe; //设置小车上的管子

ainClamp->IsMainClampHold=true;    //主夹夹紧

最后一步将部件的"安装"到机床上:

machine->parts.auxClamp=auxClamp;

……

machine->parts.mainClamp=mainClamp;

这就完成了机床的初始化操作。

5.4  工艺模拟

5.4.1            命令类

为了实现机床的控制,我们结合机床的实际,将YBC的数据转换成操作机床的数控代码。仿真系统所用的数控代码和操作机床所用的数控代码保持一致。

主要代码包括:

AT_YMOVE:进料

AT_BMOVE:转料

AT_DOB:弯料

AT_ROLL:推弯

AT_MOVE_HEAD:机头横移

AT_BENDRETURN:转臂复位

AT_HOLD_MAIN_CLAMP:主夹紧

AT_RELEASE_MAIN_CLAMP:主夹松

AT_HOLD_AUX_CLAMP:辅夹紧  

AT_RELEASE_AUX_CLAMP:辅夹松

AT_HOLD_CARRIER_CLAMP:小车夹头紧

AT_RELEASE_CARRIER_CLAMP:小车夹头松

AT_AUXPUSH_FORWARD:辅推进

AT_AUXPUSH_BACKWARD:辅推退

AT_SHIFT_DIE: 换模

这些操作指令加上操作指令所带的数据,即可操作机床。

为了实现这些指令在仿真机床上的模拟和以后的扩展,我们使用了Command设计模式[3]。Command类定义了所有命令类的基类。timeRef用于记录一个命令执行的时间。Execute是一个纯虚函数,每一个继承自Command的类都需要单独定义相应的Execute函数。它用于执行命令,返回值为True说明命令已经执行完毕。对于需要并发的一系列命令,通过AddChild、DelChild和GetChilds函数进行命令的增加、删除和获取操作。

例如AT_YMOVE指令,其结构如下:

   class AT_YMOVE : public Command

   {

   public:

       AT_YMOVE():machine(NULL){};

       AT_YMOVE(TAG_YMOVEDATASTRUCT yMoveData,Machine* machine)

       {   this->yMoveData=yMoveData;

          this->machine=machine;

       }

 
 

       virtual bool Execute(float simulateTime);

 
 

       TAG_YMOVEDATASTRUCT yMoveData;

       Machine* machine;

   };

外界给AT_YMOVE指令提供Y轴的运动数据,其数据结构如下:

struct TAG_YMOVEDATASTRUCT {

   double dblDistance;      //送料长度(distance between bend)

   double dblVelocity;  //送料速度

   double dblOffset; //送料偏移量

};

Execute函数首先计算单位时间内的移动量

   float movement=yMoveData.dblVelocity*simulateTime;

然后根据timeRef计算总共移动的量

   float totalMoved=yMoveData.dblVelocity*timeRef;

如果总共移动量大于送料长度,返回true

   if(abs(totalMoved)>=yMoveData.dblDistance){   return true;  }

如果还需要移动,控制machine的carrier部分移动movement

   else   {machine->parts.carrier->Move(osg::Vec3(0,movement,0));   }

完成命令以后,timeRef+= simulateTime

5.4.2            命令序列类

为了将一系列命令进行统一的处理,我们创建了CommandSequence类,这个类依然继承于Command类,在此基础上增加了AddCommand( Command* cmd)函数,用于在命令序列中增加一个命令。std::vector<Command*>保存所有的命令,std::vector<Command*>::iterator lastCmd用于记录最后一条执行的命令。

图 52 建立脚本

当执行命令序列的Execute函数后,就调用lastCmd的Execute函数并记录返回值到cmdFinished变量。然后通过一个循环过程,依次调用lastCmd的子命令的Execute函数,并记录下返回值到cmdChildFinished中。如果命令本身和所有的子命令都返回True,则说明这个命令已经执行完成,lastCmd指向下一个命令,并如此重复直到到达最后一个命令。此时CommandSequence的Execute函数会返回True,指示所有的命令已经完成。

如果需要重新开始加工模拟,则只需要调用Reset函数,此函数会依次调用所有的命令的Reset函数,并将timeRef赋值为0。然后再调用CommandSequence的Execute函数即可。

5.4.3            建立和执行脚本

当建立好控制弯管机的指令以后,便可翻译工艺文件,建立执行脚本,并进行仿真。基本思路如图 52。通过调用CommandSequence::Execute()函数可以仿真一帧的时间,默认情况下,按照60帧/秒的速度进行。在主循环内,只需要重复调用此语句即可实现仿真模拟。每60帧作为现实中的一秒。如果还要考虑到和现实时间同步,则需要计算每秒的帧数。

5.4.4            仿真回放的实现

图 53 仿真倒转流程

对于需要仔细观看的步骤,有必要进行回放,此功能类似于普通播放机的倒带功能。要实现回放,通常来说有以下思路:

1、对所有的部件的运动或者形变进行记录。类似于录像的功能。但要实现录像功能需要消耗很多的内存,当加工步骤越来越多时,所需要的内存容量也呈线性增长。

2、为每一个命令书写倒放的函数,将运动倒放。此方案需要重新书写函数,而且可能有些运动无法实现精确的倒放。如果从倒放点开始继续加工可能会造成积累误差。

对于我们的系统,由于使用了命令序列的设计,而且每一帧都是执行相同的函数,模拟相同的时间片。从模拟开始执行N次Execute()函数所得到加工的结果完全相同。为了实现倒放T帧,我们只需要在CommandSequence复位以后,执行Execute()函数N-T次即可。具体过程见图 53。

此法简单可行,节约了大量内存,也没有修改任何命令的实现。但由于加工时间越长,N越大,倒转T帧以后N-T次Execute()的函数调用,每次调用又要涉及到工艺的计算,计算量呈线性增长的趋势。在实际操作中,我们发现每次执行Execute()后需要对管子的形状进行建模,这个过程很消耗时间,所以将建模过程取消,只记录管形数据即可。这也就是5.2节中设置disableRebuild和ManualRebuild的原因。管形建模的过程放在绘制之前进行,这就很好的解决了计算量的矛盾,实际应用当中效果理想。

OSG项目实践之弯管仿真(4)-弯管几何建模

4.1  几何建模过程

4.1.1            按照参数生成管形几何数据

一根弯管可以分成由若干直管段和弯管段所组成的。两种管段的基本几何数据包括管子的直径(r)。另外,对于不同种类的管段还需要更多的参数:

直管段还需要包含管长(L)。

弯管段还需要包含弯曲半径(R)、弯曲角度(φ)

另外为了图形显示的需要,在此基础上,还需要给定圆周方向上的离散总步骤数(S1)和弯曲管子弯管圆周上的离散总步骤数(S2)以便于更平滑的显示。

由算法生成几何图形的几何建模的难点在于如何确定点的位置,以及如何将这些点转换OpenGL可以识别并能正确绘制的三角形[13]。考虑到常用管子分为圆管和方管,而方管只是圆管的特例而已,所以仅介绍圆管的几何建模。

4.1.2            直管段的几何建模

直管段的几何建模相对弯管简单许多,只需要确定两端圆周上的各点以及各点的连接方向即可。

设管子直放时的底面为坐标系X-Y面,其圆心为局部坐标的原点(0,0,0),有右手螺旋定则确定Z轴正方向为向上方向,其圆周上Z轴值均为0。顶面为底面向Z轴正方向移动管长L所得,其圆周上Z坐标值均为L。

以底面圆周上为例,设θ为圆周角,用于确定点的位置。其逆时针方向为正方向。

对于圆周角为θ的点,它的坐标应该为:

  

公式(41)

  

公式(42)

为了平滑显示,它的法线方向为:

  

公式(43)

经过规格化以后就得到OpenGL所需要的法线数据。

这样通过一个循环就可以得到底面和顶面的圆周上点的坐标序列和圆周方向的法线数据。下一步需要连接这些顶点、设置合适的法线才能生成可见的几何形状。

我们选择QUAD_STRIP这种连接方式可以很方便的连接底面的顶点。实现这个过程的伪语言为:

开始,直到,每一步为

{

   计算底面顶点并传输给OpenGL

计算法线并传输给OpenGL

   计算顶面顶点并传输给OpenGL

   计算法线并传输给OpenGL

}

  这样就实现了OpenGL下圆形直管的建模。对于方管,只需要将每一步递增量改为即可

图 41 弯管段的坐标系

4.1.3            弯管段的几何建模

弯管段的几何建模相对于直管段更为复杂,点的矩阵转换是一个难点。

设弯管段的坐标系如图所示建立:

设弯曲半径为R,总弧度为,细分为S2段,则两段之间的弧度为。将描述管形截面的数据进行在X正方向平移R,然后绕Y轴旋转,n为段数。这时坐标系也进行了平移和旋转。再向平移旋转后的坐标系的X正方向平移-R即可得到弧度为处的坐标值。对细分后的每一段都进行相同的操作,最终会得到所求弯管段。

整个数学过程表示如下:

设管形截面定点数据为

计算平移变换矩阵:

  

公式(44)

计算旋转变换:

  

公式(45)

  

公式(46)

计算反向平移变换:

  

公式(47)

最后计算点的变换。注意在OpenGL中,最后定义的点的变换最先应用,所以点与矩阵的乘法顺序需要颠倒,在这个例子中,点的变换如下:

  

公式(48)

这样就完成了对点的变换操作。

下一步是法线的转换:

法线的数据来自于用户的输入或者计算。对于截面形状不规则的图形需要用户输入法线方向,对于规整的图形可以通过计算得到。在这个例子中对于圆形或者方形管的段对应的法线可以根据公式(43)得到。

不同于点的转换,法线只是制定方向而已,而不是确定位置,所以法线的转换只需要进行旋转操作即可。

整个数学过程表示如下:

设法线向量的集合为

  

公式(49)

得到了转换后的定点和法线,就可以用OpenGL的QUAD_STRIP的连接方式对OpenGL提供相应的参数。具体过程与直管类似,这里就不再赘述。

4.2  数据驱动建模

通过一系列给定数据进行几何建模的方法称之为数据驱动建模。在本系统中,数据驱动建模用于通过XYZ、YBC数据,提供给用于一个可视化的直管的管形,便于用户对XYZ、YBC进行修改在生成相应的工艺文件。

4.2.1            XYZ、YBC数据

对三维数控弯管机来说,把一根直线型的圆管加工成符合图纸设计所要求的几何形状,需要使用3轴伺服系统进行Y轴的直线送料、B轴的弯曲和C轴的空间旋转动作。与其相对应,要有每一段弯曲的管形数据:送料长度DBB、弯曲角度DOB、旋转角度POB。这一组数据决定了弯管的形状以及加工过程,被称为Y、B、C加工坐标。由于弯管的几何形状很多,在Y、B、C空间里的位置比较复杂。纸设计过程中,弯管数据多是以三维坐标系中的X、Y、Z形式给出的,如图 42

图 42 三维弯管表示

由于三维弯管横截面上的点关于截面中心成对称分布,可以将其简化后看成是由若干个直线段和圆弧(弯曲部分)组成的空间图形,如图 43所示。把弯管两两相临的轴线延长,使之相交形成交点。如果已知这些交点的三维坐标X、Y、Z以及弯管半径和管壁厚度等信息,就可以完整地还原出弯管的实际形状。

图 43 利用中心对称来简化弯管

相邻两交点连线的距离和方向表示了一段弯管的矢量的大小和方向。将管形上的各直线段中心线以一系列的空间坐标适量来表示,求出它们的交点,进而求出"增量管形数据",这就是适量弯管的原始设想。

增量惯性数据也可叫做YBC数据或者LRA数据。其中Y或者L表示直线段的长度,B或者A表示弯曲角度,C或者R表示绕C轴旋转的角度。但YBC还不能直接表示成一个管形,对于每一个弯,还需要指定每个弯的半径。所以,对于一段管形,可以用一张YBCR表来表示[19],如图 44:

 

序号

Y

B

C

R

1

y1

b1

c1

r1

2

y2

b2

c2

r2

  

  

  

  

n

yn

  

  

  

图 44 YBCR表

除了螺旋形状和复合管(即两弯之间无直线段)的特殊管形管子外,在其它情况下,一段管子都可以用图 44所示表来描述。注意对于序号n的一项,是管子的最后一段直线段,所以只需要有Y一项值描述即可。

XYZ数据必须转换到YBC数据之后才能进行弯管加工,具体转换过程参考[2]

4.2.2            YBC驱动几何建模

在4.1节中已经分别阐述了直管段和弯管段的几何建模,如果能通过一个数据结构分别表示这些管段之间的关系,就可以生成需要的管形。

YBC数据是和历史相关的,也就是说每个管段的位置都是通过前一个管段的位置计算出来的。根据这个特点,最终几何形状的生成程序的数据结构也按照这个思路进行。最终成型的弯管的数据结构可以由图 45表示。

图 45 管形的数据描述

直管段N对应YBC数据中的第N项。直管段只需要Y的数据即可以描述其长度。直管段的变换矩阵N需要Y和C进行描述。按照4.1.2节所述的坐标系,直管段的变换矩阵的计算为:

  

公式(410)

弯管段N对应的YBC数据中的第N项。弯管段需要B和R的数据进行描述。按照4.1.2节所述的坐标系,弯管段的变换矩阵的计算步骤为:

计算平移变换矩阵:

 

 

公式(411)

计算旋转变换:

  

公式(412)

计算反向平移变换:

  

公式(413)

总的变换:

  

公式(414)

这些公式和公式(48)类似,但区别在于公式(48)是点的矩阵变换,矩阵是按相反的顺序相乘。而公式(414)是计算坐标系的变换,所以按照矩阵正常的顺序相乘。

根据以上描述,可以得到YBC驱动的管形的OpenSceneGraph程序片段:

   osg::ref_ptr<osg::MatrixTransform> pipe=new osg::MatrixTransform;

   osg::ref_ptr<osg::Group> lastGroup=pipe->asGroup();

  
 

   for(std::vector<PipeData>::const_iterator itr=data->begin();

       itr<data->end();

       itr++) {

       osg::ref_ptr<osg::Group> pipeSegment=new osg::Group;

       osg::ref_ptr<osg::Geode> geode=new osg::Geode;

       osg::ref_ptr<osg::Geometry> segmentGeometry;

       osg::ref_ptr<osg::MatrixTransform> matrix;

       PipeData data=*itr;

       switch(data.pipeType)       {

       case PipeData::STRAIGHT:

          segmentGeometry=createStraightPipe(radii,data.straight);

          matrix=createStraightPipeMatrix(radii,data.straight);

          break;

       case PipeData::BENDED:

          segmentGeometry=createBendedPipe(radii,data.bended);

          matrix=createBendedPipeMatrix(radii,data.bended);

          break;

       }

       geode->addDrawable(segmentGeometry.get());

       pipeSegment->addChild(geode.get());

       pipeSegment->addChild(matrix.get());

       lastGroup->addChild(pipeSegment.get());

       lastGroup=matrix->asGroup();

   }

   osg::Matrix matrix=pipe->getMatrix();

   pipe->setMatrix(matrix);

   return pipe;

4.3  事件驱动几何建模

弯管机的一个目标就是为了验证工艺文件的正确性和可行性,以便进行实际机床的加工。通过工艺文件可以驱动机床各部分的运动,这些运动的合成即可以加工一根管子。我们称这种根据实际机床部件的运动而生成管形的过程叫做事件驱动几何建模。

事件驱动几何的优点是可以不事先知道最终管形,而是在加工过程中逐步生成管形。这就很方便于可视化调节工艺文件,以便生成理想的弯管。

从实际加工出发,弯管的形成过程可以以以下三个事件驱动:

1. 送料(Feed)

2. 弯管(Bend)

3. C轴旋转(cRotate)

只要我们记录下这些事件产生的先后顺序以及相应的参数,即可以生成弯管的几何形状。

本仿真系统中的Pipe类就定义了Feed、Bend、cRotate这三个函数分别对应这三个事件。为了记录事件发生后的结果,Pipe类还定义了管段数据,包含PipeType设置管段的类型,也包含了生成相应管段的数据。事件发生后的数据记录在std::vector<PipeData>* data中,其中PipeData的结构如下:

   struct PipeData

   {

   public:

       enum PipeType

       {

          STRAIGHT,

          BENDED

       }pipeType;

 
 

       StraightPipeData straight;

       BendedPipeData bended;

   };

data数据第一段管数据是记录剩余管长,后续的管段数据记录了各个事件发生后的结果。data数据必须经过初始化才能使用。Pipe类的Create函数将对data数据进行必要的初始化,包括首先建立两段直管。初始化时,这两段直管的长度分别为workingPosition和length-workingPosition,总长度为管长length。workingPosition为弯管的开始的位置。通常来说,workingPosition等于length,这样第二段直管的长度就是0。

当送料(Feed)事件发生时,检查第二个管段的类型:

1、如果是直管,只需要增加这段管子的长度n并且减少第一段管子的长度n即可。

2、如果是弯管,则需要在原来的一、二管段之间插入一个直管,再按照方法1进行即可。

当弯管(Bend)事件发生时,检查第二段管子的类型:

1、如果是弯管,只需要增大管子的弯曲角度θ,并且减少第一段管子的长度θ*R即可

2、如果是直管,则需要在一、二段管子之间插入一个弯管,然后按照方法1 进行即可。

当C轴旋转事件发生时,检查第二个管段的类型:

1、如果是直管,则直接旋转即可。

2、如果是弯管,则需要在原来的一、二管段之间插入一个直管,再按照方法1进行即可。

按照这样的流程就会很容易得到事件发生后的类YBC数据,在经过导出,即可得到加工过程中形成管形的YBC数据。