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修改,
是可以修改块外图元的(褪色的图元).
在位编辑器是一个长事务,所以官方方法是长事务修改.
不过我在之前并不知情,所以用了一个通过差集过滤,速度肯定比官方慢的.
差集过滤
var dm = Application.DocumentManager;
var md = dm.MdiActiveDocument;
// 命令反应器命令前触发(全局)
dm.DocumentLockModeChanged += Dc_VetoCommand;
// 命令反应器命令后触发
md.CommandEnded += HatchEvent.Md_CommandEnded;
// 利用命令反应器 Refedit 启动之前创建选择集
private static HashSet<ObjectId> _refeditSS_Before = [];
// 利用命令反应器 Refedit 启动之后做差集,就是在位编辑的时候内部的图元
private static HashSet<ObjectId> _refeditSS_Interior = [];
两个反应器写法
/// <summary>
/// 反应器->命令否决触发命令前(不可锁文档)
/// </summary>
public static void Dc_VetoCommand(object sender, DocumentLockModeChangedEventArgs e)
{
//这里不可以声明database...不然关闭文档的时候,再打开会引发致命错误
if (string.IsNullOrEmpty(e.GlobalCommandName) || e.GlobalCommandName == "#")
return;
Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
switch (e.GlobalCommandName.ToUpper())
{
case "REFEDIT":
{
//在位编辑命令,使用前获取当前空间所有图元
var prompt = ed.SelectAll(_filter);//全选
if (prompt.Status != PromptStatus.OK)
return;
var _hatchIds = prompt.Value.GetObjectIds();
for (int i = 0; i < _hatchIds.Count; i++)
_refeditSS_Before.Add(_hatchIds[i]);
}
break;
}
}
/// <summary>
/// 反应器->command命令完成后(内锁文档)
/// </summary>
public static void Md_CommandEnded(object sender, CommandEventArgs e)
{
if (string.IsNullOrEmpty(e.GlobalCommandName) || e.GlobalCommandName == "#")
return;
Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
switch (e.GlobalCommandName.ToUpper())
{
case "REFEDIT":
{
//在位编辑命令,使用后获取当前空间所有图元
var prompt = ed.SelectAll(_filter);//全选
if (prompt.Status != PromptStatus.OK)
return;
var _hatchIds = prompt.Value.GetObjectIds();
for (int i = 0; i < _hatchIds.Count; i++)
if (!_refeditSS_Before.Contains(_hatchIds[i]))//Except
_refeditSS_Interior.Add(_hatchIds[i]);
var sb = new StringBuilder();
foreach (var id in _refeditSS_Interior)
sb.AppendLine(id.ToString());
Env.Printl("块内填充id:" + sb.ToString());
}
break;
case "REFSET": //加减在位编辑图元
{
//完成后必然有上次选择集
var psr = ed.SelectPrevious();
if (psr.Status != PromptStatus.OK)
return;
// 上次选择集无法利用过滤器,因此我们手动过滤.你也可以封装一个方法.
// 在位编辑时候肯定是当前数据库,所以你不需要像我这样复杂的获取
// 如果你不需要过滤填充,也不需要写这段
var _hatchIds = new HashSet<ObjectId>();
using (Transaction tr = ids[0].Database.TransactionManager.StartTransaction())
{
foreach (var id in psr.Value.GetObjectIds())
{
if (id.ToEntity(tr) is Hatch ha)
_hatchIds.Add(ha);
}
}
if (_hatchIds.Count == 0) return;
string last = CadSystem.Getvar("lastprompt"); //再获取最后一行命令
// 就是因为无法遍历到在位编辑的块内图元,只能进行布尔运算
if (last.Contains("添加") || last.Contains("Added"))// 中英文cad
{
for (int i = 0; i < _hatchIds.Count; i++)
{
_refeditSS_Before.Remove(_hatchIds[i]);
_refeditSS_Interior.Add(_hatchIds[i]);
}
return;
}
if (last.Contains("删除") || last.Contains("Removed"))// 中英文cad
{
for (int i = 0; i < _hatchIds.Count; i++)
{
_refeditSS_Interior.Remove(_hatchIds[i]);
_refeditSS_Before.Add(_hatchIds[i]);
}
return;
}
}
break;
case "REFCLOSE"://保存块,清空
_refeditSS_Interior.Clear();
break;
}
}
长事务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;
}
(完)