cad.net 封裝jig


說明

重構了一下自己的幾處jig代碼,發現可以抽象出一些公共內容,不單純每次寫類繼承(麻煩),
提供出來給大家.

Jig分為兩種情況:

graph TB Jig命令 --> 圖元在數據庫 --> 打開可寫狀態/只讀貌似也行 --> newJig.. --> 移動鼠標時修改圖元字段 --> 通過事件刷新 --> 退出 Jig命令 --> 圖元不在數據庫 --> newJig. --> 新建圖元加入隊列 --> 移動鼠標時 --> 通過隊列刷新 --> Dispose圖元 --> 新建圖元加入隊列 --> 退出 退出 --> 保留圖元 --> 不在數據庫的圖元,開啟事務提交

測試命令

using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.GraphicsInterface;
using Acap = Autodesk.AutoCAD.ApplicationServices.Application;
using System;
using System.Collections.Generic;
using JoinBox.BasalMath;

namespace JoinBox
{
    public partial class JigCmds
    {
        [CommandMethod("TestCmd_jig")]
        public static void TestCmd_jig()
        {
            var dm = Acap.DocumentManager;
            var doc = dm.MdiActiveDocument;
            var db = doc.Database;
            var ed = doc.Editor;

            var per = ed.GetEntity("\n選擇圖元:");
            if (per.Status != PromptStatus.OK)
                return;

            var pts = new List<PointV>();
            for (int i = 0; i < 2; i++)
            {
                var getPt = ed.GetPoint($"\n輸入點{i + 1}");
                if (getPt.Status != PromptStatus.OK)
                    return;
                pts.Add(getPt.Value);
            }

            //由於jig操作圖元的時候是自動提權的,所以這里即使事務傳出Entity也是可以的,
            //不過穿越事務,非必要不使用,而且cad貌似沒有其他場景許可此類操作.
            Entity? ent = null;
            db.Action(tr => {
                ent = per.ObjectId.ToEntity(tr);
            });
            if (ent == null)
                return;

            var pt1 = pts[0];
            var pt2 = pts[1];
            var pt12Dis = pt1.GetDistanceTo(pt2);
            var pt12Angle = pt1.GetVectorTo(pt2).GetAngle2XAxis();
            var roMat = Matrix3d.Rotation(-pt12Angle, Vector3d.ZAxis, pt1.ToPoint3d());

            var jig = new Jig((mousePointWCS, drawEntitys) => {
                var dotPt = pt1.DotProduct(mousePointWCS, pt2);
                var dotPtRo = dotPt.TransformBy(roMat);
                var pt2Ro = pt2.TransformBy(roMat);

                //復制的次數
                var num = (int)(Math.Abs(dotPtRo.X - pt2Ro.X) / pt12Dis);
                if (num == 0)
                    return;

                for (int i = 0; i < num; i++)
                {
                    var length = pt12Dis * (i + 1);
                    //克隆圖元(需要考慮深度克隆),旋轉到x軸
                    var entClone = (Entity)ent.Clone();
                    entClone.TransformBy(roMat);
                    entClone.EntityMove(pt1.ToPoint3d(), new Point3d(pt1.X + length, pt1.Y, pt1.Z));
                    entClone.TransformBy(roMat.Inverse());
                    drawEntitys.Enqueue(entClone);
                }
            });

            jig.SetOptions(pt2.ToPoint3d(), msg: "\n指定方向點:");
            var pr = jig.Drag();
            if (pr.Status != PromptStatus.OK)
                return;
            db.Action(tr => {
                jig.AddEntityToMsPs(tr);
            });
        }
    }
}

封裝

#if !HC2020
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.GraphicsInterface;
using Acap = Autodesk.AutoCAD.ApplicationServices.Application;
#else
using GrxCAD.DatabaseServices;
using GrxCAD.EditorInput;
using GrxCAD.Geometry;
using GrxCAD.GraphicsInterface;
using Acap = GrxCAD.ApplicationServices.Application;
#endif

using System;
using System.Collections.Generic;
using System.Linq;
using JoinBox.BasalMath;

/*  封裝jig
 *  20220503 cad22需要防止刷新過程中更改隊列,08不會有.
 *  20220326 重繪圖元的函數用錯了,現在修正過來
 *  20211216 加入塊表時候做一個差集,剔除臨時圖元
 *  20211209 補充正交變量設置和回收設置
 *  作者: 驚驚⎛⎝◕⏝⏝◕。⎠⎞ ⎛⎝≥⏝⏝0⎠⎞ ⎛⎝⓿⏝⏝⓿。⎠⎞ ⎛⎝≥⏝⏝≤⎠⎞
 *  博客: https://www.cnblogs.com/JJBox/p/15650770.html
 *
 *  例子1:
 *  var ptjig = new Jig();
 *  ptjig.SetOptions(Point3d.Origin);
 *  var pr = ptjig.Drag();
 *  if (pr.Status != PromptStatus.OK)
 *      return null;
 *
 *  例子2:
 *  var ppo1 = new PromptPointOptions(Environment.NewLine + "輸入矩形角點1:<空格退出>")
 *  {
 *      AllowArbitraryInput = true,//任意輸入
 *      AllowNone           = true //允許回車
 *  };
 *  var ppr1 = ed.GetPoint(ppo1);//用戶點選
 *  if (ppr1.Status != PromptStatus.OK)
 *      return;
 *  var getPt = ppr1.Value;
 *
 *  var recEntityJig = new Jig((mousePoint, drawEntitys) => {
 *      #region 畫櫃子圖形
 *      double length = Math.Abs(getPt.X - mousePoint.X);
 *      double high = Math.Abs(getPt.Y - mousePoint.Y);
 *      var ent = AddRecToEntity(Point3d.Origin, new Point3d(length, high, 0));
 *      drawEntitys.Enqueue(ent);
 *      #endregion
 *  });
 *  recEntityJig.SetOptions("指定矩形角點:", new Dictionary<string, string>() { { "Z", "中間(Z)" } );
 *
 *  bool flag = true;
 *  while (flag)
 *  {
 *      var pr = recEntityJig.Drag();
 *      if (string.IsNullOrEmpty(pr.StringResult))//在無輸入的時候會等於空
 *          flag = false;
 *      else
 *      {
 *          switch (pr.StringResult.ToUpper()) //注意cad保留 https://www.cnblogs.com/JJBox/p/10224631.html
 *          {
 *              case "Z":
 *                  ed.WriteMessage("\n您觸發了z關鍵字");
 *                  break;
 *              case " ":
 *                  flag = false;//空格結束
 *                  break;
 *          }
 *      }
 *  }
 *  //開啟事務之后,圖元加入數據庫
 *  db.Action(tr=>{
 *     recEntityJig.AddEntityToMsPs(tr);
 *  });
 */

namespace JoinBox
{
    public delegate void WorldDrawEvent(WorldDraw draw);
    public class Jig : DrawJig
    {
        #region 成員
        /// <summary>
        /// 事件:默認是圖元刷新,其余的:亮顯/暗顯等等工作自由補充
        /// </summary>
        public event WorldDrawEvent? WorldDrawEvent;
        /// <summary>
        /// 最后的鼠標點,用來確認長度
        /// </summary>
        public Point3d MousePointWcsLast;
        /// <summary>
        /// 最后的圖元,用來生成
        /// </summary>
        public Entity[] Entitys => _drawEntitys.ToArray();

        Autodesk.AutoCAD.Geometry.Tolerance _tolerance;

        Queue<Entity> _drawEntitys;//重復生成的圖元,放在這里刷新
        Action<Point3d, Queue<Entity>>? _action;
        JigPromptPointOptions? _options;
        const string _orthomode = "orthomode";
        bool _systemVariablesOrthomode = false; //正交修改
        bool _worldDrawFlag = false; // 防止刷新過程中更改隊列
        #endregion

        #region 構造
        /// <summary>
        /// 在界面繪制圖元
        /// </summary>
        /// <param name="action">
        /// 用來頻繁執行的回調: <see langword="Point3d"/>鼠標點,<see langword="List"/>加入顯示圖元的容器
        /// </param>
        /// <param name="tolerance">鼠標移動的容差</param>
        public Jig(Action<Point3d, Queue<Entity>>? action = null, double tolerance = 1e-6)
        {
            _action = action;
            _tolerance = new(tolerance, tolerance);
            _drawEntitys = new();
        }
        #endregion

        #region 方法
        /// <summary>
        /// 鼠標配置:基點
        /// </summary>
        /// <param name="basePoint">基點</param>
        /// <param name="msg">提示信息</param>
        /// <param name="cursorType">光標綁定</param>
        /// <param name="orthomode">正交開關</param>
        public JigPromptPointOptions SetOptions(Point3d basePoint,
            CursorType cursorType = CursorType.RubberBand,
            string msg = "點選第二點",
            bool orthomode = false)
        {
            if (orthomode && CadSystem.Getvar(_orthomode) != "1")
            {
                CadSystem.Setvar(_orthomode, "1");//1正交,0非正交 //setvar: https://www.cnblogs.com/JJBox/p/10209541.html
                _systemVariablesOrthomode = true;
            }
            var tmp = new JigPromptPointOptions(Environment.NewLine + msg)
            {
                Cursor = cursorType,   //光標綁定
                UseBasePoint = true,   //基點打開
                BasePoint = basePoint, //基點設定

                //用戶輸入控件:  由UCS探測用 | 接受三維坐標
                UserInputControls =
                    UserInputControls.GovernedByUCSDetect |
                    UserInputControls.Accept3dCoordinates
            };
            _options = tmp;
            return _options;
        }

        /// <summary>
        /// 鼠標配置:提示信息,關鍵字
        /// </summary>
        /// <param name="msg">信息</param>
        /// <param name="keywords">關鍵字</param>
        /// <param name="orthomode">正交開關</param>
        /// <returns></returns>
        public JigPromptPointOptions SetOptions(string msg, Dictionary<string, string>? keywords = null, bool orthomode = false)
        {
            if (orthomode && CadSystem.Getvar(_orthomode) != "1")
            {
                CadSystem.Setvar(_orthomode, "1");//1正交,0非正交 //setvar: https://www.cnblogs.com/JJBox/p/10209541.html
                _systemVariablesOrthomode = true;
            }

            var tmp = new JigPromptPointOptions(Environment.NewLine + msg)
            {
                //用戶輸入控件:  由UCS探測用 | 接受三維坐標
                UserInputControls =
                      UserInputControls.GovernedByUCSDetect |
                      UserInputControls.Accept3dCoordinates
            };

            //加入關鍵字,加入時候將空格內容放到最后
            string spaceValue = string.Empty;
            const string spaceKey = " ";
            if (keywords != null)
            {
                var ge = keywords.GetEnumerator();
                while (ge.MoveNext())
                {
                    if (ge.Current.Key == spaceKey)
                        spaceValue = ge.Current.Value;
                    else
                        tmp.Keywords.Add(ge.Current.Key, ge.Current.Key, ge.Current.Value);
                }
            }

            //要放最后,才能優先觸發其他關鍵字
            if (spaceValue != string.Empty)
                tmp.Keywords.Add(spaceKey, spaceKey, spaceValue);
            else
                tmp.Keywords.Add(spaceKey, spaceKey, "<空格退出>");

            _options = tmp;
            return _options;
        }

        /// <summary>
        /// 鼠標配置:自定義
        /// </summary>
        /// <param name="action"></param>
        /// <param name="orthomode">正交開關</param>
        public void SetOptions(Action<JigPromptPointOptions> action, bool orthomode = false)
        {
            var tmp = new JigPromptPointOptions();
            action.Invoke(tmp);
            _options = tmp;

            if (orthomode && CadSystem.Getvar(_orthomode) != "1")
            {
                CadSystem.Setvar(_orthomode, "1");//1正交,0非正交 //setvar: https://www.cnblogs.com/JJBox/p/10209541.html
                _systemVariablesOrthomode = true;
            }
        }

        /// <summary>
        /// 執行
        /// </summary>
        /// <returns></returns>
        public PromptResult Drag()
        {
            //jig功能必然是當前前台文檔,所以封裝內部更好調用
            var dm = Acap.DocumentManager;
            var doc = dm.MdiActiveDocument;
            var ed = doc.Editor;
            var dr = ed.Drag(this);
            if (_systemVariablesOrthomode)
                CadSystem.Setvar(_orthomode, "0");//1正交,0非正交 //setvar: https://www.cnblogs.com/JJBox/p/10209541.html
            return dr;
        }

        /// <summary>
        /// 最后一次的圖元加入數據庫
        /// </summary>
        /// <param name="tr">事務</param>
        /// <param name="removeEntity">不生成的圖元用於排除,例如刷新時候的提示文字</param>
        /// <returns></returns>
        public IEnumerable<ObjectId>? AddEntityToMsPs(Transaction tr,
            IEnumerable<Entity>? removeEntity = null)
        {
            var ents = Entitys;
            if (ents.Length == 0)
                return null;

            var dm = Acap.DocumentManager;
            var doc = dm.MdiActiveDocument;
            var db = doc.Database;

            var ids = new List<ObjectId>();
            IEnumerable<Entity> es = ents;
            if (removeEntity != null)
                es = es.Except(removeEntity);

            var ge = es.GetEnumerator();
            while (ge.MoveNext())
                ids.Add(tr.AddEntityToMsPs(db, ge.Current));// https://www.cnblogs.com/JJBox/p/14300098.html

            return ids;
        }
        #endregion

        #region 重寫
        /// <summary>
        /// 鼠標頻繁采點
        /// </summary>
        /// <param name="prompts"></param>
        /// <returns>返回狀態:令頻繁刷新結束</returns>
        protected override SamplerStatus Sampler(JigPrompts prompts)
        {
            if (_worldDrawFlag)
                return SamplerStatus.NoChange;//OK的時候拖動鼠標與否都不出現圖元

            if (_options is null)
                throw new ArgumentNullException(nameof(_options));

            var pro = prompts.AcquirePoint(_options);
            if (pro.Status == PromptStatus.Keyword)
                return SamplerStatus.OK;
            else if (pro.Status != PromptStatus.OK)
                return SamplerStatus.Cancel;

            //上次鼠標點不同(一定要這句,不然圖元刷新太快會看到奇怪的邊線)
            var mousePointWcs = pro.Value;

            //== 是比較類字段,但是最好轉為哈希比較.
            //IsEqualTo 是方形判斷(僅加法),但是cad是距離.
            //Distance  是圓形判斷(會求平方根,使用了牛頓迭代),
            //大量數據(十萬以上/頻繁刷新)面前會顯得非常慢.
            if (mousePointWcs.IsEqualTo(MousePointWcsLast, _tolerance))
                return SamplerStatus.NoChange;

            //上次循環的緩沖區圖元清理,否則將會在vs輸出遺忘 Dispose
            while (_drawEntitys.Count > 0)
                _drawEntitys.Dequeue().Dispose();

            //委托把容器扔出去接收新創建的圖元,然后給重繪更新
            _action?.Invoke(mousePointWcs, _drawEntitys);
            MousePointWcsLast = mousePointWcs;

            return SamplerStatus.OK;
        }

     


        /* WorldDraw 封裝外的操作說明:
         * 0x01
         * 我有一個業務是一次性生成四個方向的箭頭,因為cad08缺少瞬時圖元,
         * 那么可以先提交一次事務,再開一個事務,把Entity傳給jig,最后選擇刪除部分.
         * 雖然這個是可行的方案,但是Entity穿越事務本身來說是非必要不使用的.
         * 0x02
         * 四個箭頭最近鼠標的亮顯,其余淡顯,
         * 在jig使用淡顯ent.Unhighlight和亮顯ent.Highlight()
         * 需要繞過重繪,否則重繪將導致圖元頻閃,令這兩個操作失效,
         * 此時需要自定義一個集合 EntityList (不使用本函數的_drawEntitys)
         * 再將 EntityList 傳給 WorldDrawEvent 事件,事件內實現亮顯和淡顯.
         * 0x03
         * draw.Geometry.Draw(_drawEntitys[i]);
         * 此函數有問題,acad08克隆一份數組也可以用來刷新,
         * 而arx上面的jig只能一次改一個,所以可以用此函數.
         * 起因是此函數屬於異步刷新,
         * 同步上下文的刷新是 RawGeometry
         * 0x04
         * cad22測試出現,08不會,
         * WorldDraw 這個操作上單線程的,也就是重繪不會異步跳轉到Sampler(),
         * 但是它內部通過draw.RawGeometry.Draw(ent);會跳到 Sampler(),所以設置 _worldDrawFlag 返回無更改,
         * 后再進入 WorldDraw 
         * 如果 WorldDraw 重入時 if (_worldDrawFlag),那么鼠標停着的時候就看不見圖元,
         * 所以只能重繪結束的時候才允許鼠標采集,采集過程的時候不會觸發重繪,
         * 這樣才可以保證容器在重繪中不被更改.
         */

        /// <summary>
        /// 重繪圖形
        /// </summary>
        protected override bool WorldDraw(WorldDraw draw)
        {
            _worldDrawFlag = true;
            WorldDrawEvent?.Invoke(draw);
            _drawEntitys.ForEach(ent => {
                draw.RawGeometry.Draw(ent);
            });
            _worldDrawFlag = false;
            return true;
        }
        #endregion
    }
}


#if false
    | UserInputControls.NullResponseAccepted           //接受空響應
    | UserInputControls.DoNotEchoCancelForCtrlC        //不要取消CtrlC的回音
    | UserInputControls.DoNotUpdateLastPoint           //不要更新最后一點
    | UserInputControls.NoDwgLimitsChecking            //沒有Dwg限制檢查
    | UserInputControls.NoZeroResponseAccepted         //接受非零響應
    | UserInputControls.NoNegativeResponseAccepted     //不否定回復已被接受
    | UserInputControls.Accept3dCoordinates            //返回點的三維坐標,是轉換坐標系了?
    | UserInputControls.AcceptMouseUpAsPoint           //接受釋放按鍵時的點而不是按下時
    | UserInputControls.AnyBlankTerminatesInput        //任何空白終止輸入
    | UserInputControls.InitialBlankTerminatesInput    //初始空白終止輸入
    | UserInputControls.AcceptOtherInputString         //接受其他輸入字符串
    | UserInputControls.NoZDirectionOrtho              //無方向正射,直接輸入數字時以基點到當前點作為方向
    | UserInputControls.UseBasePointElevation          //使用基點高程,基點的Z高度探測
#endif

致命錯誤的重繪函數

virtual AcGiGeometry * rawGeometry() const = 0;
將當前幾何類作為AcGiGeometry返回,保證指針不是NULL.
此函數允許例程在worldDraw上下文和viewportDraw上下文中運行.

而無raw就是無上下文的異步操作,所以cad08克隆一份來刷新是成功的.

lisp的

(完)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM