cad.net 封装JIG
说明
重构了一下自己的几处JIG代码,
发现可以抽象出一些公共内容,不再每次写类继承(麻烦).
设置JIG不预览:
(setvar DRAGMODE 2) ;0是不预览,2是预览
控制进行拖动的对象的显示方式
你可以自行加入到封装或者命令层实现.
JIG每次克隆图元显示了,再立即释放,这个是为什么呢?
官方Adndev博客介绍这个操作叫做防止精度松动,
并说,现在的内存分配是很智能的,能够释放了立马再拿过来.
不需要担心内存碎片问题.
JIG这个东西有个难点,异步刷新和同步刷新,
同步刷新封装之后写起来简单,图元可以动态加入容器.
异步则需要暴露事件使用,并且是固定数组长度,毕竟它任何时候都会高频获取数组成员.
Jig分为两种情况:
graph TB
Jig命令 --> 图元在数据库 --> 打开可写状态/只读貌似也行 --> newJig.. --> 移动鼠标时修改图元字段 --> 通过事件刷新 --> 退出
Jig命令 --> 图元不在数据库 --> newJig. --> 新建图元加入队列 --> 移动鼠标时 --> 通过队列刷新 --> Dispose图元 --> 新建图元加入队列 --> 退出
退出 --> 保留图元 --> 不在数据库的图元,开启事务提交
命令
namespace JoinBox;
public partial class JigCmds {
[CommandMethod("TestCmd_jig")]
public static void TestCmd_jig() {
var per = Env.Editor.GetEntity("\n选择图元:");
if (per.Status != PromptStatus.OK)
return;
var pts = new List<Point3d>();
for (int i = 0; i < 2; i++) {
var getPt = Env.Editor.GetPoint($"\n输入点{i + 1}");
if (getPt.Status != PromptStatus.OK)
return;
pts.Add(getPt.Value);
}
// JIG操作图元是自动提权
// 所以这里即使事务传出Entity也是可以的,
// 不过穿越事务,非必要不使用,而且cad貌似没有其他场景许可此类操作.
using DBTrans tr = new();
var ent = (Entity)tr.GetObject(per.ObjectId, OpenMode.ForRead);
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());
using JigEx jig = new((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);
// 克隆图元防止精度松动
var entClone = (Entity)ent.Clone();
// 旋转到x轴
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;
jig.AddEntityToMsPs();
}
}
更多测试
/*
* 例子1:
* var ptjig = new JigEx();
* 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 JigEx((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;
* }
* }
* }
* // 开启事务之后,图元加入数据库
* recEntityJig.AddEntityToMsPs();
*/
封装
namespace JoinBox;
public delegate void WorldDrawEvent(WorldDraw draw);
public class JigEx : DrawJig {
/// <summary>
/// 最后的鼠标点,用来确认长度
/// </summary>
public Point3d MousePointWcsLast;
// 事件:默认是图元刷新,其余的:亮显/暗显等等工作自由补充
event WorldDrawEvent? WorldDrawEvent;
// 委托用来获取图元
Action<Point3d, Queue<Entity>>? _action;
// 容差
Autodesk.AutoCAD.Geometry.Tolerance _tolerance;
// 鼠标配置
JigPromptPointOptions? _options;
// 防止刷新过程中更改同步队列
bool _redrawingCompleted = false;
// 同步队列
Queue<Entity> _drawEntitys;
// 正交修改标记,1正交,0非正交
// setvar: https://www.cnblogs.com/JJBox/p/10209541.html
bool _systemVariables_Orthomode = false;
/// <summary>
/// 在界面绘制图元
/// </summary>
/// <param name="action">
/// 用来频繁执行的回调: <see langword="Point3d"/>鼠标点,<see langword="List"/>加入显示图元的容器
/// </param>
/// <param name="tolerance">鼠标移动的容差</param>
public JigEx(Action<Point3d, Queue<Entity>>? action = null, double tolerance = 1e-6) :this() {
_action = action;
_tolerance = new(tolerance, tolerance);
_drawEntitys = new();
DimensionEntitys = new();
}
/// <summary>
/// 执行
/// </summary>
public PromptResult Drag() {
// 拖拽图元必然是当前前台文档,所以封装内部更好调用.
var old = Env.OrthoMode;
if (Env.OrthoMode != _systemVariables_Orthomode)
Env.OrthoMode = _systemVariables_Orthomode;
var dr = Env.Editor.Drag(this);
Env.OrthoMode = old;
return dr;
}
/// <summary>
/// 最后一次的图元加入当前空间
/// </summary>
/// <param name="tr">事务</param>
/// <param name="removeEntity">设置Dispose排除不生成的图元,例如刷新时候的提示文字</param>
/// <returns></returns>
public List<ObjectId> AddEntityToMsPs(Action<Entity>? removeAction = null) {
// 用最后一个点来生成一次图元
// AddEntityToMsPs https://www.cnblogs.com/JJBox/p/14300098.html
var tr = DBTrans.Top;
// 过程图元全部Dispose.
while (_drawEntitys.Count > 0)
_drawEntitys.Dequeue().Dispose();
_action?.Invoke(MousePointWcsLast, _drawEntitys);
if(removeAction is not null)
_drawEntitys.ForEach(ent=> removeAction(_drawEntitys));
return _drawEntitys.Where(ent => !ent.IsDispose)
.Select(ent => tr.AddEntityToMsPs(tr.Database, ent))
.ToList();
}
/// <summary>
/// 鼠标频繁采点
/// </summary>
/// <param name="prompts"></param>
/// <returns>返回状态:令频繁刷新结束</returns>
protected override SamplerStatus Sampler(JigPrompts prompts) {
if (_redrawingCompleted) {
Debug.WriteLine($"SamplerRC线程ID:{Thread.CurrentThread.ManagedThreadId}");
return SamplerStatus.NoChange;
}
Debug.WriteLine($"Sampler线程ID:{Thread.CurrentThread.ManagedThreadId}");
var pro = prompts.AcquirePoint(_options);
if (pro.Status == PromptStatus.Keyword)
return SamplerStatus.OK;
else if (pro.Status != PromptStatus.OK)
return SamplerStatus.Cancel;
// 上次鼠标点不同(一定要这句,不然图元刷新太快会看到奇怪的边线)
// == 和 IsEqualTo 方形判断(仅加法器)
// Distance 圆形判断(会求平方根,除法器牛顿迭代)
var mpt = pro.Value;
if (mpt.IsEqualTo(MousePointWcsLast, _tolerance))
return SamplerStatus.NoChange;
// 上轮循环的缓冲区图元清理,否则将会在vs输出遗忘释放
while (_drawEntitys.Count > 0)
_drawEntitys.Dequeue().Dispose();
// 委托接收新创建的图元,然后给重绘刷新.
// 由于此处动态修改了容器,所以不能异步刷新,
// 异步刷新必须固定容器长度,然后不断更改图元数据.
_action?.Invoke(mpt, _drawEntitys);
MousePointWcsLast = mpt;
return SamplerStatus.OK;
}
/* WorldDraw 操作说明:
* 0x01
* 那么可以先提交事务,再开事务,把Entity传给JIG,最后选择删除部分.
* 虽然这是可行方案,但是Entity穿越事务本身来说是非必要不使用的.
* 0x02
* 我有个功能是一次生成四个方向的箭头,
* 四个箭头最近鼠标的亮显,其余淡显,确认之后最近保留其余删除,
* acad08缺少瞬时图元,
* 在JIG使用淡显ent.Unhighlight()/亮显ent.Highlight()需要绕过队列,
* 否则采样和释放图元将导致图元频闪,令这两个操作失效,
* 因此不使用JIG的队列容器,而是自定义队列容器,
* 再传给DatabaseEntityDraw事件实现这两个操作.
* 0x03
* WorldDraw不要用flag屏蔽,不然鼠标停着时就看不见图元.
* 0x04
* acad08没有异步刷新,acad22有异步刷新
* 同步刷新: draw.Geometry.Draw(entity) 绘制时不会跳到Sampler()
* 异步刷新: draw.RawGeometry.Draw(entity) 绘制时会跳到Sampler()重入导致单队列更改出错,
* 所以通过 _redrawingCompleted 防止刷新过程更新单队列,之后再进入WorldDraw,
* 绘制结束才允许鼠标采集,保证单容器不更改的方案.
* 所以不允许用队列,否则导致容器错误,改用DatabaseEntityDraw事件刷新外部图元.
* 并且外部容器也需要遵守不能更改容量,只能一直改图元数据.
* 双缓冲方案呢?
*/
/// <summary>
/// 重绘图形
/// </summary>
protected override bool WorldDraw(WorldDraw draw) {
if (_drawEntitys.Count > 0)
_redrawingCompleted = true;
WorldDrawEvent?.Invoke(draw);
_drawEntitys.ForEach(ent => draw.RawGeometry.Draw(ent));
_redrawingCompleted = false;
return true;
}
public void DatabaseEntityDraw(WorldDrawEvent draw) {
WorldDrawEvent = draw;
}
#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) {
_systemVariables_Orthomode = orthomode;
_options = JigPointOptions();
_options.Message = Environment.NewLine + msg;
_options.Cursor = cursorType; // 光标绑定
_options.UseBasePoint = true; // 基点打开
_options.BasePoint = basePoint; // 基点设定
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) {
_systemVariables_Orthomode = orthomode;
_options = JigPointOptions();
_options.Message = Environment.NewLine + msg;
// 加入关键字
const string spaceKey = " ";
if (keywords != null) {
foreach (var ge in keywords) {
if (ge.Key != spaceKey)
_options.Keywords.Add(ge.Key, ge.Key, ge.Value);
}
}
// 空格要放最后,才能优先触发其他关键字
var spaceValue = keywords?.Where(ge => ge.Key == spaceKey)
.Select(ge => ge.Value)
.FirstOrDefualt();
if (spaceValue is null) spaceValue = "<空格退出>";
_options.Keywords.Add(spaceKey, spaceKey, spaceValue);
return _options;
}
/// <summary>
/// 鼠标配置:自定义
/// </summary>
/// <param name="action"></param>
/// <param name="orthomode">正交开关</param>
public void SetOptions(Action<JigPromptPointOptions> action, bool orthomode = false) {
_systemVariables_Orthomode = orthomode;
_options = new JigPromptPointOptions();
action.Invoke(_options);
}
/// <summary>
/// 用户输入控制默认配置
/// <para>令jig.Drag().Status == <see cref="PromptStatus.None"/></para>
/// </summary>
/// <returns></returns>
static JigPromptPointOptions JigPointOptions() {
return new JigPromptPointOptions() {
UserInputControls =
UserInputControls.GovernedByUCSDetect // 由UCS探测用
| UserInputControls.Accept3dCoordinates // 接受三维坐标
| UserInputControls.NullResponseAccepted // 输入了鼠标右键,结束jig
| UserInputControls.AnyBlankTerminatesInput // 空格或回车,结束jig;
};
}
/// <summary>
/// 空格默认是<see cref="PromptStatus.None"/>,
/// <para>将它设置为<see cref="PromptStatus.Keyword"/></para>
/// </summary>
public void SetSpaceIsKeyword() {
if (_options is null)
throw new ArgumentNullException(nameof(_options));
if ((_options.UserInputControls & UserInputControls.NullResponseAccepted) == UserInputControls.NullResponseAccepted)
_options.UserInputControls ^= UserInputControls.NullResponseAccepted; // 输入了鼠标右键,结束jig
if ((_options.UserInputControls & UserInputControls.AnyBlankTerminatesInput) == UserInputControls.AnyBlankTerminatesInput)
_options.UserInputControls ^= UserInputControls.AnyBlankTerminatesInput; // 空格或回车,结束jig
}
#endregion
#region 注释数据
/// <summary>
/// 注释数据,在缩放的时候不受影响
/// </summary>
public DynamicDimensionDataCollection DimensionEntitys;
/// <summary>
/// 重写注释数据
/// </summary>
/// <param name="dimScale"></param>
/// <returns></returns>
protected override DynamicDimensionDataCollection GetDynamicDimensionData(double dimScale) {
base.GetDynamicDimensionData(dimScale);
return DimensionEntitys;
}
#endregion
#region IDisposable接口相关函数
~JigEx() { Dispose(false); }
public bool IsDisposed { get; private set; } = false;
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing) {
if (IsDisposed) return;
IsDisposed = true;
// 最后一次的图元如果没有加入数据库就在此销毁,
// 所以JigEx调用的时候加using
while (_drawEntitys.Count > 0) {
var ent = _drawEntitys.Dequeue();
if (ent.Database is null && !ent.IsDisposed)
ent.Dispose();
}
}
#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就是无上下文的异步操作
Lisp

(完)
浙公网安备 33010602011771号