【问题标题】:How to create an AutoCAD object through a Lambda expression and return it如何通过 Lambda 表达式创建 AutoCAD 对象并返回
【发布时间】:2018-12-17 03:52:59
【问题描述】:

我是 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?

【问题讨论】:

    标签: c# lambda autocad autocad-plugin


    【解决方案1】:

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

    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】:

    对于 AutoCAD 部分:

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

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

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

    如果您尝试绕过第 1 步和第 2 步,则效果不会很好。所以,返回ObjectID,然后在另一个事务中使用该id获取对象。

    至于C#部分:

    如果您希望使用委托返回值,Action&lt;T&gt; 不是您的朋友。 Action 不返回值,它只是“行动”,因此得名。如果您想使用委托返回一个值,您有 2 个选项:

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

    2. 使用 .NET 框架 Func&lt;T1,T2,T3,T4,TResult&gt; 提供的通用委托。

    你应该使用哪一个?在您的情况下,我可能会选择选项 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

    不要局限于我提供的两个来源。只需谷歌它,前 10 名都将是相当不错的信息。您也可以查看Pluralsight。我在那里学习了很多东西。

    【讨论】:

    • 谢谢你。这真的很有帮助,而且有效!
    【解决方案3】:

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

    如果是这样的话;我会做如下的事情:

    public MText CreateMTextObject({parameters})
    {
    //code
      Return textObject
    }
    
    public PlaceTextObject({parameters})
    {
      CreateTextObject(parameters).Commit()
    }
    

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

    【讨论】:

    • 您应该始终返回实体的 ObjectId,而不是实体本身。
    • 谢谢,我会在稍后尝试这个,MiiIr 建议返回 objectid 并让大家知道它是否有效。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多