cad.net Attsync命令更新属性位置
原因
Acad2008和Acad2019使用Attsync命令,
处理镜像的属性块
时会有不同的效果.
而桌子并不是重写新命令或者拓展函数,而是修改了原本的函数.
因为我用代码实现,依然会发生这样的事情.
演示
制作测试:
在Acad08和Acad19用旋转和镜像属性块用Attsync命令.
01,旋转的属性块会发现文字是正的.
02,镜像的属性块会发现文字是反的.
动图演示
函数更新了什么?
桌子的想法也很简单:
1,考虑了法向量.
2,对齐方式是调整
,桌子还会位移了.
这就给我们二开很不好的地方是,我们需要自行去处理不同呈现的结果.
处理方式是写一个新命令来替换掉acad自带的命令,
然后对旋转
或者镜像
分别处理,某些情况不调用SetAttributeFromBlock
.
出问题的函数
// 从块设置属性
at.SetAttributeFromBlock(attdef, brf.BlockTransform);
下面仅仅是注释一下参数意义:
public static class Helper {
/// <summary>
/// 设置块属性
/// </summary>
/// <param name="at">属性参照(块外)</param>
/// <param name="blockTransform">块的矩阵变换</param>
/// <param name="definition">属性定义(块内)</param>
public static void SetAttributeFromBlockEx(this AttributeReference at,
Matrix3d blockTransform, AttributeDefinition definition = null) {
// 为了解决SetAttributeFromBlock的新旧版本不同的问题,所以这里写一个替换函数
// 强行将acad2008的旋转方式修改成与acad2019一样.
// 因为acad2019留意到法向量的问题
// 原本这个函数会令TextString丢失,所以后面要重设(这里考虑不改变它
// 旋转矩阵则不要用它,不然会设置两次旋转
// 平移矩阵则不要用它,不然会设置两次平移
// null不对比块内信息,就直接执行矩阵.
if (definition is null)
at.SetAttributeFromBlock(blockTransform);
else
at.SetAttributeFromBlock(definition, blockTransform);
}
// 标记Tag/提示Prompt/默认TextString;
// 可以重复Tag但cad会爆红提示
// 定义(块内def)
public static Dictionary<string, AttInfo> ToDefTagMap(this BlockReference brf) {
var map = new Dictionary<string, AttInfo>();
var tr = DBTrans.Top;
using var btRec = (BlockTableRecord)tr.GetObject(brf.BlockTableRecord, OpenMode.ForRead);
foreach(var id in btRec) {
using var def = (AttributeDefinition)tr.GetObject(id, OpenMode.ForRead);
if(def is null || map.ContainsKey(def.Tag)) continue;
map.Add(def.Tag, new AttInfo(def));
// map.Add(def.Tag, def); 释放对象不能偷出去
}
return map;
}
// 参照(块外att)
public static Dictionary<string, AttInfo> ToTagMap(this BlockReference brf) {
var map = new Dictionary<string, AttInfo>();
var tr = DBTrans.Top;
foreach (ObjectId id in brf.AttributeCollection) {
using var att = (AttributeReference)tr.GetObject(id, OpenMode.ForRead);
if(att is null || map.ContainsKey(att.Tag)) continue;
map.Add(att.Tag, new AttInfo(att));
}
return map;
}
}
命令
public class Command_JJAttsync {
[CommandMethod(nameof(JJ_Attsync),
CommandFlags.Modal | CommandFlags.UsePickSet | CommandFlags.Redraw)]
public void JJ_Attsync() {
var dm = Acap.DocumentManager;
var doc = dm.MdiActiveDocument;
var db = doc.Database;
var ed = doc.Editor;
ed.WriteMessage(Environment.NewLine + "****惊惊连盒-翻转属性块更新");
// 选择块名相同
#if jing
var ids = Command_jjak.AllSelectByBlockName(ed, db, BlockTool.EnumAttIdentical.AllBlockName);
if (ids.Length == 0) return;
#else
PromptEntityOptions options = new("\n 选择属性块");
options.SetRejectMessage("\n请选择图块");
options.AddAllowedClass(typeof(BlockReference),false);
var res = ed.GetEntity(options);
var ssIdArray = new ObjectId[] { res.ObjectId };
if (ssIdArray.Length == 0) return;
#endif
using var docLocker = doc.LockDocument();
using (DBTrans tr = new()) {
ids.ForEach(id => CreateAndErase(id));
}
using (DBTrans tr = new()) {
ids.ForEach(id => MiRo(id));
}
ed.SetImpliedSelection(new ObjectId[] { });
}
void CreateAndErase(ObjectId id) {
var tr = DBTrans.Top;
using var ent = tr.GetObject(id, OpenMode.ForRead);
if (ent is not BlockReference brf) return;
var attMap = brf.ToTagMap();
var attdefMap = brf.ToDefTagMap();
// 标记Tag/提示Prompt/默认TextString;
// 根据内部def创建外部att,
// 如果外部att缺失就创建并同步矩阵
attdefMap.ForEach(attdefkv => {
var attdef = attdefkv.Value;
// 巧妙的处理释放问题,
// 1,先构造tagMap利用自动释放,因为托管类型由GC释放
// 2,using智能指针释放读取的,记住命令层从不手动释放.
// 3,新创建的对象不释放,而是写入数据库中.
// 4,2和3的后续逻辑用函数封装,这样就不会释放了新创建的.
if(attMap.ContainsKey(attdef.Tag)) {
using var att = (AttributeReference)tr.GetObject(attMap[attdef.Tag].ObjectId, OpenMode.ForWrite);
SetRefInfo(brf, att, attdef.ObjectId, attMap[attdef.Tag].TextString);
} else {
var att = attdef.CreateReference();
SetRefInfo(brf, att, attdef.ObjectId, attdef.TextString);
}
});
// 删除孤立的外部,和AS命令一样
foreach (ObjectId idatt in brf.AttributeCollection) {
using var att2 = (AttributeReference)tr.GetObject(idatt, OpenMode.ForRead);
if (attdefMap.ContainsKey(att2.Tag)) continue;
att2.UpgradeOpen();
att2.Erase();
}
}
void SetRefInfo(BlockReference brf,
AttributeReference att, ObjectId defId, string str) {
var tr = DBTrans.Top;
// 设置矩阵
using var defent = (AttributeDefinition)tr.GetObject(defId, OpenMode.ForRead);
if(defent is null) return;
att.SetAttributeFromBlockEx(brf.BlockTransform, defent);
// 和单行文字一样,先调整对齐,才能设置内容
att.AdjustAlignment(brf.Database);
att.TextString = str;
if (att.IsNewObject) {
// 向块参照添加属性对象
brf.UpgradeOpen();
brf.AttributeCollection.AppendAttribute(att);
tr.Transaction.AddNewlyCreatedDBObject(att, true);
}
}
void MiRo(ObjectId id){
var tr = DBTrans.Top;
using var ent = tr.GetObject(id, OpenMode.ForRead);
if (ent is not BlockReference brf) return;
// 旋转到当前鼠标坐标系 CoordinateSystem3d
// 储存属性参照
var attMap = brf.ToTagMap();
// 症结点在于整体缩放时候无法通过X<1来判断块的镜像
// 缩放或者变形
// 没有镜像的图元,法向量正常=>
if (brf.ScaleFactors.X >= 0 && brf.Normal.Z == 1) {
// 旋转
foreach (var attkv in attMap) {
using var at = (AttributeReference)tr.GetObject(attkv.Value.ObjectId, OpenMode.ForWrite);
if (isRo(brf)) RotateAttributeReference(brf, at);
}
} // 没有镜像的图元,法向量不正常=>
else if (brf.ScaleFactors.X >= 0 && brf.Normal.Z == -1) {
// 镜像并旋转
foreach (var attkv in attMap) {
using var att = (AttributeReference)tr.GetObject(attkv.Value.ObjectId, OpenMode.ForWrite);
MirrorAttributeReference(brf, att, att.Rotation);
}
} // 镜像的图元,法向量正常=>
else if (brf.ScaleFactors.X < 0 && brf.Normal.Z == 1) {
foreach (var attkv in attMap) {
using var att = (AttributeReference)tr.GetObject(attkv.Value.ObjectId, OpenMode.ForWrite);
// Acad年份获取https://www.cnblogs.com/JJBox/p/18511807
if(VersionTool.CurrentYear == 2008) {
// 镜像并旋转
MirrorAttributeReference(brf, att, -att.Rotation);
} else if(VersionTool.CurrentYear >= 2019) {
Alignment2019Mr1(brf, att);
} else throw new("该版本没有测试");
}
} // 镜像的图元,法向量不正常=>
else if (brf.ScaleFactors.X < 0 && brf.Normal.Z == -1) {
foreach (var attkv in attMap) {
using var att = (AttributeReference)tr.GetObject(attkv.Value.ObjectId, OpenMode.ForWrite);
if(VersionTool.CurrentYear == 2008) {
// 旋转
if (isRo(brf)) RotateAttributeReference(brf, at);
} else if(VersionTool.CurrentYear >= 2019) {
Alignment2019Mr2(brf, att);
} else throw new("该版本没有测试");
}
}
}
// IFox的函数 我的EntityAdd.TxtAlignment
bool IsTargetJustify(AttributeReference at){
return at.Justify == AttachmentPointHelper.Get("铺满") ||
at.Justify == AttachmentPointHelper.Get("调整")) {
}
/// <summary>
/// 处理2019cad的属性块2
/// </summary>
/// <param name="brf">块参照</param>
/// <param name="at">属性</param>
void Alignment2019Mr2(BlockReference brf, AttributeReference at) {
var ro = at.Rotation;
if (IsTargetJustify(at)) {
// 这个角度是不会设置到图元中的,
// 但是之后求包围盒都是0角度的了,很有趣.
at.Rotation = 0;
#if jing
// 求AABB包围盒
var boxEdge = new GeometricExtents(at).Edge3;
var boxEdge = at.GeometricExtents;
boxEdge.RotateBy(ro, at.Normal, at.Position);
var ro_RightUpper = boxEdge.RightUpper;//右上
var ro_RightDown = boxEdge.RightDown;//右下
var ro_MidstUpper = boxEdge.MidstUpper;//中上
var ro_MidstDown = boxEdge.MidstDown; //中下
var ro_Midst = boxEdge.Midst;//中间
// 先平移,不然用上下镜像会有误差的
at.EntityMove(ro_RightUpper, ro_RightDown);
// 上下镜像
var mt = at.EntityMirror(ro_MidstUpper, ro_MidstDown, out _);
at.SetAttributeFromBlockEx(mt);
if (!isRo(brf)) {
ro_Midst += ro_RightUpper.GetVectorTo(ro_RightDown);
at.EntityRotate(Math.PI, at.Normal, ro_Midst);
}
#else
var boxEdge = at.GeometricExtents;
boxEdge.TransformBy(Matrix3d.Rotation(ro, at.Normal, at.Position));
var ro_RightUpper = boxEdge.MaxPoint;//右上
var ro_RightDown = new Point3d(boxEdge.MaxPoint.X, boxEdge.MinPoint.Y, 0);//右下
var ro_MidstUpper = new Point3d(boxEdge.MaxPoint.X * 0.5 + boxEdge.MinPoint.X * 0.5, boxEdge.MaxPoint.Y, 0);// 中上
var ro_MidstDown = new Point3d(boxEdge.MaxPoint.X * 0.5 + boxEdge.MinPoint.X * 0.5, boxEdge.MinPoint.Y, 0); //中下
var ro_Midst = new Point3d(boxEdge.MaxPoint.X * 0.5 + boxEdge.MinPoint.X * 0.5, boxEdge.MinPoint.Y * 0.5 + boxEdge.MaxPoint.Y * 0.5, 0); //中间
// 先平移,不然用上下镜像会有误差的
at.TransformBy(Matrix3d.Displacement(ro_RightDown - ro_RightUpper));
// 上下镜像
var mt = Matrix3d.Mirroring(new Line3d(ro_MidstUpper, ro_MidstDown));
at.SetAttributeFromBlockEx(mt);
if (!isRo(brf)) {
ro_Midst += ro_RightUpper.GetVectorTo(ro_RightDown);
at.TransformBy(Matrix3d.Rotation(Math.PI, at.Normal, ro_Midst));
}
#endif
at.AdjustAlignment(brf.Database);
return;
}
MirrorAttributeReference(brf, at, ro);
// 保证右边的旋转是对的.
// 这里是镜像过,所以要判断brf.Rotation>=的
if (isRo(brf, true)) {
#if jing
var boxEdge = new GeometricExtents(at).Edge3;
at.EntityRotate(Math.PI, at.Normal, boxEdge.Midst);
#else
var boxEdge = at.GeometricExtents;
var boxEdge_Midst = new Point3d(boxEdge.MaxPoint.X * 0.5 + boxEdge.MinPoint.X * 0.5, boxEdge.MinPoint.Y * 0.5 + boxEdge.MaxPoint.Y * 0.5, 0); //中间
at.TransformBy(Matrix3d.Rotation(Math.PI, at.Normal, boxEdge_Midst));
#endif
}
at.AdjustAlignment(brf.Database);
}
/// <summary>
/// 处理2019cad的属性块1
/// </summary>
/// <param name="brf">块参照</param>
/// <param name="at">属性</param>
#if jing
void Alignment2019Mr1(BlockReference brf, AttributeReference at) {
if (IsTargetJustify(at)) {
// 求AABB包围盒
var boxEdge = new GeometricExtents(at).Edge3;
if (isRo(brf)) {
// 这个角度是不会设置到图元中的,
// 但是之后求包围盒都是0角度的了,很有趣.
var ro = at.Rotation;
at.Rotation = 0;
// 求AABB包围盒
boxEdge = new GeometricExtents(at).Edge3;
var leftUpper = boxEdge.LeftUpper.RotateBy(ro, at.Normal, at.Position);
at.EntityMove(leftUpper, at.Position);
}
else {
at.EntityRotate(Math.PI, at.Normal, boxEdge.Midst);
at.AdjustAlignment(brf.Database);//调整对齐
// 这个角度是不会设置到图元中的,
// 但是之后求包围盒都是0角度的了,很有趣.
var ro = at.Rotation;
at.Rotation = 0;
// 求AABB包围盒
boxEdge = new GeometricExtents(at).Edge3;
var leftUpper = boxEdge.LeftUpper.RotateBy(ro, at.Normal, at.Position);
at.EntityMove(at.Position, leftUpper);
}
return;
}
if (brf.Rotation == Math.PI / 2)
RotateAttributeReference(brf, at);
}
#else
void Alignment2019Mr1(BlockReference brf, AttributeReference at) {
// 求AABB包围盒
var boxEdge = at.GeometricExtents;
var boxEdge_Midst = new Point3d(boxEdge.MaxPoint.X * 0.5 + boxEdge.MinPoint.X * 0.5, boxEdge.MinPoint.Y * 0.5 + boxEdge.MaxPoint.Y * 0.5, 0); //中间
var boxEdge_LeftUpper = new Point3d(boxEdge.MinPoint.X, boxEdge.MinPoint.Y * 0.5 + boxEdge.MaxPoint.Y * 0.5, 0);//左上
if (IsTargetJustify(at)) {
if (isRo(brf)) {
var ro = at.Rotation;
at.Rotation = 0;
boxEdge = at.GeometricExtents;
var leftUpper = boxEdge_LeftUpper.RotateBy(ro, at.Normal, at.Position);
at.TransformBy(Matrix3d.Displacement(at.Position- leftUpper));
}
else {
at.TransformBy(Matrix3d.Rotation(Math.PI, at.Normal, boxEdge_Midst));
at.AdjustAlignment(brf.Database);//调整对齐
var ro = at.Rotation;
at.Rotation = 0;
// 求AABB包围盒
boxEdge = at.GeometricExtents;
var leftUpper = boxEdge_LeftUpper.RotateBy(ro, at.Normal, at.Position);
at.TransformBy(Matrix3d.Displacement(leftUpper - at.Position));
}
return;
}
if (brf.Rotation == Math.PI / 2)
RotateAttributeReference(brf, at);
}
#endif
/// <summary>
/// 镜像块的属性参照
/// </summary>
/// <param name="brf">块参照</param>
/// <param name="at">属性</param>
/// <param name="rotation">旋转角度,因为法向量不同而选择块的正值或负值</param>
#if jing
void MirrorAttributeReference(BlockReference brf, AttributeReference at, double rotation) {
// 克隆一份属性来旋转,计算它的最小包围盒
using var atClone = at.Clone() as AttributeReference;
atClone.Rotation = 0;
// 求AABB包围盒
var boxEdge = new GeometricExtents(atClone).Edge3;
// 将包围盒上下两个点逆旋转到文字的位置,提供给镜像使用.
var muPt = boxEdge.MidstUpper.RotateBy(rotation,
brf.BlockTransform.CoordinateSystem3d.Zaxis, atClone.Position);
var mdPt = boxEdge.MidstDown.RotateBy(rotation,
brf.BlockTransform.CoordinateSystem3d.Zaxis, atClone.Position);
// 镜像矩阵
var mt = at.EntityMirror(muPt, mdPt, out _);
at.SetAttributeFromBlockEx(mt);
if (isRo(brf)) RotateAttributeReference(brf, at);
}
#else
void MirrorAttributeReference(BlockReference brf, AttributeReference at, double rotation) {
// 克隆一份属性来旋转,计算它的最小包围盒
using var atClone = at.Clone() as AttributeReference;
atClone.Rotation = 0;
// 求AABB包围盒
var boxEdge = atClone.GeometricExtents;
var boxEdge_MidstUpper = new Point3d(boxEdge.MaxPoint.X * 0.5 + boxEdge.MinPoint.X * 0.5, boxEdge.MaxPoint.Y, 0);// 中上
var boxEdge_MidstDown = new Point3d(boxEdge.MaxPoint.X * 0.5 + boxEdge.MinPoint.X * 0.5, boxEdge.MinPoint.Y, 0); //中下
// 将包围盒上下两个点逆旋转到文字的位置,提供给镜像使用.
var muPt = boxEdge_MidstUpper.RotateBy(rotation,
brf.BlockTransform.CoordinateSystem3d.Zaxis, atClone.Position);
var mdPt = boxEdge_MidstDown.RotateBy(rotation,
brf.BlockTransform.CoordinateSystem3d.Zaxis, atClone.Position);
// 镜像矩阵
var mt = Matrix3d.Mirroring(new Line3d(muPt, mdPt));
at.SetAttributeFromBlockEx(mt);
if (isRo(brf)) RotateAttributeReference(brf, at);
}
#endif
/// <summary>
/// 旋转块的属性参照
/// </summary>
/// <param name="brf"></param>
/// <param name="at"></param>
#if jing
void RotateAttributeReference(BlockReference brf, AttributeReference at) {
// 求AABB包围盒
var boxEdge = new GeometricExtents(at).Edge3;
at.EntityRotate(Math.PI, at.Normal, boxEdge.Midst);
// 此处若用at.AttributeFromBlock(mt)会set两次角度...很奇怪耶
// 调整对齐,不然08和19不一样显示
at.AdjustAlignment(brf.Database);
}
#else
void RotateAttributeReference(BlockReference brf, AttributeReference at) {
// 求AABB包围盒
var boxEdge = at.GeometricExtents;
var boxEdge_Midst = new Point3d(boxEdge.MaxPoint.X * 0.5 + boxEdge.MinPoint.X * 0.5, boxEdge.MinPoint.Y * 0.5 + boxEdge.MaxPoint.Y * 0.5, 0); //中间
at.TransformBy(Matrix3d.Rotation(Math.PI, at.Normal, boxEdge_Midst));
at.AdjustAlignment(brf.Database);
}
#endif
// 旋转要求,
// 90~270度就改成:块旋转+180度,
// 处理镜像判断<=,那么其他的呢?引起出错吗?
bool IsRo(BlockReference brf, bool isMr = false) {
const double half = Math.PI / 2;
if(isMr) {
return half <= brf.Rotation && brf.Rotation < half * 3;
}
return half < brf.Rotation && brf.Rotation < half * 3;
}
}
class AttInfo {
public string Tag { get; }
public string Prompt { get; }
public string TextString { get; }
public ObjectId TextStyleId { get; }
public ObjectId ObjectId { get; }
public AttInfo(AttributeDefinition attdef) {
ObjectId = attdef.ObjectId;
Tag = attdef.Tag;
Prompt = attdef.Prompt;
TextString = attdef.TextString;
#if NET35
TextStyleId = attdef.TextStyle;
#else
TextStyleId = attdef.TextStyleId;
#endif
}
public AttInfo(AttributeReference att) {
ObjectId = att.ObjectId;
Tag = att.Tag;
// Prompt = att.Prompt;
TextString = att.TextString;
#if NET35
TextStyleId = att.TextStyle;
#else
TextStyleId = att.TextStyleId;
#endif
}
public AttributeReference CreateReference() {
return new AttributeReference {
Tag = this.Tag,
#if NET35
TextStyle = this.TextStyleId,
#else
TextStyleId = this.TextStyleId,
#endif
};
}
}
using的操作
CAD的类用的是DisposableWrapper
接口,
不是IDisposable
接口,造成没有自动析构.
如果通过一个事务栈,GetObject时候记录到栈内,然后统一释放,
这样就满足要求了,就可以记录在字典中,而不需要用一个数据类做代理.
释放的时候CAD会根据ent.IsNewObject来自动处理的,
加入数据库的就不释放,没加就会释放.
http://bbs.mjtd.com/forum.php?mod=viewthread&tid=191722
using System;
using System.Collections.Generic;
// 定义一个实现了IDisposable接口的类
class MyClass : IDisposable {
private bool isDisposed = false;
public bool IsDisposed => isDisposed;
// 构造函数
public MyClass() {
Console.WriteLine("MyClass instance created.");
}
// 实现IDisposable接口的Dispose方法
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
// 受保护的Dispose方法,用于实际的资源释放
protected virtual void Dispose(bool disposing) {
if (isDisposed) return;
isDisposed = true;
if (disposing){
// 释放托管资源
Console.WriteLine("Managed resources disposed.");
}
// 释放非托管资源
Console.WriteLine("Unmanaged resources disposed.");
}
}
class Program {
public static Dictionary<string, MyClass> GetDictionary() {
var dict = new Dictionary<string, MyClass>();
using (MyClass my = new MyClass()){
dict.Add("key", my);
} // 会在此处就释放了资源
return dict;
}
static void Main() {
var map = GetDictionary();
Console.WriteLine(map["key"].IsDisposed);
}
}
(完)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下