В статьях AutoCAD Architecture: First project и Введение в ACA я кратко рассказал о том что такое AutoCAD Architecture (ACA), чем он отличается от обычного Автокада, какие в нем реализованы объекты и показал простейший случай работы со стенами из .NET плагина.
В данной статье я расскажу о библиотеке Teigha — альтернативе для работы с dwg файлами и объектами ACA. Мы напишем небольшой пример, который создает дом из ACA-объектов и сохраняет его в dwg файл. Затем, попробуем открыть этот файл в AutoCAD Architecture и проверить, совместимы ли эти файлы с оригинальным Автокадом.
Альтернатива AutoCAD и ObjectARX
Для работы с dwg и acad-объектами, кроме самого Автокада и ObjectARX, существует еще одна библиотека: Teigha от Open Design Alliance. Если вы хотите программно работать с dwg форматом и объектами Автокада, то выбор у вас, по сути, только между ObjectARX и Teigha. Все сторонние компоненты и приложения, которые умеют читать и записывать dwg файлы, основаны на Teigha.
Teigha – это набор библиотек, который позволяет читать, записывать и манипулировать объектами как обычного Автокада, так и его производных типа ACA. В этой библиотеке также реализованы многие вспомогательные механизмы для работы с объектами Автокада и рендеринг-девайсы для отрисовки dwg базы.
Ниже приведена официальная информация от Open Design Alliance о возможностях библиотеки:
- Supported DWG, DXF, and BDXF file formats:
- Read support for files of version 2.5 through 2014.
- Write support for files of version 12 through 2014.
- Supported DGN file formats:
- Read support for V7 and V8 DGN (including V8 XM and V8I).
- Write support for V8 DGN.
- Convert from V7 to V8.
- Render drawing files using GDI, OpenGL, or DirectX with the ability to select entities.
- Recover and repair damaged .dwg files.
- Edit and manipulate CAD data programmatically, including:
- Explode an entity into a set of simpler entities.
- Apply a transformation to an entity.
- Modify arbitrary properties of database objects.
- Clone a database object.
- Many more.
- Export to SVG, PDF, DWF, BMP, STL, DAE (Collda).
- Import DWF/DAE/DGN files into a .dwg database.
- Support custom objects — members can create custom objects that are usable within any Teigha host application (compatible with .dwg files only).
- Support external files such as font files (TTF/SHX), linetype files (LIN/RSC), and hatch pattern files (PAT).
- Recompute dimension geometry from dimension settings.
- Support transactions and undo/redo capabilities for .dwg file data.
- Support partial .dwg file load capabilities, allowing fast access to small subsets of drawing entities (entities are loaded from disk only if they are explicitly accessed by the client).
- Customize memory management allowing member applications to control memory allocation/deallocation.
- Support .dwg file “round-trip” data. For example, when saving a 2007 .dwg file back to R14, 2007-specific properties are saved to the R14 file as xdata and then restored if the file is loaded back into a 2007-compatible application.
- Support ACIS/Parasolid data internally, including rendering (wireframe and shaded) for embedded 3D solids and access to the underlying boundary representation data.
- Implement custom commands.
- Sample source code applications
- Integrate with third-party components.
В общем, «библиотека поддерживает много чего». Для начала попробуем загрузить и отрисовать какой-нибудь dwg чертеж используя стандартный сэмпл из комплекта поставки Teigha:
Ок, dwg файл у нас прочитался и нарисовался. Впрочем, как и dwg файл, который я использовал для титульной картинки к статье.
Этот стандартный сэмпл представляет из себя оконное C++ приложение и позволяет загружать, просматривать и редактировать dwg файлы без Автокада. Еще одна важная особенность: API объектов Автокада и Teigha почти идентичны, поэтому можно легко переписать существующий ObjectARX плагин для работы с приложением, основанным на Teigha.
Большинство аналогов Автокада, такие как BricsCAD, ZWCad, IntelliCAD, используют Teigha для работы с dwg форматом и acad-объектами.
Особенности объектов AutoCAD Architecture
Теперь перейдем к архитектурным объектам и работе с ними. Для этого вкратце напомню основные моменты:
AutoCAD Architecture оперирует специальными высокоуровневыми объектами, разработанными для архитектурного проектирования: стенами, дверями, окнами, крышами и прочими конструкционными частями. Объекты viewport-dependent, т.е. они могут по-разному отрисовывать себя в зависимости от направления камеры. Объектам назначен стиль. Если изменить стиль, то изменятся все объекты этого стиля. Объекты состоят из компонентов, каждый из которых имеет свои визуальные настройки: цвет, тип линий, материал, масштаб (например, дверь в 3D состоит из рамы, полотна, стекла). Для разных вариантов отображения объект рисует разную геометрию, поэтому количество и настройки компонентов различны для разных представлений.
Библиотека Teigha for Architecture (TA)
Для работы с архитектурными объектами, кроме самого ACA и его открытого API для создания плагинов, можно использовать библиотеку Teigha for Architecture от Open Design Alliance.
TA — это библиотека С++ классов, в которой реализованы все основные примитивы ACA, такие как стены, окна, двери, крыши, балки, проемы и тд. Библиотека позволяет читать эти объекты из dwg форматов всех версий, записывать (конвертировать) в последнюю версию dwg; в библиотеке реализован рендеринг всех примитивов для различных представлений и конфигураций. Т.к. aca-объекты взаиподействуют друг с другом, в TA также реализованы вспомогательные классы и механизмы ACA — это anchor, display manager, property sets, relation graph и прочая обвязка.
Начинаем работать с TA API
Общих слов, на мой взгляд, уже более чем достаточно. Теперь посмотрим что физически из себя представляет Teigha и попробуем написать первую простую команду. Я буду использовать старую VS 2005, но библиотеки мультиплатформенны и в комплекте идет генератор солюшенов для студий вплоть до 2015. В зависимости от типа лицензии вам может быть доступен либо полный код всей библиотеки, либо построенные бинарники и заголовочные файлы.
Комплект TA-библиотек будет примерно таким:
По сути это обычные виндовые dll (можно сбилдить и под другие платформы: ios, linux, unix и тд). Lib файлы к ним идут в отдельной папке. Кроме TA будут необходимы Teigha Core библиотеки, тк TA это расширение над Core-объектами. Core реализует основные механизмы и объекты обычного автокада.
Инициализация ТА
Для начальной инициализации библиотеки нам потребуется класс, который выполняет платформо-зависимые операции с файлами.
class MyServices : public ExSystemServices, public ExHostAppServices
{
protected:
ODRX_USING_HEAP_OPERATORS(ExSystemServices);
};
В комлекте уже есть готовые расширения под Windows: ExSystemServices и ExHostAppServices; В данном случае нам их будет достаточно.
Далее, инициализируем библиотеку и графическую подсистему
OdStaticRxObject<MyServices> svcs;
odInitialize( &svcs );
odgsInitialize();
OdStaticRxObject добавляет объекту логику addRef Release; Библиотека сохраняет ссылку на объект MyServices и использует его для платформозависимых операций.
Инициализируем библиотеки ТА:
// Loading of all public Teigha Architecture DRX modules.
// Note that not all calls are necessary for some of them depend on others
// but here we list all of them.
//
// If a program uses TD doesn't modify or create binary files
// it may not load any of DRX modules on start because they will be loaded automatically.
// But if a program modifies or creates binary files then it is highly recommended
// to load all DRX modules program uses.
::odrxDynamicLinker()->loadApp( OD_T("AecBase") );
::odrxDynamicLinker()->loadApp( OD_T("AecArchBase") );
::odrxDynamicLinker()->loadApp( OD_T("AecArchDACHBase") );
::odrxDynamicLinker()->loadApp( OD_T("AecScheduleData") );
::odrxDynamicLinker()->loadApp( OD_T("AecSchedule") );
::odrxDynamicLinker()->loadApp( OD_T("AecStructureBase") );
AecBase, AecArchBase и все остальные – это tx модули (т.е. dll библиотеки) c картинки выше. Они уже слинкованы с помощью lib файлов, но этого недостаточно. Необходимо инициализировать их как модули. Что это значит? В период выполнения в памяти существует словарь загруженных классов. Этот словарь используется для механизма кастов указателей между разными типами TA-объектов и для создания самих экземпляров TA-классов через централизованный механизм псевдо-конструкторов.
Например, при выполнении команды ::odrxDynamicLinker()->loadApp( OD_T(«AecArchBase») ) внутри фреймворка будет вызвана функция AECArchBase::initApp(). Схематично, initApp() зарегистрирует в глобальном словаре классы, которые реализованы в данной библиотеке, вызвав для каждого статическую функцию rxInit():
…
AECDbSpaceBoundary::rxInit();
AECDbStair::rxInit();
AECDbWall::rxInit();
AECDbZone::rxInit();
…
После этого заработает механизм создания объектов и можно будет создать, например, стену вызовом AECDbWallPtr pWall = AECDbWall::CreateAECObject(). В противном случае попытка создать объект TA-класса выкинет исключение.
Cоздаем пустую dwg базу вызовом
OdDbDatabasePtr pDatabase = svcs.createDatabase();
Это центральный объект, он представляет из себя объектную базу данных, которая сохраняется и загружается из dwg файла. В него мы будем добавлять все созданные архитектурные объекты. По завершении, мы сохраним эту базу в dwg файл вызовом
OdWrFileBuf cBuffer( strFilename );
pDatabase->writeFile( &cBuffer, OdDb::kDwg, OdDb::kDHL_CURRENT );
Далее, еще немного инициализируем загруженные библиотеки и display manager
AECArchDACHBaseDatabase( pDatabase ).Init();
AECScheduleDatabase( pDatabase ).Init();
AECStructureBaseDatabase( pDatabase ).Init();
init_display_system( pDatabase );
В базе создается AEC-словарь с дефолтными настройками единиц измерения для длины, площади, объема, углов, настроек печати и регестрируются display representations реализованные в этих модулях. О display manager, display representations и связанных с этим механизмами я напишу отдельный пост.
На этом инициализация закончена. Если пропустить какие-то шаги, то результат может быть различен: либо у вас не будут создаваться объекты, либо они не будут отрисовываться (увидите пустой экран), либо получите еще какие-то глюки, в зависимости от того, какой шаг пропустили.
На данный момент полный код у нас выглядит вот так:
class MyServices : public ExSystemServices, public ExHostAppServices
{
protected:
ODRX_USING_HEAP_OPERATORS(ExSystemServices);
};
int wmain(int argc, wchar_t* argv[])
{
// Initialize TD with system services.
// And create single instance of hostapp services
// for TD database creation.
OdStaticRxObject<MyServices> svcs;
odInitialize( &svcs );
odgsInitialize();
// Loading of all public Teigha Architecture DRX modules.
// Note that not all calls are necessary for some of them depend on others
// but here we list all of them.
//
// If a program uses TD doesn't modify or create binary files
// it may not load any of DRX modules on start because they will be loaded automatically.
// But if a program modifies or creates binary files then it is highly recommended
// to load all DRX modules program uses.
::odrxDynamicLinker()->loadApp( OD_T("AecBase") );
::odrxDynamicLinker()->loadApp( OD_T("AecArchBase") );
::odrxDynamicLinker()->loadApp( OD_T("AecArchDACHBase") );
::odrxDynamicLinker()->loadApp( OD_T("AecScheduleData") );
::odrxDynamicLinker()->loadApp( OD_T("AecSchedule") );
::odrxDynamicLinker()->loadApp( OD_T("AecStructureBase") );
// Create empty TD database.
OdDbDatabasePtr pDatabase = svcs.createDatabase();;
// Initialize database with default Teigha Architecture content.
AECArchDACHBaseDatabase( pDatabase ).Init();
AECScheduleDatabase( pDatabase ).Init();
AECStructureBaseDatabase( pDatabase ).Init();
init_display_system( pDatabase );
// do something here with TA objects
// Perform "zoom extents" on model space.
{
OdDbViewportTablePtr pVT =
pDatabase->getViewportTableId().openObject( OdDb::kForRead );
OdDbViewportTableRecordPtr pV =
pVT->getActiveViewportId().openObject( OdDb::kForWrite );
pV->zoomExtents();
}
OdWrFileBuf cBuffer( "H:\TA_test.dwg" );
pDatabase->writeFile( &cBuffer, OdDb::kDwg, OdDb::kDHL_CURRENT );
odgsUninitialize();
odUninitialize();
return 0;
}
Я добавил команду zoom extents, чтобы при открытии созданного файла мы сразу видели объекты, которые туда добавили и симметричную деинициализацию библиотеки. Для упрощения я убрал проверку ошибок и конструкции trycatch вокруг основных действий.
Сейчас программа создаст пустой dwg файл, который можно открыть и поглядеть Автокадом.
Работа с объектами
Добавление стен в чертеж
Для демонстрации работы с ТА-классами, я хочу создать дом, состоящий из перекрытия-фундамента, стен, окон, двери и крыши. Начнем со стен.
Первым делом добавим в наш чертеж стену. Чтобы создать саму стену, необходимо сначала создать для нее стиль. Напишем функцию add_wall_style:
OdDbObjectId add_wall_style( OdDbDatabasePtr pDatabase )
{
OdDbObjectId idResult =
AECDbWallStyle::CreateAECObject( pDatabase, OD_T("Wall Style Created By Teigha(R) Architecture") );
AECDbWallStylePtr pWallStyle =
idResult.openObject( OdDb::kForWrite );
pWallStyle->SetDescription( OD_T("Wall Style Description") );
pWallStyle->SetDictRecordDescription( OD_T("Dialog caption") );
pWallStyle->SetWallWidth( 4 );
pWallStyle->SetWallWidthUsed( true );
pWallStyle->SetBaseHeight( 110 );
pWallStyle->SetBaseHeightUsed( true );
pWallStyle->SetJustification( AECDefs::ewjLeft );
pWallStyle->SetJustificationUsed( true );
pWallStyle->SetAutomaticCleanups( true );
pWallStyle->SetAutomaticCleanupsUsed( true );
pWallStyle->SetCleanupRadius( 4 );
pWallStyle->SetCleanupRadiusUsed( true );
pWallStyle->SetFloorLineOffset( 3 );
pWallStyle->SetFloorLineOffsetUsed( false );
pWallStyle->SetRoofLineOffset( -3 );
pWallStyle->SetRoofLineOffsetUsed( false );
AECDisplayManager cDM( pDatabase );
AECDbDispPropsWallModelPtr pOverrideModel =
AECDbDispPropsWallModel::cast( pWallStyle->OverrideDispProps(
cDM.UpdateDisplayRepresentation( AECDbDispRepWallModel::desc() ) ).openObject( OdDb::kForWrite ) );
if ( !pOverrideModel.isNull() )
{
pOverrideModel->SetIsDisplayOpeningEndcaps( false );
pOverrideModel->GetBoundaryCompByIndex( 0 )->SetColor( colorAt( 4 ) );
}
AECDbDispPropsWallPtr pOverridePlan =
AECDbDispPropsWall::cast( pWallStyle->OverrideDispProps(
cDM.UpdateDisplayRepresentation( AECDbDispRepWallPlan::desc() ) ).openObject( OdDb::kForWrite ) );
if ( !pOverridePlan.isNull() )
{
pOverridePlan->GetBoundaryCompByIndex( 0 )->SetColor( colorAt( 4 ) );
}
return( pWallStyle->objectId() );
}
Эта функция создает объект «стиль стены» AECDbWallStyle, выставляет ему некоторые настройки, а затем обращается к display manager и меняет цвета для plan display representation (2d вид сверху) и model display representation (3d view).
AECDisplayManager cDM( pDatabase );
AECDbDispPropsWallModelPtr pOverrideModel =
AECDbDispPropsWallModel::cast( pWallStyle->OverrideDispProps(
cDM.UpdateDisplayRepresentation( AECDbDispRepWallModel::desc() ) ).openObject( OdDb::kForWrite ) );
if ( !pOverrideModel.isNull() )
{
pOverrideModel->SetIsDisplayOpeningEndcaps( false );
pOverrideModel->GetBoundaryCompByIndex( 0 )->SetColor( colorAt( 2 ) );
}
В данном случае мы установили желтый цвет для стены в 3д представлении. На вид выглядит сложновато, но тому есть причины: так работает механизм display representations и display manager в ACA. Он гибкий и имеет много возможностей, но его логика не сразу очевидна и нуждается в некотором изучении.
OdDbObjectId — ссылка времени выполнения
OdDbObjectId – это класс, с помощью которого объекты базы ссылаются друг на друга в период выполнения. Внутри он хранит указатель на реальный объект в памяти. Используя OdDbObjectId::openObject() можно получить указатель на объект с которым связан ObjectId. Смысл в том, что пока к объекту не обратились по этому id, он может быть не загружен в память и внутренний указатель OdDbObjectId может быть равен NULL. Когда мы специально обратились по этому id для чтения или записи, тогда фреймворк создаст экземпляр реального объекта и заполнит его поля данными из базы, а внутренний указатель OdDbObjectId получит адрес созданного объекта. openObject() возвращает этот указатель.
Такой механизм позволяет осуществлять частичную загрузку крупной базы (dwg файла). В этом случае OdDbObjectId будут существовать для каждого объекта, но реально созданы в памяти будут только те объекты, к которым явно обращаются.
Если нужно чтобы ссылка на объект сохранялась между запусками, то нужно использовать OdDbHandle.
Например, функция add_wall_style вернула нам idWallStyle. В данном случае стиль был только что явно создан вызовом AECDbWallStyle::CreateAECObject() и idWallStyle хранит внутри указатель на реальный объект в памяти. Чтобы получить доступ на запись к объекту-стилю надо выполнить операцию
AECDbWallStylePtr pWallStyle = idResult.openObject( OdDb::kForWrite );
openObject() вернет реальный указатель на объект и им можно будет пользоваться.
В библиотеке вместо обычных С++ указателей используются смартпоинтеры OdSmartPtr
typedef OdSmartPtr<AECDbWallStyle> AECDbWallStylePtr
Деструктор такого смартпоинтера оповещает фреймворк о закрытии объекта, что может вызвать пересчет связанных объектов, отправку оповещений и т.д.
Теперь добавляем стену вызовом:
OdDbObjectId idWall1 = add_wall( pDatabase, idWallStyle, OdGePoint2d( 0, 0 ), OdGePoint2d( 0, 110 ) );
OdDbObjectId add_wall( OdDbDatabasePtr pDatabase, const OdDbObjectId& idStyle,
const OdGePoint2d& ptStart, const OdGePoint2d& ptEnd, double dBulge = 0 )
{
AECDbWallPtr pWall =
AECDbWall::CreateAECObject( pDatabase->getModelSpaceId(), idStyle );
pWall->Set( ptStart, ptEnd, dBulge );
pWall->SetDescription( OD_T("A Wall") );
return( pWall->objectId() );
}
Как видим, add_wall не делает ничего особого. Она создает объект AECDbWall со стилем, который мы создали немного раньше. Объект AECDbWall добавляется в model space базы – специальный словарь, в котором хранятся все объекты, которые отрисуются при рендеринге базы (это упрощение).
Далее, стене устанавливается начальная точка, конечная точка и кривизна. Т.е. стена может быть не только прямой, но и выпуклой.
Если все сделали верно, то у нас получится dwg файл с одной желтой прямоугольной стеной. В данном случае я просматриваю файл с помощью сэмпла из поставки Teigha, но в ACA он будет отрисован точно также.
Правда я вручную развернул камеру в 3D представление. По умолчанию у вас будет вид сверху.
Теперь, попробуем добавить аж 4 стены, причем одну выпуклую:
OdDbObjectId idWall1 = add_wall( pDatabase, idWallStyle,
OdGePoint2d( 0, 0 ), OdGePoint2d( 0, 110 ) );
OdDbObjectId idWall2 = add_wall( pDatabase, idWallStyle,
OdGePoint2d( 0, 110 ), OdGePoint2d( 110, 110 ) );
OdDbObjectId idWall3 = add_wall( pDatabase, idWallStyle,
OdGePoint2d( 110, 110 ), OdGePoint2d( 110, 0 ) );
OdDbObjectId idWall4 = add_wall( pDatabase, idWallStyle,
OdGePoint2d( 110, 0 ), OdGePoint2d( 0, 0 ), -1 );
Получили некий зачаток будущего домика:
Как видим, стены не просто нарисовались отдельными объектами, а между ними автоматом построился плавный переход в точках контакта друг с другом. Это одна из автоматических функций TA, называется cleanup стен.
OdDbObjectId add_wall_style( OdDbDatabasePtr pDatabase )
{
OdDbObjectId idResult =
AECDbWallStyle::CreateAECObject( pDatabase, OD_T("Wall Style Created By Teigha(R) Architecture") );
AECDbWallStylePtr pWallStyle =
idResult.openObject( OdDb::kForWrite );
pWallStyle->SetDescription( OD_T("Wall Style Description") );
pWallStyle->SetDictRecordDescription( OD_T("Dialog caption") );
pWallStyle->SetWallWidth( 4 );
pWallStyle->SetWallWidthUsed( true );
pWallStyle->SetBaseHeight( 110 );
pWallStyle->SetBaseHeightUsed( true );
pWallStyle->SetJustification( AECDefs::ewjLeft );
pWallStyle->SetJustificationUsed( true );
pWallStyle->SetAutomaticCleanups( true );
pWallStyle->SetAutomaticCleanupsUsed( true );
pWallStyle->SetCleanupRadius( 4 );
pWallStyle->SetCleanupRadiusUsed( true );
pWallStyle->SetFloorLineOffset( 3 );
pWallStyle->SetFloorLineOffsetUsed( false );
pWallStyle->SetRoofLineOffset( -3 );
pWallStyle->SetRoofLineOffsetUsed( false );
AECDisplayManager cDM( pDatabase );
AECDbDispPropsWallModelPtr pOverrideModel =
AECDbDispPropsWallModel::cast( pWallStyle->OverrideDispProps(
cDM.UpdateDisplayRepresentation( AECDbDispRepWallModel::desc() ) ).openObject( OdDb::kForWrite ) );
if ( !pOverrideModel.isNull() )
{
pOverrideModel->SetIsDisplayOpeningEndcaps( false );
pOverrideModel->GetBoundaryCompByIndex( 0 )->SetColor( colorAt( 4 ) );
}
AECDbDispPropsWallPtr pOverridePlan =
AECDbDispPropsWall::cast( pWallStyle->OverrideDispProps(
cDM.UpdateDisplayRepresentation( AECDbDispRepWallPlan::desc() ) ).openObject( OdDb::kForWrite ) );
if ( !pOverridePlan.isNull() )
{
pOverridePlan->GetBoundaryCompByIndex( 0 )->SetColor( colorAt( 4 ) );
}
return( pWallStyle->objectId() );
}
OdDbObjectId add_wall( OdDbDatabasePtr pDatabase, const OdDbObjectId& idStyle,
const OdGePoint2d& ptStart, const OdGePoint2d& ptEnd, double dBulge = 0 )
{
AECDbWallPtr pWall =
AECDbWall::CreateAECObject( pDatabase->getModelSpaceId(), idStyle );
pWall->Set( ptStart, ptEnd, dBulge );
pWall->SetDescription( OD_T("A Wall") );
return( pWall->objectId() );
}
int wmain(int argc, wchar_t* argv[])
{
// Initialize TD with system services.
// And create single instance of hostapp services
// for TD database creation.
OdStaticRxObject<MyServices> svcs;
odInitialize( &svcs );
odgsInitialize();
// Loading of all public Teigha Architecture DRX modules.
// Note that not all calls are necessary for some of them depend on others
// but here we list all of them.
//
// If a program uses TD doesn't modify or create binary files
// it may not load any of DRX modules on start because they will be loaded automatically.
// But if a program modifies or creates binary files then it is highly recommended
// to load all DRX modules program uses.
::odrxDynamicLinker()->loadApp( OD_T("AecBase") );
::odrxDynamicLinker()->loadApp( OD_T("AecArchBase") );
::odrxDynamicLinker()->loadApp( OD_T("AecArchDACHBase") );
::odrxDynamicLinker()->loadApp( OD_T("AecScheduleData") );
::odrxDynamicLinker()->loadApp( OD_T("AecSchedule") );
::odrxDynamicLinker()->loadApp( OD_T("AecStructureBase") );
// Create empty TD database.
OdDbDatabasePtr pDatabase = svcs.createDatabase();;
// Initialize database with default Teigha Architecture content.
AECArchDACHBaseDatabase( pDatabase ).Init();
AECScheduleDatabase( pDatabase ).Init();
AECStructureBaseDatabase( pDatabase ).Init();
init_display_system( pDatabase );
OdDbObjectId idWallStyle = add_wall_style( pDatabase );
OdDbObjectId idWall1 = add_wall( pDatabase, idWallStyle,
OdGePoint2d( 0, 0 ), OdGePoint2d( 0, 110 ) );
OdDbObjectId idWall2 = add_wall( pDatabase, idWallStyle,
OdGePoint2d( 0, 110 ), OdGePoint2d( 110, 110 ) );
OdDbObjectId idWall3 = add_wall( pDatabase, idWallStyle,
OdGePoint2d( 110, 110 ), OdGePoint2d( 110, 0 ) );
OdDbObjectId idWall4 = add_wall( pDatabase, idWallStyle,
OdGePoint2d( 110, 0 ), OdGePoint2d( 0, 0 ), -1 );
// Perform "zoom extents" on model space.
{
OdDbViewportTablePtr pVT =
pDatabase->getViewportTableId().openObject( OdDb::kForRead );
OdDbViewportTableRecordPtr pV =
pVT->getActiveViewportId().openObject( OdDb::kForWrite );
pV->zoomExtents();
}
OdWrFileBuf cBuffer( "H:\TA_test.dwg" );
pDatabase->writeFile( &cBuffer, OdDb::kDwg, OdDb::kDHL_CURRENT );
odgsUninitialize();
odUninitialize();
return 0;
}
Добавление окон в чертеж
Теперь добавим в наш домик окна. С окнами логика аналогична дверям: необходимо создать стиль окон, которые мы хотим добавлять в чертеж, а потом добавить объекты-окна этого стиля.
OdDbObjectId idWindowStyle = add_window_style( pDatabase );
OdDbObjectId add_window_style( OdDbDatabasePtr pDatabase )
{
OdDbObjectId idWStyle =
AECDbWindowStyle::CreateAECObject( pDatabase, OD_T("Window Style Created By Teigha(R) Architecture") );
AECDbWindowStylePtr pWindowStyle = idWStyle.openObject( OdDb::kForWrite );
pWindowStyle->SetDescription( OD_T("Window Style Description") );
pWindowStyle->SetDictRecordDescription( OD_T("Dialog caption") );
pWindowStyle->SetAutoAdjustToWidthOfWall( true );
pWindowStyle->SetFrameWidth( 2 );
pWindowStyle->SetFrameDepth( 5 );
pWindowStyle->SetSashWidth( 2 );
pWindowStyle->SetSashDepth( 3 );
pWindowStyle->SetGlassThickness( 1 );
pWindowStyle->SetWindowType( AECDefs::ewtGlider );
pWindowStyle->SetWindowShape( AECDefs::esRectangular );
AECDisplayManager cDM( pDatabase );
AECDbDispPropsWindowPtr pOverrideModel =
AECDbDispPropsWindow::cast( pWindowStyle->OverrideDispProps(
cDM.UpdateDisplayRepresentation( AECDbDispRepWindowModel::desc() ) ).openObject( OdDb::kForWrite ) );
if ( !pOverrideModel.isNull() )
{
pOverrideModel->GetFrameComp()->SetColor( colorAt( 1 ) );
pOverrideModel->GetSashComp()->SetColor( colorAt( 2 ) );
pOverrideModel->GetGlassComp()->SetColor( colorAt( 3 ) );
}
AECDbDispPropsWindowPtr pOverridePlan =
AECDbDispPropsWindow::cast( pWindowStyle->OverrideDispProps(
cDM.UpdateDisplayRepresentation( AECDbDispRepWindowPlan::desc() ) ).openObject( OdDb::kForWrite ) );
if ( !pOverridePlan.isNull() )
{
pOverridePlan->GetFrameComp()->SetColor( colorAt( 1 ) );
pOverridePlan->GetSashComp()->SetColor( colorAt( 2 ) );
pOverridePlan->GetGlassComp()->SetColor( colorAt( 3 ) );
}
return( pWindowStyle->objectId() );
}
Как видно из кода, создается объект AECDbWindowStyle и добавляется в базу. Далее, стилю выставляются некоторые настройки (хотя можно использовать и дефолтные), а затем переопределяются цвета нескольких компонентов для 2D и 3D представления. Компоненты в данном случае – это физические части окна: стекло, рама, створка.
Добавляем окно к первой стене функцией add_window:
OdDbObjectId idWindow01 = add_window( pDatabase, idWindowStyle, idWall1, 10, 10 );
// Inserts a window into a database using the specified window style.
// If idWall parameter is not null it also attaches the window to the wall.
// Returns Object ID of newly created window.
OdDbObjectId add_window( OdDbDatabasePtr pDatabase, const OdDbObjectId& idStyle, const OdDbObjectId& idWall,
double dOffsetAlongX, double dOffsetAlongZ )
{
AECDbWindowPtr pWindow = AECDbWindow::CreateAECObject( pDatabase->getModelSpaceId(), idStyle );
pWindow->SetRise( 10 );
pWindow->SetWidth( 40 );
pWindow->SetHeight( 40 );
pWindow->SetOpenPercent( 60 );
pWindow->SetMeasureTo( AECDefs::eomtOutsideFrame );
pWindow->SetLeaf( 10 );
if ( !idWall.isNull() )
{
pWindow->AttachWallAnchor( idWall );
AECDbAnchorEntToCurvePtr pAnchor = pWindow->GetAnchor().openObject( OdDb::kForWrite );
pAnchor->GetXParams()->SetOffset( dOffsetAlongX );
pAnchor->GetZParams()->SetOffset( dOffsetAlongZ );
}
return( pWindow->objectId() );
}
Функция add_window() аналогична add_wall() с одним различием: тут используется объект anchor.
Мы создаем объект AECDbWindow и помещаем его в model space базы. Затем выставляем некоторые настройки этому конкретному экземпляру AECDbWindow.
После этого мы вставляем окно в стену. Стену и окно связывает особый объект производный от AECDbAnchorEntToCurve. Он содержит отступы вдоль осей x, y, z от начала координатной системы стены до начала координатной системы окна. При вызове AttachWallAnchor() создается экземпляр этого объекта и помещается в базу. Сама стена напрямую не знает вставлены ли в нее окна или нет. Создание анкора затрагивает другой базовый механизм – relation graph. Relation graph содержит взаимоотношения между объектами – кто к кому прикреплен, кто кому принадлежит, кто кем владеет. При модификации стены relation graph получит сообщение о том что объект AECDbWall изменился, пройдет по всем зависимостям и вызовет обновление связанных объектов (в данном случае AECDbWindow). Так, если мы подвинули стену, то окна подвинутся вместе с ней, потому что получат оповещение от relation graph-а. Можно получить доступ к этому графу и запросить зависимости для конкретного объекта. Окно, в принципе, знает к кому оно прикреплено, тк хранит в себе ссылку на созданный анкор.
Смотрим что вышло:
Я специально изменил цвет стен, чтобы окно было лучше видно. В коде у вас сразу стены должны быть голубые, я просто подбирал цвета по ходу написания статьи.
В TA есть масса предопределенных стилей и типов окон, которые задаются через перечисления:
enum WindowType
{
ewtPicture = 1,
ewtSingleHung = 2,
ewtDoubleHung = 3,
ewtAwningTransom = 4,
ewtDoubleCasement = 5,
ewtGlider = 6,
ewtHopperTransom = 7,
ewtPassThrough = 8,
ewtSingleCasement = 9,
ewtSingleHopper = 10,
ewtSingleAwning = 11,
ewtVerticalPivot = 12,
ewtHorizontalPivot = 13,
ewtUnevenSingleHung = 14,
ewtUnevenDoubleHung = 15
};
enum Shape
{
esRectangular = 0,
esRound = 1,
esHalfRound = 2,
esQuarterRound = 3,
esOval = 4,
esArch = 5,
esTrapezoid = 6,
esGothic = 7,
esIsoscelesTriangle = 8,
esRightTriangle = 9,
esPeakPentagon = 10,
esOctagon = 11,
esHexagon = 12,
esCustom = 13
};
Мы выбрали AECDefs::ewtGlider и AECDefs::esRectangular, но, как видите, вариантов форм много. Используя и другие настройки можно создать очень сложный тип окна со внутренним рисунком на стеклах и многими створками. И все это не надо рисовать вручную или реализовывать программно, надо только выставить несколько настроек существующему объекту или стилю.
Вообще, все объекты TA сложны и имеют массу настроек. Это дает довольно широкие возможности прямо «из коробки».
Добавим окна во все прямые стены:
OdDbObjectId idWindow01 = add_window( pDatabase, idWindowStyle, idWall1, 10, 10 );
OdDbObjectId idWindow02 = add_window( pDatabase, idWindowStyle, idWall1, 60, 10 );
OdDbObjectId idWindow03 = add_window( pDatabase, idWindowStyle, idWall1, 10, 60 );
OdDbObjectId idWindow04 = add_window( pDatabase, idWindowStyle, idWall1, 60, 60 );
OdDbObjectId idWindow05 = add_window( pDatabase, idWindowStyle, idWall2, 10, 10 );
OdDbObjectId idWindow06 = add_window( pDatabase, idWindowStyle, idWall2, 60, 10 );
OdDbObjectId idWindow07 = add_window( pDatabase, idWindowStyle, idWall2, 10, 60 );
OdDbObjectId idWindow08 = add_window( pDatabase, idWindowStyle, idWall2, 60, 60 );
OdDbObjectId idWindow09 = add_window( pDatabase, idWindowStyle, idWall3, 10, 10 );
OdDbObjectId idWindow10 = add_window( pDatabase, idWindowStyle, idWall3, 60, 10 );
OdDbObjectId idWindow11 = add_window( pDatabase, idWindowStyle, idWall3, 10, 60 );
OdDbObjectId idWindow12 = add_window( pDatabase, idWindowStyle, idWall3, 60, 60 );
Я не стал дописывать код, но каждым окном можно управлять отдельно: изменить процент его открытия, цвета и тд. А если изменить их стиль, то это изменение отразиться на всех них разом.
Добавление дверей в чертеж
Для полноты картины добавим дверь. Для двери создадим 2D профиль для полотна (створка с дыркой-окошком), затем создать стиль с таким профилем, а потом мы сможем создавать объекты-двери этого стиля. Но можно использовать и дефолтные стили. Двери, как и окна ( и все другие проемы ) присоединяются к стенам с помощью анкора.
// Inserts profile definition into a database.
// Returns Object ID of newly created profile definition.
OdDbObjectId add_profile_def( OdDbDatabasePtr pDatabase )
{
OdDbObjectId idProfDef =
AECDbProfileDef::CreateAECObject( pDatabase, OD_T("Profile Definition Created By Teigha(R) Architecture") );
AECDbProfileDefPtr pProfileDefinition = idProfDef.openObject( OdDb::kForWrite );
AECGe::Profile2D cProfile;
cProfile.resize( 2 );
cProfile[ 0 ].appendVertex( OdGePoint2d( 0, 0 ) );
cProfile[ 0 ].appendVertex( OdGePoint2d( 1, 0 ) );
cProfile[ 0 ].appendVertex( OdGePoint2d( 1, 1 ) );
cProfile[ 0 ].appendVertex( OdGePoint2d( 0, 1 ) );
cProfile[ 0 ].setClosed();
// Forces the contour to be counter-clockwise.
// So if the contour is already ccw this call is not needed.
cProfile[ 0 ].makeCCW();
cProfile[ 1 ].appendVertex( OdGePoint2d( 0.2, 0.2 ) );
cProfile[ 1 ].appendVertex( OdGePoint2d( 0.2, 0.8 ) );
cProfile[ 1 ].appendVertex( OdGePoint2d( 0.8, 0.8 ) );
cProfile[ 1 ].appendVertex( OdGePoint2d( 0.8, 0.2 ) );
cProfile[ 1 ].setClosed();
cProfile[ 1 ].makeCCW( false );
pProfileDefinition->GetProfile()->Init( cProfile );
return( pProfileDefinition->objectId() );
}
// Inserts a door style into a database.
// Returns Object ID of newly created door style.
OdDbObjectId add_door_style( OdDbDatabasePtr pDatabase, const OdDbObjectId& idProfile )
{
OdDbObjectId idDoorStyle =
AECDbDoorStyle::CreateAECObject( pDatabase, OD_T("Door Style Created By Teigha(R) Architecture") );
AECDbDoorStylePtr pDoorStyle = idDoorStyle.openObject( OdDb::kForWrite );
pDoorStyle->SetDescription( OD_T("Door Style Description") );
pDoorStyle->SetDictRecordDescription( OD_T("Dialog caption") );
pDoorStyle->SetAutoAdjustToWidthOfWall( true );
pDoorStyle->SetFrameWidth( 2 );
pDoorStyle->SetFrameDepth( 5 );
pDoorStyle->SetStopWidth( 2 );
pDoorStyle->SetStopDepth( 3 );
pDoorStyle->SetShapeAndType( AECDefs::esCustom, AECDefs::edtSingle );
pDoorStyle->SetProfile( idProfile );
pDoorStyle->SetGlassThickness( 1 );
AECDisplayManager cDM( pDatabase );
AECDbDispPropsDoorPtr pOverrideModel =
AECDbDispPropsDoor::cast( pDoorStyle->OverrideDispProps(
cDM.UpdateDisplayRepresentation( AECDbDispRepDoorModel::desc() ) ).openObject( OdDb::kForWrite ) );
if ( !pOverrideModel.isNull() )
{
pOverrideModel->GetPanelComp()->SetColor( colorAt( 1 ) );
pOverrideModel->GetFrameComp()->SetColor( colorAt( 2 ) );
pOverrideModel->GetStopComp()->SetColor( colorAt( 3 ) );
pOverrideModel->GetSwingComp()->SetColor( colorAt( 4 ) );
pOverrideModel->GetGlassComp()->SetColor( colorAt( 5 ) );
}
AECDbDispPropsDoorPtr pOverridePlan =
AECDbDispPropsDoor::cast( pDoorStyle->OverrideDispProps(
cDM.UpdateDisplayRepresentation( AECDbDispRepDoorPlan::desc() ) ).openObject( OdDb::kForWrite ) );
if ( !pOverridePlan.isNull() )
{
pOverridePlan->GetPanelComp()->SetColor( colorAt( 1 ) );
pOverridePlan->GetFrameComp()->SetColor( colorAt( 2 ) );
pOverridePlan->GetStopComp()->SetColor( colorAt( 3 ) );
pOverridePlan->GetSwingComp()->SetColor( colorAt( 4 ) );
pOverridePlan->GetDirectionComp()->SetColor( colorAt( 5 ) );
}
return( pDoorStyle->objectId() );
}
// Inserts a door into a database using the specified door style.
// If idWall parameter is not null it also attaches the door to the wall.
// Returns Object ID of newly created door.
OdDbObjectId add_door( OdDbDatabasePtr pDatabase, const OdDbObjectId& idStyle, const OdDbObjectId& idWall,
double dOffsetAlongX, double dOffsetAlongZ )
{
AECDbDoorPtr pDoor = AECDbDoor::CreateAECObject( pDatabase->getModelSpaceId(), idStyle );
pDoor->SetRise( 10 );
pDoor->SetWidth( 40 );
pDoor->SetHeight( 50 );
pDoor->SetOpenPercent( 20 );
pDoor->SetMeasureTo( AECDefs::eomtOutsideFrame );
pDoor->SetLeaf( 10 );
if ( !idWall.isNull() )
{
pDoor->AttachWallAnchor( idWall );
AECDbAnchorEntToCurvePtr pAnchor = pDoor->GetAnchor().openObject( OdDb::kForWrite );
pAnchor->GetXParams()->SetOffset( dOffsetAlongX );
pAnchor->GetZParams()->SetOffset( dOffsetAlongZ );
}
return( pDoor->objectId() );
}
И допишем в main:
AECDbWallPtr pWall = idWall4.openObject( OdDb::kForRead );
double dLength = pWall->GetLength();
double dOWidth = 40;
double dL1 = 10;
double dL3 = dLength - dOWidth - 10;
double dL2 = dL1 + dOWidth + (dL3 - (dL1 + 2 * dOWidth)) / 2;
OdDbObjectId idDoor = add_door ( pDatabase, idDoorStyle, idWall4, dL2, 0 );
Здесь есть один новый момент: мы открываем на чтение стену и берем ее длину чтобы посчитать отступ.
В итоге у нас дверь вставилась в круглую стену:
Вставим еще окона в круглую стену:
OdDbObjectId idWindow13 = add_window ( pDatabase, idWindowStyle, idWall4, dL1, 10 );
OdDbObjectId idWindow14 = add_window ( pDatabase, idWindowStyle, idWall4, dL3, 10 );
OdDbObjectId idWindow15 = add_window ( pDatabase, idWindowStyle, idWall4, dL1, 60 );
OdDbObjectId idWindow16 = add_window ( pDatabase, idWindowStyle, idWall4, dL2, 60 );
OdDbObjectId idOpening = add_window ( pDatabase, idWindowStyle, idWall4, dL3, 60 );
В результате у нас получился очень дырявый домик без крыши и пола:
Добавление крыши в чертеж
Напишем функцию add_roof()
void add_roof( OdDbDatabasePtr pDatabase )
{
AECGe::Profile2D cProfile;
cProfile.resize( 1 );
cProfile.front().appendVertex( OdGePoint2d( 0, 0 ) );
cProfile.front().appendVertex( OdGePoint2d( 0, 110 ) );
cProfile.front().appendVertex( OdGePoint2d( 110, 110 ) );
cProfile.front().appendVertex( OdGePoint2d( 110, 0 ), -1 );
cProfile.front().setClosed();
cProfile.front().makeCCW();
AECDbRoofPtr pRoof =
AECDbRoof::CreateAECObject( pDatabase->getModelSpaceId() );
// Initialize roof profile.
// By default all edges of Roof Profile have single slope of 45 degrees.
pRoof->GetProfile()->Init( cProfile );
pRoof->SetThickness( 2 );
//// Manually modify Roof Segments.
AECGeRingSubPtr pRoofLoop = pRoof->GetProfile()->GetRingByIndex( 0 );
if ( !pRoofLoop.isNull() )
{
OdUInt32 i, iSize = pRoofLoop->GetSegmentCount();
for ( i = 0; i < iSize; i++ )
{
AECGeRoofSegmentSubPtr pSeg = pRoofLoop->GetSegments()->GetAt( i );
pSeg->SetFaceCount(1);
pSeg->SetFaceHeightByIndex(0, 110);
pSeg->SetBaseHeight(0);
pSeg->SetOverhang(10.0);
pSeg->SetFaceSlopeByIndex(0, OdaPI4);
pSeg->SetSegmentCount(10);
}
}
pRoof->setColorIndex( 3 );
}
Крыша создается на основе двумерного профиля, направление обхода которого направлено против часовой стрелки. Вызов makeCCW() как раз преобразует направление обхода, если оно было противоположным. Это важно, тк алгоритм ждет на вход именно такой профиль и не сработает в противном случае.
Профиль у нас совпадает с центральной линией стен. Далее, для каждого отрезка профиля задается наклон ската крыши, количество фейсов которыми будет представлен скат, подъем начала ската по Z относительно плоскости OXY (SetFaceHeightByIndex), и свес крыши (overhang). SetSegmentCount() работает только для отрезков у которых есть кривизна. Эта величина задает точность аппроксимации – сколько прямых отрезков будет использовано чтобы аппроксимировать полукруглый сегмент.
Получилась такая крыша:
Вариаций настройки крыш много и можно создать крышу почти любой формы – двускатные, многоскатные, шатровые и так далее. Каждый скат представляет из себя отдельный объект RoofSlab который можно редактировать вручную.
Добавление перекрытия в чертеж
Осталось добавить хотябы небольшую имитацию полафундамента. Для этого используем объект slab ( перекрытие ). Напишем функцию add_slab
void add_slab( OdDbDatabasePtr pDatabase )
{
OdDbObjectId idStyle =
AECDbSlabStyle::GetAECObject( pDatabase, OD_T("Slab Style") );
if ( idStyle.isNull() )
{
idStyle = AECDbSlabStyle::CreateAECObject( pDatabase, OD_T("Slab Style") );
}
AECDbSlabStylePtr pStyle =
idStyle.openObject( OdDb::kForWrite );
if ( !pStyle.isNull() )
{
pStyle->GetComponents()->Clear();
AECSlabStyleCompPtr pCmp = AECSlabStyleComp::createObject();
pCmp->SetName( OD_T("Base") );
pCmp->GetPosition()->GetThickness()->SetUseBaseValue( false );
pCmp->GetPosition()->GetThickness()->SetBaseValue( 6 );
pCmp->GetPosition()->GetThicknessOffset()->SetUseBaseValue( false );
pCmp->GetPosition()->GetThicknessOffset()->SetBaseValue( - 6 );
pStyle->GetComponents()->Insert( pCmp );
}
AECDbSlabPtr pSlab =
AECDbSlab::CreateAECObject( pDatabase->getModelSpaceId(), idStyle );
{
AECGe::Profile2D cBase;
cBase.resize( 1 );
cBase.front().appendVertex( OdGePoint2d( -5, -5 ), 1 );
cBase.front().appendVertex( OdGePoint2d( 115, -5 ) );
cBase.front().appendVertex( OdGePoint2d( 115, 115 ) );
cBase.front().appendVertex( OdGePoint2d( -5, 115 ) );
cBase.front().setClosed();
cBase.front().makeCCW();
pSlab->GetSlabFace()->Init( cBase );
}
pSlab->SetThickness( 5 );
pSlab->SetVerticalOffset( 0 );
pSlab->SetHorizontalOffset( 0 );
pSlab->SetPivotPoint( OdGePoint3d::kOrigin );
AECDisplayManager cDM( pDatabase );
AECDbDispPropsSlabPtr pOverrideModel =
AECDbDispPropsSlab::cast( pSlab->OverrideDispProps(
cDM.UpdateDisplayRepresentation( AECDbDispRepSlabModel::desc() ) ).openObject( OdDb::kForWrite ) );
if ( !pOverrideModel.isNull() )
{
pOverrideModel->GetBoundaryCompByIndex( 0 )->SetColor( colorAt( 1 ) );
pOverrideModel->GetBaselineComp()->SetColor( colorAt( 4 ) );
pOverrideModel->GetPivotPointComp()->SetColor( colorAt( 5 ) );
pOverrideModel->GetFasciaComp()->SetColor( colorAt( 6 ) );
pOverrideModel->GetSoffitComp()->SetColor( colorAt( 7 ) );
pOverrideModel->GetShrinkWrapBodyComp()->SetColor( colorAt( 8 ) );
}
AECDbDispPropsSlabPlanPtr pOverridePlan =
AECDbDispPropsSlabPlan::cast( pSlab->OverrideDispProps(
cDM.UpdateDisplayRepresentation( AECDbDispRepSlabPlan::desc() ) ).openObject( OdDb::kForWrite ) );
if ( !pOverridePlan.isNull() )
{
pOverridePlan->SetIsOverrideCutPlane( false );
pOverridePlan->GetHatchComp()->SetColor( colorAt( 1 ) );
pOverridePlan->GetBelowCutPlaneBodyComp()->SetColor( colorAt( 2 ) );
pOverridePlan->GetAboveCutPlaneBodyComp()->SetColor( colorAt( 3 ) );
pOverridePlan->GetBelowCutPlaneOutlineComp()->SetColor( colorAt( 4 ) );
pOverridePlan->GetAboveCutPlaneOutlineComp()->SetColor( colorAt( 5 ) );
}
}
В данном случае мы создаем новый стиль перекрытия и добавляем в него компоненты. Компонент представляет из себя кусок перекрытия и содержит такие параметры как толщина, подъем над OXY, имя, материал, индекс и пр. Перекрытие может содержать несколько компонетов, которые различаются по своим настройкам. Например, если у них разный отступ от OXY, то один объект-перекрытие такого стиля может отрисовать все полы и потолки в многоэтажном здании.
Настройки стиля применяются к конкретному объекту, который хранит форму данного перекрытия. В данном случае мы создаем slab, и инициализируем его профиль таким же контуром как низ стен, только с небольшим отступом по краям.
Далее идет работа с display manager чтобы переопределить цвета разных компонентов перекрытия.
В финале наш дом будет выглядить вот так:
Для теста, попробуем загрузить получившийся dwg файл в Autodesk ACA:
Вот наш дом, загруженный в Autocad Architecture. Выглядит еще лучше.
OdDbObjectId add_wall_style( OdDbDatabasePtr pDatabase )
{
OdDbObjectId idResult =
AECDbWallStyle::CreateAECObject( pDatabase, OD_T("Wall Style Created By Teigha(R) Architecture") );
AECDbWallStylePtr pWallStyle =
idResult.openObject( OdDb::kForWrite );
pWallStyle->SetDescription( OD_T("Wall Style Description") );
pWallStyle->SetDictRecordDescription( OD_T("Dialog caption") );
pWallStyle->SetWallWidth( 4 );
pWallStyle->SetWallWidthUsed( true );
pWallStyle->SetBaseHeight( 110 );
pWallStyle->SetBaseHeightUsed( true );
pWallStyle->SetJustification( AECDefs::ewjLeft );
pWallStyle->SetJustificationUsed( true );
pWallStyle->SetAutomaticCleanups( true );
pWallStyle->SetAutomaticCleanupsUsed( true );
pWallStyle->SetCleanupRadius( 4 );
pWallStyle->SetCleanupRadiusUsed( true );
pWallStyle->SetFloorLineOffset( 3 );
pWallStyle->SetFloorLineOffsetUsed( false );
pWallStyle->SetRoofLineOffset( -3 );
pWallStyle->SetRoofLineOffsetUsed( false );
AECDisplayManager cDM( pDatabase );
AECDbDispPropsWallModelPtr pOverrideModel =
AECDbDispPropsWallModel::cast( pWallStyle->OverrideDispProps(
cDM.UpdateDisplayRepresentation( AECDbDispRepWallModel::desc() ) ).openObject( OdDb::kForWrite ) );
if ( !pOverrideModel.isNull() )
{
pOverrideModel->SetIsDisplayOpeningEndcaps( false );
pOverrideModel->GetBoundaryCompByIndex( 0 )->SetColor( colorAt( 4 ) );
}
AECDbDispPropsWallPtr pOverridePlan =
AECDbDispPropsWall::cast( pWallStyle->OverrideDispProps(
cDM.UpdateDisplayRepresentation( AECDbDispRepWallPlan::desc() ) ).openObject( OdDb::kForWrite ) );
if ( !pOverridePlan.isNull() )
{
pOverridePlan->GetBoundaryCompByIndex( 0 )->SetColor( colorAt( 4 ) );
}
return( pWallStyle->objectId() );
}
OdDbObjectId add_wall( OdDbDatabasePtr pDatabase, const OdDbObjectId& idStyle,
const OdGePoint2d& ptStart, const OdGePoint2d& ptEnd, double dBulge = 0 )
{
AECDbWallPtr pWall =
AECDbWall::CreateAECObject( pDatabase->getModelSpaceId(), idStyle );
pWall->Set( ptStart, ptEnd, dBulge );
pWall->SetDescription( OD_T("A Wall") );
/*AECGe::Contour2D cContour;
cContour.appendVertex( OdGePoint2d( 0, pWall->GetHeight() ) );
cContour.appendVertex( OdGePoint2d( pWall->GetLength() / 2, pWall->GetHeight() * 1.5 ) );
cContour.appendVertex( OdGePoint2d( pWall->GetLength(), pWall->GetHeight() ) );
AECWallCutLineSubPtr pTop = pWall->CreateTopCutLine();
pTop->SetRawCutLine( cContour );*/
return( pWall->objectId() );
}
OdDbObjectId add_window_style( OdDbDatabasePtr pDatabase )
{
OdDbObjectId idWStyle =
AECDbWindowStyle::CreateAECObject( pDatabase, OD_T("Window Style Created By Teigha(R) Architecture") );
AECDbWindowStylePtr pWindowStyle = idWStyle.openObject( OdDb::kForWrite );
pWindowStyle->SetDescription( OD_T("Window Style Description") );
pWindowStyle->SetDictRecordDescription( OD_T("Dialog caption") );
pWindowStyle->SetAutoAdjustToWidthOfWall( true );
pWindowStyle->SetFrameWidth( 2 );
pWindowStyle->SetFrameDepth( 5 );
pWindowStyle->SetSashWidth( 2 );
pWindowStyle->SetSashDepth( 3 );
pWindowStyle->SetGlassThickness( 1 );
pWindowStyle->SetWindowType( AECDefs::ewtGlider );
pWindowStyle->SetWindowShape( AECDefs::esRectangular );
AECDisplayManager cDM( pDatabase );
AECDbDispPropsWindowPtr pOverrideModel =
AECDbDispPropsWindow::cast( pWindowStyle->OverrideDispProps(
cDM.UpdateDisplayRepresentation( AECDbDispRepWindowModel::desc() ) ).openObject( OdDb::kForWrite ) );
if ( !pOverrideModel.isNull() )
{
pOverrideModel->GetFrameComp()->SetColor( colorAt( 1 ) );
pOverrideModel->GetSashComp()->SetColor( colorAt( 2 ) );
pOverrideModel->GetGlassComp()->SetColor( colorAt( 3 ) );
}
AECDbDispPropsWindowPtr pOverridePlan =
AECDbDispPropsWindow::cast( pWindowStyle->OverrideDispProps(
cDM.UpdateDisplayRepresentation( AECDbDispRepWindowPlan::desc() ) ).openObject( OdDb::kForWrite ) );
if ( !pOverridePlan.isNull() )
{
pOverridePlan->GetFrameComp()->SetColor( colorAt( 1 ) );
pOverridePlan->GetSashComp()->SetColor( colorAt( 2 ) );
pOverridePlan->GetGlassComp()->SetColor( colorAt( 3 ) );
}
return( pWindowStyle->objectId() );
}
// Inserts a window into a database using the specified window style.
// If idWall parameter is not null it also attaches the window to the wall.
// Returns Object ID of newly created window.
OdDbObjectId add_window( OdDbDatabasePtr pDatabase, const OdDbObjectId& idStyle, const OdDbObjectId& idWall,
double dOffsetAlongX, double dOffsetAlongZ )
{
AECDbWindowPtr pWindow = AECDbWindow::CreateAECObject( pDatabase->getModelSpaceId(), idStyle );
pWindow->SetRise( 10 );
pWindow->SetWidth( 40 );
pWindow->SetHeight( 40 );
pWindow->SetOpenPercent( 60 );
pWindow->SetMeasureTo( AECDefs::eomtOutsideFrame );
pWindow->SetLeaf( 10 );
if ( !idWall.isNull() )
{
pWindow->AttachWallAnchor( idWall );
AECDbAnchorEntToCurvePtr pAnchor = pWindow->GetAnchor().openObject( OdDb::kForWrite );
pAnchor->GetXParams()->SetOffset( dOffsetAlongX );
pAnchor->GetZParams()->SetOffset( dOffsetAlongZ );
}
return( pWindow->objectId() );
}
// Inserts profile definition into a database.
// Returns Object ID of newly created profile definition.
OdDbObjectId add_profile_def( OdDbDatabasePtr pDatabase )
{
OdDbObjectId idProfDef =
AECDbProfileDef::CreateAECObject( pDatabase, OD_T("Profile Definition Created By Teigha(R) Architecture") );
AECDbProfileDefPtr pProfileDefinition = idProfDef.openObject( OdDb::kForWrite );
AECGe::Profile2D cProfile;
cProfile.resize( 2 );
cProfile[ 0 ].appendVertex( OdGePoint2d( 0, 0 ) );
cProfile[ 0 ].appendVertex( OdGePoint2d( 1, 0 ) );
cProfile[ 0 ].appendVertex( OdGePoint2d( 1, 1 ) );
cProfile[ 0 ].appendVertex( OdGePoint2d( 0, 1 ) );
cProfile[ 0 ].setClosed();
// Forces the contour to be counter-clockwise.
// So if the contour is already ccw this call is not needed.
cProfile[ 0 ].makeCCW();
cProfile[ 1 ].appendVertex( OdGePoint2d( 0.2, 0.2 ) );
cProfile[ 1 ].appendVertex( OdGePoint2d( 0.2, 0.8 ) );
cProfile[ 1 ].appendVertex( OdGePoint2d( 0.8, 0.8 ) );
cProfile[ 1 ].appendVertex( OdGePoint2d( 0.8, 0.2 ) );
cProfile[ 1 ].setClosed();
cProfile[ 1 ].makeCCW( false );
pProfileDefinition->GetProfile()->Init( cProfile );
return( pProfileDefinition->objectId() );
}
// Inserts a door style into a database.
// Returns Object ID of newly created door style.
OdDbObjectId add_door_style( OdDbDatabasePtr pDatabase, const OdDbObjectId& idProfile )
{
OdDbObjectId idDoorStyle =
AECDbDoorStyle::CreateAECObject( pDatabase, OD_T("Door Style Created By Teigha(R) Architecture") );
AECDbDoorStylePtr pDoorStyle = idDoorStyle.openObject( OdDb::kForWrite );
pDoorStyle->SetDescription( OD_T("Door Style Description") );
pDoorStyle->SetDictRecordDescription( OD_T("Dialog caption") );
pDoorStyle->SetAutoAdjustToWidthOfWall( true );
pDoorStyle->SetFrameWidth( 2 );
pDoorStyle->SetFrameDepth( 5 );
pDoorStyle->SetStopWidth( 2 );
pDoorStyle->SetStopDepth( 3 );
pDoorStyle->SetShapeAndType( AECDefs::esCustom, AECDefs::edtSingle );
pDoorStyle->SetProfile( idProfile );
pDoorStyle->SetGlassThickness( 1 );
AECDisplayManager cDM( pDatabase );
AECDbDispPropsDoorPtr pOverrideModel =
AECDbDispPropsDoor::cast( pDoorStyle->OverrideDispProps(
cDM.UpdateDisplayRepresentation( AECDbDispRepDoorModel::desc() ) ).openObject( OdDb::kForWrite ) );
if ( !pOverrideModel.isNull() )
{
pOverrideModel->GetPanelComp()->SetColor( colorAt( 1 ) );
pOverrideModel->GetFrameComp()->SetColor( colorAt( 2 ) );
pOverrideModel->GetStopComp()->SetColor( colorAt( 3 ) );
pOverrideModel->GetSwingComp()->SetColor( colorAt( 4 ) );
pOverrideModel->GetGlassComp()->SetColor( colorAt( 5 ) );
}
AECDbDispPropsDoorPtr pOverridePlan =
AECDbDispPropsDoor::cast( pDoorStyle->OverrideDispProps(
cDM.UpdateDisplayRepresentation( AECDbDispRepDoorPlan::desc() ) ).openObject( OdDb::kForWrite ) );
if ( !pOverridePlan.isNull() )
{
pOverridePlan->GetPanelComp()->SetColor( colorAt( 1 ) );
pOverridePlan->GetFrameComp()->SetColor( colorAt( 2 ) );
pOverridePlan->GetStopComp()->SetColor( colorAt( 3 ) );
pOverridePlan->GetSwingComp()->SetColor( colorAt( 4 ) );
pOverridePlan->GetDirectionComp()->SetColor( colorAt( 5 ) );
}
return( pDoorStyle->objectId() );
}
// Inserts a door into a database using the specified door style.
// If idWall parameter is not null it also attaches the door to the wall.
// Returns Object ID of newly created door.
OdDbObjectId add_door( OdDbDatabasePtr pDatabase, const OdDbObjectId& idStyle, const OdDbObjectId& idWall,
double dOffsetAlongX, double dOffsetAlongZ )
{
AECDbDoorPtr pDoor = AECDbDoor::CreateAECObject( pDatabase->getModelSpaceId(), idStyle );
pDoor->SetRise( 10 );
pDoor->SetWidth( 40 );
pDoor->SetHeight( 50 );
pDoor->SetOpenPercent( 20 );
pDoor->SetMeasureTo( AECDefs::eomtOutsideFrame );
pDoor->SetLeaf( 10 );
if ( !idWall.isNull() )
{
pDoor->AttachWallAnchor( idWall );
AECDbAnchorEntToCurvePtr pAnchor = pDoor->GetAnchor().openObject( OdDb::kForWrite );
pAnchor->GetXParams()->SetOffset( dOffsetAlongX );
pAnchor->GetZParams()->SetOffset( dOffsetAlongZ );
}
return( pDoor->objectId() );
}
void add_roof( OdDbDatabasePtr pDatabase )
{
AECGe::Profile2D cProfile;
cProfile.resize( 1 );
cProfile.front().appendVertex( OdGePoint2d( 0, 0 ) );
cProfile.front().appendVertex( OdGePoint2d( 0, 110 ) );
cProfile.front().appendVertex( OdGePoint2d( 110, 110 ) );
cProfile.front().appendVertex( OdGePoint2d( 110, 0 ), -1 );
cProfile.front().setClosed();
cProfile.front().makeCCW();
AECDbRoofPtr pRoof =
AECDbRoof::CreateAECObject( pDatabase->getModelSpaceId() );
// Initialize roof profile.
// By default all edges of Roof Profile have single slope of 45 degrees.
pRoof->GetProfile()->Init( cProfile );
pRoof->SetThickness( 2 );
//// Manually modify Roof Segments.
AECGeRingSubPtr pRoofLoop = pRoof->GetProfile()->GetRingByIndex( 0 );
if ( !pRoofLoop.isNull() )
{
OdUInt32 i, iSize = pRoofLoop->GetSegmentCount();
for ( i = 0; i < iSize; i++ )
{
AECGeRoofSegmentSubPtr pSeg = pRoofLoop->GetSegments()->GetAt( i );
pSeg->SetFaceCount(1);
pSeg->SetFaceHeightByIndex(0, 110);
pSeg->SetBaseHeight(0);
pSeg->SetOverhang(10.0);
pSeg->SetFaceSlopeByIndex(0, OdaPI4);
pSeg->SetSegmentCount(10);
}
}
pRoof->setColorIndex( 3 );
}
void add_slab( OdDbDatabasePtr pDatabase )
{
OdDbObjectId idStyle =
AECDbSlabStyle::GetAECObject( pDatabase, OD_T("Slab Style") );
if ( idStyle.isNull() )
{
idStyle = AECDbSlabStyle::CreateAECObject( pDatabase, OD_T("Slab Style") );
}
AECDbSlabStylePtr pStyle =
idStyle.openObject( OdDb::kForWrite );
if ( !pStyle.isNull() )
{
pStyle->GetComponents()->Clear();
AECSlabStyleCompPtr pCmp = AECSlabStyleComp::createObject();
pCmp->SetName( OD_T("Base") );
pCmp->GetPosition()->GetThickness()->SetUseBaseValue( false );
pCmp->GetPosition()->GetThickness()->SetBaseValue( 6 );
pCmp->GetPosition()->GetThicknessOffset()->SetUseBaseValue( false );
pCmp->GetPosition()->GetThicknessOffset()->SetBaseValue( - 6 );
pStyle->GetComponents()->Insert( pCmp );
}
AECDbSlabPtr pSlab =
AECDbSlab::CreateAECObject( pDatabase->getModelSpaceId(), idStyle );
{
AECGe::Profile2D cBase;
cBase.resize( 1 );
cBase.front().appendVertex( OdGePoint2d( -5, -5 ), 1 );
cBase.front().appendVertex( OdGePoint2d( 115, -5 ) );
cBase.front().appendVertex( OdGePoint2d( 115, 115 ) );
cBase.front().appendVertex( OdGePoint2d( -5, 115 ) );
cBase.front().setClosed();
cBase.front().makeCCW();
pSlab->GetSlabFace()->Init( cBase );
}
pSlab->SetThickness( 5 );
pSlab->SetVerticalOffset( 0 );
pSlab->SetHorizontalOffset( 0 );
pSlab->SetPivotPoint( OdGePoint3d::kOrigin );
AECDisplayManager cDM( pDatabase );
AECDbDispPropsSlabPtr pOverrideModel =
AECDbDispPropsSlab::cast( pSlab->OverrideDispProps(
cDM.UpdateDisplayRepresentation( AECDbDispRepSlabModel::desc() ) ).openObject( OdDb::kForWrite ) );
if ( !pOverrideModel.isNull() )
{
pOverrideModel->GetBoundaryCompByIndex( 0 )->SetColor( colorAt( 1 ) );
pOverrideModel->GetBaselineComp()->SetColor( colorAt( 4 ) );
pOverrideModel->GetPivotPointComp()->SetColor( colorAt( 5 ) );
pOverrideModel->GetFasciaComp()->SetColor( colorAt( 6 ) );
pOverrideModel->GetSoffitComp()->SetColor( colorAt( 7 ) );
pOverrideModel->GetShrinkWrapBodyComp()->SetColor( colorAt( 8 ) );
}
AECDbDispPropsSlabPlanPtr pOverridePlan =
AECDbDispPropsSlabPlan::cast( pSlab->OverrideDispProps(
cDM.UpdateDisplayRepresentation( AECDbDispRepSlabPlan::desc() ) ).openObject( OdDb::kForWrite ) );
if ( !pOverridePlan.isNull() )
{
pOverridePlan->SetIsOverrideCutPlane( false );
pOverridePlan->GetHatchComp()->SetColor( colorAt( 1 ) );
pOverridePlan->GetBelowCutPlaneBodyComp()->SetColor( colorAt( 2 ) );
pOverridePlan->GetAboveCutPlaneBodyComp()->SetColor( colorAt( 3 ) );
pOverridePlan->GetBelowCutPlaneOutlineComp()->SetColor( colorAt( 4 ) );
pOverridePlan->GetAboveCutPlaneOutlineComp()->SetColor( colorAt( 5 ) );
}
}
class MyServices : public ExSystemServices, public ExHostAppServices
{
protected:
ODRX_USING_HEAP_OPERATORS(ExSystemServices);
};
int wmain(int argc, wchar_t* argv[])
{
// Initialize TD with system services.
// And create single instance of hostapp services
// for TD database creation.
OdStaticRxObject<MyServices> svcs;
odInitialize( &svcs );
odgsInitialize();
// Loading of all public Teigha Architecture DRX modules.
// Note that not all calls are necessary for some of them depend on others
// but here we list all of them.
//
// If a program uses TD doesn't modify or create binary files
// it may not load any of DRX modules on start because they will be loaded automatically.
// But if a program modifies or creates binary files then it is highly recommended
// to load all DRX modules program uses.
::odrxDynamicLinker()->loadApp( OD_T("AecBase") );
::odrxDynamicLinker()->loadApp( OD_T("AecArchBase") );
::odrxDynamicLinker()->loadApp( OD_T("AecArchDACHBase") );
::odrxDynamicLinker()->loadApp( OD_T("AecScheduleData") );
::odrxDynamicLinker()->loadApp( OD_T("AecSchedule") );
::odrxDynamicLinker()->loadApp( OD_T("AecStructureBase") );
// Create empty TD database.
OdDbDatabasePtr pDatabase = svcs.createDatabase();;
// Initialize database with default Teigha Architecture content.
AECArchDACHBaseDatabase( pDatabase ).Init();
AECScheduleDatabase( pDatabase ).Init();
AECStructureBaseDatabase( pDatabase ).Init();
init_display_system( pDatabase );
OdDbObjectId idWallStyle = add_wall_style( pDatabase );
OdDbObjectId idWall1 = add_wall( pDatabase, idWallStyle,
OdGePoint2d( 0, 0 ), OdGePoint2d( 0, 110 ) );
OdDbObjectId idWall2 = add_wall( pDatabase, idWallStyle,
OdGePoint2d( 0, 110 ), OdGePoint2d( 110, 110 ) );
OdDbObjectId idWall3 = add_wall( pDatabase, idWallStyle,
OdGePoint2d( 110, 110 ), OdGePoint2d( 110, 0 ) );
OdDbObjectId idWall4 = add_wall( pDatabase, idWallStyle,
OdGePoint2d( 110, 0 ), OdGePoint2d( 0, 0 ), -1 );
AECDbWallPtr ptWall = AECDbWall::cast( idWall1.openObject( OdDb::kForRead ) );
OdDbObjectId idWindowStyle = add_window_style( pDatabase );
OdDbObjectId idWindow01 = add_window( pDatabase, idWindowStyle, idWall1, 10, 10 );
OdDbObjectId idWindow02 = add_window( pDatabase, idWindowStyle, idWall1, 60, 10 );
OdDbObjectId idWindow03 = add_window( pDatabase, idWindowStyle, idWall1, 10, 60 );
OdDbObjectId idWindow04 = add_window( pDatabase, idWindowStyle, idWall1, 60, 60 );
OdDbObjectId idWindow05 = add_window( pDatabase, idWindowStyle, idWall2, 10, 10 );
OdDbObjectId idWindow06 = add_window( pDatabase, idWindowStyle, idWall2, 60, 10 );
OdDbObjectId idWindow07 = add_window( pDatabase, idWindowStyle, idWall2, 10, 60 );
OdDbObjectId idWindow08 = add_window( pDatabase, idWindowStyle, idWall2, 60, 60 );
OdDbObjectId idWindow09 = add_window( pDatabase, idWindowStyle, idWall3, 10, 10 );
OdDbObjectId idWindow10 = add_window( pDatabase, idWindowStyle, idWall3, 60, 10 );
OdDbObjectId idWindow11 = add_window( pDatabase, idWindowStyle, idWall3, 10, 60 );
OdDbObjectId idWindow12 = add_window( pDatabase, idWindowStyle, idWall3, 60, 60 );
OdDbObjectId idProfile =
add_profile_def( pDatabase );
OdDbObjectId idDoorStyle =
add_door_style( pDatabase, idProfile );
AECDbWallPtr pWall = idWall4.openObject( OdDb::kForRead );
double dLength = pWall->GetLength();
double dOWidth = 40;
double dL1 = 10;
double dL3 = dLength - dOWidth - 10;
double dL2 = dL1 + dOWidth + (dL3 - (dL1 + 2 * dOWidth)) / 2;
OdDbObjectId idDoor = add_door ( pDatabase, idDoorStyle, idWall4, dL2, 0 );
OdDbObjectId idWindow13 = add_window ( pDatabase, idWindowStyle, idWall4, dL1, 10 );
OdDbObjectId idWindow14 = add_window ( pDatabase, idWindowStyle, idWall4, dL3, 10 );
OdDbObjectId idWindow15 = add_window ( pDatabase, idWindowStyle, idWall4, dL1, 60 );
OdDbObjectId idWindow16 = add_window ( pDatabase, idWindowStyle, idWall4, dL2, 60 );
OdDbObjectId idOpening = add_window ( pDatabase, idWindowStyle, idWall4, dL3, 60 );
add_roof(pDatabase);
add_slab(pDatabase);
// Perform "zoom extents" on model space.
{
OdDbViewportTablePtr pVT =
pDatabase->getViewportTableId().openObject( OdDb::kForRead );
OdDbViewportTableRecordPtr pV =
pVT->getActiveViewportId().openObject( OdDb::kForWrite );
pV->zoomExtents();
}
OdWrFileBuf cBuffer( "H:\TA_test.dwg" );
pDatabase->writeFile( &cBuffer, OdDb::kDwg, OdDb::kDHL_CURRENT );
odgsUninitialize();
odUninitialize();
return 0;
}
Итог
С помощью Teigha мы создали пустую базу, инициализировали ее для работы с архитектурными объектами, создали несколько самых употребимых типов объектов и успешно сохранили это все в dwg файл последнего формата.
Конечно, очень многие моменты я упростил и описал крайне поверхностно, но цель этой статьи – просто продемонстрировать возможности TA и дать общее представление о Teigha как об альтернативной возможности для работы с dwg файлами и объектами Автокада.
Автор: aabramovsky