Как создать объект AutoCAD с помощью лямбда-выражения и вернуть его

Я новичок в программировании на C# (и программировании в целом), но я начинаю понимать, что такое разработка AutoCAD с использованием AutoDesk .NET API для проектов на работе.

В AutoCAD dev есть определенные повторяющиеся задачи, для которых я создавал вспомогательные методы, чтобы упростить мой код. Чтобы создать объект (линии, полилинии, аннотации и т. Д.) В AutoCAD через.API, программист должен написать довольно запутанный оператор, который обращается к среде 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();
    }
}

Тогда мое лямбда-выражение, которое создает общий 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 объект. Однако мой код выше не может быть использован, потому что он ничего не возвращает.

Итак, мой вопрос сводится к тому, как я могу создать метод, подобный выше, для создания объекта, но вместо того, чтобы просто создать этот объект, он должен вернуть этот объект для использования другим фрагментом кода?

Также есть ли хорошие ресурсы для начинающих по лямбда-выражениям? Книги, сайты, YouTube?

3 ответа

Для части AutoCAD:

Как заявил Мииир в комментарии, не возвращайте объект, а скорее ObjectId, Экземпляр объекта принадлежит транзакции, поэтому если вы откроете объект с помощью какой-либо транзакции, зафиксируете транзакцию и попробуете использовать этот объект в другой транзакции, AutoCAD в основном просто рухнет.

Работа с AutoCAD API всегда идет по следующему принципу:

  1. Начать транзакцию
  2. Создайте новый объект или используйте транзакцию, чтобы получить существующий объект. Это делается либо имея ObjectID или перебирая таблицы и ища любые интересующие вас атрибуты (т.е. BlockTable, BlockTableRecord, LayerTable, так далее.)
  3. Делать вещи с объектом.
  4. Подтвердить или отменить транзакцию.

Если вы попытаетесь обойти шаги 1 и 2, это не сработает. Итак, вернемся ObjectID, а затем используйте идентификатор, чтобы получить объект в другой транзакции.

Что касается части C#:

Если вы хотите вернуть значение с помощью делегата, Action<T> не твой друг Action не возвращает значение, оно только "действует", то есть имя. Если вы хотите использовать делегата для возврата значения, у вас есть 2 варианта:

  1. Определите пользовательский тип делегата.

  2. Используйте универсальный делегат, предоставляемый.NET Framework 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;
}

и конкретный метод с лямбда:

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.

Учебные ресурсы:

И наконец, чтобы понять лямбда-выражения, вам нужно начать с понимания делегатов, если вы этого еще не сделали. Все лямбды - это синтаксический сахар для создания экземпляра объекта делегата, который указывает либо на метод, либо на анонимный метод, как вы сделали в своем примере. Этот урок выглядит довольно хорошо. И помните, такие делегаты, как Action, Func а также Predicateили не отличается. Поэтому, определяете ли вы свой собственный делегат или используете готовое решение, лямбда-выражения не имеют значения.

Для лямбда-обзора, проверьте этот учебник.

Не ограничивайте себя двумя источниками, которые я предоставил. Просто Google это, и лучшие 10 хитов будут довольно хорошей информацией. Вы также можете проверить Pluralsight. Я много учусь там.

Вместо использования делегатов я бы предпочел использовать методы расширения, вызываемые из транзакции в вызывающем методе.

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();
        }
    }

Я не знаком с AutoCad API, но кажется, что "action.Commit()"- это строка, которая фактически выполняет действие по размещению MText в вашей модели.

если это так; я бы сделал что-то вроде следующего:

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

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

Таким образом, вы можете оставить текстовый объект для дальнейших манипуляций, при этом оставляя возможность применить его за один раз. У него также будет только один кодовый блок для создания объекта, что гарантирует отсутствие различий в реализации между этими двумя методами.

Другие вопросы по тегам