如何通过Lambda表达式创建AutoCAD对象并返回它

时间:2018-12-12 15:04:24

标签: c# lambda autocad autocad-plugin

我是C#编程的新手(以及一般的编程),但是我正在为使用中的项目使用AutoDesk .NET API进行AutoCAD开发。

AutoCAD开发人员中有一些重复性任务,我一直在创建帮助程序方法以简化代码。为了通过.API在AutoCAD中创建对象(线,折线,注释等),程序员必须编写相当复杂的语句来访问AutoCAD环境,获取当前图形,获取对象的数据库。当前的图形文件,开始与数据库//do work进行事务,然后将创建的实体附加到数据库,最后提交并结束事务。

因此,我编写了以下代码来简化此任务:

public static void CreateObjectActionWithinTransaction(Action<Transaction, Database, BlockTable, BlockTableRecord> action)
{
    var document = Application.DocumentManager.MdiActiveDocument;
    var database = document.Database;
    using (var transaction = document.TransactionManager.StartTransaction())
    {
        BlockTable blocktable = transaction.GetObject(database.BlockTableId, OpenMode.ForRead) as BlockTable;
        BlockTableRecord blockTableRecord = transaction.GetObject(blocktable[BlockTableRecord.ModelSpace], OpenMode.ForWrite) as BlockTableRecord;
        action(transaction, database, blocktable, blockTableRecord);

        transaction.Commit();
    }
}

然后我的Lambda表达式创建一个通用的MText并为其设置一些参数:

public static void createMtext(Point3d location, AttachmentPoint attachmentpoint, string contents, double height, short color, bool usebackgroundmask, bool usebackgroundcolor, double backgroundscale)
{
    CreateObjectActionWithinTransaction((transaction, database, blocktable, blocktablerecord) =>
    {
        MText mt = new MText();
        mt.SetDatabaseDefaults();
        mt.Location = location;
        mt.Attachment = attachmentpoint;
        mt.Contents = contents;
        mt.Height = height;
        mt.Color = Color.FromColorIndex(ColorMethod.ByAci, color);
        mt.BackgroundFill = usebackgroundmask;
        mt.UseBackgroundColor = usebackgroundcolor;
        mt.BackgroundScaleFactor = backgroundscale;
        blocktablerecord.AppendEntity(mt);
        transaction.AddNewlyCreatedDBObject(mt, true);
    });
}

最后,当我实际上在某个地方创建MText时,我可以在一行中创建它,并为所有参数传递值,而不必为此编写巨大的事务代码:

Helpers.createMtext(insertpoint, AttachmentPoint.MiddleLeft, "hello world", .08, colors.AutoCAD_Red, true, true, 1.2);

所以这很棒,当我想自己创建一个MText并将其放在某个地方时,它可以工作。但是,在某些其他情况下,我不仅要创建MText并将其放置在图形中,还希望使用与上述相同的基本前提来创建MText,但是将其作为值返回给在其他地方使用。

AutoCAD具有一个称为Multileaders的注释对象,与上面一样,它们实际上只是一个MText,但附加到一些线和一个箭头以指向图形中的某物。在API中,您需要定义一个MText并将其附加到Multileader对象。但是我上面的代码无法使用,因为它不返回任何内容。

所以我的问题归结为,如何创建像上面那样的方法来创建对象,但不仅仅是创建该对象,而是让它返回该对象以供另一段代码使用?

对于Lambda表达式的初学者来说还有什么好的资源?书籍,网站,YouTube?

3 个答案:

答案 0 :(得分:2)

对于AutoCAD部件:

如Miiir在评论中所述,不要返回对象,而是返回ObjectId。一个对象实例属于一个事务,因此,如果使用某个事务打开该对象,提交该事务并尝试在另一个事务中使用该对象,则AutoCAD基本上只会崩溃。

始终使用AutoCAD API 遵循以下基本模式:

  1. 开始交易
  2. 创建新对象或使用事务获取现有对象。这可以通过拥有ObjectID或循环遍历表并查找您感兴趣的任何属性(即BlockTableBlockTableRecordLayerTable等)来完成。 / li>
  3. 对对象进行填充。
  4. 提交或中止交易。

如果您尝试绕过步骤1和步骤2,效果将不太理想。因此,返回ObjectID,然后使用ID在另一个事务中获取对象。

关于C#部分:

如果要使用委托返回值,Action<T>不是您的朋友。 Action不返回值,仅“操作”,因此返回名称。如果要使用委托返回值,则有两个选项:

  1. 定义自定义委托类型。

  2. 使用.NET框架Func<T1,T2,T3,T4,TResult>提供的通用委托。

您应该使用哪个?在您的情况下,我可能会选择选项1,原因很简单,因为您的代码将更加整洁且易于维护。我将在此示例中使用它。使用Func的工作方式完全相同,只是您的函数签名看起来有些难看。

自定义代表:

//somewhere in your code inside a namespace (rather than a class)
public delegate ObjectId MyCreateDelegate(Transaction transaction, Database db,
         BlockTable blockTable, BlockTableRecord blockTableRecord);

然后是常规方法

public static ObjectId CreateObjectActionWithinTransaction(MyCreateDelegate createDel)
{
    ObjectId ret;
    var document = Application.DocumentManager.MdiActiveDocument;
    var database = document.Database;
    using (var transaction = document.TransactionManager.StartTransaction())
    {
        BlockTable blocktable = transaction.GetObject(database.BlockTableId, OpenMode.ForRead) as BlockTable;
        BlockTableRecord blockTableRecord = transaction.GetObject(blocktable[BlockTableRecord.ModelSpace], OpenMode.ForWrite) as BlockTableRecord;
        //here createMtext will get called in this case, and return ObjectID
        ret = createDel(transaction, database, blocktable, blockTableRecord);
        transaction.Commit();
    }
    return ret;
}

以及lambda的具体方法:

public ObjectId createMtext(Point3d location, AttachmentPoint attachmentpoint, string contents, double height, short color, bool usebackgroundmask, bool usebackgroundcolor, double backgroundscale)
{
    //here you can return the result the general function
    return CreateObjectActionWithinTransaction((transaction, database, blocktable, blocktablerecord) =>
    {
        MText mt = new MText();
        mt.SetDatabaseDefaults();
        mt.Location = location;
        mt.Attachment = attachmentpoint;
        mt.Contents = contents;
        mt.Height = height;
        mt.Color = Color.FromColorIndex(ColorMethod.ByAci, color);
        mt.BackgroundFill = usebackgroundmask;
        mt.UseBackgroundColor = usebackgroundcolor;
        mt.BackgroundScaleFactor = backgroundscale;
        blocktablerecord.AppendEntity(mt);
        transaction.AddNewlyCreatedDBObject(mt, true);
        //make sure to get ObjectId only AFTER adding to db.
        return mt.ObjectId;
    });
}

最后,像这样使用它

ObjectId mtId = Helpers.createMtext(insertpoint, AttachmentPoint.MiddleLeft, "hello world", .08, colors.AutoCAD_Red, true, true, 1.2);
//now use another transaction to open the object and do stuff to it.

学习资源:

最后,要了解lambda表达式,您需要先了解委托(如果尚未了解)。所有的lambda都是语法糖,用于实例化您在示例中所做的指向方法或匿名方法的委托对象。 This tutorial看起来不错。请记住,诸如ActionFuncPredicate之类的代表,还是没有区别。因此,无论您定义自己的委托还是使用开箱即用的解决方案,lambda表达式都不在乎。

有关lambda概述,请check out this tutorial

不要将自己局限于我提供的两个来源。只是Google吧,排名前10的热门歌曲都是相当不错的信息。您也可以签出Pluralsight。我在那里学习很多。

答案 1 :(得分:2)

我宁愿使用从事务中的调用方法中调用的扩展方法,也不使用委托。

static class ExtensionMethods
{
    public static BlockTableRecord GetModelSpace(this Database db, OpenMode mode = OpenMode.ForRead)
    {
        var tr = db.TransactionManager.TopTransaction;
        if (tr == null)
            throw new Autodesk.AutoCAD.Runtime.Exception(ErrorStatus.NoActiveTransactions);
        return (BlockTableRecord)tr.GetObject(SymbolUtilityServices.GetBlockModelSpaceId(db), mode);
    }

    public static void Add(this BlockTableRecord btr, Entity entity)
    {
        var tr = btr.Database.TransactionManager.TopTransaction;
        if (tr == null)
            throw new Autodesk.AutoCAD.Runtime.Exception(ErrorStatus.NoActiveTransactions);
        btr.AppendEntity(entity);
        tr.AddNewlyCreatedDBObject(entity, true);
    }

    public static MText AddMtext(this BlockTableRecord btr, 
        Point3d location, 
        AttachmentPoint attachmentpoint, 
        string contents, 
        double height, 
        short color = 256, 
        bool usebackgroundmask = false, 
        bool usebackgroundcolor = false, 
        double backgroundscale = 1.5)
    {
        MText mt = new MText();
        mt.SetDatabaseDefaults();
        mt.Location = location;
        mt.Attachment = attachmentpoint;
        mt.Contents = contents;
        mt.Height = height;
        mt.ColorIndex = color;
        mt.BackgroundFill = usebackgroundmask;
        mt.UseBackgroundColor = usebackgroundcolor;
        mt.BackgroundScaleFactor = backgroundscale;
        btr.Add(mt);
        return mt;
    }
}

使用示例:

        public static void Test()
    {
        var doc = AcAp.DocumentManager.MdiActiveDocument;
        var db = doc.Database;
        using (var tr = db.TransactionManager.StartTransaction())
        {
            var ms = db.GetModelSpace(OpenMode.ForWrite);
            var mt = ms.AddMtext(Point3d.Origin, AttachmentPoint.TopLeft, "foobar", 2.5);
            // do what you want with 'mt'
            tr.Commit();
        }
    }

答案 2 :(得分:0)

我不熟悉AutoCad API,但看来“ transaction.Commit()”是实际执行将MText放置在模型上的动作的行。

如果是这种情况;我会做类似以下的事情:

public MText CreateMTextObject({parameters})
{
//code
  Return textObject
}

public PlaceTextObject({parameters})
{
  CreateTextObject(parameters).Commit()
}

这样,您可以选择保留文本对象以进行进一步操作,同时仍然允许一次性应用它。它还只有一个用于制作对象的代码块,以确保两种方法之间的实现没有差异