Тема: Как перевести пример с VBA на С++?

Нужно добавить штриховку в чертёж.
В хелпе нашел пример. Но не могу перевести его на С++.
Получаю сообщение “Invalid object array”.
Подскажите, пожалуйста, как это делается.
Вот пример:
Sub Example_AddHatch()
    ' This example creates an associative hatch in model space.
    Dim hatchObj As AcadHatch
    Dim patternName As String
    Dim PatternType As Long
    Dim bAssociativity As Boolean
    ' Define the hatch
    patternName = "ANSI31"
    PatternType = 0
    bAssociativity = True
    ' Create the associative Hatch object in model space
    Set hatchObj = ThisDrawing.ModelSpace.AddHatch(PatternType, patternName, bAssociativity)
    ' Create the outer boundary for the hatch. (a circle)
    Dim outerLoop(0 To 0) As AcadEntity
    Dim center(0 To 2) As Double
    Dim radius As Double
    center(0) = 3: center(1) = 3: center(2) = 0
    radius = 1
    Set outerLoop(0) = ThisDrawing.ModelSpace.AddCircle(center, radius)
    ' Append the outerboundary to the hatch object, and display the hatch
    hatchObj.AppendOuterLoop (outerLoop)
    hatchObj.Evaluate
    ThisDrawing.Regen True
End Sub

Re: Как перевести пример с VBA на С++?

> Andrey
Ты бы лучше свой код на C++ (ObjectARX) привел... Тогда было бы ясно где ошибка.

Re: Как перевести пример с VBA на С++?

Так будет правильно:

//  Функция добавления примитива в базу чертежа
Acad::ErrorStatus postToDb(AcDbEntity* ent, AcDbObjectId& objId, const char *blkname = ACDB_MODEL_SPACE);
// Недокументированная функция для регенерации активного чертежа
void ads_regen(void);
//---------------------------------
// Обработчик команды AddHatch
//---------------------------------
void AddHatch(void)
{
  Acad::ErrorStatus es;
  AcDbHatch* pHatch = new AcDbHatch();
  // Установим нормаль штриховки
  AcGeVector3d normal(0.0, 0.0, 1.0);
  pHatch->setNormal(normal);
  // Установим уровень штриховки
  pHatch->setElevation(0.0);
  // Установим шаблон штриховки
  pHatch->setPattern(AcDbHatch::kPreDefined, "ANSI31");
  // Установим ассоциативность
  pHatch->setAssociative(Adesk::kTrue);
  // Создадим окружность
  AcDbCircle *circle = new AcDbCircle();
  AcGePoint3d cenPt(3.0,3.0,0.0);
  circle->setNormal(normal);
  circle->setCenter(cenPt);
  circle->setRadius(1.0);
  AcDbObjectId cirId;
  // Внесем окружность в пространство модели активного чертежа
  postToDb(circle, cirId);
  AcDbObjectIdArray dbObjIds;
  dbObjIds.setLogicalLength(0);
  dbObjIds.append(cirId);
  pHatch->appendLoop(AcDbHatch::kExternal, dbObjIds);
  // Обработка контуров штриховки
  pHatch->evaluateHatch();
  AcDbObjectId hatchId;
  // Внесем штриховку в пространство модели активного чертежа
  postToDb(pHatch, hatchId);
  // Добавим ассоциативность к окружности
  {
    AcDbObjectPointer<AcDbEntity> pEnt(cirId,AcDb::kForWrite);
    if ((es = pEnt.openStatus()) != Acad::eOk) {
      acutPrintf("\nError open circle for write: %s",acadErrorStatusText(es));
      return;
    }
    pEnt->addPersistentReactor(hatchId);
  }
  // Можно выполнить и регенерацию, хотя это необязательно
  ads_regen();
}
//-----------------------------------------------
//  Функция добавления примитива в базу чертежа
//-----------------------------------------------
Acad::ErrorStatus postToDb(AcDbEntity* ent, AcDbObjectId& objId, const char *blkname)
{
  Acad::ErrorStatus es;
  AcDbBlockTable* pBlockTable;
  AcDbBlockTableRecord* pSpaceRecord;
  if (ent==NULL) return Acad::eNullObjectPointer;
  if (acdbHostApplicationServices()->workingDatabase()==NULL)
    return Acad::eNoDatabase;
  if ((es = acdbHostApplicationServices()->workingDatabase()->getBlockTable(pBlockTable, AcDb::kForRead))!=Acad::eOk)
    return es;
  if ((es =pBlockTable->getAt(blkname,pSpaceRecord,AcDb::kForWrite)) != Acad::eOk) {
    pBlockTable->close(); return es;
  }
  pBlockTable->close();
  if ((es = pSpaceRecord->appendAcDbEntity(objId, ent)) != Acad::eOk) {
    pSpaceRecord->close(); return es;
  }
  pSpaceRecord->close();
  return ent->close();
}

Re: Как перевести пример с VBA на С++?

Доброе утро, Александр! Спасибо за ответ.
Вот мой код:
#include "acad.h" //from type library acad.tlb
....
....
    IAcadApplication app;
    IAcadDocuments oDocs;
    IAcadDocument oDoc;
    IAcadModelSpace ModSp;
    IAcadHatch Hatch;
    COleVariant    covOptional((long)DISP_E_PARAMNOTFOUND, VT_ERROR);
    app.CreateDispatch("AutoCAD.Application");
    app.SetVisible(TRUE);
   
    oDocs = app.GetDocuments();
    oDoc= oDocs.Add(covOptional);   
    ModSp = oDoc.GetModelSpace();
    double center[]={100,100,0};
    COleSafeArray saCenter;
    saCenter.CreateOneDim(VT_R8,3,center);
    IAcadCircle circle= ModSp.AddCircle(saCenter,50);
    IAcadEntity entity;
        entity =(IAcadEntity)circle;
    Hatch = ModSp.AddHatch(0,"ANSI31",TRUE);
        VARIANT var;
        var.vt = VT_DISPATCH;
        var.pdispVal = entity.m_lpDispatch
    Hatch.AppendOuterLoop(var);
Как я понимаю, вся проблема в том, чтобы передать в AppendOuterLoop правильный VARIANT. Но как его сформировать?

Re: Как перевести пример с VBA на С++?

Граница (loop) - это не один примитив, а массив примитивов. Поэтому:

    COleSafeArray outerLoop;
    outerLoop.CreateOneDim(VT_DISPATCH,1,&circle);
    Hatch.AppendOuterLoop(outerLoop);
    Hatch.Evaluate();

Re: Как перевести пример с VBA на С++?

Александр, спасибо за ответ!
Заработало!
А как быть, если вместо одного примитива circle нужно использовать несколько примитивов?
В результате мучительных экспериментов совершенно случайно нашлось решение.
По аналогии с хелпом надо создать массив примитивов ( IAcadEntity *entity).
Выделить память и заполнить массив вручную.
Затем этот массив надо обернуть в VARIANT (COleSafeArray outerLoop2). Это уже делается автоматом в цикле.
Вот код (может кому-нибудь пригодится):

#include “acad.h”    //с помощью ClassWizard из “acad.tlb” (у меня AutoCAD2000, VC++6.0, Windows2000rus)
void CTestACAD03Dlg::OnAcad()
{
    IAcadApplication app;
    app.CreateDispatch("AutoCAD.Application");
    IAcadDocuments oDocs;
    oDocs = app.GetDocuments();
    IAcadDocument oDoc;
    oDoc = oDocs.Add(COleVariant((short)FALSE));
    IAcadModelSpace modsp;
    modsp = oDoc.GetModelSpace();
    double p11[]={100,100,0};
    double p12[]={100,200,0};
    double p21[]={100,200,0};
    double p22[]={200,200,0};
    double p31[]={200,200,0};
    double p32[]={200,100,0};
    double p41[]={200,100,0};
    double p42[]={100,100,0};
    COleSafeArray s11,s12,s21,s22,s31,s32,s41,s42;
    s11.CreateOneDim(VT_R8,3,p11);
    s12.CreateOneDim(VT_R8,3,p12);
    s21.CreateOneDim(VT_R8,3,p21);
    s22.CreateOneDim(VT_R8,3,p22);
    s31.CreateOneDim(VT_R8,3,p31);
    s32.CreateOneDim(VT_R8,3,p32);
    s41.CreateOneDim(VT_R8,3,p41);
    s42.CreateOneDim(VT_R8,3,p42);
    IAcadLine ln1,ln2,ln3,ln4;
    ln1 = modsp.AddLine(s11,s12);
    ln2 = modsp.AddLine(s21,s22);
    ln3 = modsp.AddLine(s31,s32);
    ln4 = modsp.AddLine(s41,s42);
    double centr[]={150,150,0};
    COleSafeArray sacentr;
    sacentr.CreateOneDim(VT_R8,3,centr);
    IAcadCircle circle;
    circle = modsp.AddCircle(sacentr,40);
/*
//    для окружности - это единственный примитив
  IAcadHatch hatch;
    hatch = modsp.AddHatch(0,"ANSI31",TRUE);
    COleSafeArray outerLoop;
    outerLoop.CreateOneDim(VT_DISPATCH,1,&circle);
    hatch.AppendOuterLoop(outerLoop);
    hatch.Evaluate();
*/
//    для нескольких примитивов
    IAcadEntity *entity;
    entity = new IAcadEntity[4];
    entity[0] = (IAcadEntity)ln1;
    entity[1] = (IAcadEntity)ln2;
    entity[2] = (IAcadEntity)ln3;
    entity[3] = (IAcadEntity)ln4;
    IAcadHatch hatch2;
    hatch2 = modsp.AddHatch(0,"ANSI31",TRUE);
    
    COleSafeArray outerLoop2;
    outerLoop2.CreateOneDim(VT_DISPATCH,4);
    for(long i =0; i<4; i++) outerLoop2.PutElement(&i,entity[i]);
    hatch2.AppendOuterLoop(outerLoop2);
    hatch2.Evaluate();
    app.SetVisible(TRUE);
    app.ZoomAll();
    app.Quit();
}

Еще нужно в начало функции InitInstance() поместить вызов AfxOleInit():

BOOL CTestACAD03App::InitInstance()
{
    AfxOleInit();
    ......
}

Также совершенно случайно нашлось ещё одно решение, но намного более сложное.
Это такие дебри COM-технологи!
См: http://www.rsdn.ru/Forum/?mid=591449
Теперь буду мучить полилинию (как использую полилинию нарисовать заштрихованный контур).

Re: Как перевести пример с VBA на С++?

> Andrey
Ты правильно понял мою мысль. :)

Re: Как перевести пример с VBA на С++?

Для полилинии не намного сложнее:

void AddHatch(void)
{
  IAcadApplication app;
  IAcadDocument    oDoc;
  IAcadModelSpace  ModSp;
  IAcadHatch       Hatch;
  try {
      app.CreateDispatch("AutoCAD.Application");
      app.put_Visible(TRUE);
      oDoc  = app.get_ActiveDocument();
      ModSp = oDoc.get_ModelSpace();
      // Внешний прямоугольник
      double Pts_out[][3] =
      { {0,     0,  0}, // { X, Y, Z} каждой вершины
        {0,   200,  0},
        {100, 200,  0},
        {100,   0,  0},
      };
      // Внутренний прямоугольник
      double Pts_inn[][3] =
      { {25,    25,  0},
        {25,   175,  0},
        {75,   175,  0},
        {75,    25,  0},
      };
      // Массив одномерный(!!!) длиной 3*количество вершин полилинии
      COleSafeArray vts_out,vts_inn;
      vts_out.CreateOneDim(VT_R8,sizeof(Pts_out)/sizeof(double),Pts_out);
      vts_inn.CreateOneDim(VT_R8,sizeof(Pts_inn)/sizeof(double),Pts_inn);
      IAcadPolyline pline_out = ModSp.AddPolyline(vts_out);
      IAcadPolyline pline_inn = ModSp.AddPolyline(vts_inn);
      pline_out.put_Closed(TRUE); pline_inn.put_Closed(TRUE);
      //
      // Изменим кривизну контуров (для красоты) :-D
      //
      for (int i=0; i < ELEMENTS(Pts_inn); i++) {
        pline_inn.SetBulge(i,0.25);
        pline_out.SetBulge(i,0.25);
      }
      Hatch = ModSp.AddHatch(0,"ANSI31",TRUE);
      COleSafeArray outLoop;  outLoop.CreateOneDim(VT_DISPATCH,1,&pline_out);
      COleSafeArray innLoop;  innLoop.CreateOneDim(VT_DISPATCH,1,&pline_inn);
      Hatch.AppendOuterLoop(outLoop);
      Hatch.AppendInnerLoop(innLoop);
      Hatch.Evaluate();
      // oDoc.SaveAs(...)
      // app.Quit();
      // app.DetachDispatch();
  }
  catch (...)
  {
  }
}

Re: Как перевести пример с VBA на С++?

Александр, воспользовался вашей подсказкой.
Теперь пытаюсь создать контур outerLoop из двух объектов: полилинии и простой линии
Видимо нельзя использовать разнородные объекты sad
Даже на VBA не получается:

Sub aaa()
 Dim hatchObj As AcadHatch
    Dim patternName As String
    Dim PatternType As Long
    Dim bAssociativity As Boolean
 Dim outerLoop(0 To 1) As AcadEntity
 Dim objCircle As AcadCircle
 Dim objLine As AcadLine
    patternName = "ANSI31"
    PatternType = 0
    bAssociativity = True
    Set hatchObj = ThisDrawing.ModelSpace.AddHatch(PatternType, patternName, bAssociativity)
    Dim center(0 To 2) As Double
    Dim p1(0 To 2) As Double
    Dim p2(0 To 2) As Double
    Dim radius As Double
    p1(0) = 5: p1(1) = 30: p1(2) = 0
    p2(0) = 55: p2(1) = 30: p2(2) = 0
    center(0) = 30: center(1) = 30: center(2) = 0
    radius = 20
    Set objCircle = ThisDrawing.ModelSpace.AddCircle(center, radius)
    Set objLine = ThisDrawing.ModelSpace.AddLine(p1, p2)
    Set outerLoop(0) = [b]objCircle[/b]
    Set outerLoop(1) = [b]objLine[/b]
    hatchObj.AppendOuterLoop (outerLoop)
    hatchObj.Evaluate
    ThisDrawing.Regen True
End Sub

И на VC:

    double o1[]={95,100,0};
    double o2[]={205,100,0};
    COleSafeArray sao1,sao2;
    sao1.CreateOneDim(VT_R8,3,o1);
    sao2.CreateOneDim(VT_R8,3,o2);
    IAcadLine oLine;
    oLine = modsp.AddLine(sao1,sao2);
    double Dkar = 40;
    double Lkar = 10;
    double l = 2;
    double n = 60;
    double Pkar[][3] =
    {
        {100,100,0},
        {100,100+Dkar/2,0},
        {100+Lkar,100+Dkar/2,0},
        {100+Lkar,100,0}
    };
    IAcadPolyline poly;
    COleSafeArray saPkar;
    saPkar.CreateOneDim(VT_R8,sizeof(Pkar)/sizeof(double),Pkar);
    poly = modsp.AddPolyline(saPkar);
    IAcadEntity *entity;
    entity = new IAcadEntity[2];
    entity[0] = (IAcadEntity)poly;
    entity[1] = (IAcadEntity)oLine;
    IAcadHatch hatch3;
    hatch3 = modsp.AddHatch(0,"ANSI31",TRUE);
    
    COleSafeArray outerLoop3;
    outerLoop3.CreateOneDim(VT_DISPATCH,2);
    for(long i =0; i<2; i++) outerLoop3.PutElement(&i,entity[i]);
    hatch3.AppendOuterLoop(outerLoop3);
    hatch3.Evaluate();

Есть ли в AutoCAD-е средство записи макросов на лету как в ворде?
Или их надо писать вручную (или, например, копировать из хелпа и вставлять в редакторVBA)
Ведь если создавать штриховку из линии и полилинии не программно, а непосредственно в AutoCAD-е, то всё получается. Выбираешь в диалоговом окне Pick Points, указываешь точку,  AutoCAD сам создаёт нужный контур – и штриховка готова. Хотелось бы записать действия AutoCAD-а в макрос и посмотреть, что он там делает.
Может быть для разнородных примитивов нужен другой тип массива (не IAcadEntity)?
Заранее благодарен.

Re: Как перевести пример с VBA на С++?

Andrey пишет:

Теперь пытаюсь создать контур outerLoop из двух объектов: полилинии и простой линии
Видимо нельзя использовать разнородные объекты sad

Можно. Только нужно понимать, что они должны образовывать непрерывный замкнутый контур.
Я, например, попробовал с незамкнутой полилинией и замыкающим ее отрезком - все работает.
А у тебя ни в примере на VBA, ни на C++ этого не происходит (окружность уже сама по себе замкнутый контур).

Есть ли в AutoCAD-е средство записи макросов на лету как в ворде?

Ну ты и наивняк! biggrin Насмешил... В AutoCAD'е VBA - это "пасынок". Далеко не все, что можно делать в AutoCAD'е реализовано через ActiveX интерфейс. Так что о записи макросов речи быть не может - разве что ты сам напишешь такую программу. :)

Хотелось бы записать действия AutoCAD-а в макрос и посмотреть, что он там делает.

Делает он "элементарную" операцию - анализирует все пространство вокруг указанной точки и строит из окружающих примитивов замкнутый контур. Аналогично команде _-BOUNDARY (или _BPOLY). А уже затем этот контур штрихует. Предвосхищаю твой вопрос - специального API для аналогичной операции не существует. Так что все нужно писать самому.

Re: Как перевести пример с VBA на С++?

Да, действительно, контур должен быть замкнутым. И это есть в хелпе по Hatch.
Также там указано, что контур может быть из след. объектов:
     линии,
     полилинии,
     окружности,
     эллипса,
     сплайна,
     и региона.
Странно, что такая нужная команда, как BOUNDARY недоступна из программы на VC++.
Жаль.
Интересно, а можно ли вызвать эту команду используя ObjectARX, а не OLE Automation?
Тогда придётся переходить на ObjectARX.

Re: Как перевести пример с VBA на С++?

Тогда придётся переходить на ObjectARX.

Кажется сагитировал! biggrin
А если серьезно, то из ObjectARX можно вызвать команду -BOUNDARY и получать результат ее работы (замкнутую полилинию).
Через ActiveX можно тоже запустить эту команду (через IAcadDocument.Sendcommand(...))

Re: Как перевести пример с VBA на С++?

Попробовал SendCommand("-bhatch\n ля-ля-ля ")
Вроде получилось!
Только непонятно как получить объект IAcadHatch для созданной таким образом штриховки.
Или как получить объект IAcadPolyline после выполнения SendCommand("-boundary\n трали-вали")?
Наверное на ObjectARX это проще, так как там есть база данных чертежа, где и появится новый объект. Я правда не знаю как там в ObjectARX.
Но у меня такая простая задача, что не хочется стрелять из пушки по воробьям.

Re: Как перевести пример с VBA на С++?

В ObjectARX действительно проще - есть специальная функция acdbEntLast().
Через ActiveX тоже (IMHO) можно:

IAcadDocument.ActiveSelectionSet.Select(acSelectionSetLast);

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

Re: Как перевести пример с VBA на С++?

Придумал еще один (более простой и надежный) способ получения последнего примитива в ModelSpace:

// Номер последнего примитива
long nLastEnt = modsp.get_Count()-1;
// Есть хоть один примитив в ModelSpace?
if (nlastEnt >=0) {
  // Это последний примитив
  IAcadEntity ent = modsp.Item(COleVariant(nLastEnt));
}

Пользуйся! :)

Re: Как перевести пример с VBA на С++?

> Andrey
Есть ли в AutoCAD-е средство записи макросов на лету как в ворде?

Такая программа есть, она называется VBARECORDER. Вот адресок, по которому можно скачать:
http://www.freecadapps.com/swdetails.ph … lor=ffffcc

Re: Как перевести пример с VBA на С++?

> LeonidSN
Интересная штучка! Жаль, что работает только под AutoCAD 2000...2002.