目录
一、功能需要
二、所需处理实现
三、代码借鉴及魔改
四、附加内容分析
五、效果图:
六、部署中遇到的问题
1.高程图:原理就是按照X/Y/Z按照单个维度的方向上进行着色,一般都是Z向维度
2.边界框:在游戏里面也叫包围盒,可以是不规则的,做碰撞检测用,在工业中就是一个简单的外接包围矩形体
3.LCC:连通域分析切割,空间中点的密集程度分析切块标记分割。
实际的核心功能CC的CoreLib都已经提供,说白了就是API的调用了,唯一要做的就是把相关参数输入和结果输出呈现在我们自己的UI上面。
不过有了前面的魔改项目的相关经验,具体可参考笔者的前面对整个UI界面的重新构造及魔改:
QT cmake cc级联pcl 魔改项目-CSDN博客
所以实际上处理起来也就没什么压力了,但毕竟年纪大了记忆不怎么好,所以主要就是记录一下,谨防遗忘。
虽然CC都已经有了,但是还是同样的问题需要,从CC代码中分析有用的代码并按照自己的流程放入自己的工程中实现。
所以最重要的还是分析原代码,整个CC的渲染显示部分前面已经做过基本分析,可以参考
QT cmake cc级联pcl 魔改项目-CSDN博客
剩余就是通过界面上的按钮顺腾摸瓜找自己想要的东西了,及过程中遇到的问题,
顺便整理一下过程遇到的代码,如下:
class MainWindow : public QMainWindow,public ccMainAppInterface{
inline const ccHObject::Container& getSelectedEntities() const override { return m_selectedEntities; }
//LCC 连通域---相关处理逻辑---------比较简单-------
menuSegmentation-》actionLabelConnectedComponents
connect(m_UI->actionLabelConnectedComponents, &QAction::triggered, this, &MainWindow::doActionLabelConnectedComponents);
doActionLabelConnectedComponents();
{
.........
//参数设置界面调用
........
//一些调用核心库的常规操作
.......
//ccCommon.h 依赖
.......
.......
//连通域的切块 显示相关
createComponentsClouds()
}
createComponentsClouds(){
//......
//对分析出来的点云进行显示等
//....
}
//高程渲染直接操作内容:
//menuEdit->menuColors->actionSetColorGradient
connect(m_UI->actionSetColorGradient, &QAction::triggered, this, &MainWindow::doActionSetColorGradient);
doActionSetColorGradient()
{
/*ccHObject::Container m_selectedEntities 什么时候被选中更改的
ccEntityAction::setColorGradient(m_selectedEntities, this)
-->ccEntityAction.h-->
*/
ccEntityAction::setColorGradient(.....)
{
for (ccHObject* ent : selectedEntities)
{
bool lockedVertices = false;
//此处一直返回0x00,实体内容选中错误,导致类型检测一直不对,返回的错误
ccGenericPointCloud* cloud = ccHObjectCaster::ToGenericPointCloud(ent,&lockedVertices);
if (lockedVertices)
{
ccUtils::DisplayLockedVerticesWarning(ent->getName(), selectedEntities.size() == 1);
continue;
}
if (cloud && cloud->isA(CC_TYPES::POINT_CLOUD)) // TODO
{
ccPointCloud* pc = static_cast(cloud);
bool success = false;
if (ramp == ccColorGradientDlg::Banding)
success = pc->setRGBColorByBanding(dim, frequency);
else
success = pc->setRGBColorByHeight(dim, colorScale);
if (success)
{
ent->showColors(true);
ent->showSF(false); //just in case
ent->prepareDisplayForRefresh();
}
}
}
}
return;
refreshAll();
updateUI();
{
updateUIWithSelection()
{//此处更新掉了 m_selectedEntities,
m_selectedEntities.clear();
if (m_ccRoot)
{
m_ccRoot->getSelectedEntities(m_selectedEntities, CC_TYPES::OBJECT, &selInfo);
}
}
//对于本项目没什么用注释掉即可
//updateMenus();
//updatePropertiesView();
}
}
//边界包围盒操作---ROI区域设定----涉及内容比较多,主要是交互式操作选项----
void MainWindow::activateClippingBoxMode(){
/*---------相关头文件依赖项----------
ccReservedIDs.h
ccContourExtractor.h ccContourExtractor.cpp
ccCropTool.h ccCropTool.cpp
ccClippingBoxTool.h ccClippingBoxTool.cpp clippingBoxDlg.ui
ccContourExtractorDlg.h ccContourExtractorDlg.cpp contourExtractorDlg.ui
ccBoundingBoxEditorDlg.h ccBoundingBoxEditorDlg.cpp BoundingBoxEditorDlg.ui
ccClippingBoxRepeatDlg.h ccClippingBoxRepeatDlg.cpp ClippingBoxRepeatDlg.ui
ccClippingBoxTool.cpp{
removeLastContour()->MainWindow::TheInstance();
}
*/
m_clipTool;//重要参数,CCoreLib核心库工具对象
m_clipTool->linkWith(win);
if (m_clipTool->start())
{
//相关联的界面控件及内容更新,本项目没由那么多,需要更新,直接注释
//3D视图窗口中上层关联的窗口显示及位置调整
//registerOverlayDialog(m_clipTool, Qt::TopRightCorner);
//freezeUI(true);
//updateOverlayDialogsPlacement();
//deactivate all other GL windows
//disableAllBut(win);
//将对应关联操作面板移动到3D视图的窗口位置
//直接由registerOverlayDialog()方法改造而来
repositionOverlayDialog(m_clipTool, Qt::TopRightCorner);
}
else{
.......
......
}
}
//3D视图窗口中上层关联的窗口显示及位置调整
void MainWindow::registerOverlayDialog(ccOverlayDialog* dlg, Qt::Corner pos){
....
....
//本项目中不需要MDI 所以核心方法就下面的这个
repositionOverlayDialog(mdi);
....
}
struct ccMDIDialogs
{
ccOverlayDialog* dialog;
Qt::Corner position;
//! Constructor with dialog and position
ccMDIDialogs(ccOverlayDialog* dlg, Qt::Corner pos)
: dialog(dlg)
, position(pos)
{}
};
void repositionOverlayDialog(ccMDIDialogs& mdiDlg){
}
void MainWindow::unregisterOverlayDialog(ccOverlayDialog* dialog){
}
}
实际上CC整个界面层qcc项目中所有界面相关管理和点云数据的管理都依赖于:
ccDBRoot,qcc界面层上MDI 树形图控件 点云显示及可视化操作后的结果相关管理类
ccHObject ,CCoreLib对外的一个核心类(承上启下,上与界面层对接,下与核心库交互操作),这个类结构上是同qcc界面层的自定义树形图DBtree需要显示的树形图样式是一致的。
所以这个两个类中的东西相对比较核心的,大概分析如下:
//-------------------ccHObject------------------------------
class QCC_DB_LIB_API ccObject : public ccSerializableObject{}
class QCC_DB_LIB_API ccHObject : public ccObject, public ccDrawableObject{
protected:
//! Parent
ccHObject* m_parent;//注意是自身类型对象
//! Children
Container m_children;//注意是个容器,点云的实体
public:
//这个对象都是在对 m_parent,m_children这两个变量的操作,一个父子的关系链
inline ccHObject* getParent() const { return m_parent; }
inline unsigned getChildrenNumber() const { return static_cast(m_children.size()); }
inline ccHObject* getChild(unsigned childPos) const { return (childPos < getChildrenNumber() ? m_children[childPos] : nullptr); }
int getIndex() const;
int ccHObject::getIndex() const
{
return (m_parent ? m_parent->getChildIndex(this) : -1);
}
int getChildIndex(const ccHObject* aChild) const;
int ccHObject::getChildIndex(const ccHObject* child) const
{
for (size_t i=0; i(i);
return -1;
}
//ccHObject::Container clouds;
filterChildren(clouds, true, CC_TYPES::POINT_CLOUD)
unsigned ccHObject::filterChildren( Container& filteredChildren,
bool recursive/*=false*/,
CC_CLASS_ENUM filter/*=CC_TYPES::OBJECT*/,
bool strict/*=false*/,
ccGenericGLDisplay* inDisplay/*=0*/) const
{
for (auto child : m_children)
{
if ( (!strict && child->isKindOf(filter))
|| ( strict && child->isA(filter)))
{
if (!inDisplay || child->getDisplay() == inDisplay)
{
//warning: we have to handle unicity as a sibling may be in the same container as its parent!
if (std::find(filteredChildren.begin(), filteredChildren.end(), child) == filteredChildren.end()) //not yet in output vector?
{
filteredChildren.push_back(child);
}
}
}
if (recursive)
{
child->filterChildren(filteredChildren, true, filter, strict, inDisplay);
}
}
return static_cast(filteredChildren.size());
}
}
//------关联 DBtree 选中操作---》-ccDBRoot 管理操作类对象-------------
class ccDBRoot : public QAbstractItemModel{
QStandardItemModel* m_propertiesModel;//选中的实体 属性数据模型
ccPropertiesTreeDelegate* m_ccPropDelegate;
//包含了整个树控件显示的内容及格式
ccHObject* m_treeRoot;
//重要--相关的点云对象存储对象的指针 都会存储在此控件item模型的指针中
QTreeView* m_dbTreeWidget;//本项目中不需要使用直接注释
//构造函数中调用,模型关联到树形图控件上
m_dbTreeWidget->setModel(this);//this ==> ccDBRoot
m_treeRoot = new ccHObject("DB Tree");
//特别重要--------重写自QT 视图模型 接口------对点云实体数据做的一些显示操作
bool ccDBRoot::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (index.isValid())
{
if (role == Qt::EditRole)
{
if (value.toString().isEmpty())
{
return false;
}
ccHObject *item = static_cast(index.internalPointer());
assert(item);
if (item)
{
item->setName(value.toString());
//particular cases:
// - labels name is their title (so we update them)
// - name might be displayed in 3D
if (item->nameShownIn3D() || item->isKindOf(CC_TYPES::LABEL_2D))
if (item->isEnabled() && item->isVisible() && item->getDisplay())
item->getDisplay()->redraw();
reflectObjectPropChange(item);
emit dataChanged(index, index);
}
return true;
}
else if (role == Qt::CheckStateRole)
{
ccHObject *item = static_cast(index.internalPointer());
assert(item);
if (item)
{
if (value == Qt::Checked)
item->setEnabled(true);
else
item->setEnabled(false);
redrawCCObjectAndChildren(item);
//reflectObjectPropChange(item);
}
return true;
}
}
return false;
}
//特别重要--------重写自QT 视图模型 接口----对应obj 与 item 关联的 对应索引 点云实体索引
data(...);
parent(....);
index(ccHObject* obj){
assert(object);
if (object == m_treeRoot)
{
return QModelIndex();
}
ccHObject* parent = object->getParent();
if (!parent)
{
//DGM: actually, it can happen (for instance if the entity is displayed in the local DB of a 3D view)
//ccLog::Error(QString("An error occurred while creating DB tree index: object '%1' has no parent").arg(object->getName()));
return QModelIndex();
}
int pos = parent->getChildIndex(object);
assert(pos >= 0);
return createIndex(pos, 0, object);//---重要 关联到 对应索引的 Item QModelIndex
}
//添加点云数据时候使用,操作比较绕...对链的操作,
void ccDBRoot::addElement(ccHObject* object, bool autoExpand/*=true*/)
{
if (!m_treeRoot)
{
assert(false);
return;
}
if (!object)
{
assert(false);
return;
}
bool wasEmpty = (m_treeRoot->getChildrenNumber() == 0);
ccHObject* parentObject = object->getParent();
//parentObject == 0 ==》object->getParent()==0 条件才会满足执行此逻辑
if (!parentObject)
{
parentObject = m_treeRoot;//object->getParent() = m_treeRoot。
m_treeRoot->addChild(object);//
}
else
{
//DGM TODO: how could we check that the object is not already inserted in the DB tree?
//The double insertion can cause serious damage to it (not sure why excatly though).
//The code below doesn't work because the 'index' method will always return a valid index
//as soon as the object has a parent (index creation is a purely 'logical' approach)
//QModelIndex nodeIndex = index(object);
//if (nodeIndex.isValid())
// return;
}
//look for insert node index in tree
QModelIndex insertNodeIndex = index(parentObject);//行数第1级
int childPos = parentObject->getChildIndex(object);//ccHObject_.中的child 是一个 vector的动态数组容器 并列
//QModelIndex &parent,int first,int last,按照实参只可能是两级,一个顶级和一个子级
//(子级下面并列的)
//row insertion operation (start)
beginInsertRows(insertNodeIndex, childPos, childPos);
//row insertion operation (end)
endInsertRows();
if (autoExpand)//树型图控件是否要展开节点操作,本项目中不需要 全部注释掉
{
//expand the parent (just in case)
m_dbTreeWidget->expand(index(parentObject));
//and the child
m_dbTreeWidget->expand(index(object));
}
else //if (parentObject)
{
m_dbTreeWidget->expand(insertNodeIndex);
}
if (wasEmpty && m_treeRoot->getChildrenNumber() != 0)
{
emit dbIsNotEmptyAnymore();//发射信号函数回到Mainwindows中去做一些ui刷新
}
}
//重要获取选中的 点云实体对象
getSelectedEntities(...)
{
selectedEntities.clear();
QItemSelectionModel* qism = m_dbTreeWidget->selectionModel();
QModelIndexList selectedIndexes = qism->selectedIndexes();
try
{
int selCount = selectedIndexes.size();
for (int i = 0; i < selCount; ++i)
{
//重要----selectedEntities----
//来源于m_dbTreeWidget->selectionModel()->selectedIndexes();
//关联到ccDBRoot->m_treeRoot对象
ccHObject* object = static_cast(selectedIndexes[i].internalPointer());
if (object && object->isKindOf(filter))
selectedEntities.push_back(object);
}
}
catch (const std::bad_alloc&)
{
//not enough memory!
}
。。。。。
}
connect(m_dbTreeWidget->selectionModel(), &QItemSelectionModel::selectionChanged, this, &ccDBRoot::changeSelection);
void ccDBRoot::changeSelection(const QItemSelection & selected, const QItemSelection & deselected){
ccHObject* element = static_cast(selectedItems.at(i).internalPointer());
assert(element);
if (element)
{
element->setSelected(true);
element->prepareDisplayForRefresh();
}
updatePropertiesView();//重要---对模型填充,包括选中 边界盒等
MainWindow::RefreshAllGLWindow();
emit selectionChanged();//发射回 MainWindow 中的函数将选中的点云实体更新
}
//右键菜单
connect(m_dbTreeWidget,&QWidget::customContextMenuRequested, this, &ccDBRoot::showContextMenu);
==>{
connect(m_toggleSelectedEntitiesColor, &QAction::triggered, this, &ccDBRoot::toggleSelectedEntitiesColor);
toggleSelectedEntitiesProperty();
{
ccHObject* item = static_cast(selectedIndexes[i].internalPointer());
if (!item)
{
assert(false);
continue;
}
switch (prop)
{
case TG_ENABLE: //enable state
item->setEnabled(!item->isEnabled());
break;
case TG_VISIBLE: //visibility
item->toggleVisibility();
break;
case TG_COLOR: //color
item->toggleColors();
break;
case TG_NORMAL: //normal
item->toggleNormals();
break;
case TG_SF: //SF
item->toggleSF();
break;
case TG_MATERIAL: //Materials/textures
item->toggleMaterials();
break;
case TG_3D_NAME: //3D name
item->toggleShowName();
break;
}
item->prepareDisplayForRefresh();
}
}
}
-------以下---2023---10----07----补充----
实际结构分布:
m_treeRoot{ (ccHObject*)
parent = 0x00
child
[0](ccHObject*)
{
parent = m_treeRoot
child[0](ccPointCould* /ccHObject*)
}
[1](ccHObject*)
{
parent = m_treeRoot
child[0](ccPointCould* /ccHObject*)
}
}
-------以上---2023---10----07----补充----
高程图:
BoundBox
LCC连通域:
实际整个项目已经初具雏形并且尝试了实际部署,过程中遇到的问题及记录如下:
缺少 QT 平台相关dll,参考下方链接:
https://blog.csdn.net/Mmagic1/article/details/109603056
通过安装的QT 快速 将依赖的qt dll 拷贝到目录下。
注意:qwindows 这个dll需要在 platforms/目录下
plugins/QPCL_IO_PLUGIN.dll
plugins/QPCL_PLUGIN.dll
对应目录下有这两个dll 却是加载失败,
这两个dll 相关依赖项没找到,安装pcl all in one 1.10.1 即可