• CAA DMU模块仿真


    背景

    本人由于项目原因,需要基于CATIA格式文件研究CAM的一些操作,其中就包括仿真功能,而CATIA中适合实现仿真功能的模块就是 DMU (Digital Mock-Up) 模块,本人研究了很长时间,尝试了很多方案,特地记录下来方便后续查看。

    CATIA提供的CAA可以为C++编程提供支持,但是CAA编程又与一般的C++编程有所不同,主要是CATIA是基于COM组件架构构建的,CATIA使用C++来实现它的COM架构,在CAA中除了少数的几个数学和几何类可以实例化,其它的类型都是接口。举个简单的例子,例如有一个特征对象,要获取这个特征对象的别名,

    // 拿到特征对象
    CATISpecObject* piSpecObj = GetSpecObj();
    // 获取别名
    CATIAlias* piAlias = NULL;
    pSpecObj->QueryInterface(IID_CATIAlias, (void**)&piAlias);
    if (NULL != piAlias)
    	printf("特征名称 %s\n", piAlias->GetAlias().ConvertToChar());
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    并不是通过访问特征对象的属性实现的,而是通过,CATISpecObject 继承自父类 CATBaseUnknown 的 QueryInterface 方法来查询当前对象是否支持 CATIAlias 接口,从而判断是否能获取它的别名。有关COM架构的内容可以参考《COM技术内幕》这本书,这也是开始的时候最让我困惑的地方(居然几乎没有可以实例化的类型,好不容易拿到了对象我竟然不知道它的具体类型,只有一个模糊的CATISpecObject* 这样的基类指针,有时候甚至是 CATBaseUnknow* 指针,更不知道怎么访问)。

    但这也同样是COM架构的优势,它不需要事先知道另一个组件的细节,因此可以置换另一个组件,只要实现相同的接口。
    在这里插入图片描述
    同时它还可以实现像OLE这种在我看来十分神奇的技术,它可以让你通过一段VBA或者Python脚本,拿到正在运行的CATIA应用程序对象,例如下面的VBA脚本

    Set CATIA = GetObject(,"CATIA.application");
    
    • 1

    然后可以访问它的 Documents,Cameras,Windows 这些属性,可以实现类似CAA一样的脚本操作。
    在这里插入图片描述

    DMU模块基础概念

    CAA 开发资料最主要是 CATIA 软件下载时自带的一个 百科全书 里面有各种模块的描述还有一些案例(在CATIA软件安装目录下的 CAAV5HomePage.htm 的HTML文件,可以使用浏览器打开)这是它的一个在线版本
    还有就是根目录下 intel_a\code\bin 下有一些可执行程序,里面有一个 CNextHelpViewer.exe 是对 百科全书中接口对象的一个整理。
    另外我所知道的就是 icaxCOE 论坛的CAA板块,有时可以找到一些讨论。

    在百科全书主页中,点击 site map, 搜索DMU关键字可以找到所有DMU部分的资料
    在这里插入图片描述
    在这里插入图片描述

    在这里插入图片描述
    在这里插入图片描述

    其中ENOVIA模块中的资料是重复的
    Kinematics Overview 中可以了解到 构建运动的基本概念(Product, 关节,命令,机制)
    Product 是部件,关节是对Product之间运动约束的描述(构造运动副),命令可以为关节的运动约束指定参数,机制是Product和关节的集合,其中的一个Product附着在地面上,被定义为Fix。

    Simulation Overview 中可以了解到 仿真操作的基本概念(动画对象,状态,采样,通道,重放)
    大概就是,重放是一个场景,里面有很多的动画对象,每一个动画对象包含很多属性,这些属性会随着时间的推移发生变化,使用通道(Channel )刻画一个属性随着时间的变化,采样是每个时刻对应的属性值,状态是属性值。

    DMU模块仿真

    使用 DMU 仿真,首先需要定义运动学,之后计算仿真动画(重放)

    定义运动学

    定义运动学,可以在CATIA中手动操作,也可以使用CAA代码实现,我这演示手动操作的步骤,基本流程如下

    定义完驱动命令,然后自由度降为0,设置好 fix 部件,提示可以仿真即可。

    计算仿真动画

    这个步骤是CAA仿真的难点,实现仿真功能其实并不是太难,只需要了解各个部件之间怎么运动,设置对应的层级关系和变换矩阵,使用例如OSG库是非常容易实现的,但是找到CAA中支持的接口来实现确实难上加难,我尝试过一些方案,但是每个方案的实现都遇到了问题,首先我尝试了使用装配树移动的功能,装配树(CATIProduct)有 CATIMovable 接口,可以查询子 CATIProduct 或 CATIPart 的位置,并且可以移动它们的位置,但是当我想计算变换矩阵的时候却遇到了很大的问题, 因为 CATIMovable 接口查询到的位置,并不是 CATIPart 的默认坐标系原点的位置(即Part三个平面的交点位置),而且在CATIPart 坐标系下也找不到 CATIMovable 接口查询到的位置,这让这个变换的链条断掉了,目前还没有找到解决方案,不过在COE论坛上似乎有VBA下的解决方案,但是我尝试后还是无法实现,貌似接口不一样。于是,我又尝试了新建线程 + CATIKinMechanism设置命令参数 的方案,但是找不到CATIA对多线程线程安全的支持,新线程中 CATIKinMechanism设置命令参数 会导致场景更新,如果场景更新没有结束立马有调用 设置命令参数的方法,程序就会崩溃,使用Sleep函数需要设置一个非常大的间隔才能保证安全,但是这已经无法满足连续仿真的要求了。在这之后我找到了教程 Creating a Product’s Motion in a Document 但是我开始的时候直接否定了这个方案,因为CATIReplayChannelProductMove添加Sample要求的参数是变换矩阵,而我知道CAA中计算一个准确的变化矩阵是不可行的。于是我开始尝试 CATIReplayChannelScalarObserver,但是构造的 Replay 无论如何都无法开始播放。

    最后,当我遍历并打印 装配树重放结点 想看看程序构建的回放和CATIA DMU模块中操作创建出的回放有什么不同时,发现他是用正是CATIReplayChannelProductMove,再当我回头看 CATIKinMechanim 接口时找到了 GetProductMotion 方法,这才走通了DMU仿真的过程。
    在这里插入图片描述
    简单来说,DMU仿真的过程,假设你已经构造好了运动学,有一组命令了,可以通过 CATIKinMechanism 接口的 SetCmdValues 方法设置命令参数,之后通过 GetProductMotion 方法拿到这组命令参数对特定的Product产生的变换矩阵,下面是通过顶层Product获取CATIKinMechanism 的示例

    CATDocument* pDoc = Utils::GetDocumentFromProduct(pTopProduct);
    
    CATIKinMechanismFactory* piMechanismFactoryOnDocument = NULL;
    HRESULT HR = pDoc->QueryInterface(IID_CATIKinMechanismFactory,(void**)&piMechanismFactoryOnDocument);
    if (!SUCCEEDED(HR)) return;
    
    CATLISTP(CATBaseUnknown) listOfMechanisms;
    HR = piMechanismFactoryOnDocument->ListInstances (listOfMechanisms);
    if (!SUCCEEDED(HR)) return;
    
    if (listOfMechanisms.Size() < 1)
    {
    	Utils::DisplaySimpleWindowMsg("提示", "创建机制,没有可仿真的机制");
    	return;
    }
    
    // 获取第一个机制
    CATIKinMechanism* piMechanism = NULL;
    listOfMechanisms[1]->QueryInterface(IID_CATIKinMechanism, (void**)&piMechanism);
    CATIAlias_var mechanismAlias = piMechanism;
    Utils::Log("use mechanism %s", mechanismAlias->GetAlias().ConvertToChar());
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    之后通过从相应Product对象中获取的 CATIReplayChannelProductMove接口的 AddSample 方法,将时间和 对应的变换矩阵填入即可,下面是一段示例代码。

    // 获取 IReplayFactory
    CATIReplayFactory* piReplayFactory = NULL;
    HRESULT HR = pDoc->QueryInterface(IID_CATIReplayFactory,(void**)&piReplayFactory);
    if (!SUCCEEDED(HR)) return;
    // 创建一个新的 Replay
    CATIReplay* piReplay = NULL;
    HR = piReplayFactory->CreateInstance (&piReplay);
    if (!SUCCEEDED(HR)) return;
    // 获取 ReplayChannelProductMoveFactory
    CATIReplayChannelProductMoveFactory* piReplayChannelProductMoveFactory = NULL;
    HR = piReplay->QueryInterface(IID_CATIReplayChannelProductMoveFactory,(void**)&piReplayChannelProductMoveFactory);
    
    
    CATIProduct* topProduct = Utils::GetTopProduct();
    CATListValCATBaseUnknown_var* pProductList = topProduct->GetChildren("CATIProduct");
    if (NULL == pProductList) 
    {
    	Utils::DisplaySimpleWindowMsg("提示", "没有找到仿真部件");
    	return;
    }
    
    // 遍历相关的Product,将它们对应的 ReplayChannelProductMove 存入 channelProductMoves 中
    int nProducts = pProductList->Size();
    CATIProduct* targetProduct = Utils::GetTargetProduct();
    std::vector<CATIProduct*> childProducts;
    std::vector<CATIReplayChannelProductMove*> channelProductMoves;
    for(int i=1; i <= nProducts; i++)
    {
    	CATIProduct* pProduct = NULL;
    	(*pProductList)[i]->QueryInterface(IID_CATIProduct, (void**)&pProduct);
    	if ( pProduct != targetProduct && pProduct)
    	{
    		childProducts.push_back(pProduct);
    
    		CATIReplayChannelProductMove* pChannelProductMove = NULL;
    		piReplayChannelProductMoveFactory->CreateInstance((CATBaseUnknown **)&pProduct, &pChannelProductMove);
    		if (pChannelProductMove != NULL)
    		{
    			channelProductMoves.push_back(pChannelProductMove);
    		}
    
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    下面是我的AddReplayFrame 函数的定义,供大家参考

    
    void SimulationCmd2::ApplyFrame(CATIKinMechanism* piMechanism, const Utils::Frame& frame)
    {
    	CATLISTP(CATBaseUnknown)* listOfCmds = NULL;
    	piMechanism->GetCmdList(&listOfCmds);
    
    	int size = listOfCmds->Size();
    	double *cmdValues = new double[size];
    
    
    	std::vector<std::pair<CATUnicodeString, double>> vec;
    	vec.push_back(std::pair<CATUnicodeString, double>("X", frame.x));
    	vec.push_back(std::pair<CATUnicodeString, double>("Y", frame.y));
    	vec.push_back(std::pair<CATUnicodeString, double>("Z1", frame.z1));
    	vec.push_back(std::pair<CATUnicodeString, double>("Z2", frame.z2));
    	vec.push_back(std::pair<CATUnicodeString, double>("A", frame.a));
    	vec.push_back(std::pair<CATUnicodeString, double>("B", frame.b));
    
    	for(std::vector<std::pair<CATUnicodeString, double>>::iterator iterVec = vec.begin(); iterVec != vec.end();iterVec++)
    	{
    		std::map<CATUnicodeString,int>::iterator iter = m_commandFieldMap.find(iterVec->first);
    		if (iter != m_commandFieldMap.end())
    		{
    			if (iter->second >= 0 && iter->second < size)
    			{
    				cmdValues[iter->second] = iterVec->second;
    			}
    		}
    	}
    
    	static int index = 0;
    	Utils::Log("apply cmd values index %d", ++index);
    	for(int i=0; i < size; i++)
    		Utils::Log("index %d %f", i, cmdValues[i]);
    	Utils::Log("end ");
    	piMechanism->SetCmdValues(size, cmdValues);
    	
    	
    	delete[] cmdValues;
    }
    
    void SimulationCmd2::AddReplayFrame(CATIKinMechanism* piMechanism, 
    					const std::vector<CATIProduct*>& products, 
    					const std::vector<CATIReplayChannelProductMove*>& channelProductMoves,
    					const Utils::Frame& frame, double timeMillSec)
    {
    	ApplyFrame(piMechanism, frame);
    	Utils::Log("ApplyFrame, x=%f,y=%f,z1=%f, z2=%f,a=%f,b=%f, time=%f", frame.x, frame.y, frame.z1, frame.z2, frame.a, frame.b, timeMillSec);
    
    	int nProducts = products.size();
    	assert(products.size() == channelProductMoves.size());
    
    	for(int j=0; j < nProducts; j++)
    	{
    		CATIProduct* pProduct = products.at(j);
    		double *motion = NULL;
    		piMechanism->GetProductMotion(pProduct, &motion);
    
    		channelProductMoves.at(j)->AddSample(timeMillSec, motion);
    
    		delete motion;
    
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
  • 相关阅读:
    技术学习:Python |欲先善其事,必先利其器(基础知识)
    设置Oracle环境变量
    [计算机网络基础]数据链路层
    GBase 8c V3.0.0数据类型——文本检索操作符
    免费开源 | 基于SSM的校园订餐系统
    配置请求头Content-Type
    BTC价格预测:灰度突如其来的胜利是否会打破“九月魔咒”?
    【Java中的线程】java.lang.Thread 类分析
    操作系统学习笔记3 | 操作系统简史
    Shell 脚本入门 ①
  • 原文地址:https://blog.csdn.net/Zz_er/article/details/133484268