兵器升级之IFoxCad库

使用CAD原生API进行二次开发代码过于繁琐,IFoxCad是一个更高层次的库,它对原生API进行了大量封装,可以让我们的代码更加简洁,显著提升我们的开发效率。一起升级我们的兵器吧!

环境搭建

1
2
3
4
# 安装项目模板,注意装最新版
dotnet new uninstall IFoxCad.Templates
# 卸载项目模板
dotnet new uninstall IFoxCad.Templates
  • 创建IFoxCadTemplate项目,项目结构如下所示,可以修改AutoCAD.NET的版本

  • 如何实现“主项目+子项目”的工程方案?

以上通过模板创建的项目引入了IFox.Basal.Source和IFox.CAD.Source两个源码包,这意味着该源码会被编译到生成文件中。

如果整个工程中只有一个这样的项目,那么完全没问题;但是,如果采用“主项目+子项目”的开发方案,势必相同代码会同时存在主库和子库中,从而导致编译出错。如何解决这个问题呢?

很简单,我们采用模板新建一个Base项目,它的作用只是引入IFox.Basal.Source和IFox.CAD.Source源码包。对于主项目和子项目,同样采用模板创建,但创建完成之后,需要删除两个IFox源码包。这种方法就避免了相同源码包含在多个生成文件中的问题。

核心用法

图元创建和添加

IFoxCad提供了一系列扩展方法,用于创建常见图元:

1
2
3
4
5
6
7
8
9
10
11
// 两点画圆
var circle1 = CircleEx.CreateCircle(new Point3d(0, 0, 0), new Point3d(1, 0, 0));
// 三点画圆
var circle2 = CircleEx.CreateCircle(new Point3d(-2, 0, 0), new Point3d(2, 0, 0), new Point3d(0, 2, 0));

// 起点,圆心,终点
Arc arc1 = ArcEx.CreateArcSCE(new Point3d(2, 0, 0), new Point3d(0, 0, 0), new Point3d(0, 2, 0));
// 起点,圆心,弧度
Arc arc2 = ArcEx.CreateArc(new Point3d(4, 0, 0), new Point3d(0, 0, 0), Math.PI / 2);
// 起点,圆上一点,终点
Arc arc3 = ArcEx.CreateArc(new Point3d(1, 0, 0), new Point3d(0, 0, 0), new Point3d(0, 1, 0));

可以由Point3d的List直接创建多段线:

1
2
3
4
5
6
7
8
var pts = new List<Point3d>
{
new(0, 0, 0),
new(0, 1, 0),
new(1, 1, 0),
new(1, 0, 0)
};
var pline = pts.CreatePolyline();

在IFoxCad中,事务处理和图元添加操作大大简化。

1
2
3
4
5
6
7
[CommandMethod(nameof(HelloWorld))]
public void HelloWorld() {
using DBTrans tr = new();
Line line1 = new(new Point3d(0, 0, 0), new Point3d(100, 50, 0));
Line line2 = new(new Point3d(-100, 0, 0), new Point3d(100, 50, 0));
tr.CurrentSpace.AddEntity(line1,line2);
}

“九表多词典”的增删改查

对“九表多词典”进行增删改查是日常开发面临最多的操作,IFoxCad库提供了非常方便的方法。DBTrans中有大量属性直接指向这些核心表和词典。可以直接从DBTrans拿到各种表和词典对象。

  • 表查:可以统一使用Has方法
1
2
3
4
using DBTrans tr = new();
if (tr.BlockTable.Has("自定义块")) {
//要执行的操作
}
  • 表增:可以使用相同形式的Add方法,第一个参数通常是名称,第二个参数是Action,在该Action中可以指定新创建记录的各种属性
1
2
3
4
5
6
using var tr = new DBTrans();
tr.LayerTable.Add("MyLayer",it =>{
it.Color = Color.FromColorIndex(ColorMethod.ByColor, 1);
it.LineWeight = LineWeight.LineWeight030;
it.IsPlottable = true;
});
  • 表改:可以使用相同形式的Change方法,第一个参数通常是名称,第二个参数是Action,在该Action中可以指定新属性值
1
2
3
4
5
6
7
8
using var tr = new DBTrans();
if (tr.LayerTable.Has("MyLayer2")){
tr.LayerTable.Change("MyLayer2", lt => {
lt.Name = "MyLayer3";
lt.Color = Color.FromColorIndex(ColorMethod.ByAci, 2);
lt.IsPlottable = false;
});
}
  • 表删:对不同表记录的删除方法有所差异
1
2
3
4
5
6
7
8
9
10
11
12
tr.BlockTable.Remove("自定义块");

tr.LayerTable.Delete("3");// 删除图层 3

var textId = tr.TextStyleTable["宋体"];
tr.TextStyleTable.Remove(textId);

tr.LinetypeTable["CENTER"].Erase();

tr.DimStyleTable["DimStyleName"].Erase()

tr.ViewTable.Remove("View2");
  • 词典

通过DBTrans可以拿到各种常用的词典,其类型为DBDictionary,实现了IDictionary接口,是一个字典数据结构,对其进行增删改查很方便。

1
2
3
4
5
DBDictionary dict = tr.MLStyleDict;
foreach (var item in dict) {
string key = item.Key;
ObjectId value = item.Value;
}

以下给一个综合实例,涉及到对图层表、块表、文字样式表的操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
[CommandMethod(nameof(HelloWorld))]
public void HelloWorld() {
using DBTrans tr = new();
// 图层表
short colorIndex = 1;
if (tr.LayerTable.Has("layer1")) {
tr.LayerTable.Delete("layer1");
}
tr.LayerTable.Add("layer1", l => {
l.Color = Color.FromColorIndex(ColorMethod.ByColor, colorIndex);
l.Description = "这是新增图层";
});
// 添加图元
var line1 = new Line(new Point3d(0, 0, 0), new Point3d(100, 600, 0));
line1.Layer = tr.LayerTable["layer1"].IsValid ? "layer1" : "0";
tr.CurrentSpace.AddEntity(line1);
// 块表
if (!tr.BlockTable.Has("自定义块1")) {
tr.BlockTable.Add("自定义块1",
b => {
b.Origin = new Point3d(0, 0, 0);
},
() => {
return new List<Entity> {
new Line(new Point3d(0,0,0),new Point3d(600,300,0)),
new Line(new Point3d(600,300,0),new Point3d(600,900,0)),
CircleEx.CreateCircle(new Point3d(600,300,0),new Point3d(600,900,0)),
ArcEx.CreateArc(new Point3d(600,900,0),new Point3d(800,1100,0),Math.PI*0.45)
};
},
() => {
var id1 = new AttributeDefinition() { Position = new Point3d(0, 0, 0), Tag = "点1", Height = 30 };
var id2 = new AttributeDefinition() { Position = new Point3d(600, 300, 0), Tag = "点2", Height = 30 };
return new List<AttributeDefinition> { id1, id2 };
});
}
// 插入块
tr.CurrentSpace.InsertBlock(new Point3d(-1000, 1000, 0),
tr.BlockTable["自定义块1"],
new(1, 2, 1),
atts: new() { { "点1", "李浩1" }, { "点2", "李浩2" } });
// 文字样式
if (tr.TextStyleTable.Has("字体样式1"))
tr.TextStyleTable.Remove("字体样式1");
tr.TextStyleTable.Add("字体样式1", t => {
t.FileName = "gbenor.shx";
t.BigFontFileName = "gbcbig.shx";
});
}

扩展数据

  • 添加扩展数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var line1 = new Line(new Point3d(0, 0, 0), new Point3d(100, 600, 0));
// 给line1添加扩展数据
if (!tr.RegAppTable.Has("李浩数据")) {
tr.RegAppTable.Add("李浩数据");
}
if (!tr.RegAppTable.Has("李浩数据2")) {
tr.RegAppTable.Add("李浩数据2");
}
line1.XData = new XDataList() {
{DxfCode.ExtendedDataRegAppName,"李浩数据" },
{ DxfCode.ExtendedDataAsciiString,"Hello World"},
{1070, 12 },
{DxfCode.ExtendedDataRegAppName,"李浩数据2" },
{ DxfCode.ExtendedDataAsciiString,"Have Fun"},
{1070, 12 },
};
tr.CurrentSpace.AddEntity(line1);
  • 删除扩展数据,针对某一注册程序下的扩展数据,可以删除所有或者指定的扩展数据
1
2
3
4
5
6
7
8
9
10
11
using DBTrans tr = new();
var res = Env.Editor.GetEntity("\n select the entity:");
if (res.Status == Autodesk.AutoCAD.EditorInput.PromptStatus.OK) {
var ent = tr.GetObject<Entity>(res.ObjectId);
if (ent == null || ent.XData == null)
return;
// 删除某一注册程序下所有的扩展数据
ent.RemoveXData("李浩数据");
// 删除某一注册程序下指定的扩展数据
ent.RemoveXData("李浩数据2", DxfCode.ExtendedDataAsciiString);
}
  • 获取扩展数据,也需要遍历判断,这一点封装不是很直接
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using DBTrans tr = new();
var res = Env.Editor.GetEntity("\n select the entity:");
if (res.Status == Autodesk.AutoCAD.EditorInput.PromptStatus.OK) {
var ent = tr.GetObject<Entity>(res.ObjectId);
if (ent == null || ent.XData == null)
return;
var xdata = ent.XData;
string text = null;
foreach (var item in xdata) {
if (item.TypeCode == (int)DxfCode.ExtendedDataAsciiString) {
text = (string)item.Value;
}
}
Env.Print(text);
}
  • 修改扩展数据,非常方便
1
ent.ChangeXData("李浩数据2", DxfCode.ExtendedDataAsciiString, "nonofun");

过滤器

使用原生API编写复杂图元过滤器,经常是一件痛苦的事,IFoxCad进一步封装,使得代码更直观地呈现布尔含义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var fd =
new OpOr
{
!new OpAnd
{
{ 0, "line" },
{ 8, "0" },
},
new OpAnd
{
!new OpEqual(0, "circle"),
{ 8, "2" },
{ 10, new Point3d(10,10,0), ">,>,*" }
},
};
editor.SelectAll(fd);

与以下Lisp过滤器含义相同:

关键语法解析

扩展方法

C#提供扩展方法机制,可以为既有类创建一些方法,使用起来就像是类自己的成员方法一样,调用起来更方便。

这种语法机制在框架设计中大量用到,特别基于既有库进行二次封装。

Action和Func的妙用

在IFoxCad库中大量使用到如下代码形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
tr.BlockTable.Add("自定义块1",
b => {
b.Origin = new Point3d(0, 0, 0);
},
() => {
return new List<Entity> {
new Line(new Point3d(0,0,0),new Point3d(600,300,0)),
new Line(new Point3d(600,300,0),new Point3d(600,900,0)),
CircleEx.CreateCircle(new Point3d(600,300,0),new Point3d(600,900,0)),
ArcEx.CreateArc(new Point3d(600,900,0),new Point3d(800,1100,0),Math.PI*0.45)
};
},
() => {
var id1 = new AttributeDefinition() { Position = new Point3d(0, 0, 0), Tag = "点1", Height = 30 };
var id2 = new AttributeDefinition() { Position = new Point3d(600, 300, 0), Tag = "点2", Height = 30 };
return new List<AttributeDefinition> { id1, id2 };
});

该函数原型如下,最后三个参数都是Action或者Func类型:

1
2
3
4
5
public static ObjectId Add(this SymbolTable<BlockTable, BlockTableRecord> table,
string name,
Action<BlockTableRecord>? action = null,
Func<IEnumerable<Entity>>? ents = null,
Func<IEnumerable<AttributeDefinition>>? attdef = null)

为什么使用Action或者Func作为参数可以给相应的符号表添加符号表记录呢?原因其实很简单,看下列示例代码就一目了然:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
internal class Program {
static List<MyData> Datas = new List<MyData>();

static void AddData(Action<MyData> action, Func<List<MyData>> func) {
MyData data = new MyData();
action(data);
Datas.Add(data);

var ds = func();
Datas.AddRange(ds);
}

static void Main(string[] args) {
AddData(d => { d.Id = 1; d.Name = "李浩"; },
() => {
return new List<MyData>() {
new() { Id = 2, Name = "小红" },
new() { Id = 3, Name = "小明" }};
});
Console.ReadKey();
}
}

internal class MyData {
public int Id { get; set; }
public string Name { get; set; }
}

在函数内部,通过调用Action和Func,可以得到新的实例,将这些实例添加到目标数组中即可。

由于Lambda表达式可以为Action和Func形参传参,所以就可以极大简化代码,很巧妙!

评论