cad.net 编辑器和在位编辑器原理

块编辑器

块编辑器,不是在位编辑,
有个现象是,
用代码克隆一个块表记录进来,
按道理来说是会更新的,
这在普通块上面只需要刷新一下就可以了.
但是在动态块上面就不一样了.

我通过块编辑器移动某个图元0距离就可以更新,
而不能打开块编辑器之后直接保存,
可能这是块编辑器内置了一个flag,看某个图元是不是有改过.

在位编辑器

疑问

1, 在位编辑的时候会产生0-RefEdit0图层,不知道何用?

2,如何将选择的图元加减到在位编辑器内外.
答:命令或者长事务API.

3,在位编辑时候,克隆图元加入当前空间会被加入在块内,这是怎么产生的?
答:这是由于克隆的图元id会被长事务记录.

4,褪色度来控制?
不过偶尔cad会发生褪色度错误的情况.

原理

在位编辑的本质是一个长事务,它不是mysql的长时间事务的意思.
长事务启动时,
会映射一份源图元编辑图元的关系表,也就是DictionaryHashmap.
这个操作非常重要,
它是在位编辑块编辑器的基础.
编辑器保存时候,将全部数据回写映射对象.

判断块内外图元

在位编辑状态时,判断块内外图元.
这个实际上困扰了我很久(大概两年),直到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
)

(完)

posted @ 2019-07-14 18:51  惊惊  阅读(1121)  评论(0编辑  收藏  举报