Create DWGs as if it were 2016 with Teigha For Architecture

In this piece, we explore Teigha, a library offering a new approach to working with DWG files and ACA elements. We’ll craft a code snippet that constructs a house using ACA elements.

When it comes to programmatically interacting with DWG files and AutoCAD objects, developers are typically limited to ObjectARX or Teigha. Third-party tools designed to read and write DWG files often rely on Teigha as their foundation.

Produce DWGs Like It's 2016: Teigha For Architecture
Accelerate Your DWG Production With Teigha Architecture

Teigha Architecture is a collection of libraries enabling the reading, writing, and manipulation of objects from both the original AutoCAD and its derivatives, including ACA. These libraries provide supplementary tools for managing AutoCAD objects, along with rendering engines for visualizing the DWG database.

Here are Teigha’s standout features:

  • Compatibility with DWG, DXF, BDXF, and DGN file formats.
  • Rendering of drawing files using GDI, OpenGL, or DirectX, with the ability to select entities.
  • Programmatic editing and manipulation of CAD data, including:
  • Exploding an entity into a set of simpler entities.
  • Applying transformations to entities.
  • Modifying specific properties of database objects.
  • Cloning database objects.
  • Exporting to formats like SVG, PDF, DWF, BMP, STL, and DAE (Collada).
  • Importing DWF/DAE/DGN files into a .dwg database.
  • Support for custom objects, allowing developers to create objects usable within any Teigha host application (compatible with .dwg files only).
  • Internal support for ACIS/Parasolid data, including wireframe and shaded rendering for embedded 3D solids, and access to underlying boundary representation data.
  • Implementation of custom commands.

Why Teigha Matters for Designers

AutoCAD stores its data in the .dwg file format, a proprietary binary format for two and three-dimensional design data and metadata. DWG is an industry standard, with a vast number of existing drawings requiring support. Beyond AutoCAD itself, Teigha stands out as the sole library capable of loading, saving, and manipulating objects within DWG files.

Here’s why Teigha might be preferred over AutoCAD ObjectARX:

  • Cost-effectiveness: When building an application that works with .dwg files, developers face a choice: create an AutoCAD plugin or base their application on Teigha. Plugins necessitate an AutoCAD license for all users, a costly requirement. Teigha, however, offers a significantly more affordable pricing model.
  • Enhanced Flexibility: Teigha empowers developers to create custom CAD applications tailored to specific client needs, free from the constraints of AutoCAD’s core and GUI. Developers gain the freedom to design their own GUIs or leverage existing Teigha-based CAD applications (like BricsCAD or ZWCad) as hosts for their plugins.
  • Cross-platform Support: Teigha allows for building standalone CAD applications for diverse platforms, including mobile environments like iOS and Android, catering to specialized demands.
  • Source Code Access: Founding members gain access to Teigha’s source code. This enables customization and the implementation of unique features not present in the standard library.

While Autodesk offers RealDWG, a library allowing C++ and .NET developers to work with AutoCAD® DWG and DXF files, it comes at a higher cost, involves recurring fees, and provides only loading and saving functionality for DWG files. Teigha, in contrast, offers rendering engines and a broader API for comprehensive CAD application development.

Produce DWGs Like It's 2016: Teigha For Architecture

Beyond AutoCAD and ObjectARX: An Alternative Approach

Let’s begin by loading and rendering a DWG drawing using a standard sample from Teigha’s distribution package:

Produce DWGs Like It's 2016: Teigha For Architecture

We’ve successfully loaded and rendered the DWG file. (The initial image in this article is a rendering of a different file.) This standard sample is a C++ windowed application, allowing for DWG file loading, viewing, and editing without requiring AutoCAD. Notably, the API for AutoCAD objects closely mirrors that of Teigha objects, simplifying the adaptation of existing ObjectARX plugins for Teigha-based applications.

Numerous AutoCAD alternatives, such as BricsCAD, ZWCad, and IntelliCAD, rely on Teigha for DWG format handling and interaction with ACAD objects.

Understanding AutoCAD Architecture Objects

Let’s delve into architectural objects and their manipulation. AutoCAD Architecture utilizes specialized high-level objects for architectural design, including walls, doors, windows, roofs, etc. These objects are viewport-dependent, meaning their rendering can vary based on camera perspective. Each object is style-driven, inheriting properties from assigned styles. Modifications to a style propagate to all associated objects. Objects are composed of components, each with its own visual attributes like color, line type, material, and scale. For instance, a 3D door might comprise a frame, a door panel, and a glass pane. Rendering can involve different geometries depending on the view mode, leading to variations in component count and settings across different presentations.

Introducing Teigha for Architecture (TA)

For handling architectural objects, developers can either opt for ACA and its open API for plugin creation or leverage Teigha for Architecture, a library developed by Open Design Alliance.

Teigha Architecture is a C++ class library encompassing all fundamental ACA primitives, such as walls, windows, doors, roofs, beams, openings, and more. This library enables loading these objects from any DWG format version and writing (converting) them to the latest DWG version. TA can render any primitives in various views and configurations. Given the interactive nature of ACA objects, TA also supports auxiliary ACA classes and mechanisms, including anchors, display managers, property sets, relation graphs, and more.

Getting Started with the Teigha Architecture API

Having outlined Teigha’s core features, let’s examine a simple command implementation using Teigha. While we’ll use Visual Studio 2005 for this example (despite its age), it’s important to note that Teigha libraries are multi-platform and the distribution package includes solution generators for Visual Studio versions up to 2015. Depending on the license type, developers have access to either the complete source code or pre-built binaries and header files.

The set of TA libraries appears as follows:

Produce DWGs Like It's 2016: Teigha For Architecture

These are essentially standard Windows DLL files (though they can be built for other platforms like iOS, Linux, UNIX, etc.). Corresponding LIB files are located in a separate folder. Along with TA, we’ll need the Teigha Core libraries, as TA functions as an extension built upon Core objects, which implement the primary mechanisms and objects of the original AutoCAD.

Initializing Teigha Architecture

Initializing the library requires a class for platform-specific file operations.

1
2
3
4
5
class MyServices : public ExSystemServices, public ExHostAppServices
{
protected:
  ODRX_USING_HEAP_OPERATORS(ExSystemServices);
};

The distribution package provides two pre-built Windows extensions, ExSystemServices and ExHostAppServices, which we can utilize. Next, we initialize the library and the graphics subsystem:

1
2
3
OdStaticRxObject<MyServices> svcs;
odInitialize( &svcs );
odgsInitialize();

_OdStaticRxObject_ introduces _addRef/Release_ logic to an object. The library stores a reference to the MyServices object for platform-specific operations.

Let’s initialize the TA libraries:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
  // 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 TX modules (DLL libraries) shown in the previous screenshot. While linked using LIB files, they also need initialization as modules. This runtime initialization populates a dictionary of loaded classes in memory. This dictionary facilitates reference casting between TA object types and enables instance creation through a centralized pseudo-constructor mechanism.

For instance, executing the command ::odrxDynamicLinker()->loadApp( OD_T("AecArchBase") ) invokes the AECArchBase::initApp() function within the framework. This function, in turn, registers all library classes in the global dictionary by calling the static rxInit() function for each class:

1
2
3
4
5
6

AECDbSpaceBoundary::rxInit();
AECDbStair::rxInit();
AECDbWall::rxInit();
AECDbZone::rxInit();

This registration process enables object creation. We can then create elements like walls using code like _AECDbWallPtr pWall = AECDbWall::CreateAECObject()_. Without this, attempts to create TA class objects would result in exceptions.

Next, we create an empty DWG database:

1
2

OdDbDatabasePtr pDatabase = svcs.createDatabase();

This database serves as a central repository for all created architectural objects. Once populated, it can be saved to a DWG file using:

1
2
3

OdWrFileBuf cBuffer( strFilename );
pDatabase->writeFile( &cBuffer, OdDb::kDwg, OdDb::kDHL_CURRENT );

We proceed by initializing additional loaded libraries and the display manager:

1
2
3
4
5
6

AECArchDACHBaseDatabase( pDatabase ).Init();
AECScheduleDatabase( pDatabase ).Init();
AECStructureBaseDatabase( pDatabase ).Init();
  
init_display_system( pDatabase );

An AEC dictionary, containing default measurement units for length, area, volume, angle, and print settings, is created within the database. Display representations from the modules are also registered.

With initialization complete, skipping steps can lead to issues like object creation failures, rendering problems (empty screens), or other unexpected behavior.

Here’s the complete code up to this point:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

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;
}

We’ve added a “zoom extents” command for immediate visualization of added objects when the created file is opened, along with symmetrical library deinitialization. For simplicity, error checking and try/catch blocks around core actions have been omitted.

At this stage, the program generates an empty DWG file viewable in AutoCAD.

Working with Objects

To illustrate working with TA classes, let’s construct a house comprising a floor/foundation, walls, windows, a door, and a roof.

Constructing Walls

We’ll start by adding a single wall. This requires creating a wall style first. Here’s the add_wall_style function:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

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() );
}

This function generates an AECDbWallStyle object and configures its parameters. It then utilizes the display manager to adjust colors for plan (2D top view) and model (3D) display representations.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10

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, the wall is set to yellow in the 3D view. While this might seem complex, it reflects the way display representations and the display manager function in ACA. This flexible mechanism offers extensive capabilities at the expense of some initial learning curve.

OdDbObjectId: Runtime References

Database objects reference each other using ObjectId objects, which can be used to obtain object pointers. This mechanism allows for on-demand object loading. Objects don’t need to reside in memory unless actively accessed, enabling partial database loading. ObjectId objects exist for all database entities, but the actual objects are loaded only when needed. This also facilitates memory management by swapping out unused objects.

OdDbHandle should be used for persistent references across program launches.

For instance, the add_wall_style function returned idWallStyle. This style was explicitly created using AECDbWallStyle::CreateAECObject(), and idWallStyle holds the pointer to the object in memory. To modify the style, we need write access:

1
2

AECDbWallStylePtr pWallStyle = idResult.openObject( OdDb::kForWrite );

openObject() provides the object pointer for manipulation.

The library utilizes OdSmartPtr smart pointers instead of standard C++ pointers:

1
2

typedef OdSmartPtr<AECDbWallStyle> AECDbWallStylePtr

Smart pointer destructors notify the framework when an object is closed, triggering potential recalculations, notifications, and updates to related objects.

We can now add the wall:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16

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() );
}

The add_wall function is straightforward, creating an AECDbWall object with the predefined style. This object is added to the database’s model space, a dictionary containing all renderable objects.

The wall’s start point, end point, and curvature are then defined. Walls don’t have to be straight; they can be convex.

If successful, we obtain a DWG file containing a single yellow rectangular wall. While the provided code sample uses the Teigha distribution package for viewing, the rendering remains consistent in ACA.

A DWG file with one yellow rectangular wall

The camera has been manually rotated in the 3D view for clarity. The default view is from the top.

Let’s add four walls, including a convex one:

1
2
3
4
5
6
7
8
9

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 );

This provides the basic house structure:

Basic structure of the house

Notice the smooth junctions between walls, automatically generated by TA’s “cleanup” functionality.

Incorporating Windows

We’ll now add windows to our house. Similar to doors, this involves creating a window style and then adding window objects with that style.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

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() );
}

The code demonstrates the creation and database addition of an AECDbWindowStyle object. Style settings are then configured (defaults could be used). Colors for various components are redefined for both 2D and 3D views. These components represent the window’s physical parts: frame, sash, and glass pane.

Let’s add a window to the first wall:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

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 resembles add_wall() but introduces the anchor object concept.

An AECDbWindow object is created and added to the model space. Settings for this specific window instance are applied. Then, the window is placed within the wall. An AECDbAnchorEntToCurve-derived object handles the attachment.

This anchor object stores X, Y, and Z offsets from the wall’s coordinate system origin to the window’s origin. Calling AttachWallAnchor() creates and adds an instance of this object to the database. While the wall itself remains unaware of any windows, the anchor creation utilizes the relation graph, a mechanism storing object relationships: attachments, inclusions, ownership, etc. Wall modifications notify the relation graph, which in turn updates related objects. This ensures that moving the wall automatically repositions the windows. The relation graph can be accessed to query relationships for specific objects. Windows, however, maintain a reference to their associated anchor, making them aware of their attachment.

Here’s the result:

The result

Wall colors have been adjusted for better window visibility. (The original code uses blue walls; colors were chosen dynamically for this article.) TA offers numerous predefined window styles and types accessible via enumeration:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

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
    };

Here, AECDefs::ewtGlider and AECDefs::esRectangular are chosen. A wide array of shapes are available, enabling the creation of intricate window designs with multiple sashes and patterned glass. The key takeaway is that this complexity doesn’t necessitate manual piece-by-piece creation or extensive programming. Simple parameter adjustments to existing objects or styles suffice.

Teigha Architecture objects are inherently complex and highly customizable, providing extensive functionality “out of the box.”

Let’s add windows to all flat walls:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15

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 );
The rendered shape with multiple walls showing.

While the code isn’t fully fleshed out, it demonstrates the ability to manipulate individual windows, adjusting their opening percentage, color, etc. Style changes, however, affect all windows using that style.

Adding Doors

To complete the basic structure, let’s add a door. This involves creating a 2D profile for the door panel (leaf with a window), defining a style using this profile, and finally, creating door objects based on this style. Default styles are also an option. Like windows (or any opening), doors are attached to walls using anchors. Here’s the code for add_profile_def, add_door_style, and add_door:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111

// 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() );
}

Adding the following code to the main function:

1
2
3
4
5
6
7
8
9

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  );

A notable difference here is that we open the wall with read access to obtain its length for offset calculation.

The result is a door in the convex wall:

A door in the convex wall

Let’s add windows to the convex wall as well:

1
2
3
4
5
6

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 );

We now have a house lacking only a roof and a floor:

A house without any roof or floor

Let’s create the roof using the add_roof() function:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

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 generated from a 2D profile with a counterclockwise traversal direction. makeCCW() ensures this direction, as the algorithm relies on it.

The profile aligns with the walls’ centerline. We then define the slope angle for each profile segment, the number of roof faces, the elevation (Z coordinate) of each face’s top point (SetFaceHeightByIndex), and the overhang. SetSegmentCount() affects only curved segments, controlling the approximation accuracy (number of line segments used to represent the arc).

Here’s the completed roof:

The house with the roof added in

The extensive roof settings allow for creating various roof types: gable, hip, hip-and-valley, and more. Each face is a separate editable RoofSlab object.

Adding the Floor

Finally, let’s add a basic floor/foundation using the slab object and the add_slab function:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
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 ) );
  }
}

We define a new floor style and add components to it. Each component represents a floor piece with attributes like thickness, elevation, name, material, index, etc. A floor can have multiple components with varying settings. For instance, different elevations allow a single floor object to represent all floors and ceilings in a multi-story building.

Style settings are applied to a specific object defining the floor’s shape. In this case, a slab is created with a profile matching the walls’ base contour, incorporating a slight offset at the edges. The display manager is used to customize the colors of different floor components.

Our completed house:

The finished house, including the floor

Let’s load the generated DWG file in Autodesk ACA for verification:

The rendered DWG file

The house, now loaded in AutoCAD Architecture, showcasing a more refined rendering.

Conclusion

Leveraging Teigha, we’ve created an empty database, configured it for architectural objects, generated various object types, and successfully saved them to a DWG file in the latest format.

While certain aspects have been simplified for clarity, this exploration highlights the capabilities of Teigha Architecture and positions Teigha as a viable alternative for handling DWG files and interacting with AutoCAD objects.

Licensed under CC BY-NC-SA 4.0