奇瑞万达端子线号查询定制项目总结

引言

奇瑞万达需要实现快速选择、对比、修改端子功能端子查询功能,做一下项目的总结。

为实体添加扩展数据

删除端子实体时需要一个识别标记,我选择加入扩展数据作为标识符:

  1. 注册一个不会重名的 appName
  2. 创建你所需要的 xData 缓冲区链表
  3. 把这个数据放到实体中
const CString appName = L"SelectXDataApp";

acdbRegApp(appName);
struct resbuf* rb = acutBuildList(AcDb::kDxfRegAppName, appName,
AcDb::kDxfXdAsciiString, TEXT("xxxxxxx"),
RTNONE);

pPline->setXData(rb);
acutRelRb(rb);

acutBuildList() 创建链表时前两个默认为 APP 类型和 APP 名称,后面也是以 type-value 的形式两两创建链表数据。

模态对话框中实现用户和 CAD 的交互操作

问题描述

现在的业务场景需要在弹出模态对话框之后再返回 CAD 选择实体(也就是端子),再重新显示对话框。

最开始我的处理办法是将模态对话框改为非模态对话框,但在对话框的生命周期控制上很难把控,问题比较多,所以目光又重新回到如何使用模态对话框处理这类场景。

文档查阅

在模态对话框中实现用户和AutoCAD 的交互操作 这篇文章提到使用 BeginEditorCommand() 这个方法去从模态对话框切换到 CAD 应用程序,下面是 官方文档 的描述:

Call this method to indicate an AutoCAd interactive command is starting.

还贴心的给了一个使用示例:

BeginEditorCommand();
if (DoMyInteractiveCommand())
CompleteEditorCommand();
else
CancelEditorCommand();
  • BeginEditorCommand 函数用于将控制权(焦点)交给 CAD,一般用于开始一个交
    互操作
  • CompleteEditorCommand 函数用于从一个在 CAD 中完成的交互命令返回到应用
    程序
  • CancelEditorCommand 函数用于从一个在 CAD 中被取消的交互命令返回到应用程

这三个函数组合使用,能够在模态对话框中实现用户和 CAD 的交互操作。

解决方案

ads_point point{ 0 };
ads_name selectUnit { 0, 0 };

BeginEditorCommand();
auto res = acedEntSel(L"\n请选择需要提取的端子:\n", selectUnit, point);
CompleteEditorCommand();

实体高亮显示

问题描述

奇瑞万达方面需要在选择所有端子后所有都高亮显示,并且能够显示出各自的夹点。

一开始不知道高显这个效果该如何实现,尝试直接调用实体的 highlight() 方法高亮。这种方式没有夹点,在缩放后也没有虚化边框那样明显的视觉效果,客户方面不接受这样的高显只能另寻他法。后面在网上查阅到 ARX亮显问题,里面提到了使用 acedSSSetFirst 这个方法。

文档查阅

由于之前从来没用过这个方法,本着严谨的态度去 官方文档 上又查了一下如何使用。

int acedSSSetFirst(
const ads_name pset,
const ads_name unused
);
  • pset:Set of entities to be added to the pickfirst selection set and on which grips will be displayed
  • unused:Ignored

This function sets which objects are selected and gripped.

The parameters have the following data type definition:

typedef long ads_name[2];

The selection set of objects specified by the gset argument are gripped, and the selection set of objects specified by pset are both gripped and selected. If any objects are common to both selection sets, acedSSSetFirst() grips and selects the selection set specified by pset only (it does not grip the gset set).

If gset is NULL and pset is specified, acedSSSetFirst() grips and selects pset. If gset and pset are NULL, acedSSSetFirst() turns off any existing grips and selections.

You are responsible for creating a valid selection set. For example, you may need to verify that a background paper space viewport (DXF group code 69) is not included in the selection set. You may also need to ensure that selected objects belong to the current layout.

Note The addCommand() optional flags ACRX_CMD_USEPICKSET and ACRX_CMD_REDRAW must be used in order for acedSSSetFirst() to work.

Do not call acedSSSetFirst() when AutoCAD is in the middle of executing a command.

解决方案

照着人家给的示例代码照猫画虎,也算达到效果了:

ads_name ssName, ssTemp;
acedSSAdd(NULL, NULL, ssTemp);

acedSSGet(NULL, NULL, NULL, NULL, ssName);
Adesk::Int32 len = 0;
acedSSLength(ssName, &len);

for (auto i = 0; i < len; i++)
{
ads_name ent = {0, 0};
if (acedSSName(ssName, i, ent) != RTNORM) continue;

AcDbObjectId idEnt = AcDbObjectId::kNull;
acdbGetObjectId(idEnt, ent);

AcDbEntity *pEnt = NULL;
if (acdbOpenAcDbEntity(pEnt, idEnt, AcDb::kForRead) != eOk) continue;
acedSSAdd(ent, ssTemp, ssTemp);

pEnt->close();
}
acedSSFree(ssName);
acedSSSetFirst(ssTemp, NULL);

acedSSFree(ssTemp);

注意启动命令要设置为 ACRX_CMD_REDRAW | ACRX_CMD_USEPICKSETacedSSSetFirst 可以控制加点或者选择的显示,但要注意注册命令的参数。

acedRegCmds->addCommand(_T("xxxxxx"), _T("xxx"), _T("xxx"), ACRX_CMD_TRANSPARENT | ACRX_CMD_USEPICKSET | ACRX_CMD_REDRAW, function);

基于LRU的缓存算法实现

奇瑞万达业务上需要下拉框有记忆功能(即下拉框顺序需要以 最近最少使用 的原则去缓存),在 LeetCode 中也有类似的题目(146. LRU 缓存)。

题目描述

请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。

实现 LRUCache 类:

  • LRUCache(int capacity)正整数 作为容量 capacity 初始化 LRU 缓存
  • int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1
  • void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。

函数 getput 必须以 O(1) 的平均时间复杂度运行。

示例:

输入
["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
输出
[null, null, null, 1, null, -1, null, -1, 3, 4]

解释
LRUCache lRUCache = new LRUCache(2);
lRUCache.put(1, 1); // 缓存是 {1=1}
lRUCache.put(2, 2); // 缓存是 {1=1, 2=2}
lRUCache.get(1); // 返回 1
lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}
lRUCache.get(2); // 返回 -1 (未找到)
lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}
lRUCache.get(1); // 返回 -1 (未找到)
lRUCache.get(3); // 返回 3
lRUCache.get(4); // 返回 4

提示:

  • 1 <= capacity <= 3000
  • 0 <= key <= 10000
  • 0 <= value <= 10^5
  • 最多调用 2 * 10^5getput
class LRUCache {
public:
LRUCache(int capacity) {

}

int get(int key) {

}

void put(int key, int value) {

}
};

/**
* Your LRUCache object will be instantiated and called as such:
* LRUCache* obj = new LRUCache(capacity);
* int param_1 = obj->get(key);
* obj->put(key,value);
*/

解决方案

思路

  1. 保持把新鲜数据往链表头移动。新鲜的定义:刚被修改(put),或者访问过(get),就算新鲜,就需要 splice 到链表头
  2. 过期键直接 pop_back(),链表节点越往后,越陈旧
class LRUCache {
public:
LRUCache(int capacity) : _capacity(capacity) {

}

int get(int key) {
auto it = _table.find(key);
if (it != _table.end()) {
_lru.splice(_lru.begin(), _lru, it->second);
return it->second->second;
}
return -1;
}

void put(int key, int value) {
auto it = _table.find(key);
if (it != _table.end()) {
_lru.splice(_lru.begin(), _lru, it->second);
it->second->second = value;
return;
}

_lru.emplace_front(key, value);
_table[key] = _lru.begin();

if (_table.size() > _capacity) {
_table.erase(_lru.back().first);
_lru.pop_back();
}
}
private:
unordered_map<int, std::list<std::pair<int, int>>::iterator> _table;
std::list<std::pair<int, int>> _lru;
int _capacity;
};

代码要领

  1. map 中保存的是 <key, 链表节点的指针>,这样查找的时候就不用需要去遍历链表了,使用 unordered_map 就能很快找到链表节点指针
  2. 判断容量的时候,最好不使用 std::list::size() 方法,在 c++ 里,这个方法可能不是 O(1) 的。