In this article, I will introduce you to Teigha, a library that provides an alternative way of handling DWG files and ACA objects. We’ll write a small piece of code that creates a house out of ACA objects.
If you want to handle DWG files and AutoCAD objects programmatically, the only platform options areObjectARX and Teigha. All third-party components and applications that can read and write DWG files use Teigha as a base.
Teigha Architecture is a set of libraries that enable you to read, write, and handle objects of the original AutoCAD and its derivatives like ACA. The libraries also provide many auxiliary mechanisms to help you handle AutoCAD objects and rendering devices to render the DWG database.
Teigha’s main features are:
- Support DWG, DXF, BDXF, DGN file formats
- Render drawing files using GDI, OpenGL, or DirectX with the ability to select entities.
- 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.
- Export to SVG, PDF, DWF, BMP, STL, DAE (Collada).
- 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 ACIS/Parasolid data internally, including rendering (wireframe and shaded) for embedded 3D solids and access to the underlying boundary representation data.
- Implement custom commands.
Why Should Designers Consider Teigha?
AutoCAD saves its data to .dwg file format. DWG is a proprietary binary file format used for storing two and three-dimensional design data and metadata. DWG is an industrial standard. Thousands and thousands of DWG drawings exist and need to be supported. Besides AutoCAD, there is only one library which can load\save and manipulate objects stored as a DWG file: Teigha.
There are several reasons to use Teigha rather than AutoCAD ObjectARX:
- Cost: If you develop an application that works with information stored as a .dwg file, you have two options: develop an AutoCAD plugin or develop your application based on Teigha. Developing AutoCAD plugin means that all of your customers have to own an AutoCAD license, which cost a fortune. Teigha pricing is very affordable.
- Flexibility: Based on Teigha you can develop your own CAD application from scratch designed to fulfill your customer’s certain needs. You are not bound to AutoCAD core and GUI. You can develop your own particular GUI for your CAD application. Or, if you need a CAD-like GUI, you can use one of the Teigha-based cheap CAD applications available on the market ( like BricsCAD, ZWCad ) as a host to your plugin.
- Many supported platforms: for example, you can build a standalone CAD application for Mobile platforms like IOS and Android, which is very demanded in certain areas.
- Source code access: Teigha sources are available for founding members. If you need a specific functionality which is not provided or change existing functionality for your needs – you can buy Teigha sources and change anything you need.
Autodesk offers RealDWG - a software library that allows C++ and .NET developers to read and write AutoCAD® software DWG and DXF files. But again, it is much more expensive, has annual fees and provides only load\save functionality for DWG files, while Teigha provides rendering devices and other useful API for building your own CAD application.
An Alternative to AutoCAD and ObjectARX
For starters, let’s load and render a DWG drawing. We can use the standard sample from Teigha’s distribution package:
We’ve successfully loaded and rendered the DWG file. (By the way, the picture that you could see in the beginning of this article is the result of rendering a different file.) The standard sample is a windowed C++ application. It allows you to load, view, and edit DWG files without using AutoCAD. I should also mention that the API of AutoCAD objects is almost identical to the API of Teigha objects, so you can easily modify an existing ObjectARX plugin to support Teigha-based applications.
Most alternatives to AutoCAD, such as BricsCAD, ZWCad, and IntelliCAD, use Teigha to handle the DWG format and ACAD objects.
The Peculiarities of AutoCAD Architecture Objects
Now I will introduce you to architectural objects and how to handle them. AutoCAD Architecture handles special high-level objects intended for architectural design: walls, doors, windows, roofs, etc. The objects are viewport dependent, that is, they can be rendered differently depending on the camera direction. Objects are style based. A certain style is assigned to each object. If you change the style, all objects with that style will change. Every object consists of components, and every component has its own visual settings: color, line type, material, and scale. For example, a 3D door may consist of a frame, a door panel, and a sheet of glass. For different view modes, the object is rendered using different geometries, so the number and settings of components are different for different presentations.
Teigha for Architecture (TA)
To handle architectural objects, you can use ACA and its open API that enables you to create plugins. Alternatively, you can use Teigha for Architecture, a library developed by the Open Design Alliance.
Teigha Architecture is a С++ class library that implements all basic primitives of ACA, such as walls, windows, doors, roofs, beams, openings, etc. The library allows you to load these objects from any version of the DWG format and write (convert) to the latest DWG version. TA can render any primitives in different views and configurations. ACA objects interact with each other, so TA also supports ACA’s auxiliary classes and mechanisms, such as anchor, display manager, property sets, relation graph, etc.
Starting with Teigha Architecture API
I’ve already described Teigha’s main features. Let’s take a closer look at Teigha and write our first command, a very simple one. I’ll be using Visual Studio 2005, which is obviously outdated, but the Teigha libraries are multi-platform, and the distribution package includes a solution generator for all Visual Studio versions up to 2015. Depending on your license type, you will have access to the complete code of the whole library, or only to the pre-built binary files and header files.
The set of TA libraries looks like this:
Basically, these are regular Windows DLL files (you can also build them for other platforms: iOS, Linux, UNIX, etc.). You can find LIB files for them in a separate folder. In addition to TA, we will need the Teigha Core libraries, because TA is an extension on top of the Core objects. Core implements the main mechanisms and objects of the original AutoCAD.
Initializing Тeigha Architecture
To initialize the library, we need a class that performs platform-specific operations on files.
class MyServices : public ExSystemServices, public ExHostAppServices
{
protected:
ODRX_USING_HEAP_OPERATORS(ExSystemServices);
};
The distribution package includes two ready-made extensions for Windows, ExSystemServices and ExHostAppServices, which we can use in this case. Then we need to initialize the library and the graphics subsystem:
OdStaticRxObject<MyServices> svcs;
odInitialize( &svcs );
odgsInitialize();
_OdStaticRxObject_
adds the _addRef/Release_
logic to an object. The library saves the reference to the MyServices object and uses that object for platform-specific operations.
Let’s initialize the ТА libraries:
// 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, etc. are the TX modules (that is, DLL libraries) shown in the screenshot above. They have already been linked using LIB files, but that is not enough. We also need to initialize them as modules. What does it mean? At runtime, the memory contains a dictionary of loaded classes. The dictionary allows you to cast references between different types of TA objects and create instances of TA classes by using a centralized pseudo-constructor mechanism.
For example, when the command
::odrxDynamicLinker()->loadApp( OD_T("AecArchBase") )
is being executed, the function AECArchBase::initApp()
will be called in the framework. Basically, initApp()
will register all classes of the library in the global dictionary by calling the static function rxInit()
for each of them:…
AECDbSpaceBoundary::rxInit();
AECDbStair::rxInit();
AECDbWall::rxInit();
AECDbZone::rxInit();
…
After that, the object creation mechanism will be available. We will be able to create, for example, a wall by calling
_AECDbWallPtr pWall = AECDbWall::CreateAECObject()_
. Otherwise, an exception will be thrown if we try to create an object of the TA class.
Let’s create an empty DWG database by calling
OdDbDatabasePtr pDatabase = svcs.createDatabase();
This database is a central object. It is an object database that is saved to and loaded from a DWG file. We are going to add all architectural objects that we create to that database. When we are finished, we will save the database to a DWG file by calling:
OdWrFileBuf cBuffer( strFilename );
pDatabase->writeFile( &cBuffer, OdDb::kDwg, OdDb::kDHL_CURRENT );
Now let’s initialize some more loaded libraries and the display manager:
AECArchDACHBaseDatabase( pDatabase ).Init();
AECScheduleDatabase( pDatabase ).Init();
AECStructureBaseDatabase( pDatabase ).Init();
init_display_system( pDatabase );
An AEC dictionary is created in the database. That dictionary contains the default measurement units for length, area, volume, and angle, as well as print settings. Display representations implemented in the modules are registered.
The initialization is complete. If you have skipped some steps, the result will depend on which step you skipped: Objects will not be created or will not be rendered (you will see an empty screen), or there may be other glitches.
So far, the complete code looks as follows:
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;
}
I’ve added the “zoom extents” command, so that when we open the created file, we can instantly see the objects added to it and the symmetrical deinitialization of the library. To make things simpler, I removed error checking and the try/catch constructs around the main actions.
Now the program will create an empty DWG file, which we can open and view in AutoCAD.
Handling objects
To show you how to work with the TA classes, I’m going to create a house consisting of a floor/foundation, walls, windows, a door, and a roof. Let’s begin with the walls.
For starters, we’ll add one wall to our drawing. To create the wall, we need to create a style for it first. Let’s write the
add_wall_style
function:
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() );
}
The function creates the AECDbWallStyle object and sets some of its parameters. Then it calls the display manager and changes the colors for the plan display representation (2D top view) and the 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 ) );
}
In this case, we’ve set the yellow color for the wall in the 3D view. It looks complicated, but there is a reason for that: The display representations and display manager mechanism in ACA work this way. The mechanism is flexible and has many capabilities, but its logic is not obvious and takes some learning on your part.
OdDbObjectId: The Runtime Reference
Database objects reference other database objects using ObjectId objects, and a database object pointer can always be obtained from a valid ObjectId objects. The effect of this mechanism is that database objects do not have to reside in memory unless they are explicitly being examined or modified by the user.
The user must explicitly open an object before reading or writing to it, and should release it when the operation is completed. This functionality allows Teigha to support partial loading of a database, where ObjectId objects exist for all objects in the database, but the actual database objects need not be loaded until they are accessed. It also allows database objects that are not in use to be swapped out of memory, and loaded back in when they are accessed.
If you need to preserve references to objects between program launches, use OdDbHandle.
For example, the
add_wall_style
function has returned idWallStyle
. In this case, the style has just been created explicitly by calling AECDbWallStyle::CreateAECObject()
, and idWallStyle
contains the pointer to the actual object in the memory. To get write access to the style object, we need to perform this operation:
AECDbWallStylePtr pWallStyle = idResult.openObject( OdDb::kForWrite );
As a result, ‘openObject()’ will return the actual pointer to the object, which we can use.
Instead of the regular С++ pointers, the library uses the OdSmartPtr smart pointers:
typedef OdSmartPtr<AECDbWallStyle> AECDbWallStylePtr
The smart pointer’s destructor will notify the framework if the object has been closed. As a result, the related objects may be recalculated, notifications sent, etc.
Now we can add the wall by this call:
OdDbObjectId idWall1 = add_wall( pDatabase, idWallStyle, OdGePoint2d( 0, 0 ), OdGePoint2d( 0, 110 ) );
The add_wall Listing
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() );
}
As you can see, add_wall doesn’t do anything special. It simply creates the AECDbWall object with the style that we created earlier. The AECDbWall object is added to the model space of the database. In simple terms, the model space is a special dictionary that contains all objects to be rendered when we render the database.
Then the start point, the end point, and curvature are defined for the wall. Note that a wall doesn’t have to be flat. If you wish, you can make it convex.
If we did everything right, we will get a DWG file with one yellow rectangular wall. I’m using the code sample from the Teigha distribution package to view the file, but it will be rendered in exactly the same way in ACA.
Actually, I’ve manually rotated the camera in the 3D view. By default, you will have the top view.
Now let’s add 4 walls, including one convex wall:
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 );
We’ve got the basic structure of our house:
As you can see, the walls were not simply rendered as separate objects. Instead, smooth junctions were added automatically. It is one of TA’s automatic functions known as “cleanup.”
Adding windows
Now let’s add windows to our house. We can handle windows just like doors: We need to create a style for the windows that we want to add to the drawing, and then add the window objects with that style.
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() );
}
As you can see in the code, the AECDbWindowStyle object is created and added to the database. Then some settings are set for the style (though we could have used the default ones). After that, colors for a few components are redefined for the 2D view and the 3D view. In this case, the components are physical parts of a window: a frame, a sash, and a sheet of glass.
Let’s add a window to the first wall by calling the
add_window
function:
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() );
}
The
add_window()
function is similar to add_wall()
, but there is a difference: It uses the anchor object.
We create the AECDbWindow object and add it to the model space of the database. Then we set some settings for that specific instance of AECDbWindow. After that, we put the window into the wall. A special object derived from AECDbAnchorEntToCurve attaches the window to the wall.
That object contains the offsets along the X, Y, and Z axes from the origin of the coordinate system of the wall to the origin of the coordinate system of the window. When we call AttachWallAnchor(), an instance of that object is created and added to the database. The wall itself doesn’t know if it has any windows. Creating an anchor involves another basic mechanism, the relation graph, which contains relations between objects: Who is attached to whom, who includes whom, and who owns whom. If you modify the wall, the relation graph will be notified that the AECDbWall object has changed. It will check all relations and trigger the updating of the related objects. In this case, AECDbWindow will be updated. For example, if you move the wall, the windows in it will move automatically, because they will be notified by the relation graph. You can get access to the relation graph and request relations for a specific object. Actually, a window knows to which object it is attached, because each window contains a reference to the anchor created.
Let’s take a look at the result:
I’ve changed the color of the walls so that you can see the window more clearly. (The code creates blue walls, I just picked colors while writing this article.) TA contains lots of predefined window styles and types, which you can use through enumeration:
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
};
I’ve selected
AECDefs::ewtGlider and AECDefs::esRectangular
. As you can see, a lot of other shapes are available, too. By using these and other settings, you can create a very complex window type, with multiple sashes and an internal pattern on the glass sheets. The best thing is that you don’t have to create it piece by piece manually or implement everything programmatically. All you need to do is set a few parameters for the existing object or style.
Actually, all Teigha Architecture objects are pretty complex and have a lot of settings. Thanks to that, Teigha for Architecture provides massive opportunities “out of the box.”
Let’s add windows to all flat walls:
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 );
I didn’t bother to complete the code, but you can handle each window separately: change its opening percentage, color, etc. If you change the style, the change will be applied to all windows with that style at once.
Adding doors to the drawing
To complete the picture, let’s add a door. First, we’ll create a 2D profile for the door panel (a door leaf with a window hole). Then we’ll create a style with that profile. Finally, we’ll be able to create door objects with that style. Alternatively, we could use the default styles. Just like windows (or any other openings), doors are attached to walls with an anchor. The
add_profile_def
, add_door_style
, and add_door
listing.
// 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() );
}
Let’s add the following code to 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 );
There is a difference, though: We open a wall with the read access and get its length in order to calculate the offset.
As a result, we’ve put a door in the convex wall:
Let’s also add windows to the convex wall:
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 );
The result is a house without any roof or floor:
Let’s write the ‘add_roof()’ function.
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 );
}
The roof is created based on a 2D profile whose direction of traversal is counterclockwise. Calling
makeCCW()
changes the direction of traversal if it was clockwise. It is important to do because the algorithm expects a profile whose direction of traversal is counterclockwise, otherwise it won’t work.
The profile coincides with the central line of the walls. Then we need to set the slope angle for each segment of the profile, the number of faces in the roof, the elevation (Z coordinate) of the top point of each face above the XY plane (SetFaceHeightByIndex), and the overhang.
SetSegmentCount()
works only with segments that have a curvature. This parameter sets the approximation accuracy, that is, the number of line segments that approximate an arc.
Here’s our roof:
There are lots of roof settings, so you can create a roof of almost any form: a gable roof, a hip roof, a hip-and-valley roof, you name it. Each face is a separate RoofSlab object which you can edit manually.
Adding a floor to the drawing
Now we need to add at least a very basic floor/foundation. To do it, we can use the slab object. Let’s write the
add_slab
function.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 ) );
}
}
In this case, we create a new floor style, and then add components to it. A component is a floor piece that contains such parameters as thickness, elevation above the XY plane, name, material, index, etc. A floor can consist of several components with different settings. For example, if they have different elevations above the XY plane, you can use one floor object of that style to render all the floors and ceilings in a multistory building.
Style settings are applied to a specific object that contains the shape of the floor. In this case, we create a slab and initialize its profile with the same contour as the bottom part of the walls, only with a small offset at the edges. Then we use the display manager to redefine the colors of different components of the floor.
At last, we’ve created a house that looks like this:
Just to be sure, let’s try to load the resulting DWG file in Autodesk ACA:
That’s our house loaded in AutoCAD Architecture. It looks even better here, doesn’t it?
Conclusion
Using Teigha, we’ve created an empty database, initialized it for handling architectural objects, created a few commonly-used object types, and successfully saved all of them to a DWG file of the latest format.
Of course, I’ve simplified many things and described them in a cursory manner. But the purpose of this article is to demonstrate the capabilities of Teigha Architecture and give you a general idea of Teigha as an alternative solution for handling DWG files and AutoCAD objects.
No comments:
Post a Comment