在CAD图形交互系统开发中,视图与树控件的双向联动是提升用户体验的关键功能。OpenCasCade(OCCT)7.7.0作为成熟的几何建模内核,提供了完整的交互事件处理机制。我们先要理解几个核心概念:
AIS_InteractiveContext是OCCT的交互总控中心,负责管理所有可视化对象和选择操作。就像交通指挥中心,它协调着图形选取、高亮显示等交互行为。实际开发中,我们通过它的DetectedOwner()和SelectedOwner()方法获取当前鼠标悬停或点击的对象。
TopoDS_Shape是几何实体的数据表示,而AIS_Shape是其可视化包装。这就像建筑图纸(数据)和立体模型(显示)的关系。在双向联动系统中,我们需要建立它们与树节点之间的映射关系。我通常会创建一个类似这样的映射类:
cpp复制public ref class ShapeMapping {
public:
// 树节点唯一标识
System::String^ NodeId;
// 图形对象句柄
NCollection_Handle<Handle(AIS_Shape)> VisualShape;
// 原始几何数据
TopoDS_Shape GeometryShape;
};
处理引用图形时有个常见陷阱:直接显示引用的TopoDS_Shape会导致重复渲染。正确的做法是通过GetLocation()获取变换矩阵并应用:
cpp复制TopLoc_Location parentLoc = shapeTool->GetLocation(parentLabel);
TopLoc_Location finalLoc = parentLoc * shapeTool->GetLocation(childLabel);
referencedShape.Location(finalLoc, false);
鼠标移动时的悬停检测是交互的基础。OCCT通过SelectMgr_SelectionManager管理选择区域,实际开发中要注意这几个要点:
cpp复制viewer->SetPixelTolerance(3); // 3像素的选取敏感度
cpp复制Handle(SelectMgr_EntityOwner) hoverOwner = aisContext->DetectedOwner();
if (!hoverOwner.IsNull()) {
Handle(AIS_Shape) hoverShape = Handle(AIS_Shape)::DownCast(hoverOwner->Selectable());
// 获取原始几何进行比较
TopoDS_Shape hoverGeometry = hoverShape->Shape();
}
cpp复制static Standard_Integer lastX = 0, lastY = 0;
if (abs(currentX - lastX) < 2 && abs(currentY - lastY) < 2)
return;
lastX = currentX;
lastY = currentY;
鼠标点击处理比悬停更复杂,因为可能涉及多选和框选。核心代码结构如下:
cpp复制aisContext->SelectDetected();
for (aisContext->InitSelected(); aisContext->MoreSelected(); aisContext->NextSelected()) {
Handle(SelectMgr_EntityOwner) owner = aisContext->SelectedOwner();
// 处理BRep实体
if (Handle(StdSelect_BRepOwner) brepOwner = Handle(StdSelect_BRepOwner)::DownCast(owner)) {
TopoDS_Shape selectedShape = brepOwner->Shape();
// 与映射表中的形状比较
if (shapeMapping->GeometryShape.IsSame(selectedShape)) {
treeView->SelectedNode = shapeMapping->NodeId;
}
}
}
实际项目中我遇到过选择失效的问题,后来发现是AIS_Shape的显示层未正确设置选择模式。解决方法:
cpp复制shape->SetSelectionMode(1); // 启用面选择
shape->SetSelectionMode(0); // 启用边选择
建立高效映射关系是双向联动的核心。推荐使用Dictionary来存储映射关系,关键实现步骤:
cpp复制Dictionary<String^, ShapeMapping^>^ shapeDict = gcnew Dictionary<String^, ShapeMapping^>();
for each (Label label in shapeLabels) {
ShapeMapping^ mapping = gcnew ShapeMapping();
mapping->NodeId = GenerateUniqueId();
mapping->VisualShape = new NCollection_Handle<Handle(AIS_Shape)>(aisShape);
mapping->GeometryShape = topoShape;
shapeDict->Add(mapping->NodeId, mapping);
}
cpp复制treeNode->Tag = shapeDict[mapping->NodeId]; // WinForms实现
双向高亮需要处理四种状态变化:
cpp复制void TreeView_NodeMouseClick(Object^ sender, TreeNodeMouseClickEventArgs^ e) {
ShapeMapping^ mapping = (ShapeMapping^)e->Node->Tag;
aisContext->SetSelected(mapping->VisualShape->Value, true);
aisContext->UpdateCurrentViewer();
}
cpp复制void View_SelectionChanged() {
TreeNode^ node = FindNodeByShape(selectedShape);
treeView->SelectedNode = node;
treeView->ScrollToNode(node);
}
cpp复制void View_MouseMove() {
if (hoverShape != null) {
aisContext->Highlight(hoverShape, false); // 取消之前的高亮
aisContext->Highlight(newHoverShape, true);
}
}
cpp复制void View_BackgroundClick() {
aisContext->ClearSelected();
treeView->SelectedNode = nullptr;
}
当处理复杂模型时,直接比较TopoDS_Shape可能成为性能瓶颈。我总结了几种优化方案:
cpp复制TDF_Label label = shapeTool->FindShape(selectedShape);
if (!label.IsNull()) {
String^ nodeId = GetLabelName(label);
// 通过标签名快速查找节点
}
cpp复制Bnd_Box bbox;
BRepBndLib::Add(shape, bbox);
if (!view->Camera()->IsCulled(bbox)) {
// 只在视锥体内的对象参与检测
}
cpp复制Task::Run(gcnew Action(() => {
// 耗时的形状比较操作
Invoke(gcnew Action(() => {
// UI线程更新
}));
}));
cpp复制// 在渲染前统一处理高亮状态
void BeforeRendering() {
aisContext->ClearDetected();
if (hoverShape)
aisContext->SetDetected(hoverShape);
}
cpp复制// C++/CLI中特别注意托管与非托管对象的释放
~ShapeMapping() {
if (!VisualShape.IsNull()) {
aisContext->Remove(VisualShape->Value);
}
}
在实际项目中,我曾遇到树节点同步延迟的问题,最终发现是WinForms的TreeView.BeginUpdate/EndUpdate没有正确配对使用。正确的做法是:
cpp复制treeView->BeginUpdate();
try {
// 批量更新节点
} finally {
treeView->EndUpdate();
}