Archicad C++ API
About Archicad add-on development using the C++ API.

Export a Surface Material as an XML by Index to Edit

KirthikaSrinivasan-L
Contributor

I'm working in a problem where I want to replace one surface material in the project (For example A) with Another Surface Material (B), with all the instances in the project which has A as Surface Material, changing from A to B.

I have tried to replace the material in A's index with B. It doesn't seem to work.

 

I'm wondering if there is a way to export the Surface Material's XML, from Attribute Manager, using any Archicad API and edit the <Index></Index> and import the same XML, to achieve the Material replacement.

 

Is there any way to export the Surface material by Index, from the Attribute Manager using Archicad API, as shown in the screenshot below?

 

Thanks in Advance!


image.png
6 REPLIES 6
Barry Kelly
Moderator

Re-index only works to change the index number to another number that is not being used.

 

What you want to do is 'Delete' the attribute and then use the 'Delete & Replace' option to change it to the attribute that you want it to be.

All placed instances of elements using that attribute will be changed to the new one.

The original attribute will be deleted completely.

 

Barry.

One of the forum moderators.
Versions 6.5 to 27
i7-10700 @ 2.9Ghz, 32GB ram, GeForce RTX 2060 (6GB), Windows 10
Lenovo Thinkpad - i7-1270P 2.20 GHz, 32GB RAM, Nvidia T550, Windows 11

@Barry Kelly 

How can I perform 'Delete & Replace' in the plugin code? Is there any Archicad API to do this?

Sorry, I completely missed this was in the API forum.

Just reading unread posts and not looking in actual forums.

 

I have no idea what you need to do in API.

 

Barry.

One of the forum moderators.
Versions 6.5 to 27
i7-10700 @ 2.9Ghz, 32GB ram, GeForce RTX 2060 (6GB), Windows 10
Lenovo Thinkpad - i7-1270P 2.20 GHz, 32GB RAM, Nvidia T550, Windows 11

You should be able to change the attributes of elements like walls through the API, but how you do that for surfaces may not be straightforward. The surface may actually come from the assigned building material, or it could be an override of the building material. Can you post some code showing how you are currently trying to change the surface?

Ralph Wessel BArch
Central Innovation

Yes, I'm unable to replace Materials in Surfaces of Windows, Doors etc. I'm facing APIERR_BADINDEX when the element is window/door. This is the code that I'm trying to use.

Please let me know how can I replace the Surface Material from one to another, for all the elements.

 

GSErrCode BrowserPalette::ReplaceSurfaceMaterial(const char* oldMaterialName, const char* newMaterialName) {
	 API_AttributeIndex oldSurfaceIndex = FindSurfaceMaterialIndex(oldMaterialName);
	 API_AttributeIndex newSurfaceIndex = FindSurfaceMaterialIndex(newMaterialName);

	API_Attribute attr;
	BNZeroMemory(&attr, sizeof(API_Attribute));
	attr.header.typeID = API_MaterialID;
	attr.header.index = oldSurfaceIndex;

	// Get the existing attribute
	GSErrCode err = ACAPI_Attribute_Get(&attr);
	if (err != NoError) {
		ACAPI_WriteReport("Failed to get old attribute!", false);
		return 0;
	}

	API_Element element;
	API_ElementMemo memo;
	memo = {};
	API_Element mask;
	ACAPI_ELEMENT_MASK_CLEAR(mask);

	GS::Array<API_ElemTypeID> elementTypes = { API_WallID, API_SlabID, API_RoofID, API_ColumnID, API_BeamID, API_ObjectID, API_ShellID, API_WindowID, API_DoorID };
	GS::Array<API_Guid> elementsToUpdate;

	for (const auto& elemType : elementTypes) {
		GS::Array<API_Guid> elemList;
		ACAPI_Element_GetElemList(elemType, &elemList);

		for (const auto& guid : elemList) {
			BNZeroMemory(&element, sizeof(API_Element));
			element.header.guid = guid;


			//DBPrintf("ElemType: %d", elemType);
			bool updated = false;

			if (ACAPI_Element_Get(&element) == NoError) {
				
				// Update material in different element types
				if (element.header.type == API_WallID) {
					if (element.wall.refMat.value == oldSurfaceIndex) {
						element.wall.refMat.value = newSurfaceIndex;
						updated = true;
					}
					if (element.wall.oppMat.value == oldSurfaceIndex) {
						element.wall.oppMat.value = newSurfaceIndex;
						updated = true;
					}
					if (element.wall.sidMat.value == oldSurfaceIndex) {
						element.wall.sidMat.value = newSurfaceIndex;
						updated = true;
					}
				}
				if (element.header.type == API_SlabID) {
					if (element.slab.topMat.value == oldSurfaceIndex) {
						element.slab.topMat.value = newSurfaceIndex;
						updated = true;
					}
					if (element.slab.botMat.value == oldSurfaceIndex) {
						element.slab.botMat.value = newSurfaceIndex;
						updated = true;
					}
					if (element.slab.sideMat.value == oldSurfaceIndex) {
						element.slab.sideMat.value = newSurfaceIndex;
						updated = true;
					}
				}
				else if (element.header.type == API_RoofID) {
					if (element.roof.shellBase.topMat.value == oldSurfaceIndex) {
						element.roof.shellBase.topMat.value = newSurfaceIndex;
						updated = true;
					}
					if (element.roof.shellBase.botMat.value == oldSurfaceIndex) {
						element.roof.shellBase.botMat.value = newSurfaceIndex;
						updated = true;
					}
					if (element.roof.shellBase.sidMat.value == oldSurfaceIndex) {
						element.roof.shellBase.sidMat.value = newSurfaceIndex;
						updated = true;
					}
				}
				else if (element.header.type == API_ColumnID) {

					ACAPI_Element_GetMemo(element.header.guid, &memo, APIMemoMask_ColumnSegment);

					if (memo.columnSegments == nullptr)
						return 0;

					if (memo.columnSegments[0].extrusionSurfaceMaterial.value == oldSurfaceIndex) {
						memo.columnSegments[0].extrusionSurfaceMaterial.value = newSurfaceIndex;
						updated = true;
					}
				}
				else if (element.header.type == API_ShellID) {
					if (element.shell.shellBase.topMat.value == oldSurfaceIndex) {
						element.shell.shellBase.topMat.value = newSurfaceIndex;
						updated = true;
					}
					if (element.shell.shellBase.botMat.value == oldSurfaceIndex) {
						element.shell.shellBase.botMat.value = newSurfaceIndex;
						updated = true;
					}
					if (element.shell.shellBase.sidMat.value == oldSurfaceIndex) {
						element.shell.shellBase.sidMat.value = newSurfaceIndex;
						updated = true;
					}
				}
				else if (element.header.type == API_WindowID || element.header.type == API_DoorID) {

					// Check if new attribute already exists
					API_Attribute newAttr;
					BNZeroMemory(&newAttr, sizeof(API_Attribute));
					newAttr.header.typeID = API_MaterialID;
					newAttr.header.index = newSurfaceIndex;

					err = ACAPI_Attribute_Get(&newAttr);
					if (err != NoError) {
						ACAPI_WriteReport("New attribute does not exist! Cannot proceed.", false);
						return 0;
					}

					attr.header.index = newSurfaceIndex;
					newAttr.header.index = oldSurfaceIndex;

					err = ACAPI_Attribute_Modify(&attr, nullptr);
					if (err != NoError) {
						DBPrintf("Error: Failed to modify material attributes!\n");
						return err;
					}

					err = ACAPI_Attribute_Modify(&newAttr, nullptr);
					if (err != NoError) {
						DBPrintf("Error: Failed to modify material attributes!\n");
						return err;
					}
				}

					// Modify the element if changes were made
					if (updated) {
						
						GSErrCode err = ACAPI_CallUndoableCommand("Change General Parameters", [&]() -> GSErrCode {
							return ACAPI_Element_Change(&element, nullptr, &memo, 0, true);
						});

					}

				}
			}
		}

	ACAPI_WriteReport(" Updated %d elements to use new surface material!", false, elementsToUpdate.GetSize());

	// Delete old surface material safely
	err = ACAPI_Attribute_Delete(attr.header);
	if (err == NoError) {
		ACAPI_WriteReport("Successfully deleted old material (Index: %d)!", false, oldSurfaceIndex);
	}
	else {
		ACAPI_WriteReport(" Failed to delete old material (Index: %d). It may still be in use!", false, oldSurfaceIndex);
	}

	return NoError;

}

 

I think this code needs a rethink. For walls etc, the rendered surface depends on whether it's an override of the wall building material surface. And if you change the surface attribute, you also need to specify that it's an override.

 

With doors and windows it appears you're trying to swap the surface attributes without even checking if they are relevant to the door/window. And if there are multiple doors/windows, they would be repeatedly swapped - the end result would depend on whether it was an odd/even number of doors/windows.

 

And finally a delete is attempted on the old material - why is that necessary for a swap operation?

Ralph Wessel BArch
Central Innovation