Revit Journal Scripting & F# – Unveiling the Black Arts Part II

In Part I of this article I talked about a monotonous task that I was about to undertake and how I decided to automate the process with Revit journal script files and F#.  In Part II we take a look at the F# code that dissected the data, generated and modified hundreds of journal files, and passed those files to Revit for processing.

As is often the case for tasks that CAD Managers face, this task was a run once and throw away the code kind of task.  My goal was to write, debug, and run the code faster than it would take me to do the project manually.  I’m happy to report that the venture was a success, and as a bonus I also got to learn a bit more about F# and Revit in the process.

Let’s move on to the code and have a look at exactly what it needs to do.  It has to take each elevation letter for each group type and combine that with the abbreviation for each elevation style, create a journal script file that will create that particular square footage table, and then pass it to Revit to execute.  Maybe a hard example is needed to better explain.  The template journal file has a couple of text strings that need to be replaced, and replaced in multiple locations.

Elevation ACA
Arts & Crafts Group1

Here are some examples of the desired replacement strings for elevations in Group1

Elevation ACA
Elevation ACB
..
Elevation ACK
Elevaiton SCA
Elevation SCB
..
Elevation SCK
Elevation TRA
Elevation TRB
..
Elevation TRK

Arts & Crafts Group1
Spanish Colonial Group1
Traditional Group1

And all of that is repeated for Group2 & Group3.  Again, I’m only showing 3 elevation styles in these examples, but in reality I have 16 styles.  This is certainly doable in any language, but with F#’s functional programming abilities and it’s access to the .NET library, I knew that it would it would chew threw this data with a style and elegance that an imperative language would be hard pressed to match.

The fist step was to model the data and I was able to model it in code exactly the same way it is organized in our internal documentation, see lines 5 – 11 in the code listing below and compare them to the data tables in Part I.  Then I started writing functions that manipulated the data.

Lines 60-64 is the code that takes the elevStyles list and the list of elevation characters for a group and generates all possible elevation names for that group.  Simple, elegant, and quite powerful for such a tiny bit of code. If you have an AutoLISP background then that code should look familiar and inviting.

The only other interesting bit of code is in lines 23 – 44.  It’s one of those recursive loops that we talked about in the Manipulating Polylines series.  It loops over the list of elevation names and descriptions that lines 60 – 65 created,

[(“Elevation ACA”, “Arts & Crrafts Group1”); (“Elevation ACB”, “Arts & Crafts Group1”); …(“Elevation TRK”, “Traditional Group1)]

and generates the journal files for each.  It also generates a list of the journal file names as it’s working.  It does this by adding each new filename to the beginning of the list of file names, see line 41.  This will generate the list in the reverse order in which the files were created.  If list order isn’t important then this doesn’t matter, but in this case, order is very important.  Each journal file contains a row number for the area scheme it creates and the journals must be executed in the order they were created or the scripts will fail.  To handle that the function reverses the list before returning it, see line 47.

I think the rest of the code is self explanatory.  Have a look and feel free to ask a question if you have one.

As a side note, this entire process took a little over 2 hours to run.  The code to generate the script files only took a second or so, but opening and closing Revit 416 times took a while.  As I get more familiar with journal files my goal is to see if I can generate bits and pieces of journal code that performs specific tasks and combines them into a single journal file that would open Revit once, perform all tasks, and then close.  I can only guess, but my hypothesis is that this would have reduced the run time for this process by more than 90%.

   1: open System
   2: open System.IO
   3: open System.Diagnostics
   4:  
   5: let elevStyles = [("AC", "Arts & Crafts"); 
   6:                   ("SC", "Spanish Colonial"); 
   7:                   ("TR", "Traditional")]
   8:                 
   9: let groups = [("Group1", ['A'..'K']); 
  10:               ("Group2", ['L'..'T']); 
  11:               ("Group3", ['U'..'Z'])]
  12:            
  13:  
  14: let CreateViewFile groupName =
  15:   let viewFileName = @"c:\Journals\Area Schedules " + groupName + ".rvt"
  16:   File.Copy(@"C:\Beazer Revit Toolset\Journal Projects\Area Schedules Template.rvt", viewFileName, true)
  17:   viewFileName
  18:   
  19:   
  20: let CreateJournalScripts viewFileName elevInfoList =  
  21:   let journalTemplateText = File.ReadAllText(@"C:\Journal Projects\journal.0013.txt")
  22:   
  23:   let rec Loop schemeRowNumber inputList journalFiles =
  24:     match inputList with
  25:     | hd::tl ->
  26:       let elevName, desc = hd
  27:       
  28:       //Replace all spaces with "_" in file names
  29:       let journalFileName = 
  30:         sprintf "c:\\Journals\\Journal.%s_%s.txt" elevName desc
  31:         |> fun str -> str.Replace(" ", "_")
  32:      
  33:       journalTemplateText
  34:       |> fun str -> str.Replace("..\\..\\..\\Journal Projects\\Area Schedules Template.rvt", viewFileName)
  35:       |> fun str -> str.Replace("\"21\" , \"21\"", sprintf "\"%i\" , \"%i\"" desc.Length desc.Length)
  36:       |> fun str -> str.Replace(", \"3\" ,", sprintf ", \"%i\" ," schemeRowNumber)
  37:       |> fun str -> str.Replace("Elevation ACA", sprintf "Elevation %s" elevName)
  38:       |> fun str -> str.Replace("Arts & Crafts Group1", desc)
  39:       |> fun newContents -> File.WriteAllText(journalFileName, newContents)
  40:       
  41:       journalFileName::journalFiles
  42:       |> Loop (schemeRowNumber + 1) tl
  43:  
  44:     | [] -> journalFiles
  45:   
  46:   Loop 3 elevInfoList []
  47:   |> List.rev
  48:  
  49:  
  50: let RunJournalFile file =
  51:   use revit = new Process()
  52:   revit.StartInfo <- new ProcessStartInfo("C:\\Program Files\\Revit Architecture 2009\\Program\\Revit.exe", file)
  53:   revit.Start() |> ignore
  54:   revit.WaitForExit()
  55:   
  56:      
  57: let CreateFiles (groupName, elevSuffixList) =
  58:   let viewFileName = CreateViewFile groupName
  59:   
  60:   List.map 
  61:     (fun elevSuffix -> 
  62:       List.map (fun (elevPrefix, styleDesc) -> (elevPrefix + elevSuffix.ToString(), styleDesc + " " + groupName)) elevStyles
  63:     ) 
  64:     elevSuffixList
  65:   |> List.concat
  66:   |> CreateJournalScripts viewFileName
  67:   |> List.iter RunJournalFile
  68:  
  69:   
  70: List.iter CreateFiles groups
This entry was posted in Customization. Bookmark the permalink.

Leave a comment