Archicad Python API
About automating tasks in Archicad using the Python API.

Python Error with GenerateLayoutBookfromExcel.py

ares997
Contributor
Does anyone know why when I run the GenerateLayoutBookFromExcel.py script with the Python plug-in I get this error:
Bad Name: A1 Landscape
GenerateLayoutBookFromExcel.py(108): AttributeError: 'NoneType' object has no attribute 'db'

Below is the Python script:
import os
import sys
import re
import xlrd

excelFileName = "LayoutBook.xls"
excelFilePath = os.path.join (os.path.dirname (sys.argv[0]), excelFileName)

if os.path.isfile (excelFilePath) is False:
	sys.exit ("File does not exists: {}".format (excelFilePath))

xl_workbook = xlrd.open_workbook(excelFilePath)
xl_sheet = xl_workbook.sheet_by_index(0)

layoutTree = GetNavigatorTree (API_LayoutMap)
subSetTree = [(list(layoutTree)[0], 0)]

def GetItemsFromTree (tree, typeSet = None):
	l = []
	for k in tree.keys():
		if typeSet is None or k.itemType in typeSet:
			l.append (k)
		l.extend (GetItemsFromTree (tree, typeSet))
	return l

masters = GetItemsFromTree (layoutTree, {API_MasterLayoutNavItem})

def GetChildrenFromTree (tree, subSetTree):
	if not subSetTree:
		return list(tree.keys())
	for k in tree.keys():
		if k.name == subSetTree[0][0].name and k.uiId == subSetTree[0][0].uiId:
			return GetChildrenFromTree (tree, subSetTree[1:])
	return []

def FindItemByName (items, name):
	for item in items:
		if item.name == name:
			return item
	PrintError('Bad Name: {}'.format(name))
	return None

def FindItemByIDName (items, ID, name):
	for item in items:
		if item.name == name and item.uiId == ID:
			return item
	return None

def CreateSubSetItem (ID, name, parent):
	newSubSet = APIObject ()
	newSubSet.uiId = ID
	newSubSet.customUiId = True
	newSubSet.name = name
	newSubSet.customName = True
	newSubSet.itemType = API_SubSetNavItem
	newSubSet.mapId = API_LayoutMap
	CreateNavigatorItem (newSubSet, parent)
	return newSubSet

def CreateOrChangeLayoutItem (ID, name, masterName, parent, oldItem = None):
	newLayout = APIObject () if oldItem is None else oldItem
	newLayout.uiId = ID
	newLayout.customUiId = True
	newLayout.name = name
	newLayout.customName = True
	newLayout.itemType = API_LayoutNavItem
	newLayout.mapId = API_LayoutMap
	newLayout.db = APIObject ()
	newLayout.db.typeID = APIWind_LayoutID
	masterLayoutItem = FindItemByName (masters, masterName)
	newLayout.db.masterLayoutUnId = masterLayoutItem.db.databaseUnId
	if oldItem is None:
		parentChildren = GetChildrenFromTree (layoutTree, subSetTree)
		if not parentChildren:
			CreateNavigatorItem (newLayout, parent)
		else:
			CreateNavigatorItem (newLayout, parent, parentChildren[-1])
	else:
		ChangeNavigatorItem (newLayout)
	return newLayout

def GetParent (actIndex):
	global subSetTree
	for i in reversed(range(1,len(subSetTree))):
		if subSetTree[1] >= actIndex:
			del subSetTree
	return subSetTree[-1][0]

newSubSetNumber, newLayoutNumber = 0, 0
for row_idx in range(1, xl_sheet.nrows):
	for column_idx in range(1, xl_sheet.ncols):
		idCell = xl_sheet.cell(row_idx, column_idx)
		if idCell.value != '':
			ID = idCell.value.strip()
			NAME = xl_sheet.cell(row_idx, column_idx + 1).value.strip()
			MASTER_NAME = xl_sheet.cell(row_idx, column_idx + 3).value.strip()

			PARENT = GetParent(column_idx)
			oldItem = FindItemByIDName (GetChildrenFromTree (layoutTree, subSetTree), ID, NAME)
			if MASTER_NAME == '':
				newSubSetNumber += 1
				if oldItem is None:
					subSetTree.append((CreateSubSetItem (ID, NAME, PARENT), column_idx))
				else:
					subSetTree.append((oldItem, column_idx))
			else:
				newLayoutNumber += 1
				CreateOrChangeLayoutItem (ID, NAME, MASTER_NAME, PARENT, oldItem)

			layoutTree = GetNavigatorTree (API_LayoutMap)
			break
print("Created {} subsets and {} layouts based on {}".format(newSubSetNumber, newLayoutNumber, excelFileName))
Archicad 25 (5005), Windows 11, AMD RYZEN 7 3900 (64 GB RAM)
1 REPLY 1
ares997
Contributor
Figured it out.

Just had to have a Master Layout named correctly "A1 Landscape".
Archicad 25 (5005), Windows 11, AMD RYZEN 7 3900 (64 GB RAM)