cad.net 封装jig

说明

重构了一下自己的几处jig代码,
发现可以抽象出一些公共内容,不再每次写类继承(麻烦).

设置JIG不预览:
setvar DRAGMODE 0是不预览,2是预览
控制进行拖动的对象的显示方式
你可以自行加入到封装或者命令层实现.

JIG每次克隆图元显示了,再立即释放,这个是为什么呢?
官方Adndev博客介绍这个操作叫做防止精度松动,
并说,现在的内存分配是很智能的,能够释放了立马再拿过来.
不需要担心内存碎片问题.

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的

(完)

posted @ 2021-12-06 17:11  惊惊  阅读(1673)  评论(2编辑  收藏  举报