Dynamo/Python script for document transmittal

@GavinCrump Sorry Gavin, I just have one more question.

In terms of the python code and using /t to create different columns. I understand that the way its coded is basically like pressing tab in excel and therefore moving to the next column?

Where do you think would be my best next page/website/tutorial in expanding on that to either add a “move down one” seperator Or even dictating which row certain elements go? Why I ask is that in my practice we use a template for drawing transmittals (screen shot is below) so id love to be able to manipulate the data so it pastes into our template cleanly, rather than adjusting our template to suit if that makes sense?

Thanks again

If it’s just a spare line then you could add a blank line whenever changing series also in the copy/paste structure. This could be done by continually checking each sheet’s series and adding a blank line, or the series header whenever it changes from the last checked sheet. Did that mod for a client, it isn’t too tricky.

If you want to write into a pre-formatted file it is much more difficult and bespoke to your company needs at that point. If not Bumbelebee, Microsoft Interop + Python or C# would be the next step. Have built tools of this nature where I work and it takes a lot more time/work than just my tutorial.

I much prefer just having architects copy/paste into a preformatted schedule. If that’s too much work for them, then they’re likely out of a job in near future.

1 Like

After having a look at bumblebee I can see why in your tutorial you said you prefer this method haha. The architects will be fine with it, its the directors who still get the old t squares out that will have the trouble :stuck_out_tongue:

In terms of adding blank lines into the script. I did a bit of digging and managed to add this to the code

That at least added a blank line between the header and the data, so at least I am off the mark so to speak. With how the script is colour coded. Am I right in thinking that green means its a method?

The next modification I wanted to try is to have a column at the start of the revisions called ‘Current Revision’ which has the current rev for each sheet.

When thinking how it might be done, I would assume that I would need to mod the code so that it creates a copy of the last indices in the list and puts it at 0?

To get the current revision you could use this method (yes in that example they are green):

Sorry for the long break between replies. I have been going through a Python Udemy course to try get up to speed.

I tried to use the GetCurrentRevision method like this. Added it to the start of this section where it sends the revisions out to the rows

I then added this to append the rows out so current is at the start

But unfortunately I get a ‘Revision’ object has no attribute ‘GetCurrentRevision’

Which I assume means that how I have wrote it, its trying to get a method from the Revision class which issnt there? Which is correct because its in the ViewSheet class but I am not sure how to re-write to fix it up.

If its something that is simple and ive just missed it please feel free to respond but if its more complicated and you dont have time you are more than welcome to leave me to it haha

Thanks again!

here is the whole code if it helps

# Made by Gavin Crump
# Free for use
# BIM Guru, www.bimguru.com.au

# Boilerplate text
import clr

clr.AddReference("RevitServices")
import RevitServices
from RevitServices.Persistence import DocumentManager 

clr.AddReference("RevitAPI")
import Autodesk 
from Autodesk.Revit.DB import *
clr.AddReference("System")

from System import TimeZoneInfo, DateTime

# Assuming utc_datetime is a UTC DateTime object
utc_datetime = DateTime.UtcNow

# Convert to local time
local_time = TimeZoneInfo.ConvertTimeFromUtc(utc_datetime, TimeZoneInfo.Local)

# Current doc/app/ui
doc = DocumentManager.Instance.CurrentDBDocument

# Define list/unwrap list functions
def uwlist(input):
    result = input if isinstance(input, list) else [input]
    return UnwrapElement(result)

# Preparing input from dynamo to revit
sheets = uwlist(IN[0])

# Get revision sequences and revisions
revSeqs = FilteredElementCollector(doc).OfClass(RevisionNumberingSequence).ToElements()
revIds  = Revision.GetAllRevisionIds(doc)
revs    = [doc.GetElement(i) for i in revIds]

# Get sequence name per revision


# Empty variable lists to store to
revSeqNames, revCharSeqs = [],[]

# Get sequence names and characters
for r in revs:
	# Get sequence
	rsId = r.RevisionNumberingSequenceId
	rs   = doc.GetElement(rsId)
	# Append the name and start a sequence
	try:
		revSeqNames.append(rs.Name)
	except:
		continue
	charSequence = []
	# Get sequence characters for numeric...
	if rs.NumberType == RevisionNumberType.Numeric:
		settings  = rs.GetNumericRevisionSettings()
		minDigits = settings.MinimumDigits
		prefix    = settings.Prefix
		suffix    = settings.Suffix
		for n in range(settings.StartNumber,99,1):
			char_str = str(n)
			pad_str  = char_str.rjust(minDigits,"0")
			char_seq = prefix + pad_str + suffix
			charSequence.append(char_seq)
	# ... or Alphanumeric
	else:
		settings = rs.GetAlphanumericRevisionSettings()
		prefix    = settings.Prefix
		suffix    = settings.Suffix
		for a in settings.GetSequence():
			char_seq = prefix + a + suffix
			charSequence.append(char_seq)
	# Append the character sequence
	revCharSeqs.append(charSequence)

# Get revision sequences per sheet
rowsOut = []
sep = "\t"
sep_2 = "\n"
for s in sheets:
    trackRevs = []
    sheetRevs = s.GetAllRevisionIds()
    rowOut = ""
    rowOut += s.Name + sep + s.SheetNumber + sep
    # current revision
    for i in revIds:
        if i in sheetRevs:
            revision = doc.GetElement(i)
            revision_date = revision.RevisionDate.ToLocalTime()
            if most_recent_revision_date is None or revision_date > most_recent_revision_date:
                most_recent_revision_date = revision_date
                most_recent_revision_row = rowOut
    
    # If a most recent revision is found, insert it at the beginning of the row
    if most_recent_revision_row:
        rowsOut.insert(0, most_recent_revision_row)
    else:
        rowsOut.append(rowOut)
    
    # For each revision in the document
    for i in revIds:
        # Check if the sheet has it
        if i in sheetRevs:
            # Get the sequence Id name and get its name
            r = doc.GetElement(i)
            rsId = r.RevisionNumberingSequenceId
            rs = doc.GetElement(rsId)
            rsn = rs.Name
            # Find the index of the sequence name
            i_sq = revSeqNames.index(rsn)
            # Find out how many times the sequence occurred so far
            i_ch = trackRevs.count(rsn)
            # Get the sequence character, then track the sequence
            d = revCharSeqs[i_sq][i_ch] + sep
            trackRevs.append(rsn)
        else:
            d = "" + sep
        rowOut += d
    
    # Add current revision to start
    if current_revision_row:
        rowsOut.insert(0, current_revision_row)
    else:
        rowsOut.append(rowOut)

    # Add the value to the end
    rowsOut.append(rowOut)


# Make the top header
header = sep + sep + sep.join([r.RevisionDate for r in revs]) + sep + sep_2
rowsOut.insert(0,header) 

# Preparing output to Dynamo

Try using this method instead (I pinned in top comment for video too), it’s far easier than the approach I used at the time of the tutorial. I came across it afterwards unfortuntaely.

You can effectively just say:
for each sheet…
for each revision…
get revision number on sheet, write to a list (row)

Thanks for that Gavin. Might be a bit advanced for me at the moment as although I know the basics of python, so I can read code and follow it along in a way, I am very new to understanding how to use the Revit API.

But having a bit of a crack, using the GetRevisionNumberOnSheet method. I would be inserting it somewhere in this part of the code?

for s in sheets:
	trackRevs = []
	sheetRevs = s.GetAllRevisionIds() #get all the revisions ids on each sheet
	rowOut = ""
	rowOut += s.Name  + sep + s.SheetNumber + sep 
	# For each revision in the document
	for i in revIds: #uses that early variable that collects all the revision ids
		# Check if the sheet has it
		if i in sheetRevs:
			# Get the sequence Id name and get its name
			r    = doc.GetElement(i)
			rsId = r.RevisionNumberingSequenceId
			rs   = doc.GetElement(rsId)
   			rsn  = rs.Name
			# Find the index of the sequence name
			i_sq = revSeqNames.index(rsn)
			# Find out how many times the sequence occured so far
			i_ch = trackRevs.count(rsn)
			# Get the sequence character, then track the sequence
			d = revCharSeqs[i_sq][i_ch] + sep
			trackRevs.append(rsn)
		else:
			d = "" + sep
		rowOut += d
	# Add the value to the end
	rowsOut.append(rowOut)

So my understanding of the above is (in simpleton language)

  • Starts a loop that goes over each element in sheets

  • Then starts a nested loop where for each element in the sheets loop

  • it goes through each element in revIds (so basically every revision id in the document)

  • if it comes across an element in revIds thats also in the sheetRevs variable

  • it then gets its revision sequence id (rsId)

  • then gets the sequence itself ( rs)

  • its name (rsn) by using the rs variable that contains its sequence

  • It then gets the index of each sequence name assigning it the variable i_sq

  • and then counts how many names in the element (or each sheet) and adds it to the list trackRevs

  • then gets the revCharSeqs variable in each element and creates a list of i_sq and i_ch

  • then uses the tab serperator and then ads the revision name to the trackRevs list

  • with else it then skips the element if there are no revisions in it (in other words, no revision on the sheet)

Then finally adds the rowOut to the empty list rowsOut which was created before the loop
Then goes through the next element in revIds

So based on that (if what I wrote above is correct) I would remove the for i in revIds loop (and the nested if statement) and replace it with a loop that iterates over each element ( s) in sheets and uses the GetRevisionNumberOnSheet method to build the rowsOut part at the bottom?

Man I hope what ive said here makes sense to you haha.

Thanks again.

This workflow does away with the need for any revision sequence ID. The alternative method just takes a revision and returns its number on sheet if any.

You could iterate across each sheet, then each revision and check number on sheet for each. Append them to the sheets row and append the row to the report.

I’m adding a Node to Crumple to do this, see below code:

# Made by Gavin Crump
# Free for use
# BIM Guru, www.bimguru.com.au

# Boilerplate text
import clr

# Define list/unwrap list functions
def uwlist(input):
    result = input if isinstance(input, list) else [input]
    return UnwrapElement(result)

# Preparing input from dynamo to revit
sheets    = uwlist(IN[0])
revisions = uwlist(IN[1])
noRev     = IN[2]

# Revision Ids
revIds = [r.Id for r in revisions]
report = []

# For each sheet...
for s in sheets:
	# Empty row
	row = []
	# For each revision Id
	for r in revIds:
		# Get number on sheet
		numOnSht = s.GetRevisionNumberOnSheet(r)
		# If it has one, append
		if numOnSht:
			row.append(numOnSht)
		# If not append no rev
		else:
			row.append(noRev)
	# If row is one object, we go to 1D
	if len(row) == 1:
		row = row[0]
	# Append row
	report.append(row)

# Preparing output to Dynamo
OUT = report

Thanks again for the reply. If I could ask one more thing, with what I described above, how what I thought that part of the code was doing, was I close to being correct?

Thanks

Yes your general approach was sound, similar in nature to the video if a bit simpler even. Hopefully the alternative proves easier!