Macro to PyRevit Script using AI - a Success Story :)

I wanted to share a small victory :slight_smile:

I still know basically nothing about coding in python. I’ve started a course & watched some videos but still super ignorant.

Anyway a few in the office are using a macro
Revit API - Single Line Text to Multiline Text (google.com)

I was curious how an AI Chatbot would do converting it…

It took almost 2 hours but I got it to function.

It had many errors, which I fed back to it and it would rewrite /update the code.
Eventually it produced a script that did not create any errors… But also did Nothing! LOL (well it was probably doing something just nothing I could see and not what I wanted ).

Before giving up I realized I never included in the instructions to the chatbot what I was trying to accomplish, I only asked it to convert the macro to a pyrevit script. So we went through the dance again… and after 3 iterations it worked! Then I asked it to add another function (deleting the initial text). This worked on its first try!

An interesting exercise. I imagine someone who understands python could likely have accomplished that in far less than 2 hours.
The more I can understand what it is generating the better I will be at giving it the correct prompts. Or spotting errors without having the reload the tools and test it first.

What are your thoughts?

Update… I just did another test.
This time I gave it only prompts… without giving it the initial Macro it was never able to make progress.
30+ errors (only 50 lines of code) it’s attempts to fix the errors never made any difference.

Hi, I’m afraid that GPTs are not so good at handling specific libraries such as Revit .NET API, and even worse creating pyRevit IronPython code, a very niche subject compared to the huge amount of code that was used to train the AI.

I use ChatGPT and Bard to have a laugh from time to time, they always come up with miraculous python libraries to pip install and use right away, when in reality there’s no library at all…

Just out of curiosity: did you remove the comments in your c# code at the first try? there are many lines of unneeded, commented code that could be mistaken for actual input info by the AI.

You would be better off using something like github copilot, codeium or other coding assistants inside an IDE such as Visual Studio Code or PyCharm, but then again, there aren’t many open source projects on GitHub that use the Revit API, so the AIs are not so trained at handling what you ask for.

1 Like

Thanks @sanzoghenzo I’m just playing around right now. I’ll take a look at those coding assistants you mentioned.

I did not remove the comments from the original.
Here is the final version that I got to work:

# Importing necessary modules
from Autodesk.Revit.DB import XYZ, Transaction, BuiltInParameter, TextNoteType
from pyrevit import revit, DB

# Get the active document and UIDocument
doc = __revit__.ActiveUIDocument.Document
uidoc = __revit__.ActiveUIDocument

# Get the element selection of the current document
selection = uidoc.Selection

# Store element ids
selectedIds = uidoc.Selection.GetElementIds()

# Make a list for holding items that are not text notes, so they are not deleted
mylist = []

# Create a list to hold the text strings
textStrings = []

# Go through each selected item
for id in selectedIds:
    # Get the element from the id
    e = doc.GetElement(id)

    try:
        # Check if the element has a text parameter
        if e.get_Parameter(BuiltInParameter.TEXT_TEXT):
            # Get the text parameter and the text contained in the parameter
            myTextParam = e.get_Parameter(BuiltInParameter.TEXT_TEXT)
            myText = myTextParam.AsString()

            # Cast the element as a TextNote so the coordinates can be obtained
            t = e if isinstance(e, DB.TextNote) else None
            if t is not None:
                textStrings.append(myText)
            else:
                mylist.append(id)
                print("Element with ID {0} is not a TextNote.".format(id))
        else:
            print("Element with ID {0} does not have a text parameter.".format(id))

    # If an item is not a text note, it should be caught here
    except Exception as ex:
        print("Exception: {0}".format(ex))
        mylist.append(id)

# Combine text strings into one multiline text
combinedText = '\n'.join(textStrings)

# Create a new multiline text note at the location of the first selected text note
if len(textStrings) > 0:
    firstTextNoteId = selectedIds[0]  # Get the first element ID from the list
    firstTextNote = doc.GetElement(firstTextNoteId)
    textLocation = firstTextNote.Coord
    textTypeId = firstTextNote.GetTypeId()
    
    # Start a transaction to create a new multiline text note
    with Transaction(doc, "Combine Text Notes") as transaction:
        transaction.Start()
        
        # Create a new multiline text note
        newTextNote = DB.TextNote.Create(doc, doc.ActiveView.Id, textLocation, combinedText, textTypeId)
        
        # Delete the old text notes
        for oldTextNoteId in selectedIds:
            doc.Delete(oldTextNoteId)
        
        transaction.Commit()

    print("Text notes combined into a new multiline text note, and old text notes deleted.")
else:
    print("No text notes selected.")

print("\nNot Text Ids:")
print(mylist)

Is it working properly? From what I can see it removes also the selected non-ext element, despite having saved them in the mylist variable, it doesn’t check if oldTextNoteId is part of that list.

The rest of the code is… acceptable for a tool that doesn’t have consciousness, I’ve seen far worse code written by humans :rofl:

I’m getting curious to try this myself, and maybe try other different AIs… Did you use chatgpt 3.5?

Yep chat gpt3.5. (I tried DeepAI first, it didnt get me there). But yes, it’s working. I had a CAD Detail that I exploded and each line of text was separate. The first ‘Working’ version created the new combined text but left the original single line text. Then I asked it to delete the original text. And the Version I shared does delete the original.

Try to select non-text elements along with the texts and you’ll see what I’m talking about :wink:

Oh, I see that fun error message now. :stuck_out_tongue:

So, I went back and tried to see if it could fix it. It took about 6 tries I no longer get an error if non-text elements are selected, the text is combined but now the text moved to a new location…

This was a fun exercise, I don’t really need this, but I think I’d like to come back to it once I get a better grasp of python and see if I can make it work…

Anyway, here’s the code ChatGPT updated if you’re interested:

from Autodesk.Revit.DB import Transaction, BuiltInParameter, ElementId
from pyrevit import revit, DB

# Get the active document and UIDocument
doc = __revit__.ActiveUIDocument.Document
uidoc = __revit__.ActiveUIDocument

# Get the element selection of the current document
selection = uidoc.Selection

# Store element ids
selectedIds = uidoc.Selection.GetElementIds()

# Make a list for holding items that are not text notes, so they are not deleted
mylist = []

# Create a list to hold the text strings
textStrings = []

# Go through each selected item
for id in selectedIds:
    # Get the element from the id
    e = doc.GetElement(id)

    try:
        # Check if the element has a text parameter
        text_param = e.get_Parameter(BuiltInParameter.TEXT_TEXT)
        if text_param:
            myText = text_param.AsString()
            textStrings.append(myText)
        else:
            mylist.append(id)
            print("Element with ID {0} does not have a text parameter.".format(id))

    # If an item is not a text note, it should be caught here
    except AttributeError:
        try:
            # Check if the element has coordinates (Location and Point properties)
            if hasattr(e, "Location") and hasattr(e.Location, "Point"):
                textStrings.append("No text parameter")  # Use a placeholder text
            else:
                mylist.append(id)
                print("Element with ID {0} is not a TextNote.".format(id))
        except Exception as ex:
            print("Exception: {0}".format(ex))
            mylist.append(id)

# Combine text strings into one multiline text
combinedText = '\n'.join(textStrings)

# Create a new multiline text note at the location of the first selected text note
if len(textStrings) > 0:
    firstTextNoteId = selectedIds[0]  # Get the first element ID from the list
    firstTextNote = doc.GetElement(firstTextNoteId)

    # Use the Location.Point property if available, otherwise use the origin point
    if isinstance(firstTextNote.Location, DB.LocationPoint):
        textLocation = firstTextNote.Location.Point
    else:
        textLocation = firstTextNote.Location.Origin

    # Get a valid text type ID or use the default text type
    defaultTextTypeId = doc.GetDefaultElementTypeId(DB.ElementTypeGroup.TextNoteType)
    textTypeId = firstTextNote.GetTypeId() if defaultTextTypeId.IntegerValue == -1 else defaultTextTypeId

    # Start a transaction to create a new multiline text note
    with Transaction(doc, "Combine Text Notes") as transaction:
        transaction.Start()

        # Create a new multiline text note
        newTextNote = DB.TextNote.Create(doc, doc.ActiveView.Id, textLocation, combinedText, textTypeId)

        # Delete the old text notes
        for oldTextNoteId in selectedIds:
            if oldTextNoteId not in mylist:
                doc.Delete(oldTextNoteId)

        transaction.Commit()

    print("Text notes combined into a new multiline text note, and old text notes deleted.")
else:
    print("No text notes selected.")

print("\nNot Text Ids:")
print(mylist)

It solved the previous bug, but it introduced a new one!

Now if the element is not a text but has the Location.Point attribute, it will be deleted and the text “No text parameter” will be put in your multiline text.

The except AttributeError block should just add the id to mylist.

Also, id is a python built-in, it should be avoided.

I’m more curious to see what we’re your prompts rather than the code.
From my brief experience I also noted that I needed at least 8 iterations to get something usable, but at that time it was more likely a loop of the same wrong answers, over and over… So I resort to it only if I can’t find a good answer via Google/stack overflow…

In the brief time I used chatgpt4 I saw big improvements (it is not perfect yet, but has less hallucinations) so it might be worth to try it!

1 Like

Been using chatgpt and its relatives to help me figure out what kind of solution I need for problems, but it always falls flat on its face when you try to ask it very specific things.

It’s really good for passing on questions and asking it to explain things, but it’s a far cry from being useful in other ways than that.
As stated above, gpt is not trained on the niche workings of ironpython. And sometimes the answers that do come out of it are completely made up, or very very outdated.

That being said, I am glad that you managed to get your script sort-of working using gpt.

We are definitly heading towards a future where code assistants are going to help us code things faster. But replacing programming outright is probably not in the cards just yet.

1 Like

Catchy post title. A bit presomptuous to my taste though :smirk:

Definitely.


Agents are good at explaining basic stuffs and code, bad at coding. Take them as a friend that knows a bit about programming and can give you hints and pointers.
I have used Microsoft Github Copilot for almost two years now. Great helper for repetitive and small stuff, bad for big chunks and new methods from niche API like mentioned above.
Worth it, Yes, but sometimes it brought some stupid bugs…

1 Like

That makes sense. I use it in a similar way for writing. I needed to write a dozen or so reviews for my previous team. I fed it all the information I wanted covered. Then spent about 30min-hour adjusting those rough drafts into my voice, and removing some silly comments that either didn’t apply or were phrases I would never use, etc…

I can imagine code being similar.

My first prompt:

  • “convert this C# macro to pyrevit script”

Then:

  • Prompted it to debug error messages. x6
  • “it runs without error BUT does not combine the separate text strings into one, which is what I am trying to accomplish”

Its next solution was to Group the Text. ( I didnt even try that version of the script ) I replied:

  • “sorry that is not the goal. I want to select multiple lines of text that are each individual elements and combine them into one text string”
  • Debug Error messages x3

At that point the script would combine the text but originals remained. My next prompt:

  • “that creates the new multi line text note! Now can we make it also remove the old text notes?”

The result from that was the script I posted.

1 Like