說明
重構了一下自己的幾處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的
(完)