cad.net 编辑器和在位编辑器原理
块编辑器
块编辑器,不是在位编辑,
有个现象是,
用代码克隆一个块表记录进来,
按道理来说是会更新的,
这在普通块上面只需要刷新一下就可以了.
但是在动态块上面就不一样了.
我通过块编辑器移动某个图元0距离就可以更新,
而不能打开块编辑器之后直接保存,
可能这是块编辑器内置了一个flag,看某个图元是不是有改过.
在位编辑器
疑问
1, 在位编辑的时候会产生0-RefEdit0
图层,不知道何用?
2,如何将选择的图元加减到在位编辑器内外.
答:命令或者长事务API.
3,在位编辑时候,克隆图元加入当前空间会被加入在块内,这是怎么产生的?
答:这是由于克隆的图元id会被长事务记录.
4,褪色度来控制?
不过偶尔cad会发生褪色度错误的情况.
原理
在位编辑
的本质是一个长事务,它不是mysql的长时间事务
的意思.
长事务启动时,
会映射一份源图元
到编辑图元
的关系表,也就是Dictionary
或Hashmap
.
这个操作非常重要,
它是在位编辑
和块编辑器
的基础.
当编辑器
保存时候,将全部数据回写映射对象.
判断块内外图元
在位编辑
状态时,判断块内外图元.
这个实际上困扰了我很久(大概两年),直到koz帮忙了.
在位编辑
命令触发前: 选择当前空间的图元id;
在位编辑
命令触发后: 选择当前空间的图元id;
触发后的图元必然比触发前多,然后差集运算得出多余的就是块内的.
选择的函数就是editor.SelectAll()
,使用命令事件来操作就可以了.
你必须要知道的是,
命令事件的操作要注意锁文档,防止致命错误,
但同时要防止你再次调用了命令,而它内锁了文档,你再锁就会出错.
以上操作虽然很精彩,但是有长事务的API调用,才是正规操作.
代码
cad.net 动态编译生成命令
书写上面链接的代码期间,
发现了在位编辑
时,选择图元并使用API修改,
是可以修改块外图元的(褪色的图元).
在位编辑器是一个长事务,所以官方方法是长事务修改.
不过我在之前并不知情,所以用了一个通过差集过滤,速度肯定比官方慢的.
.NET长事务API
internal static class DocumentEx
{
/// <summary>
/// 判断对象是否在工作集中(判断在位编辑块时块内外图元)
/// </summary>
/// <param name="doc">块所在文档</param>
/// <param name="entityId">文档中的实体Id</param>
/// <param name="includingErased">是否包含删除的对象</param>
/// <returns></returns>
public static bool WorkSetHas(this Document doc, ObjectId entityId, bool includingErased = false)
{
var id = Acap.LongTransactionManager.CurrentLongTransactionFor(doc);
if (id.IsNull) return false; //不存在当前长工作集就返回 false
var tr = DBTrans.GetTop(doc.Database);
using var ltr = (LongTransaction)tr.GetObject(id, openErased: includingErased);
return ltr.WorkSetHas(entityId, includingErased);
}
}
差集过滤
public class RefeditCmd {
public HashSet<RefeditWork> RefeditWorks = new();
// 通过注册表启动,必须用文档事件等待界面完成,直到文档管理器出现.
[IFoxInitialize]
public void Initialize() {
var dm = Acap.DocumentManager;
if (dm is null) return;
if (dm.Count != 0) {
foreach(var doc in dm)
RwsAdd(doc);
}
// 打开文档就加入rws
dm.DocumentCreated += DmCreated;
// 关闭文档移除rws.Remove
dm.DocumentToBeDestroyed += DmDestroyed;
}
// 打开文档,如果数据库已经存盘就会加入,否则跳过
void DmCreated(object sender, DocumentCollectionEventArgs e) {
var doc = ((Document)sender);
RwsAdd(doc);
}
// 文档关闭就释放对应的字典
void DmDestroyed(object sender, DocumentCollectionEventArgs e) {
var doc = ((Document)sender);
RefeditWorks.Remove(new(doc, null));
}
// 多个文档都使用在位编辑,需要使用不同的实例化
void RwsAdd(Document doc){
TypedValue[] filterList = new {
new TypedValue((int)DxfCode.Start, "LINE")
};
var filter = new SelectionFilter(filterList);
RefeditWorks.Add(new(doc, filter));
}
public class RefeditWork {
// 工作集图元
HashSet<ObjectId> _set = [];
ReadOnlyCollection<ObjectId> WorkSet = _set.AsReadOnly();
Document _doc;
public override int GetHashCode(){
return _doc.Database.DwgFile.GetHashCode();
}
SelectionFilter _filter;
HashSet<string> _filterMap;
HashSet<string> ToMap(this SelectionFilter sf){
HashSet<string> map = new();
foreach (var item in sf.GetFilter()) {
if (item.Key == (int)DxfCode.Start)
map.Add(item.Value);
}
}
public RefeditWork(Document doc, SelectionFilter filter = null) {
_filter = filter;
_filterMap = _filter?.ToMap();
_doc = doc;
if (_doc is null) return;
_doc.CommandEnded += CommandEnded;
_doc.CommandWillStart += CommandWillStart;
}
public ~RefeditWork(){
if (_doc is null) return;
_doc.CommandEnded -= DocCommandEnded;
_doc.CommandWillStart -= DocCommandWillStart;
}
/// <summary>
/// 反应器->命令否决触发命令前(全局/不可锁文档)
/// </summary>
// void DmVetoCommand(object sender, DocumentLockModeChangedEventArgs e)
// 命令反应器命令前触发(全局)
// dm.DocumentLockModeChanged += DmVetoCommand;
/// <summary>
/// 命令完成前(内锁文档)
/// </summary>
void DocCommandWillStart(object sender, CommandEventArgs e)
{
// 这里不可以声明database...不然关闭文档的时候,再打开会引发致命错误
// 过滤噪声
if (string.IsNullOrEmpty(e.GlobalCommandName)
|| e.GlobalCommandName == "#")
return;
var doc = Acap.DocumentManager.MdiActiveDocument;
var ed = doc.Editor;
switch (e.GlobalCommandName.ToUpper())
{
case "REFEDIT":
{
// 在位编辑命令使用前,获取当前空间所有图元
var prompt = ed.SelectAll(_filter); // 全选
if (prompt.Status != PromptStatus.OK)
return;
var ids = prompt.Value.GetObjectIds();
// 在位编辑状态后,若直接通过ctrl+z回滚,
// 没有"REFCLOSE",造成没有清空set.
_set.Clear();
_set.UnionWith(ids);
}
break;
}
}
/// <summary>
/// 命令完成后(内锁文档)
/// </summary>
void DocCommandEnded(object sender, CommandEventArgs e)
{
// 过滤噪声
if (string.IsNullOrEmpty(e.GlobalCommandName)
|| e.GlobalCommandName == "#")
return;
var doc = ((Document)sender);
var ed = doc.Editor;
switch (e.GlobalCommandName.ToUpper())
{
case "REFEDIT":
{
// 在位编辑命令使用后,获取当前空间所有图元,必然多出图元
var prompt = ed.SelectAll(_filter); // 全选
if (prompt.Status != PromptStatus.OK)
return;
var ids = prompt.Value.GetObjectIds();
HashSet<ObjectId> set = new();
foreach(var id in ids) {
if(!_set.Contains(id))
set.Add(id);
}
_set = set;
}
break;
case "REFSET": // 加减在位编辑图元
{
// 完成后必然有上次选择集,无法利用过滤器
var psr = ed.SelectPrevious();
if (psr.Status != PromptStatus.OK)
return;
var ids = prompt.Value.GetObjectIds();
HashSet<ObjectId> set = new();
if (_filterMap is null) {
set.UnionWith(ids);
} else {
using DBTrans tr = new();
foreach (var id in ids) {
// using var ent = (Entity)tr.GetObject(id, OpenMode.ForRead);
// 我想直接利用过滤器呢?
// GetDxfName见: https://www.cnblogs.com/JJBox/p/12489648.html
var dxfName = id.GetDxfName();
if (_filterMap.Contains(dxfName)) {
set.Add(id);
}
}
}
// 再获取最后一行命令
string last = CadSystem.Getvar("lastprompt");
if (last.Contains("添加") || last.Contains("Added"))// 中英文cad
_set.UnionWith(set);
if (last.Contains("删除") || last.Contains("Removed"))// 中英文cad
_set.ExceptWith(set);
}
break;
case "REFCLOSE":// 保存块,清空
_set.Clear();
break;
}
}
ARX长事务API
通过长事务判断在位编辑块内外cpp,看e大介绍是acad08就有这个API了
http://bbs.mjtd.com/thread-190822-1-1.html
ads_name ent;
ads_point pt;
if(RTNORM != acedEntSel(_T("\n选择对象: "),ent,pt)){
return;
}
AcDbObjectId objId;
acdbGetObjectId(objId,ent);
//直接判断
//if(acdbIsInLongTransaction(objId))
//判断并移除工作集
if(isWorksetAndRemove(objId))
acutPrintf(_T("\n在长事务中"));
else
acutPrintf(_T("\n不在长事务中"));
// 从工作集中移除
static bool isWorksetAndRemove(AcDbObjectId objId){
AcDbObjectId longtransId = acapLongTransactionManagerPtr()->currentLongTransactionFor(curDoc());
if (AcDbObjectId::kNull != longtransId)
{
AcDbObjectPointer<AcDbLongTransaction> pLongTrans(longtransId,AcDb::kForRead);
if(Acad::eOk != pLongTrans.openStatus()) return false;
//判断是否在工作集
if(pLongTrans->workSetHas(objId))
{
//升级打开
pLongTrans->upgradeOpen();
if(pLongTrans->isWriteEnabled())
{
//移除工作集
pLongTrans->removeFromWorkSet(objId);
}
return true;
}
}
return false;
}
Lisp
来自这里:
http://bbs.mjtd.com/thread-191620-1-1.html
;($block-refedit$(car (entsel))nil)
(defun $block-refedit$ (b lst / ss e es obj)
(if (and b (= (type b) 'ename) (entget b))
(progn
(setq ss (lst->ss (list b)))
(and ss (sssetfirst ss ss))
(if ss
(progn
(setq e (vlax-vla-object->ename
(setq obj (VLA-ADDPOINT
(vla-get-ModelSpace
(vla-get-ActiveDocument
(vlax-get-acad-object)
)
)
(VLAX-3D-POINT (LIST 0 0 0))
)
)
)
)
(vl-cmdf "-refedit" "O" "ALL" "Y")
(vl-catch-all-apply 'vla-put-visible
(list
(vl-catch-all-apply 'VLA-ITEM
(list
(vl-catch-all-apply 'vla-get-toolbars
(list
(vl-catch-all-apply 'vla-Item
(list (vla-get-MenuGroups (vlax-get-Acad-Object)) "ACAD" )
)
)
)
"参照编辑"
)
)
0
)
)
;关闭
(setq ss nil)
(setq es nil)
(while (AND e (setq e (entnext e)))
(setq es (cons e es))
) ;进入refedit后调用这个
(vla-delete obj)
)
)
)
)
es
)
(完)