December 8, 2020
by Viktor Kovacs
modified at December 17, 2020

Archicad Maze Generator Add-On Tutorial – Part 1

This tutorial series will guide you through the process of creating an Archicad Add-On. The goal is to write a fully functional Add-On for Archicad that generates a random maze using real BIM elements.

The code for this tutorial is available here.

The tutorial is divided into multiple parts:

The end result will look something like this:

The Maze Generation Algorithm

At the end of the day the Add-On will generate a random maze. We won’t discuss the details of the maze generation, but you can browse the code, and read about the algorithm on Wikipedia. The maze is generated by Prim’s randomized algorithm.

First Steps

To set up your development environment, please read Getting started with Archicad Add-Ons, and follow the steps you find there. For this tutorial you can download the source code from this link. Please note that the example Add-On works only with the DEMO version of Archicad.

Creating BIM Elements

The maze generator algorithm will generate a bunch of coordinates where we should place the walls. We will also create a slab under the walls.

Units

In the Archicad code everything is stored in meters, so this is the case for Add-Ons as well. It means that every API function will return and expect values in meter, square meter or cubic meter.

On the other hand, users can define units inside Archicad, so if you show a measured value to the user, you have to convert meter to working unit, calculation unit or dimension unit. For user interface it’s a very easy task, because Archicad’s own dialog framework handles unit conversion automatically.

Undo Scope

Every modification in Archicad should be done within an undo scope. It means that the modifications will generate undo steps, and all modifications that fall within one undo scope will be undone in one step. If you try to modify anything without an open undo scope you will get an error from the API call.

The example below shows you how to open an undo scope. You can also specify a string that appears in the Edit menu to show the users what will be undone (“Undo Create wall”).

ACAPI_CallUndoableCommand ("Create wall", [&] () -> GSErrCode {
	// Do some modifications
	return NoError;
});

In case of errors you can return APIERR_CANCEL from the lambda function. In this case all of the modifications in the current undo scope will cancelled.

Creating Elements

To create an element in Archicad you have to define a lot of parameters. Usually you don’t want to define all of them one by one, just use the default values. In Archicad there are default settings for every element. You can set the default settings by clicking on the tool icon without a selected element instance.

These settings are also available via the Archicad API, so you can use them for every parameter you don’t want to specify. Element creation is generally done in three steps:

  1. Retrieve the element type’s default settings with the ACAPI_Element_GetDefaults function.
  2. Modify some of the settings of the element by modifying the API_Element structure.
  3. Add geometrical information, and create the element with the modified settings using the ACAPI_Element_Create function.

Creating a Wall

The example below accesses the wall element’s default settings, modifies its start position, end position and reference line location, and places a new wall.

static GSErrCode CreateWallElement (double begX, double begY, double endX, double endY)
{
	GSErrCode err = NoError;

	API_Element wallElement = {};
	wallElement.header.typeID = API_WallID;
	err = ACAPI_Element_GetDefaults (&wallElement, nullptr);
	if (err != NoError) {
		return err;
	}

	wallElement.wall.begC = { begX, begY };
	wallElement.wall.endC = { endX, endY };
	wallElement.wall.referenceLineLocation = APIWallRefLine_Center;
	err = ACAPI_Element_Create (&wallElement, nullptr);
	if (err != NoError) {
		return err;
	}

	return NoError;
}

Creating a Slab

Creating a slab is a bit trickier, because it’s a polygon based element. It means that we have to define the polygon of the slab. To achieve this we will use the element memo concept. The API_ElementMemo structure can contain dynamic data for the element. Polygon is a dynamic data set because it can have any number of vertices, so this is why it’s stored in the memo.

We have to do the following things to create a polygonal element:

  1. Set the number of polygon vertices in the API_Element structure. Please note that for closed polygons the first and last vertex should be the same. So if you want to create a polygon with 4 edges, than the number of vertices should be 5.
  2. Set the number of sub-polygons in the API_Element structure. This is useful when you want to have holes in the polygon. If the polygon doesn’t contain any holes then the number of sub-polygons should be 1.
  3. Set the coordinates in the API_ElementMemo structure. You have to allocate an array for the coordinates. The indexing starts from 1, so you have to allocate space for an extra vertex.
  4. Set the sub-polygon end indices in the API_ElementMemo structure. The indexing starts from 1, so you have to allocate space for an extra index.

You can see all of the above steps altogether here:

static GSErrCode CreateSlabElement (double begX, double begY, double endX, double endY)
{
	GSErrCode err = NoError;

	API_Element slabElement = {};
	slabElement.header.typeID = API_SlabID;
	err = ACAPI_Element_GetDefaults (&slabElement, nullptr);
	if (err != NoError) {
		return err;
	}

	slabElement.slab.poly.nCoords = 5;
	slabElement.slab.poly.nSubPolys = 1;

	API_ElementMemo	slabMemo = {};

	slabMemo.coords = (API_Coord**) BMhAllClear ((slabElement.slab.poly.nCoords + 1) * sizeof (API_Coord));
	(*slabMemo.coords)[1] = { begX, begY };
	(*slabMemo.coords)[2] = { endX, begY };
	(*slabMemo.coords)[3] = { endX, endY };
	(*slabMemo.coords)[4] = { begX, endY };
	(*slabMemo.coords)[5] = (*slabMemo.coords)[1];

	slabMemo.pends = (Int32**) BMhAllClear ((slabElement.slab.poly.nSubPolys + 1) * sizeof (Int32));
	(*slabMemo.pends)[1] = slabElement.slab.poly.nCoords;

	err = ACAPI_Element_Create (&slabElement, &slabMemo);
	ACAPI_DisposeElemMemoHdls (&slabMemo);
	if (err != NoError) {
		return err;
	}

	return NoError;
}

Please note that after using memos you always have to call the ACAPI_DisposeElemMemoHdls function to release the allocated memory.

Putting All Together

The final element creation code in an undo scope should look something like this:

static void GenerateMaze ()
{
	static const int MazeRowCount = 10;
	static const int MazeColCount = 20;
	static const double MazeCellSize = 2.0;
	static const double SlabPadding = 2.0;

	std::vector<MG::WallGeometry> mazeWalls;
	GenerateMazeWallGeometries (MazeRowCount, MazeColCount, MazeCellSize, mazeWalls);

	double slabBegX = -SlabPadding;
	double slabBegY = -SlabPadding;
	double slabEndX = MazeCellSize * MazeColCount + SlabPadding;
	double slabEndY = MazeCellSize * MazeRowCount + SlabPadding;

	GS::UniString undoString = RSGetIndString (AddOnStringsID, UndoStringID, ACAPI_GetOwnResModule ());
	ACAPI_CallUndoableCommand (undoString, [&] () -> GSErrCode {
		GSErrCode err = NoError;
		for (const MG::WallGeometry& mazeWall : mazeWalls) {
			err = CreateWallElement (mazeWall.begX, mazeWall.begY, mazeWall.endX, mazeWall.endY);
			if (err != NoError) {
				return APIERR_CANCEL;
			}
		}
		err = CreateSlabElement (slabBegX, slabBegY, slabEndX, slabEndY);
		if (err != NoError) {
			return APIERR_CANCEL;
		}
		return NoError;
	});
}

Please note that the undo string comes from a resource. String resources can be accessed with the RSGetIndString function.

Summary

This tutorial explained the basics of BIM element creation via the Archicad API.

You can find more resources here: