Главная »Статьи »AutoCAD и Delphi »Интересные стрелки (MultiLeader)
Интересные стрелки (MultiLeader)

Открытая истина делается проста.
Н. Чернышевский.

Появление MLeaders (Multileaders, мультивыносок) в AutoCAD расширило его возможности, но и, к сожалению, добавило хлопот их, подчас, непредсказуемым поведением. С программной точки зрения также не все просто. Ситуацию усугубляет невнятная документация и терминология. Поэтому с нее и начнем.

Графические элементы MLeader

MLeader состоит из кластеров и содержимого. MLeader может содержать только 1 или 2 кластера (leader) — левый и правый. Соответственно их индексы 0 и 1. Каждый кластер может содержать неограниченное количество выносок (leaderLine). Каждая выноска может иметь полку (DogLeg) и неограниченное количество узлов. Содержимое (Content) может отсутствовать, быть MText-ом или блоком (в т.ч. пользовательским).

Стили MLeader AutoCAD хранит в словаре ACAD_MLEADERSTYLE. Пример получения списка стилей в lbMLeaderStyles: TListBox:

. . .
var
  MLeaderDictionary, MLeaderStyle: OleVariant;
  i, N, C: Integer;
  CurrStyle, S: String;
begin
  // Список стилей
  try
    MLeaderDictionary:= Acad.ActiveDocument.Dictionaries.Item('ACAD_MLEADERSTYLE');
  except
    raise EOleError.Create('Стили MultiLeader не обнаружены!');
  end;
  // Текущий стиль
  CurrStyle:= Acad.ActiveDocument.GetVariable('CMLEADERSTYLE');
  // Получить и показать список стилей MultiLeader
  lbMLeaderStyles.Clear;
  lbMLeaderStyles.Items.BeginUpdate;
  try
    N:= MLeaderDictionary.Count; // Количество элементов в словаре
    for i:= 0 to N - 1 do        // Перебор всех элементов
    begin
      // Если имя объекта = 'AcDbMLeaderStyle' - этот элемент есть стиль MLeader
      if MLeaderDictionary.Item(i).ObjectName = 'AcDbMLeaderStyle' then
      begin
        MLeaderStyle:= MLeaderDictionary.Item(i);
        S:= MLeaderStyle.Name;
        if AnsiCompareText(S, CurrStyle) = 0 then // Это текущий стиль?
          C:= 1 // Да - установить метку "Текущий стиль"
        else
          C:= 0;
        // Сохранить вместе с меткой "Текущий стиль"
        lbMLeaderStyles.Items.AddObject(S, TObject(C));
      end;          
    end;
  finally
    lbMLeaderStyles.Items.EndUpdate;
  end;
end;

Как видно из этого примера, AutoCAD хранит имя текущего стиля в системной переменной CMLEADERSTYLE. Соответственно изменение текущего стиля производится записью в эту переменную нужного имени:

Acad.ActiveDocument.SetVariable('CMLEADERSTYLE', 'Test');

Стиль с указанным именем должен существовать, иначе попытка установки приведет к ошибке "Error setting system variable".

Для создания нового стиля в словарь ACAD_MLEADERSTYLE добавляется новый объект AcDbMLeaderStyle:

. . .
var
  MLeaderDictionary, MLeaderStyle: OleVariant;
begin
. . .
  // Создать новый стиль
  MLeaderStyle:= MLeaderDictionary.AddObject('Test', 'AcDbMLeaderStyle');
  // Указать имя текстового стиля
  MLeaderStyle.TextStyle:= Acad.ActiveDocument.ActiveTextStyle.Name;
  // Установить другие свойства при необходимости
  MLeaderStyle.LeaderLineType:= acSplineLeader;
end;

Примечания:

  1. Если стиль MLeader с таким именем уже существует (имена сравниваются без учета регистра) — он будет переопределен новыми значениями.
  2. Если при добавлении нового стиля MLeader не задать имя текстового стиля — в диалоге "Modify Multileader Style" (Редактирование стиля мультивыносок) в списке "Text Style" будет пусто. Хотя к ошибкам при работе в AutoCAD это не приведет, но при попытке чтения этого свойства получаем "EOleException with message "Null object ID", до тех пор пока AutoCAD-ом вручную не установим TextStyle не пустым.

С параметрами стиля MLeader можно ознакомиться по прилагаемому примеру, в котором считываются и демонстрируются все свойства указанного стиля.

Получение информации о геометрии MLeader

Полезные свойства и методы MLeader:

// Возвращает количество кластеров
property LeaderCount: SYSINT;
// Возвращает индексы массива выносок кластера leaderIndex
function GetLeaderLineIndexes(leaderIndex: SYSINT): OleVariant;
// Возвращает массив 3D-координат выноски leaderLineIndex
function GetLeaderLineVertices(leaderLineIndex: SYSINT): OleVariant;

Все выноски всех кластеров хранятся в одном массиве. В следующем примере в командную строку AutoCAD выводится количество кластеров и координаты всех узлов выносок указанной MLeader:

. . .
const
  fmtClusterCount = #13#10'Количество кластеров = %d.'#13#10;
  fmtClusterInfo = #13#10'Кластер = %d. Количество выносок = %d. Координаты узлов X, Y, Z:'#13#10;
var
  BasePnt, Entity, LineIndexes, Coords: OleVariant;
  Obj: IDispatch;
  i, j, k, LeaderLineCounter, LeaderCount, iLBnd, iHBnd, cLBnd, cHBnd: Longint;
  S: String;
begin
  try // Указать объект
    Acad.ActiveDocument.Utility.GetEntity(Obj, BasePnt, #13'Выделите MLeader: ');
    Entity:= Obj;
  except
    Exit;
  end;
  // Если это не MLeader - на выход
  if Entity.EntityType <> acMLeader then Exit;

  LeaderCount:= Entity.LeaderCount; // Количество кластеров
  Acad.ActiveDocument.Utility.Prompt(Format(fmtClusterCount, [LeaderCount]));
  // Перебираем кластеры
  LeaderLineCounter:= 0;
  for i:= 0 to LeaderCount - 1 do
  begin
    // Индексы выносок i-го кластера. Количество выносок = (iHBnd - iLBnd) + 1
    LineIndexes:= Entity.GetLeaderLineIndexes(i);
    iLBnd:= VarArrayLowBound(LineIndexes, 1);
    iHBnd:= VarArrayHighBound(LineIndexes, 1);
    // Выводим заголовок информации по кластеру
    Acad.ActiveDocument.Utility.Prompt(Format(fmtClusterInfo, [i, (iHBnd - iLBnd) + 1]));
    // Перебираем выноски i-го кластера
    for j:= iLBnd to iHBnd do
    begin
      // Получаем массив 3D-координат текущей выноски
      Coords:= Entity.GetLeaderLineVertices(LeaderLineCounter);
      cLBnd:= VarArrayLowBound(Coords, 1);
      cHBnd:= VarArrayHighBound(Coords, 1);
      // Перебираем массив координат выноски и выводим их
      S:= '';
      for k:= cLBnd to cHBnd do
        S:= S + Format('%8.3f', [Double(Coords[k])]);
      Acad.ActiveDocument.Utility.Prompt(S + #13#10);
      // Увеличиваем счетчик отработанных выносок
      Inc(LeaderLineCounter);
    end;
  end;
end;

Пример для MLeader с одним кластером:

Количество кластеров = 1.
Кластер = 0. Количество выносок = 2. Координаты узлов X, Y, Z:
   0,000  50,000   0,000  30,000  70,000   0,000
   5,000  50,000   0,000  30,000  70,000   0,000

Пример для MLeader с двумя кластерами:

Количество кластеров = 2.
Кластер = 0. Количество выносок = 3. Координаты узлов X, Y, Z:
   0,000  50,000   0,000  30,000  70,000   0,000
   5,000  50,000   0,000  30,000  70,000   0,000
  10,000  50,000   0,000  30,000  70,000   0,000
Кластер = 1. Количество выносок = 1. Координаты узлов X, Y, Z:
  83,000  50,000   0,000  76,667  70,000   0,000

Построение MLeader

MLeader строится текущим стилем методом AddMLeader коллекций AcadModelSpace, AcadPaperSpace, AcadBlock.

// В функцию передается массив 3-D координат узлов выносок.
// Возвращает объект AcadMLeader и индекс кластера
function AddMLeader(PointsArray: OleVariant; out leaderLineIndex: SYSINT): OleVariant;

Массив PointsArray должен содержать координаты не менее 2-х точек, т.е. иметь число элементов не менее 6-ти.

Простая MLeader с текстом:

. . .
var
  P1, P2, V, Leader: OleVariant;
  N: Integer;
begin
  // Запрос точек
  P1:= Acad.ActiveDocument.Utility.GetPoint(, 'Начальная точка: ');
  P2:= Acad.ActiveDocument.Utility.GetPoint(P1, 'Конечная точка: ');
  // Массив координат (2 точки MLeader, следовательно 6 элементов в массиве)
  V:= VarArrayCreate([0, 5], varDouble);
  V[0]:= P1[0];
  V[1]:= P1[1];
  V[2]:= P1[2];
  V[3]:= P2[0];
  V[4]:= P2[1];
  V[5]:= P2[2];
  // Добавляем MLeader. Если мы хотим добавить MLeader с текстом, то текущим
  // стилем должен быть стиль у которого контент настроен на MText
  Leader:= Acad.ActiveDocument.ModelSpace.AddMLeader(V, N);
  // Настраиваем свойства
  Leader.TextString:= '555-555';          // собственно текст (может быть многострочным)
  Leader.ArrowheadType:= acArrowDefault;  // обычная залитая стрелка
  Leader.ArrowheadSize:= 4;               // длина стрелки
  Leader.DogLegged:= False;               // без горизонтальной полки
  Leader.DoglegLength:= 0;                // длина полки = 0
  Leader.LandingGap:= 2;                  // отступ текста от полки по-горизонтали
  // Подчеркиваем 1-ю строку текста - получается как бы текст над полкой
  Leader.TextLeftAttachmentType:= acAttachmentBottomOfTopLine;
  Leader.TextRightAttachmentType:= acAttachmentBottomOfTopLine;

  Leader.TextFrameDisplay:= False;        // текст без рамки
  Leader.TextBackgroundFill:= False;      // текст без заливки фона
  Leader.TextDirection:= acLeftToRight;   // текст слева-направо
  Leader.TextHeight:= 3.5;                // высота текста
  Leader.TextWidth:= 0;                   // ширина текста (используется для переноса текста по словам)
  Leader.TextJustify:= acAttachmentPointBottomCenter; // выравнивание текста
end;

Простая MLeader с блоком:

. . .
var
  P1, P2, V, Leader: OleVariant;
  N: Integer;
begin
  // Запрос точек
  P1:= Acad.ActiveDocument.Utility.GetPoint(, 'Начальная точка: ');
  P2:= Acad.ActiveDocument.Utility.GetPoint(P1, 'Конечная точка: ');
  // Массив координат (2 точки MLeader, следовательно 6 элементов в массиве)
  V:= VarArrayCreate([0, 5], varDouble);
  V[0]:= P1[0];
  V[1]:= P1[1];
  V[2]:= P1[2];
  V[3]:= P2[0];
  V[4]:= P2[1];
  V[5]:= P2[2];
  // Добавляем MLeader. Если мы хотим добавить MLeader с блоком, то текущим
  // стилем должен быть стиль у которого контент настроен на блок
  Leader:= Acad.ActiveDocument.ModelSpace.AddMLeader(V, N);
  // Настраиваем свойства
  Leader.ContentBlockType:= acBlockBox;   // тип блока (рамка)
  Leader.LeaderType:= acStraightLeader;   // линия стрелки - прямая (не сплайн)
  Leader.ArrowheadType:= acArrowDefault;  // обычная залитая стрелка
  Leader.ArrowheadSize:= 4;               // длина стрелки
  Leader.DogLegged:= True;                // с горизонтальной полкой
  Leader.DoglegLength:= 5;                // длина полки = 5
end;

Для добавления новых выносок служит метод:

// В функцию передается индекс кластера (0 или 1) и массив 3-D координат узлов выносок.
// Возвращаемое значение - индекс созданной выноски
function AddLeaderLine(leaderIndex: SYSINT; pointArray: OleVariant): SYSINT;

Массив координат должен содержать не менее 6-ти элементов, т.е. 2-х точек. Чтобы новая выноска имела более чем 1 сегмент (например ломаная) необходимо задать массив координат из 9-ти и более элементов.

Пример построения MLeader с 2-мя выносками:

...
var
  P1, P2, V, Leader: OleVariant;
  N: Integer;
begin
  // Запрос точек
  P1:= Acad.ActiveDocument.Utility.GetPoint(, 'Начальная точка: ');
  P2:= Acad.ActiveDocument.Utility.GetPoint(P1, 'Конечная точка: ');
  // Массив координат (2 точки MLeader, следовательно 6 элементов в массиве)
  V:= VarArrayCreate([0, 5], varDouble);
  V[0]:= P1[0];
  V[1]:= P1[1];
  V[2]:= P1[2];
  V[3]:= P2[0];
  V[4]:= P2[1];
  V[5]:= P2[2];
  // Добавляем MLeader. Она содержит только 1 кластер с индексом 0
  Leader:= Acad.ActiveDocument.ModelSpace.AddMLeader(V, N);
  // Массив координат для второй выноски
  V[0]:= V[0] + 5; // Координату X начальной точки смещаем вправо на 5
  // Добавляем вторую выноску в нулевой кластер
  Leader.AddLeaderLine(0, V);
  // Настраиваем остальные свойства
  Leader.TextString:= edTextString.Text;
end;

Для добавления кластера используется метод:

// Возвращаемое значение - индекс нового кластера
function AddLeader: SYSINT;

Пример построения двух-кластерной MLeader:

...
var
  P1, P2, V, Leader: OleVariant;
  N: Integer;
begin
  // Запрос точек
  P1:= Acad.ActiveDocument.Utility.GetPoint(, 'Начальная точка: ');
  P2:= Acad.ActiveDocument.Utility.GetPoint(P1, 'Конечная точка: ');
  // Массив координат (2 точки MLeader, следовательно 6 элементов в массиве)
  V:= VarArrayCreate([0, 5], varDouble);
  V[0]:= P1[0];
  V[1]:= P1[1];
  V[2]:= P1[2];
  V[3]:= P2[0];
  V[4]:= P2[1];
  V[5]:= P2[2];
  // Добавляем MLeader. Она содержит только 1 кластер
  Leader:= Acad.ActiveDocument.ModelSpace.AddMLeader(V, N);
  // Добавляем второй кластер
  N:= Leader.AddLeader;
  // Массив координат для второго кластера (используем тот же, что для основной)
  V[0]:= V[0] + 5; // Координату X начальной точки смещаем вправо на 5
  Leader.AddLeaderLine(N, V); // Добавляем второй кластер

  // Настраиваем остальные свойства
  Leader.TextString:= edTextString.Text;
AddLeader можно использовать многократно, добавляя все новые кластеры, но количество кластеров более 2-х не корректно.

Метод AddLeaderLineEx.

// В функцию передается 3-D массив координат узлов выносок.
// Возвращаемое значение - индекс созданной выноски
function AddLeaderLineEx(pointArray: OleVariant): SYSINT;

Этот метод стоит несколько особняком, потому что позволяет не только добавить новую выноску, но и создает, при необходимости, новый кластер. Если выноска располагается ближе к стороне с имеющимся кластером, она просто добавляется к этому кластеру. Если же выноска располагается ближе к стороне противоположного кластера (даже если он в данный момент еще не существует), она добавится к этому кластеру, а сам кластер создается автоматически.

В прилагаемом примере создан интерфейс, позволяющий при построении настроить все параметры MLeader.

Внимание!

Если добавляется MLeader с блоком, то текущим стилем должен быть стиль, у которого контент настроен на блок, если добавляется MLeader с текстом — текущий стиль должен быть настроен на MText. Иначе при попытке установки свойств, присущих только тому или другому типу контента возникает исключение: "EOleException with message "Неопознанная ошибка".

К статье прилагается пример на Delphi 7.


Внимание! Запрещается воспроизведение данной статьи или ее части без согласования с автором. Если вы желаете разместить эту статью на своем сайте или издать в печатном виде, свяжитесь с автором.
Автор статьи: Вершинин И.В.

 
Используются технологии uCoz