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

2

Я новичок в программировании на С# (и программировании в целом), но я начинаю понимать, что такое разработка 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?

Теги:
lambda
autocad
autocad-plugin

3 ответа

0

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

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();
        }
    }
  • 0
    Нравится твой образ мышления!
0

Для части AutoCAD:

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

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

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

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

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

Если вы хотите вернуть значение с помощью делегата, 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. Я много учусь там.

  • 0
    Спасибо тебе за это. Это было действительно полезно, и это работает!
0

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

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

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

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

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

  • 0
    Вы всегда должны возвращать ObjectId объекта, а не сам объект.
  • 0
    Спасибо, я попробую это через некоторое время с предложением MiiIr вернуть объект и сообщить всем, работает ли он.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню