cad.net 封装JIG
说明
重构了一下自己的几处JIG代码,
发现可以抽象出一些公共内容,不再每次写类继承(麻烦).
设置JIG不预览:
setvar DRAGMODE 0是不预览,2是预览
控制进行拖动的对象的显示方式
你可以自行加入到封装或者命令层实现.
JIG每次克隆图元显示了,再立即释放,这个是为什么呢?
官方Adndev博客介绍这个操作叫做防止精度松动
,
并说,现在的内存分配是很智能的,能够释放了立马再拿过来.
不需要担心内存碎片问题.
Jig分为两种情况:
命令
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();
#if 双缓冲
MyQueueCache.Clear();
#endif
}
/// <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);
#if 双缓冲
MyQueueCache.Add(_drawEntitys);
#endif
MousePointWcsLast = mpt;
return SamplerStatus.OK;
}
/* WorldDraw 操作说明:
* 0x01
* 那么可以先提交事务,再开事务,把Entity传给JIG,最后选择删除部分.
* 虽然这是可行方案,但是Entity穿越事务本身来说是非必要不使用的.
* 0x02
* 我有个功能是一次生成四个方向的箭头,
* 四个箭头最近鼠标的亮显,其余淡显,确认之后最近保留其余删除,
* acad08缺少瞬时图元,
* 在JIG使用淡显ent.Unhighlight()/亮显ent.Highlight()需要绕过队列,
* 否则采样和释放图元将导致图元频闪,令这两个操作失效,
* 因此不使用JIG的队列容器,而是自定义队列容器,
* 再传给DatabaseEntityDraw事件实现这两个操作.
* 0x03
* acad08没有异步刷新
* acad22有异步刷新
* 方案一 draw.Geometry.Draw(entity);
* 重绘时候不会跳到Sampler()
* 方案二 draw.RawGeometry.Draw(entity);
* 重绘时候会跳到Sampler()重入导致单队列更改出错,
* 所以通过_redrawingCompleted防止刷新过程更新单队列,
* 之后再进入WorldDraw,
* 重绘结束才允许鼠标采集,保证单容器不更改的方案.
* 0x04
* WorldDraw不要用flag屏蔽,不然鼠标停着时就看不见图元.
* 0x05
* 异步刷新
* 方案一: 不允许用队列,否则导致容器错误.
* 改用DatabaseEntityDraw刷新.
* 并且外部容器也需要遵守不能更改容量,只能一直改图元数据.
* 方案二: 双缓冲方案 @零点 崩溃
*/
/// <summary>
/// 重绘图形
/// </summary>
protected override bool WorldDraw(WorldDraw draw) {
#if 双缓冲
Debug.WriteLine($"WorldDraw线程ID:{Thread.CurrentThread.ManagedThreadId}");
MyQueueCache.ReadDraw(ent => draw.RawGeometry.Draw(ent));
WorldDrawEvent?.Invoke(draw);
#else
if (_drawEntitys.Count > 0)
_redrawingCompleted = true;
WorldDrawEvent?.Invoke(draw);
_drawEntitys.ForEach(ent => draw.RawGeometry.Draw(ent));
_redrawingCompleted = false;
#endif
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();
}
#if 双缓冲
MyQueueCache.Clear();
#endif
}
#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
双缓冲
// 首先要有两个队列,一个负责存,一个负责画.
// 每画一个都会切到鼠标采样,采用里面就会用另一个存,存了之后二者对调..
// 所以每次画完要判断当前容器是否切换.
// 那存的岂不是原子性保护?
using System;
using System.Collections.Generic;
using System.Threading;
public class MyQueueCache {
static int IsDrawNum = 0;
static Queue<Entity>[] _q = {new Queue<Entity>(), new Queue<Entity>()};
static Queue<Entity> GetDepositQueue() => _q[IsDrawNum];
static Queue<Entity> GetDrawQueue() => _q[IsDrawNum == 0 ? 1 : 0];
public static void Add(IEnumerable<Entity> ents) {
var q = GetDepositQueue();
// 上次没有刷新完的需要释放
while (q.Count > 0) q.Dequeue().Dispose();
foreach (var ent in ents) q.Enqueue(ent);
Interlocked.Exchange(ref IsDrawNum, IsDrawNum == 0 ? 1 : 0);
}
public static void ReadDraw(Action<Entity> action) {
var q = GetDrawQueue();
while (q.Count > 0) {
var ent = q.Dequeue();
// 每次绘制都可能切换,之后需要重新获取队列
action(ent);
ent.Dispose();
q = GetDrawQueue();
}
}
public static void Clear() {
var q0 = _q[0];
while (q0.Count > 0) q0.Dequeue().Dispose();
var q1 = _q[1];
while (q1.Count > 0) q1.Dequeue().Dispose();
}
}
备注
virtual AcGiGeometry * rawGeometry() const = 0;
将当前几何类作为AcGiGeometry返回,保证指针不是NULL.
此函数允许例程在worldDraw上下文和viewportDraw上下文中运行.
而无raw就是无上下文的异步操作
Lisp
(完)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
2020-12-06 cad.net WPF嵌入技术1_嵌入WPF到cad(MFC,win32窗体),Win32API嵌入WPF位置跳走的解决方案