背景
本人由于项目原因,需要基于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());
并不是通过访问特征对象的属性实现的,而是通过,CATISpecObject 继承自父类 CATBaseUnknown 的 QueryInterface 方法来查询当前对象是否支持 CATIAlias 接口,从而判断是否能获取它的别名。有关COM架构的内容可以参考《COM技术内幕》这本书,这也是开始的时候最让我困惑的地方(居然几乎没有可以实例化的类型,好不容易拿到了对象我竟然不知道它的具体类型,只有一个模糊的CATISpecObject* 这样的基类指针,有时候甚至是 CATBaseUnknow* 指针,更不知道怎么访问)。
但这也同样是COM架构的优势,它不需要事先知道另一个组件的细节,因此可以置换另一个组件,只要实现相同的接口。
同时它还可以实现像OLE这种在我看来十分神奇的技术,它可以让你通过一段VBA或者Python脚本,拿到正在运行的CATIA应用程序对象,例如下面的VBA脚本
Set CATIA = GetObject(,"CATIA.application");
然后可以访问它的 Documents,Cameras,Windows 这些属性,可以实现类似CAA一样的脚本操作。
DMU模块基础概念
CAA 开发资料最主要是 CATIA 软件下载时自带的一个 百科全书 里面有各种模块的描述还有一些案例(在CATIA软件安装目录下的 CAAV5HomePage.htm 的HTML文件,可以使用浏览器打开)这是它的一个在线版本
还有就是根目录下 intel_a\code\bin 下有一些可执行程序,里面有一个 CNextHelpViewer.exe 是对 百科全书中接口对象的一个整理。
另外我所知道的就是 icax 和 COE 论坛的CAA板块,有时可以找到一些讨论。
在百科全书主页中,点击 site map, 搜索DMU关键字可以找到所有DMU部分的资料
其中ENOVIA模块中的资料是重复的
Kinematics Overview 中可以了解到 构建运动的基本概念(Product, 关节,命令,机制)
Product 是部件,关节是对Product之间运动约束的描述(构造运动副),命令可以为关节的运动约束指定参数,机制是Product和关节的集合,其中的一个Product附着在地面上,被定义为Fix。
Simulation Overview 中可以了解到 仿真操作的基本概念(动画对象,状态,采样,通道,重放)
大概就是,重放是一个场景,里面有很多的动画对象,每一个动画对象包含很多属性,这些属性会随着时间的推移发生变化,使用通道(Channel )刻画一个属性随着时间的变化,采样是每个时刻对应的属性值,状态是属性值。
DMU模块仿真
使用 DMU 仿真,首先需要定义运动学,之后计算仿真动画(重放)
定义运动学
定义运动学,可以在CATIA中手动操作,也可以使用CAA代码实现,我这演示手动操作的步骤,基本流程如下
- 定义固定部件
- 首先需要定义各个组件之间的运动学关系,定义不同类型的关节(运动副),关于关节的定义可以参考 DMU 运动机构仿真教程、DMU 运动机构分析
- 定义驱动命令
定义完驱动命令,然后自由度降为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());
之后通过从相应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);
}
}
}
下面是我的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;
}
}