How to extract Revit ID from schedule xls?

Hello PyReviter,

i want to extract, better to say, trigger transactions by RevitID.
Background is i got a filled excelsheet from a client. I have the RevitID as a reference. I have to fill based on that 30 parameters (columns)

In dynamo i already solved it. I have difficulties to get my data sorted, my code so far…

import clr
import xlrd
import sys

import os, sys, datetime
from Autodesk.Revit.DB import *
from Autodesk.Revit.UI import *
from Autodesk.Revit.DB.Architecture import *

# pyRevit
from pyrevit import forms, revit, script

# Excel
clr.AddReference("Microsoft.Office.Interop.Excel")
from Microsoft.Office.Interop import Excel
from System.Runtime.InteropServices import Marshal


# .NET Imports
clr.AddReference('System')
from System.Collections.Generic import List
from System import Array

# List_example = List[ElementId]()

clr.AddReference('System.Drawing')
import System.Drawing
from System.Drawing import *

import time

doc = __revit__.ActiveUIDocument.Document
uidoc = __revit__.ActiveUIDocument          #type: UIDocument
app   = __revit__.Application               # Application class
selection = uidoc.Selection # type: Selection

active_view  = doc.ActiveView
active_level = active_view.GenLevel
rvt_year     = int(app.VersionNumber)
PATH_SCRIPT  = os.path.dirname(__file__)

# ╦  ╦╔═╗╦═╗╦╔═╗╔╗ ╦  ╔═╗╔═╗
# ╚╗╔╝╠═╣╠╦╝║╠═╣╠╩╗║  ║╣ ╚═╗
#  ╚╝ ╩ ╩╩╚═╩╩ ╩╚═╝╩═╝╚═╝╚═╝ VARIABLES
#====================================================================================================


time_start = time.time()

# 🍉 pick excel
xcl_file = forms.pick_excel_file(title = "Select Excel File")
if not xcl_file:
    sys.exit()

# 📃 open excel

path = xcl_file
ex = Excel.ApplicationClass()
ex.Visible = False
ex.DisplayAlerts = False

# 📖 get Workbook
workbook = ex.Workbooks.Open(filename = path)

# 📕 report
ws = workbook.Sheets[1]
table = ws.Name

# methode BIMGuru
xlDirecDown = System.Enum.Parse(Excel.XlDirection, "xlDown")
xlDirecRight = System.Enum.Parse(Excel.XlDirection, "xlToRight")
xlDirecUp = System.Enum.Parse(Excel.XlDirection, "xlUp")
xlDirecLeft = System.Enum.Parse(Excel.XlDirection, "xlToLeft")


class ExcelUtils():
    def __init__(self, lstData, filepath):
        self.lstData = lstData
        self.filepath = filepath

    def importXls(self, wsName):
        ex = Excel.ApplicationClass()
        ex.Visible = False
        lst_xls = []
        workbook = ex.Workbooks.Open(self.filepath)
        try:
            ws = ex.Sheets(wsName)
            wsFound = True
        except:
            ws = workbook.Worksheets[0]
            wsFound = False
        ##get number of Rows not empty ##
        rowCountF = max(ws.Range(i).End(xlDirecUp).Row for i in
                        ["A65536", "B65536", "C65536", "D65536", "E65536", "F65536", "G65536", "H65536"])
        # other method if column A is empty
        # rowCountF = ws.Range("B65536").End(xlDirecUp).Row
        # rowCountF = ws.Columns[1].End(xlDirecDown).Row
        ##get number of Coloun not empty ##
        colCountF = max(ws.Range(i).End(xlDirecLeft).Column for i in
                        ["ZZ1", "ZZ2", "ZZ3", "ZZ4", "ZZ5", "ZZ6", "ZZ7", "ZZ8", "ZZ9"])
        # other methods
        # colCountF = ws.Range("ZZ9").End(xlDirecLeft).Column
        # colCountF = ws.Rows[1].End(xlDirecRight).Column
        self.fullrange = ws.Range[ws.Cells(1, 1), ws.Cells(rowCountF, colCountF)]
        self.fullvalue = list(self.fullrange.Value2)
        # split list into sublist with number of colum
        n = colCountF

        # close Excel
        ex.Workbooks.Close()
        ex.Quit()
        # other proper way to make sure that you really closed and released all COM objects
        if workbook is not None:
            Marshal.ReleaseComObject(workbook)
        if ex is not None:
            Marshal.ReleaseComObject(ex)
        workbook = None
        ex = None
        return [list(self.fullvalue[i:i + n] for i in range(0, len(self.fullvalue), n)), wsFound]

# Try to import the excel file
try:
    xclObj = ExcelUtils([], xcl_file)
    xclData = xclObj.importXls(table)
    result = "Import successful"
except:
    result = "Import unsuccessful"
    xclData = [None, False]

# Output
print(xclData[0], xclData[1], result)

for raw in xclData[0]:
    for i in raw:
        print(i)

print(xclData[1])
sys.exit()

my dynamo script is “rocksolid” ( tested with 4 parameters)


here the link unwraped :wink:

I made some progress…

i am able to sort the stuff ?

where put i my transactions? have i to create a group Transaction? or transaction with subtransactions ?

i tried severial but i am still confused


2024-05-08_08h44_35

all_elements = FilteredElementCollector(doc, active_view.Id).WhereElementIsNotElementType().ToElements()


directory = forms.pick_excel_file(False, 'Select File')
wb = xlrd.open_workbook(directory)

sheet = wb.sheet_by_index(0)

data_dict = {}
element_dict = {element.Id: element for element in all_elements}

for rw in range(1, sheet.nrows):
    key = sheet.cell_value(rw, 0)
    value = sheet.row_values(rw)[1:]
    data_dict[key] = value

with Transaction(doc, __title__) as t:
    t.Start()

    for k, v in data_dict.items():
        print("{}:{}".format(k, v))
        # this is just a print statement to show you that the value is a list
        for item in v:     # this is to loop through the list in values
            if item == '':      # if item is None type, it will skip
                continue

            element = doc.GetElement(element_dict.get(ElementId(int(k))))

            if element:
                if element.Id == ElementId(int(k)):
                    ifc_guid = element.LookupParameter("IfcGUID")
                    ifc_guid.Set(v[0]).AsString()
                    fab_type = element.LookupParameter("Fabrikationsnummer/Type")
                    fab_type.Set(v[1]).AsString()
                    """
                    write the rest of the parameter here and see what you will get
                    """

    t.Commit()

i got also this, is this related to the transaction?


@andreasd811

try to use the pyRevit Transaction handler, it is simpler:

with revit.Transaction("Transaction name"): # context manager using the pyrevit revit module Transaction, it takes care of the start/commit/disposal of transactions
    for k, v in data_dict.items():
        print("{}:{}".format(k, v))
        for item in v:
            if item == "":
                continue
            element = doc.GetElement(element_dict.get(ElementId(int(k))))
            if element:
                if element.Id == ElementId(int(k)):
                    ifc_guid = element.LookupParameter("IfcGUID")
                    ifc_guid.Set(v[0]).AsString()
                    fab_type = element.LookupParameter("Fabrikationsnummer/Type")
                    fab_type.Set(v[1]).AsString()
1 Like

@Jean-Marc

i stuck at this when i run my script on my laptop


how can i convert my Instance to a Reference?

Please share the whole code so that I don’t have to feel like Indiana Jones doing some archeology :face_with_hand_over_mouth:

1 Like

@Jean-Marc

here is the whole code

# -*- coding: utf-8 -*-
__title__ = "ImportExcel"
__doc__ = """Version = 1.0
Date    = 29.10.2023Version = 1.0

Date    = 29.10.2023
_____________________________________________________________________
Description:
This is a template file for pyRevit Scripts.
_____________________________________________________________________
How-to:
-> Click on the button
_____________________________________________________________________
Last update:
- [24.04.2022] - 1.0 RELEASE
_____________________________________________________________________
To-Do:
- 
"""
import sys
import xlrd


from Autodesk.Revit.DB import *
from Autodesk.Revit.UI.Selection import ISelectionFilter, ObjectType, Selection

from pyrevit import revit, forms


import clr


clr.AddReference("Microsoft.Office.Interop.Excel")
from Microsoft.Office.Interop import Excel
from System.Runtime.InteropServices import Marshal


clr.AddReference("System")
from System.Collections.Generic import List

doc = __revit__.ActiveUIDocument.Document
uidoc = __revit__.ActiveUIDocument
app = __revit__.Application





import time
start_time = time.time()

all_elements = FilteredElementCollector(doc).WhereElementIsNotElementType().ToElements()

directory = forms.pick_excel_file(False, 'Select File')
wb = xlrd.open_workbook(directory)

sheet = wb.sheet_by_index(0)

data_dict = {}
# element_dict = {element.Id: element for element in all_elements}

for rw in range(1, sheet.nrows):
    key = sheet.cell_value(rw, 0)
    value = sheet.row_values(rw)[1:]
    data_dict[key] = value

with Transaction(doc, __title__) as t:
    t.Start()

    for k, v in data_dict.items():
        print("{}:{}".format(k, v))
        # this is just a print statement to show you that the value is a list
        for item in v:     # this is to loop through the list in values
            if item == '':      # if item is None type, it will skip
                continue
            # element = doc.GetElement(element_dict.get(ElementId(int(k))))
            for element in all_elements:
                if element:
                    if element.Id == ElementId(int(k)):
                        fab_typ = element.LookupParameter("Fabrikationsnummer/Type")
                        fab_typ.Set(v[1])
                        gew_beg = element.LookupParameter("Gewaehrleiszungsbeginn")
                        gew_beg.Set(v[2])
                        gew_end = element.LookupParameter("Gewaehrleiszungsende")
                        gew_end.Set(v[3])
                        herstel = element.LookupParameter("Hersteller")
                        herstel.Set(v[4])
    t.Commit()

end_time = time.time()
print('\n Laufzeit: {} Sekunden'.format(end_time - start_time))

Can you also share a Revit file and excel that is supposed to work with that?

Hard to figure out otherwise.

1 Like

@Jean-Marc

i have no permission to upload a demo, it is limited to certain formats

Use a Dropbox link or zip and rename the extension

some recommendations:
Here is how I handled transactions when writing from excel to parameters. I just wrapped everything the script does in a single function process_hangers() and put the single function in the transaction to keep it simple.

import clr
from System.Runtime.InteropServices import Marshal
clr.AddReference('Microsoft.Office.Interop.Excel')
from Microsoft.Office.Interop import Excel

....functions...

    excel_file = 'Inserts.xlsx'
    excel_file_path = os.path.join(script_path, excel_file)
    excel_app = Excel.ApplicationClass()
    workbook = excel_app.Workbooks.Open(excel_file_path)
    worksheet = workbook.Sheets[final_selection]
    
    worksheet_name = worksheet.Name
    transaction_name = "Set insert: {}".format(worksheet_name)

    ...rest of script...

    with Transaction(transaction_name, swallow_errors=True):
        try:
            process_hangers(selected_hangers)
        except Exception as e:
            mlogger.error('An overall error occurred: {0}'.format(e))
        finally:
            if 'workbook' in locals():
                workbook.Close(False)
                excel_app.Quit()
                Marshal.ReleaseComObject(worksheet)
                Marshal.ReleaseComObject(workbook)
                Marshal.ReleaseComObject(excel_app)

-Note the finally and cleanup at the end to release resources after opening excel file in background

You are importing the office interop stuff, but then using xlrd.

haven’t used xlrd in a script before but the “finally” part is what I use for cleanup after background open I describe above, so not sure if it necessary with xlrd

-you collect every single element in the document and loop through them… Probably better to loop through the list of IDs in your excel file and grab only the specific elements you need. Which brings up another thing to watch out for…

-Revit makes no guarantee on the ElementId remaining stable. It could change when you change element type, it could change when somebody does a sync to central, or it could change for seemingly no reason at all.
“element.UniqueId” will not change, unless you delete the element and place a new one.

Autodesk states this in the API docs for UniqueId as well

2 Likes

@Jean-Marc

i hope this works, i do not use dropbox, it is forbidden :wink:

grafik

a simplified version:

# -*- coding: utf-8 -*-
import os
import xlrd
from pyrevit import forms, revit, script, DB

output = script.get_output()
output.close_others()

PATH_SCRIPT  = os.path.dirname(__file__)

doc = revit.doc
with xlrd.open_workbook("H:\\_DOCS\\test.xls") as wb:
    sheet = wb.sheet_by_index(0)
    DATA_DICT = {}
    for rw in range(1, sheet.nrows):
        key = int(sheet.cell_value(rw, 0))
        value = sheet.row_values(rw)[1:]
        DATA_DICT[key] = value
    PARAMETERS = sheet.row_values(0)[1:]


def set_parameters(elements, parameters=PARAMETERS, data_dict = DATA_DICT):
    not_found = {}
    for element in elements:
        element_id = element_id 
        if element and element_id in data_dict.keys():
            element_entry = data_dict[element_id ]
            not_found[element_id] = []
            for i, parameter in enumerate(parameters[1:]):
                try:
                    element.LookupParameter(parameter).Set(element_entry[i])
                except Exception as e:
                    not_found[element.Id.IntegerValue].append(parameter)
    return not_found


all_elements = DB.FilteredElementCollector(doc).WhereElementIsNotElementType().ToElements()

with revit.Transaction("Assign parameters from excel DB"):
    not_found = set_parameters(all_elements)
    print(not_found) # printing element that do not have the printed parameters

A few advices to get you further _ I am not saying my code is perfect, for from it _ :

  • You need to modularize things a bit. Create definitions whenever possible.
  • Create loops when you repeat things (getting the parameter then setting it to a value)
  • Import just the modules you need
  • Do not over comment (yes there is such thing as over-commenting, your initial code was hard to read)
  • Use what pyrevit modules have to offer (thinking about the transaction handling)
  • Handle exceptions: if the category of the element return None parameter, you need to know it and not being stopped by it.
  • learn how to use dictionaries
  • try not to mix and match Dynamo and pyRevit approaches: while similar in many aspects, they are two different beasts
1 Like