cad.net 动态块
动态块名
测试版本: Acad2008
问题一:
BlockReference.IsDynamicBlock
判断是否为动态块,当频繁使用的时候,会出现报错:
eInvalidObjectId错误. System.AccessViolationException的异常
我是在频繁重复空格执行上次命令时候报这个错误,
修复方法是,在所有GetObject
语句,最前面加using
.
或者最后加.Dispose() 一样效果的.
问题二:
动态块的块参照,Z比例为0,或许不等比缩放,
就会令动态块变成普通块,
导致BlockReference.IsDynamicBlock
判断动态块失效.
问题三:
动态块不拉伸时候就是原本名称,一旦拉伸块表记录就是u名,
所以既要普通块名
,又要动态块名
,
针对问题二和问题三,
我发现打开动态块记录直接读名称就好了,不需要判断它是不是动态块的.
namespace JoinBox;
public static partial class BlockHelper {
/// <summary>
/// 动态块真实块名获取
/// </summary>
/// <param name="brRec">块参照</param>
/// <returns>成功返回:块的真实名称,失败返回:string.Empty</returns>
public static string GetBlockName(this BlockReference brRec)
{
var tr = DBTrans.Top; // IFox事务栈顶获取事务
string blockName = string.Empty;
if (brRec.DynamicBlockTableRecord.IsOk()) {
using var btRec = (BlockTableRecord)tr.GetObject(brRec.DynamicBlockTableRecord, OpenMode.ForRead);
blockName = btRec.Name;
}
return blockName;
}
}
public static partial class EntityEdit {
/// <summary>
/// id有效,未被删除
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public static bool IsOk(this ObjectId id) {
return !id.IsNull && id.IsValid && !id.IsErased && !id.IsEffectivelyErased && id.IsResident;
}
}
动态块过滤器
注意,此过滤器会获取当前空间全部动态块(不止仅限同名)
和同名普通块
,
要根据块真名循环时候过滤
一次.
若不想这样做,需要自己构建索引组织表.
但是似乎过于麻烦
参考 https://www.cnblogs.com/JJBox/p/18580188
#if !HC2020
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
#else
using GrxCAD.DatabaseServices;
using GrxCAD.EditorInput;
#endif
using System.Collections.Generic;
namespace JoinBox;
public static class SsgetFilterEx {
// https://blog.csdn.net/yxp_xa/article/details/72229202?tdsourcetag=s_pcqq_aiomsg
/// <summary>
/// 转义通配符,加入过滤器必须转义
/// </summary>
/// <param name="blockName">块名</param>
/// <returns></returns>
static string BlockNameEscape(string blockName)
{
return blockName.Replace("#", "`#").Replace("@", "`@");
}
// https://www.cnblogs.com/edata/p/6797362.html
/// <summary>
/// 通过块信息,设置动态块过滤器或普通块过滤器
/// </summary>
/// <param name="baseId">块的图元id</param>
/// <returns>过滤器</returns>
public static SelectionFilter GetBlockSelectionFilter(ObjectId baseId) {
var tr = DBTrans.Top;
// 选择集过滤器
var list = new List<TypedValue>();
list.Add(new TypedValue((int)DxfCode.Start, "INSERT"));
// 过滤得到同一个空间下图元,布局名称(布局视口内则为模型)
string layoutName = CadSystem.GetMouseSpace();
list.Add(new TypedValue((int)DxfCode.LayoutName, layoutName));
// 动态块过滤器{动态块名,真实名称(匿名块不提供)}
using var brRec = (BlockReference)tr.GetObject(baseId, OpenMode.ForRead);
string blockName = BlockNameEscape(brRec.GetBlockName());
list.Add(new TypedValue((int)DxfCode.BlockName, $"`*U*,{blockName}"));
return new SelectionFilter(list.ToArray());
}
// 过滤得到同名的块参照
public static List<ObjectId> SelectBlockReference(ObjectId baseId, List<ObjectId> blockIds){
List<ObjectId> ids = new();
var tr = DBTrans.Top;
using var brRec = (BlockReference)tr.GetObject(baseId, OpenMode.ForRead);
string blockName = brRec.GetBlockName();
foreach(var id in blockIds){
using var brRec = (BlockReference)tr.GetObject(id, OpenMode.ForRead);
if (brRec is null) continue;
if (brRec.GetBlockName() == blockName)
ids.Add(id);
}
return ids;
}
}
public static partial class CadSystem {
/// <summary>
/// 当前布局名称(视口内为模型)
/// </summary>
/// <returns>模型或布局名称</returns>
public static string GetMouseSpace() {
string layoutName = LayoutManager.Current.CurrentLayout;
// SpatialPoint 在 https://www.cnblogs.com/JJBox/p/11464977.html
if (CadSystem.SpatialPoint() == SpatialPosition.Viewport) {
layoutName = "Model";
}
return layoutName;
}
}
动态块参数
这个方法封装不好,会导致外面已经提权,内部重复提权.
public static void ChangeDynamicValue(BlockReference brf, string key, string value) {
if (!brf.IsDynamicBlock) return;
brf.UpgradeOpen();
// 动态块属性集
var props = brf.DynamicBlockReferencePropertyCollection;
foreach (DynamicBlockReferenceProperty item in props) {
// 参数名:可见性/距离之类的
if (item.PropertyName.Contains(key))
item.Value = value; // 参数值
}
brf.DowngradeOpen();
}
好的写法:
因为任何提权行为都不应该封装,
而是传入id,再打开对象,再提权,再处理,再降权,再释放对象.
/// <summary>
/// 更改动态块拉伸参数
/// </summary>
/// <param name="brfId">动态块参照的图元id</param>
/// <param name="name">动作参数名</param>
/// <param name="value">动作参数值</param>
public static void ChangeDynamicValue(ObjectId brfId, string key, string value) {
var tr = DBTrans.Top;
if (!brfId.IsOk) return;
using var brf = (BlockReference)tr.GetObject(brfId, OpenMode.ForRead);
if (brf is null || !brf.IsDynamicBlock) return;
br.UpgradeOpen();
// 动态块属性集
var props = br.DynamicBlockReferencePropertyCollection;
foreach (DynamicBlockReferenceProperty item in props) {
// 参数名:可见性/距离之类的
if (item.PropertyName.Contains(key))
item.Value = value; // 参数值
}
br.DowngradeOpen();
}
可见性名称
例如:我需要找到可见性
被改名之后的参数名
才能设置参数值
,
不然缺失参数名
则无法操作(因为不能枚举字符串),
发现是301组码下面:
那么C#如何得到这个组码呢?
cad.net 句柄遍历数据库
那么Lisp如何得到这个组码呢?
(setq Pros (vlax-invoke (vlax-ename->vla-object (car (entsel))) 'GetDynamicBlockProperties))
(mapcar '(lambda (X)(cons (vlax-get X 'PropertyName) (vlax-get X 'Value)))Pros)
分解块参照(炸开)
插入动态块再根据鼠标修改参数是很正常的功能,
青蛙在动态块外面再加了一层普通块A,然后炸开,会发现动态块的参数丢失了.
解决方案是深度克隆之后,块表记录有动态块记录的,直接插入就好了,不需要普通块A再炸开.
E1旧版炸开: 标注关联× 动态块参数√
var objects = new DBObjectCollection();
blkRef.Explode(objects);
foreach(DB0bject entity in objects) {
space.AppendEntity(entity as Entity);
}
trans.AddNewlyCreatedDBObject(entity,true);
E2新版炸开: 标注关联√ 动态块参数√
blkRef.ExplodeToOwnerSpace();
未解决的案例
我有一个动态块,无论新版炸开函数还是旧版都存在问题.
新版:导致阵列跑位.
旧版:丢失标注关联.
这个动态块是利用高版本阵列,制作过程如下:
1,画一个圆,然后用AR阵列,再组块,然后进入块编辑状态.
2,点击参数管理器,新增两个key(参数)名称是可以改的.
3,选中阵列,在特性面板中,直接输入第二步的key名.
4,保存块编辑器后,选中块参照,在特性面板中可以看到刚才设置的列数和水平距离,并能够修改值.
实现了一种外部驱动块内阵列的效果.
经过研究之后,E2新版炸开
是可以的.
炸开之前需要逆变换到原点,否则直接炸开的话,高版本阵列会跑位.
而旧版炸开会丢失标注关联.
组块
0,选择或创建图元/属性定义.
1,基变换:插入点到原点
2,获取绘图次序之后深度克隆,然后创建块表记录并加入其中
https://www.cnblogs.com/d1742647821/p/18325096
using DBTrans tr = new();
var objectIdList = tr.CurrentSpace.OfType<ObjectId>().ToList();
if (objectIdList.Count == 0) return;
// 绘图次序排序
using var drt = (DrawOrderTable)tr.GetObject(tr.CurrentSpace.DrawOrderTableId, OpenMode.ForRead);
var objectIdCollection = objectIdList
.OrderBy(id => drt.GetSortHandle(id).Value)
.ToCollection();
// 创建块表记录
var btrId = tr.BlockTable.Add("*U",
btr => { btr.DeepCloneEx(objectIdCollection); });
// 插入当前空间
tr.CurrentSpace.InsertBlock(Point3d.Origin, btrId);
// 删除旧图元
var ents = objectIdList.GetObject<Entity>(OpenMode.ForWrite);
foreach (var ent in ents) {
ent.Erase();
}
3,注释性
https://gitee.com/inspirefunction/ifoxcad/issues/I820Z0
4,根据属性定义附加属性参照
5,附加动态参数和动作(这里甚至没有完成)
6,基变换:原点到插入点
炸开(分解)
理论上炸开底层实现是克隆,可以改用克隆到当前块表记录,然后标注关联要依靠什么什么重建(或者保留key关系)才对.
没找到C#怎么重建标注关联...
注意:
新旧版本炸开不同,不能单纯认为可以相互替代,尤其是新版组块旧版打开炸开.
E1旧版炸开: BlockReference.Explode();
E2新版炸开: BlockReference.ExplodeToOwnerSpace()
旧版表现:
标注关联被取消,新版阵列被炸开会怎么样等等.
我们应该用插件进行反向支持,在低版本补充炸开高版本炸开功能才对.
官方保存低版本dwg有没有自动转换动态块内容呢?
新版表现:
能够保留块内的标注关联.
如果遇到新版阵列,那么需要把块参照进行逆变换到原点,否则阵列内容会跑位.
这个函数没有提供返回值,炸开内容只能通过db.ObjectAppended事件获取,
而事件会将全部层次结构和过程id给平铺了.
可以通过遍历事件收集的id输出DBObject.Gettype().Name
得知:
它们并不都是Entity,会有IsErased==true
的,会有重复的,会有动态块参数和动作的.
而直接把全部内容进行矩阵变换,则会把图元变换了两次.
阵列原理:
https://www.cnblogs.com/ztcad/p/14707974.html
https://www.cnblogs.com/ztcad/p/14707880.html
一个罕见函数,
转为静态块:
BlockReference.ConvertToStaticBlock();
删除对象 br.Erase();
实际上是修改IsErased=true,并通知数据库把它放入UndoLog用于撤回
它仍然在内存暂留,所以仍然需要降权.
真正删除数据行破坏内存:
https://adndevblog.typepad.com/autocad/2012/04/reclaiming-memory-of-erased-objects.html
测试代码:
public class TestBlock {
// 容器记录炸开内容
List<ObjectId> _ids = new();
// 通过事件获取炸开内容,保存入容器中
void ObjectAppended(object sender, ObjectEventArgs e) {
_ids.Add(e.DBObject.ObjectId);
}
[CommandMethod(nameof(cs21))]
public void cs21() {
PromptEntityOptions peo = new("选择块参照: ");
var per = Env.Editor.GetEntity(peo);
if (per.Status != PromptStatus.OK) return;
db.ObjectAppended += ObjectAppended;
Matrix3d mat = Matrix3d.Identity;
using(DBTrans trans = new()){
using var ent = (Entity)trans.GetObject(per.ObjectId, OpenMode.ForRead);
if (ent is not BlockReference br) return;
br.UpgradeOpen();
// 记录块矩阵,给炸开后的内部图元使用
mat = br.BlockTransform;
// 把块参照逆变换到原点
br.TransformBy(mat.Inverse());
// 新版炸开
br.ExplodeToOwnerSpace();
br.Erase();
br.DowngradeOpen();
}
db.ObjectAppended -= ObjectAppended;
// 目的:炸开后的图元全部变换到块矩阵
// 从事件获取内容
// 1,存在过程图元,有被删除的图元.(过程图元会恢复吗?)
// 2,动态块参数也在其中.(它是隐藏的吗?)
// 3,仅排除ent.IsErased不行的,有些东西无法矩阵变换.
// 4,如果将可以变换的都进行矩阵变换,会把阵列内容移动了两次,
// 而仅仅移动前面十个就不会(正确变换),如何排除呢?
// 前面十个中有AcDbAssocArrayActionBody是不是就是阵列参数,
// 而十个之后是阵列图元?
using (DocumentLock docLock = doc.LockDocument()) {
using(DBTrans t2 = new()){
// 统计重复(开窗函数),发现都是1,就不用输出了
var map = new Dictionary<ObjectId,int>();
for (int j = 0; j < _ids.Count; j++){
var id = _ids[j];
if (map.ContainsKey(id)) {
++map[id];
}else{ map.Add(id,1); }
}
// 打印统计信息
var sb = new StringBuilder();
for (int j = 0; j < _ids.Count; j++){
var id = _ids[j];
string name = "";
/*
已经删除打开会报错eWasErased
无论是id.Open,还是OpenMode.ForNotify模式
那么Acad撤回时候怎么用UndoLog复活它呢?
先设置id.IsErased=true吗?还是说深度克隆?
只允许内部实现?通过id的话那么图元内容无法获取.
if (id.IsErased){
using var objx = t2.GetObject(id, OpenMode.ForRead);
name=objx.GetType().Name;
}
*/
if (id.IsErased) name+="-"
if (id.IsNull) name+="*"
// 全有 if(id.IsValid) name+="+"
if (id.IsEffectivelyErased) name+="/"
// 全有 if(id.IsResident) name+=">"
name += id.ObjectClass.Name;
sb.AppendLine($"序号:{j},句柄:{new Handle(id.Handle.Value)},重复:{map[id]},图元名称:{name}");
}
Debug.Print(sb.ToString());
// 测试前面十个
for (int j = 0; j < 10; j++){
var id = _ids[j];
if (id.IsErased) continue;
using var objx = t2.GetObject(id, OpenMode.ForRead);
if (objx.GetType().Name is "BlockBegin" or "BlockEnd") continue; //跳过不能矩阵变换的对象,它将导致eNotApplicable.
if (objx is not Entity ent) continue;
ent.TransformBy(mat);
ent.DowngradeOpen();
}
}//tr
}//lock
// 清理,这里仅仅是重置容器计数,而不释放内存容量,下次使用将从0进行覆盖内容.
// 容器持有对象不释放,并不导致对象引用计数(菱形引用计数从不为0,无法释放对象),不需要改用WeakMap.
// 因为容器持有的id是二级指针,它指向的Entity才是真正对象,所以你甚至会发现桌子设计数据库时,Entity字段从来不持有另一个Entity而是持有id,标识符模式.
_ids.Clear();
}
}
未解决块内关联标注
插入动态块后更新关联标注不成功
建议不要把标注做到动态块内...这样就规避问题了...
[CommandMethod(nameof(CS116), CommandFlags.UsePickSet | CommandFlags.Redraw)]
public static void CS116() {
using var tr = new DBTrans();
ObjectId blkRefId = ObjectId.Null;
// 插入...试试不要用insert,用深度克隆看看?
tr.Task(()=>{
blkRefId = tr.CurrentSpace.InsertBlock(
new Point3d(0, 0, 0), "1", new Scale3d(1, 1, 1), 0, null);
});
// 分开插入和修改刷新为两个命令,居然是成功的.
var map = new Dictionary<string, double>
{
{ "test1", 1000 },
{ "test2", 2000 }
};
ChangeDynamicValue(blkRefId, map);
// 发送刷新后关联标注能够更新
Env.Document.SendStringToExecute("regen\n", true, false, true);
}
// 修改动态块数值
public static void ChangeDynamicValue(ObjectId id, Dictionary<string, double> map) {
var tr = DBTans.Top;
using var bref = tr.GetObject<BlockReference>(id, OpenMode.ForWrite);
// 动态块属性集
var props = bref?.DynamicBlockReferencePropertyCollection;
if (props is null) return;
foreach (DynamicBlockReferenceProperty item in props) {
if (map.TryGetValue(item.PropertyName, out var v)) // 参数名:可见性/距离
item.Value = v;
}
// 是不是这里缺少了遍历块参照内,而不是块参照块表记录,然后就能刷新标注了?
// 关联数据AssocBody打开句柄获取?
// https://www.cnblogs.com/ztcad/p/14107353.html
// 动画,更新标注
// https://www.cnblogs.com/JJBox/p/11354224.html
bref.RecordGraphicsModified(true);
CadSystem.UpdateScreenEx(bref);
}
未解决动态块纯代码创建
怎么用纯代码的方式新建一个动态块?
edata思路:
1:弄一个空的DXF;
2:构造一个有动态块的块定义;
3:删除后的;
然后比较这三者,但是怎么用DXF组码来创建的呢?对应的API是什么?
解析动态块
本文来自 https://blog.csdn.net/jiangyb999/article/details/128725215
块有2种,普通块和动态块;
普通块
大家应该很熟悉,定义好块,插入到某个布局中,就成了块参照.
对块参照能做的无非就是缩放旋转等常规操作,块内的图形是不能再被编辑的;
动态块
也是像普通快一样插入,缩放旋转的常规操作自然也是具备的,
但是,它的超能力来自于,我们可以修改参照内的图形;
听起来还是蛮简单的,但是动态块的内容十分庞大.
其实,动态块不应该当做图块来看,
定义一个动态块就是定义了一段程序,
在程序的辅助下,我们才能实现复杂的修改;
按照Autodesk的说法,动态块是基于DAG的一套求解器;
DAG就是有向无环图(Directed Acycle Graph),
这个图是由Node和Edge构成的一个网络;
Node代表动作,
Edge是对象属性之间的依赖关系,并且Edge是有方向的;
现在该是去探究一下动态块的存储了;
在解析DWG文件时,并没有所谓的动态块图元类型.
我们知道,对象扩展字典是一个很强大的工具.
二次开发者可以借助该字典对元对象实现无限扩展.
动态块可以认为是Autodesk假装自己是第三方开发者,
然后对普通块进行扩展的极好示范,
借助扩展字典就无需对DWG格式做任何修改,
动态块是2006版引入的功能,而2006版使用的DWG格式和2004是一样的.
实验
创建一个动态块BB1,
由4条直线组成,
其中加入了1个点参数/1个线性参数/2个拉伸动作;
去除了不相关的部分,该块描述如下:
Object(BLOCK_HEADER/31)
handle: 0.1.F1
owner: 4.1.1
xdicobjhandle: 3.2.5BF
name: *U
entries:4
inserts:2
1), 3.2.5BB
2), 3.2.5BC
3), 3.2.5BD
4), 3.2.5BE
block:3.1.F2
endblk:3.1.F3
Object(BLOCK/4)
handle: 0.1.F2
owner: 4.0.F1
xdicobjhandle: 0.0.0
name: BB1
Object(LINE/13)
handle: 0.2.5BB
owner: 4.1.F1
start: 0.000000, 0.000000, 0.000000
end : 36.067069, 49.732573, 0.000000
------------
Object(LINE/13)
handle: 0.2.5BC
owner: 4.1.F1
start: 36.067069, 49.732573, 0.000000
end : 69.634529, 29.107227, 0.000000
------------
Object(LINE/13)
handle: 0.2.5BD
owner: 4.1.F1
start: 44.258738, 7.308900, 0.000000
end : 69.634529, 29.107227, 0.000000
------------
Object(LINE/13)
handle: 0.2.5BE
owner: 4.1.F1
start: 44.258738, 7.308900, 0.000000
end : 0.000000, 0.000000, 0.000000
然后在模型空间插入此块,只调整了比例;
可以看出,参照的块名是"BB1";
这时,我们再到解析的数据里找句柄为669的块参照,
看看它的数据,注意看它引用的块的句柄,
正是我们刚定义的动态块BB1的句柄.
Object(INSERT/7)
handle: 0.2.669
owner: 0.0.0
xdicobjhandle: 0.0.0
entmode: 2 (MSPACE)
insert point: (989.208137, -530.611402)
block header: 5.1.F1
就是说,未对块参照执行动态调整时,
它是直接引用原始的块定义的,并且没有扩展字典;
我们使用动态参数来调整一下,形状明显与上面的不同.
保存DWG,再解析一遍,查看669的数据;
Object(INSERT/7)
handle: 0.2.669
owner: 0.0.0
xdicobjhandle: 3.2.6BC
entmode: 2 (MSPACE)
insert point: (989.208137, -530.611402)
block header: 5.2.6B4
看到没,这时引用的块定义变成 6B4 了,而且有扩展字典,
我们再看看块 6B4 是咋个样子;
Object(BLOCK_HEADER/31)
handle: 0.2.6B4
owner: 4.1.1
xdicobjhandle: 3.2.6BB
name: *U
entries:4
inserts:1
1), 3.2.6B7
2), 3.2.6B8
3), 3.2.6B9
4), 3.2.6BA
block:3.2.6B5
endblk:3.2.6B6
Object(BLOCK/4)
handle: 0.2.6B5
owner: 4.0.6B4
xdicobjhandle: 0.0.0
name: *U25
Object(LINE/13)
handle: 0.2.6B7
owner: 4.0.6B4
start: 0.000000, 0.000000, 0.000000
end : 36.067069, 49.732573, 0.000000
------------
Object(LINE/13)
handle: 0.2.6B8
owner: 4.0.6B4
start: 36.067069, 49.732573, 0.000000
end : 53.107381, 39.262245, 0.000000
------------
Object(LINE/13)
handle: 0.2.6B9
owner: 4.0.6B4
start: 44.258738, -7.308900, 0.000000
end : 53.107381, 39.262245, 0.000000
------------
Object(LINE/13)
handle: 0.2.6BA
owner: 4.0.6B4
start: 44.258738, -7.308900, 0.000000
end : 0.000000, 0.000000, 0.000000
块 "*U25" 中的4条直线 6B7,6B8,6B9,6BA
分别对应 块"BB1"中的 5BB,5BC,5BD和5BE,
由于做了参数调整,他们的坐标值发生了变化;
这一切说明了什么呢?
当我们使用动态块时,99.99999%的概率是要对它修改的,
所以,插入动态块后,AutoCAD会复刻一份原始动态块的定义,
生成一个匿名动态块,作为块参照的引用块;
所有对块参照的修改,都反映在这个匿名块身上,
然后像使用普通块一样使用这个匿名块,
这样做,可以保证原始的动态块定义永远保持不变;
当然,这是在你不人为修改它的定义的前提下,
如果你参照它生成多个块参照对象后,再修改它的定义,
AutoCAD总会询问你:是否更新这些块参照.
还记得这个画面吗?
总结
到这里,可以更新开头我对动态块的解读认知了;
解析INSERT图元,如果要生成图形,
直接使用其参考的块包含的对象就可以了,
不用分辨它是动态块还是普通块,
动态调整的过程AutoCAD已经帮我们完成了;
起初我还以为,
生成动态块的参照,需要从参数/动作等自己去完成,
那对大多数人来说都是不可能完成的任务,
好在AutoCAD保存了块参照参考的匿名块的最后状态,
而不是仅仅保存该状态的自定义参数值,
不过想想也是,
它不保存不是难为自己吗?是我想的复杂了;
没有时间去比对原始动态块和新的匿名块,
他们的扩展字典的情况了,不影响大局,
那是APP(这里是AutoCAD)要关心的事;
(完)