F# Skins the Fibonacci Cat, or So Much To Learn, So Little Brain Power

In my quest to learn F# I came across the Project Euler site, projectEuler.net.  It’s a site with mathematical problems designed to be solved with computer algorithms.  Most of the problems are very academic in nature and don’t apply to real world scenarios, but I’ve enjoyed using them to help me learn F#.  I highly recommend it to anyone learning a new language as there are plenty of problems to solve with little danger of corrupting your workplace with bad code, unless of course you can use all the prime numbers below 2 million in your workplace.

Yesterday I ran across Kean’s blog entry, “Creating Fibonacci spirals in AutoCAD using F#”.  In it he talks about creating a tail recursive function to create a portion of the Fibonacci sequence.  It reminded me of a very similar problem on the Project Euler site.  Below is a slightly modified version of my Fibonacci code for the Project Euler problem.  It generates the same output as Kean’s using a different approach.  Thanks Kean for showing me yet another unexplored area of F#; I’ve noticed this recursive loop pattern in too much of my F# code and I need to branch out.

   1: let FibSeq count =
   2:   let rec fibs iter t1 t2 terms  =
   3:     if iter < count then fibs (iter + 1) t2 (t1 + t2) ((t1 + t2) :: terms)
   4:     else terms
   5:      
   6:   fibs 2 0I 1I [1I;0I]
   7:   |> List.rev
Posted in Customization | Leave a comment

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
Posted in Customization | Leave a comment

Revit Journal Scripting & F# – Unveiling the Black Arts

F# isn’t really a black art, it’s just new, and it made for a catchy title.  Revit journal scripting, on the other hand, is a black art.  This was my very first exposure to them and this article barely qualifies as even touching the tip of the iceberg.  In truth it’s not an area that I see a long term necessity for learning; considering that it’s undocumented, unsupported, and the .NET API is gaining momentum with every release.  Until the .NET API is fully functional, however, this is a useful tool for the CAD manager and power user.

First a little background so you’ll understand what I’m doing and why I’m doing it.  Each of our Revit models has multiple elevations in it.  We use Revit’s design option feature to handle switching between them.  Each elevation will have it’s own square footage chart, as the square footage will vary between every elevation.  To handle this we must create a Revit area scheme, an area plan, and an area schedule for each elevation.  At first we just created these on the fly as we added elevations to the model.  However, we quickly identified this as a time consuming and error prone process that could be simplified by creating a template file with the area schemes and schedules pre-created, and then importing the schedules as needed.  So I started gathering info on just how many schedules I needed to create in the template.

Our design team has standardized on 16 styles of elevations, with names like Arts & Crafts, Spanish Colonial, and the ever popular Traditional style.  Each of those styles is assigned a two letter abbreviation.  For the sake of brevity I’ll only use these three for the article.

Arts & Crafts

AC

Spanish Colonial

SC

Traditional

TR

 

They have also identified buyer profiles that a home is designed to target.  For the sake of simplicity we’ll call them group1, group2, & group3.  So a house designed for group1 could have a French Country, Spanish Colonial, and a Traditional elevation; or any number of the other 16 styles for that matter.  Not only that, but it’s common for a house to have multiple elevations for any one style.  To accommodate that scenario each group is assigned a range of elevation characters.

Group1

A – K

Group2

L – T

Group3

U – Z

 

By combining the elevation abbreviation and an elevation character, we get elevation names for our plans.  So Plan #1200 designed for group1 could have elevations ACA, ACB, SCA, & TRA.  Plan #1300 designed for group3 may have elevations ACU, ACB, SCU, & TRU.  Each elevation in the plan would have it’s own Square Footage chart.  In order to build a template that has all possible elevations, how many schedules would I need to create?  Let’s get out the calculator.  There are 26 letters in the alphabet times 16 elevation styles.  That’s 416 square footage tables.  Revit doesn’t allow you to duplicate a schedule and then change the area scheme it applies to; and with our calculated fields, sorting and grouping entries, & formatting requirements, it takes a while to build a square footage table, which is what drove us to pre-create them in the first place.  If I was able to create each table in 3 minutes, without a single interruption, it would have taken me about 20 hours to create them all.  In reality, this project would have taken well over a week to complete and due to the monotonous nature of the task it’s more than likely that mistakes would be made.  I didn’t have anyone on staff that I could pawn it off on and I didn’t have the time or the energy to do it myself.  I made the decision to automate the process.  Enter Revit journal files and F#.

Every time you open Revit it creates a journal file that records everything that you do in that session; look for the Journals folder under your Revit installation folder.  Not only does it record what you do, it records it in a scripting language that Revit can read and execute.  Drag a journal file onto your Revit icon and Revit will launch and execute the script, exactly mimicking the session that generated the script.  Be *very* careful experimenting with these files.  Running a journal script on a live Revit model could have disastrous effects unless you know what you’re doing.  I’ll be the first to admit that I don’t.  I will, however, share the assumptions that I made after opening and viewing a journal file.  Below is an excerpt from one off my machine.

‘ 0:< Initial VM measure: 1945 MB available, 33 MB used
‘ 0:< 03-Jun-2009 11:34:34.179; started recording journal file
‘ Build: 20080602_1900
Dim Jrn
Set Jrn = CrsJournalScript
‘C 03-Jun-2009 11:34:34.195;   0:< ->InitInstance
‘ 0:< VM Delta: Available -257 MB -> 1688 MB, Used +92 MB -> 125 MB
‘ 0:< DBG_WARN: No Pantone interface: line 70 of .\DocView\ColorUtils.cpp.
‘C 03-Jun-2009 11:34:39.369;   0:< Protein : Register Preview Factories
‘C 03-Jun-2009 11:34:39.369;   0:< Protein : Create Fbx Manager
‘C 03-Jun-2009 11:34:39.369;   0:< Protein : Initialize the Renderer
‘C 03-Jun-2009 11:34:39.369;   0:< Protein : Before open Libraries
‘ 0:< VM Delta: Available -15 MB -> 1674 MB, Used +10 MB -> 135 MB
‘C 03-Jun-2009 11:34:40.010;   0:< Protein : Load fbxrenderermr Plugin
‘C 03-Jun-2009 11:34:40.026;   0:< Protein : After open Libraries
‘C 03-Jun-2009 11:34:40.026;   0:< Protein : 3rd Party plug-ins initialization
‘C 03-Jun-2009 11:34:40.057;   0:< Protein : EmitPluginsEvent
‘ 0:< VM Delta: Available -11 MB -> 1664 MB, Used +9 MB -> 145 MB
‘C 03-Jun-2009 11:34:41.573;   0:< Protein : Register Output Factory
‘ 0:< Revit Architecture 2009
‘ 0:< load point = C:\Program Files\Revit Architecture 2009\Program
‘ 0:< this journal = C:\Program Files\Revit Architecture 2009\Journals\journal.0001.txt
‘ 0:< Journal Init
‘H 03-Jun-2009 11:34:41.652;   0:<
Jrn.Directive "Version"  _
        , "2009.000", "1.811"
‘H 03-Jun-2009 11:34:41.652;   0:<
Jrn.Directive "Username"  _
        , "user 1"

My first assumption was that all lines starting with the ‘ character are comments.  My second was that, considering my lack of understanding, I needed to be careful of what I changed in the file.  Once those two assumptions where out of the way, I created my journal script.  To do that I opened Revit and went through all the steps to create a single Square Footage table, including saving and closing the file.  I then saved a copy of the journal file and started perusing it for what I needed to change to have it create all of my tables.  I found the items I thought I would find, the name of the file to open and the name & description of the square footage table.  These could easily be replaced with new names & descriptions.  The only thing that gave me pause was adding the area scheme.  Below is the section of the journal file that created the area scheme with the comments removed for clarity.

Jrn.Command "Menu" , "Area and Volume Calculation  , ID_SETTING_AREACALCULATIONS"

  Jrn.TabCtrl "Modal , Area and Volume Computations , 0" _
           , "IDS_FIELD_USED_BY_CALC_VAL" _
           , "Select" , "Computations"

  Jrn.TabCtrl "Modal , Area and Volume Computations , 0" _
           , "IDS_FIELD_USED_BY_CALC_VAL" _
           , "Select" , "Area Schemes"

  Jrn.PushButton "Page , Area Schemes , Dialog_Revit_AreaMeasure" _
           , "New, Control_Revit_AreaMeasureNew"

  Jrn.Grid "Control; Page , Area Schemes , Dialog_Revit_AreaMeasure; Control_Revit_AreaMeasure" _
          , "MoveCurrentCell" , "3" , "Name"

  Jrn.Grid "Control; Page , Area Schemes , Dialog_Revit_AreaMeasure; Control_Revit_AreaMeasure" _
          , "PartialEdit" , "3" , "Name" , "Elevation ACA" , "13" , "13"

  Jrn.Grid "Control; Page , Area Schemes , Dialog_Revit_AreaMeasure; Control_Revit_AreaMeasure" _
          , "MoveCurrentCell" , "3" , "Description"

  Jrn.Grid "Control; Page , Area Schemes , Dialog_Revit_AreaMeasure; Control_Revit_AreaMeasure" _
          , "PartialEdit" , "3" , "Description" , "Arts & Crafts Group1" , "20" , "20"

  Jrn.Grid "Control; Page , Area Schemes , Dialog_Revit_AreaMeasure; Control_Revit_AreaMeasure" _
          , "EditIfValid" , "3" , "Description" , "Arts & Crafts Group1"

  Jrn.PushButton "Modal , Area and Volume Computations , 0" _
           , "OK, IDOK"

  Jrn.Data "Transaction Successful"  _
          , "Area and Volume Calculation"

You can follow through and see where it selects the Area and Volume Computations from the pull down menu and then navigates to the Area Schemes tab on the dialog box.  Then it issues a Jrn.Grid command.  The important parameters are “MoveCurrentCell”, “3”, “Name”.  Looking at the dialog, you can surmise what it’s doing.  It’s moving to the 3rd row and setting the “Name” column current.  The next line issues a Jrn.Grid “PartialEdit” command to change the value to “Elevation ACA”.  There are two additional parameters to this call, “13”, “13”, appear to be the length of the string being entered in the grid.  The Jrn.Grid “MoveCurrentCell” and “PartialEdit” commands are then repeated for the Description field.

 

Armed with this information my plan was to write an F# application that would generate a list of all possible square footage tables, generate journal script files for each, and pass those scripts to Revit in a batch process, all while I sat back munching on some snackage watching the show.  I’ll post the F# code in Part II.

Posted in Customization | Leave a comment

Manipulating Polylines In AutoCAD With F# – Part VI

Part I
Part II
Part III
Part IV
Part V
Part VI

To run code when an application loads you need to create a class that implements the IExtensionApplication interface.  I haven’t created a class in F# yet, and certainly not one that implements a specific interface, so I did what I always do when trying something new, I searched Kean’s blog.  And sure enough, he’s covered this very topic.

I don’t think mine varies much from Kean’s, other than I’m calling a function from another module instead of coding it all inline.  It didn’t start that way, but as the code grew it just made sense to refactor it and create a UI module.  I’ve also wrapped the call in a try block.  It’s not that I expect the code to fail on startup, but if it does, AutoCAD in a defensive maneuver against poorly written add-ons will simply not load an assembly that throws an exception in the Initialize method.  And if there is an error, it doesn’t display any messages.  So a try block is useful, especially when debugging.

   1: #light
   2:  
   3: namespace BobbyCJones
   4:  
   5: open Autodesk.AutoCAD.Runtime
   6: open BobbyCJones
   7:  
   8: type InitBOMTools () =
   9:   class
  10:     interface IExtensionApplication with
  11:       member x.Initialize() =
  12:         try
  13:           UI.InitBOMPaletteSet()
  14:         with
  15:           | _ as err ->
  16:             System.Windows.Forms.MessageBox.Show(err.Message) |> ignore
  17:         
  18:       member x.Terminate() =
  19:         ()
  20:   end
  21:  
  22:   
  23: module Init =
  24:   [<assembly: ExtensionApplication(typeof<InitBOMTools>)>]
  25:   do ()

 

One final note before I wrap up this series.  F# is very dependent on code order.  You can’t reference an identifier in a file prior to it being declared.  In the initialization module above, we couldn’t move the code

module Init =
    [<assembly: ExtensionApplication(typeof<InitBOMTools>)]
    do ()

to the top of the file, because it reference the InitBOMTools type and must come after the InitBOMTools declaration.  And not only is code order within a file important, but the compile order of files in a project is important.  As you can see in the image below, each module resides in it’s own file.  The init.fs file has to come after the ui.fs file, because the init.fs file references code inside the ui.fs file.  And the ui.fs file references code in the polyTools.fs file, so it must come after polyTools.fs.  The order of the files listed in Visual Studio is the order that they will be provided to the compiler.  Visual Studio allows you to sort the files after you create them as well as add new files above and below existing files.

Looking back at my original goals for this project, I am very happy with the outcome.  I have a working application that’s been in use for a couple of weeks now.  It was easy and very fun to write.  I took my original C# project of 430 lines of code spread over 7 files and reduced that to about 250 lines of code spread over 4 files.  That 250 number counts white space and comments, whereas the 430 does not, so the delta is even greater than what you see at first glance.

As a CAD manager I see myself using F# much more in the future.  It’s a great tool that fits well in the CAD world.

Posted in Uncategorized | Leave a comment

Manipulating Polylines In AutoCAD With F# – Part V

Part I
Part II
Part III
Part IV
Part V
Part VI

This next listing is not F# code, it is the BOMUserControl and it’s C#.  The F# CTP version does not have a designer that creates WinForms in F#, and I haven’t installed the 2010 beta to see if it will; my gut says it won’t.  With that being the case, you have several options to create and use forms with F#.  You can create your F# assemblies and reference them inside your C# or VB assemblies that contain your forms.  Or you can do like I did here and create your forms in a C# or VB.NET project and then provide members on the form that are exposed to your F# code.  This is all very easy to do since a single Visual Studio solution can contain any number of projects and each project can be written using any .NET language.  Which method to use will depend on the project.  For this project I choose to import the form into the F# project because I’m doing all the work in F#.  There is one other choice and that is to create your forms in F# code without a designer.  Some will say that is the best way to create forms anyway; I’m not one of those people 🙂

For this control I created two events, DrawPlines & SelectPlines, that are fired when the user presses the draw and select buttons.  I also created four properties that, when set, update various labels on the form (See the listing above for how to hook into these events and set the properties).

As a side note, although I’ve gone with a WinForm here, you can just as easily use WPF.

   1: using System;
   2: using System.Windows.Forms;
   3:  
   4: namespace BobbyCJones
   5: {
   6:   public partial class BOMUserControl : UserControl
   7:   {
   8:     public BOMUserControl()
   9:     {
  10:       InitializeComponent();
  11:  
  12:       this.btnSumAreas.Text = "No Selection";
  13:       this.btnSumAreas.Enabled = false;
  14:  
  15:       this.btnSumLengths.Text = "No Selection";
  16:       this.btnSumLengths.Enabled = false;
  17:  
  18:       this.lblSelectedCount.Text = "";
  19:       this.lblOpenCount.Text = "";
  20:       this.lblClipboard.Text = "";
  21:     }
  22:  
  23:     public event EventHandler DrawPlines;
  24:     public event EventHandler SelectPlines;
  25:  
  26:     private void btnDraw_Click(object sender, EventArgs e)
  27:     {
  28:       if (DrawPlines != null)
  29:       {
  30:         DrawPlines(this, e);
  31:       }
  32:     }
  33:  
  34:     private void btnSelect_Click(object sender, EventArgs e)
  35:     {
  36:       if (SelectPlines != null)
  37:       {
  38:         SelectPlines(this, e);
  39:       }
  40:     }
  41:  
  42:     private void btnSumLengths_Click(object sender, EventArgs e)
  43:     {
  44:       Clipboard.SetText(this.PolyLengths.ToString());
  45:       this.lblClipboard.Text = this.PolyLengths.ToString();
  46:     }
  47:  
  48:     private void btnSumAreas_Click(object sender, EventArgs e)
  49:     {
  50:       Clipboard.SetText(this.PolyAreas.ToString());
  51:       this.lblClipboard.Text = this.PolyAreas.ToString();
  52:     }
  53:  
  54:     private double _length;
  55:     public double PolyLengths 
  56:     { 
  57:       get {return _length;}
  58:       set
  59:       {
  60:         _length = value;
  61:         this.btnSumLengths.Text = _length.ToString("F2") + " Feet";
  62:         this.btnSumLengths.Enabled = true;
  63:       }
  64:     }
  65:  
  66:     private double _area;
  67:     public double PolyAreas 
  68:     {
  69:       get { return _area; }
  70:       set
  71:       {
  72:         _area = value;
  73:         this.btnSumAreas.Text = _area.ToString("F2") + " SF";
  74:         this.btnSumAreas.Enabled = true;
  75:       }
  76:     }
  77:  
  78:     private int _selectedCount;
  79:     public int TotalSelected 
  80:     {
  81:       get { return _selectedCount; }
  82:       set
  83:       {
  84:         _selectedCount = value;
  85:         this.lblSelectedCount.Text = _selectedCount.ToString();
  86:       } 
  87:     }
  88:  
  89:     private int _openPolyCount;
  90:     public int OpenPolyCount 
  91:     {
  92:       get { return _openPolyCount; }
  93:       set
  94:       {
  95:         _openPolyCount = value;
  96:         this.lblOpenCount.Text = _openPolyCount.ToString();
  97:       }
  98:     }
  99:  
 100:   }
 101: }

 

At this point we have a fully functional application.  The user can load it and type SHOWBOM to display the form or GETPOLYS to print the info to the command line.  This is great but, for most of my apps that have a palette I like to display the palette as soon as the code loads, instead of forcing the user to type a command. 

Posted in Uncategorized | Leave a comment

Manipulating Polylines In AutoCAD With F# – Part III (Creating a Palette)

 

In Part I we ended up with a small module containing functions that allowed a user to select polylines and print the sum of the lengths of those polylines to the command line.  In the workhorse function, GetTotalPolylineLength, we took a good look at a tail recursive loop and how to use an accumulator in a recursive loop.  Then we marveled at how good the F# compiler was at inferring type data.

In Part II we refined the code, looked at F# tuples, thought about how generic code and type inference can be a real time saver, not just marketing speak, and finally looked deeper into the power of pattern matching.  I don’t know about you, but I’m really starting to like this language.

In this installment, we’re going to finalize the code, organize it into separate files and modules, and then create a UserControl to host on an AutoCAD palette.  First I want to show you the UserControl layout.  A couple of buttons for drawing and selecting polylines, a couple of buttons for sending data to the clipboard, and some informative labels to let the user know what going on.  This will give you a frame of reference so you can see what the code is ultimately going to do.  We’ll come back to how to create it in a bit.

 

Below is a full listing of the polyTools.fs file.  This is the final version of the code we’ve been working on in the first two posts.  The only changes are the addition of the helper functions, ConvertInchesToFeet and ConvertSquareInchesToSquareFeet, on lines 22 – 26, the SumLengthsAndAreas function is now named ProcessPolys and it returns a tuple in this format (CountOfSelectedPolys, SumOfLengthsInFeet, SumOfAreasInSquareFeet, ListOfOpenPolys), and the function to print the results to the command line have been moved to another file in the project.

This listing does all the heavy lifting.  A scant 62 lines of code, including comments, white space, & my admittedly verbose coding style.  We gained a couple of lines, or is that lost a couple, because the new CTP sets the #light syntax declaration as the default and we no longer have to explicitly declare it. 

The only thing I’m not happy with is the ProcessPolys function.  The vague nature of it’s name and the varied output data it’s creating tells me that it’s doing to much.  But that’s not an F#, issue; it was a design decision that was made up front when I decided that my very terse function looped over the data to many times and I moved all the working code into one loop.  It leaves me itching to refactor, but that will have to wait until the next time the BOM team asks for a change to the application.

   1: module BobbyCJones.PolyTools
   2:  
   3: open Autodesk.AutoCAD.Runtime
   4: open Autodesk.AutoCAD.ApplicationServices
   5: open Autodesk.AutoCAD.DatabaseServices
   6: open Autodesk.AutoCAD.EditorInput
   7: open Autodesk.AutoCAD.Internal
   8:  
   9: open BobbyCJones
  10:  
  11: let GetActiveDoc () =
  12:   Application.DocumentManager.MdiActiveDocument
  13:   
  14: let GetCurrentEditor () =
  15:   GetActiveDoc().Editor
  16:  
  17: let ConvertInchesToFeet distance =
  18:   distance / 12.0
  19:   
  20: let ConvertSquareInchesToSquareFeet area =
  21:   area / 144.0
  22:   
  23: //Get a selection of polylines from the user
  24: let GetPolylineIdsFromUser () =
  25:   let ssOpts = new PromptSelectionOptions()
  26:   ssOpts.MessageForAdding <- "Select polylines to count: "
  27:   ssOpts.AllowDuplicates <- false
  28:   
  29:   let ssFilter = new SelectionFilter [|new TypedValue((int)DxfCode.Start, "LWPOLYLINE")|]
  30:   
  31:   let ssResult = GetCurrentEditor().GetSelection(ssOpts, ssFilter)
  32:   
  33:   Utils.PostCommandPrompt()
  34:  
  35:   //Return a list of ObjectIds
  36:   match ssResult.Status with
  37:   | PromptStatus.OK -> [for selectedEnt in ssResult.Value -> selectedEnt.ObjectId]
  38:   | _ -> []
  39:   
  40:  
  41: //Get sum of lengths, sum of areas of closed polys, and a list of open polys
  42: let ProcessPolys inputList =
  43:   use trans = GetActiveDoc().TransactionManager.StartTransaction()
  44:   
  45:   //Recursive loop
  46:   let rec Loop (totalLength, totalArea, openPolys) inputList =
  47:     match inputList with
  48:     | [] -> (totalLength, totalArea, openPolys)
  49:     | hd::tl ->
  50:       let polyline = trans.GetObject(hd, OpenMode.ForRead) :?> Polyline
  51:       
  52:       //If closed, then get its area, else add it to the openPolys list
  53:       match polyline.Closed with
  54:       | true -> (totalLength + polyline.Length, totalArea + polyline.Area, openPolys)
  55:       | _ -> (totalLength + polyline.Length, totalArea, hd::openPolys)
  56:       |> (fun polyInfo -> Loop polyInfo tl)
  57:   
  58:   //Start the loop
  59:   Loop (0.0, 0.0, []) inputList
  60:   
  61:   |> (fun (totalLength, totalArea, openPolys) ->
  62:        (inputList.Length, totalLength |> ConvertInchesToFeet, totalArea |> ConvertSquareInchesToSquareFeet, openPolys))

The remainder of the code listings are related to building the UI or hooking into AutoCAD in order to execute code when the assembly loads.  We’ll look at them in Part IV.

Posted in Customization | 1 Comment

Manipulating Polylines In AutoCAD With F# – Part IV

Part I
Part II
Part III
Part IV
Part V
Part VI

This next listing is the ui.fs file.  It contains, you guessed it, user interface code.  On lines 10 –34 we create a PaletteSet.  This illustrates what I’ve read is a standard pattern when instantiating & initializing objects in F#.  First you declare an identifier to tie to the object, then you create a temporary identifier that you’ll use to set the object to it’s desired state.  It’s a nice neat way to keep instantiation and initialization code close. 

On line 11 we create an identifier, bomControl, to hold an instance of a UserControl and then immediately declare a tempUC identifier.  We’ll use tempUC to set the initial state of the control, which in our case is only to subscribe to two events that the UserControl exposes, SelectPlines & DrawPlines.  These two events correspond to the two buttons at the top of the UserControl.  Notice the very clean syntax for consuming events in F#.  Simply call the Add method of the event and provide a function.  For both buttons we’ve provide lambda functions, although named functions work as well.

As you step through the callback for SelectPlines you see that it’s calling code from the PolyTools module.  First we get a selection of plines from the user, then we pipe those to the ProcessPolys function; the tuple it generates is piped to a lambda function that sets properties on the UserControl and then returns the tuple, unchanged.  That tuple is piped to another lambda that grabs the list of open polylines and selects them in the AutoCAD editor.  In the end I could have just returned the list of open polys, but when I wrote it I wasn’t sure what other functions down the pipeline would need the other information.

Now, Look at that callback code real close.  Did you catch the reference to the tempUC identifier?  Look at it’s referenced in the callback code and then look at where it’s declared.  Is that freaking cool or what?  We declared an identifier and then later in a lambda function nested inside another lambda function that gets called at the whim of the user, that nested function has access to an identifier that was not declared inside it, nor directly passed into it.  The best description I’ve read for this is that F# functions carry parts of their environment around with them.  I’m not sure that analogy is technically accurate, but it sure helps me wrap my head around this aspect of functional programming.

After that, the remainder of the UserControl initialization code is decidedly boring.  The DrawPline event is assigned a callback that simply starts the pline command and the instantiated and initialized UserControl is returned and assigned to the bomControl identifier.  We then move on to creating the PaletteSet utilizing the tempPS identifier.

Line 28 is a little interesting.  Here we add our UserControl to the PaletteSet.  The Add method returns a palette object, but we don’t do anything with it.  We don’t even want to do anything with it.  We just want to ignore it.  This is so common in functional programming that there is a standard ignore function that takes an argument and ignores it, returning a unit value.  In our particular scenario we can actually leave out the ignore function and the code will compile, with just a warning.  But considering the importance of return values in functional programming it’s best to be explicit when you want to ignore a return, because in other situations you won’t get a warning, you’ll get an error and won’t be able to compile.

The next couple of functions are for showing the PaletteSet.  The command is a little on the brute force end of the spectrum in that it forces an undocked paletteset to 200,200.  This is because most of my users who loose their palette tend to do so because it is somewhere off-screen and I rarely have the spare time to walk around changing screen resolutions to force hidden windows to show.  Feel free to make your version a bit friendlier.

The final two functions are just some methods left over from testing, before I created the UserControl.  I stole the Print function from Kean; I think it was in his AU code.

   1: module BobbyCJones.UI
   2:  
   3: open Autodesk.AutoCAD.Windows
   4: open Autodesk.AutoCAD.Runtime
   5: open Autodesk.AutoCAD.ApplicationServices
   6: open BobbyCJones
   7:  
   8: let BOMPaletteSet =
   9:   let bomControl =
  10:     let tempUC = new BOMUserControl() in     
  11:     tempUC.SelectPlines.Add(fun _ -> 
  12:                              PolyTools.GetPolylineIdsFromUser() 
  13:                              |> PolyTools.ProcessPolys 
  14:                              |> (fun (count, length, area, openPolys) ->
  15:                                   tempUC.TotalSelected <- count
  16:                                   tempUC.PolyLengths <- length
  17:                                   tempUC.PolyAreas <- area
  18:                                   tempUC.OpenPolyCount <- openPolys.Length
  19:                                   (count, length, area, openPolys))
  20:                              |> (fun (_, _, _, openPolys) -> 
  21:                                   PolyTools.GetCurrentEditor().SetImpliedSelection(List.to_array openPolys)))
  22:     tempUC.DrawPlines.Add(fun _ -> 
  23:                            Application.DocumentManager.MdiActiveDocument.SendStringToExecute("_.pline ", true, false, true))
  24:     tempUC
  25:       
  26:   let tempPS = new PaletteSet("BOM Tools", new System.Guid("46ED07F7-8B71-4832-B506-0AE3A64C458D")) in
  27:   tempPS.MinimumSize <- new System.Drawing.Size(305, 505)
  28:   tempPS.Add("Polyline Tools", bomControl) |> ignore
  29:   tempPS
  30:   
  31:   
  32: let InitBOMPaletteSet () =
  33:   BOMPaletteSet.Visible <- true
  34:   
  35:   
  36: [<CommandMethod("ShowBOM")>]
  37: let ShowBOM () =
  38:   InitBOMPaletteSet()
  39:   
  40:   match BOMPaletteSet.Dock with
  41:   | DockSides.None -> BOMPaletteSet.Location <- new System.Drawing.Point(200, 200)
  42:   | _ -> ()
  43:   
  44:   
  45: let rec Print inputList =
  46:   match inputList with
  47:   | [] -> PolyTools.GetCurrentEditor().WriteMessage("\n")
  48:   | hd::tl -> 
  49:     PolyTools.GetCurrentEditor().WriteMessage("\n" + hd.ToString())
  50:     Print tl
  51:  
  52:   
  53: [<CommandMethod("GetPolys")>]
  54: let Main () =
  55:   PolyTools.GetPolylineIdsFromUser () 
  56:   |> PolyTools.ProcessPolys 
  57:   |> (fun (count, lengths, areas, openPolys) ->
  58:        PolyTools.GetCurrentEditor().WriteMessage ("\nTotal Selected: " + count.ToString() +
  59:                                                   "\nLengths: " + lengths.ToString() + 
  60:                                                   "\nAreas: " + areas.ToString() + 
  61:                                                   "\nOpenPolys Count: " + openPolys.Length.ToString())
  62:        Print openPolys
  63:      )
Posted in Customization | Leave a comment

May F# CTP Update Released Today 05-20-09

I’m putting the finishing touches on part III of the manipulating polyline with F# series and should have it posted tomorrow or Friday.  I just wanted to let everyone know that the MAY F# CTP was released today along with VS 2010 Beta 1 that includes .NET 4.0 Beta 1 and F# as a core language that installs with VS.  You can read about and download it here.

I have not installed it yet and I won’t until after I finish the polyline series.  If the update breaks the polyline code I’ll upload an update in a future post.

After some further reading last night I decided that there was no danger of breaking the code in the polyline seriese and I updated to the new release with no problems.  I haven’t had a chance to do much with it yet, but the one thing I can say is that building an F# project is much faster.  My suggestion is to update just as fast as you can.

Posted in Uncategorized | Leave a comment

Manipulating Polylines In AutoCAD With F# – Part II

Part I
Part II
Part III
Part IV
Part V
Part VI

In Part I we ended up with a small module containing functions that allowed a user to select polylines and print the sum of the lengths of those polylines to the command line.  In the workhorse function, GetTotalPolylineLength, we took a good look at a tail recursive loop and how to use an accumulator in a recursive loop.  Then we marveled at how good the F# compiler was at inferring type data.

In this second part we’re going to clean up the code a bit, introduce another F# data structure, the tuple, and see how all that magical and marvelous type inference is more than just hype and how it can save time when developing and modifying code.

The first thing is to clean up are those mutable identifiers, activeDoc and currentEditor.  I replaced them with a pair of functions and replaced all calls to activeDoc and currentEditor with GetActiveDoc() and GetCurrentEditor().  This is a much safer practice and in the future I promise to try to stay away from mutable variables as much as possible.  Another benefit is that these library type functions can be placed in a module and reused.  For the purpose of this blog post however, I’m going to leave them where they are.

   1: //Declare some shared variables for the functions in the module
   2: //let mutable activeDoc = Application.DocumentManager.MdiActiveDocument
   3: //let mutable currentEditor = activeDoc.Editor
   4:  
   5: let GetActiveDoc () =
   6:   Application.DocumentManager.MdiActiveDocument
   7:   
   8: let GetCurrentEditor () =
   9:   GetActiveDoc().Editor

The next thing is to modify the GetTotalPolylineLength function to return the sum of the areas as well as the lengths.  I avoided this at first because I wasn’t sure how to return both pieces of data from one function.  I found one very easy way to do it with a tuple.  A tuple is simply an ordered list of values.  But unlike F# lists, the values in a tuple do not have to be the same type.

Here is an update to the GetTotalPolylineLength function.  I renamed it to better reflect its purpose.  I then modified the loop to accept and return a tuple with two values, totalLength and totalArea.  You declare a tuple by simply placing identifiers, or actual values, in parentheses with each value separated by a comma.  This is where things get interesting.

The tuple itself is pretty straight forward.  The cool thing is that I didn’t have to change any of the other functions, even though this function changed from returning an integer to returning a tuple.  This is because F# allowed us to create every function and every identifier as generic, aside from our single cast to the Polyline type on line 9 in the listing below, and the compiler was able to infer all other type information.  I don’t anticipate changing function return values always having zero impact on surrounding code, but I do anticipate this being a real time saver when creating a group of interrelated functions where you don’t know up front the value types that they’ll be passing around; or when you’re going back to modify existing code to add functionality.  We’ll put this ability to further use as we finalize this module.

   1: let SumLengthsAndAreas inputList =
   2:   use trans = GetActiveDoc().TransactionManager.StartTransaction()
   3:   
   4:   //Recursive loop to accumulate total length
   5:   let rec loop (totalLength, totalArea) inputList =
   6:     match inputList with
   7:     | [] -> (totalLength, totalArea)
   8:     | hd::tl ->
   9:       let polyline = trans.GetObject(hd, OpenMode.ForRead) :?> Polyline
  10:       loop (totalLength + polyline.Length, totalArea + polyline.Area) tl
  11:   
  12:   //Start the loop
  13:   loop (0.0, 0.0) inputList

 

OK, now we’re on a role and our function is returning area as well as length.  I was going to save the next bit of functionality for a future post, but decided to implemented it now, considering how easy adding the last bit of functionality turned out to be.  One of the requirements for this app is to give the total length of all selected polylines, but only calculate area for closed polylines.  It also needs to show the user the open polylines so they can determine if it’s an error or if they’re open by design.

To accomplish this we check the polyline.Closed property and if it’s closed, total its length and area.  If it’s not closed, however, just total the length, not the area, and store the ObjectId in a list, openPolys, that we added to the end of the tuple.

   1: //Recursive loop
   2: let rec Loop (totalLength, totalArea, openPolys) inputList =
   3:   match inputList with
   4:   | [] -> (totalLength, totalArea, openPolys)
   5:   | hd::tl ->
   6:     let polyline = trans.GetObject(hd, OpenMode.ForRead) :?> Polyline
   7:     
   8:     //If closed, then get its area, else add it to the openPolys list
   9:     match polyline.Closed with
  10:     | true -> (totalLength + polyline.Length, totalArea + polyline.Area, openPolys)
  11:     | _ -> (totalLength + polyline.Length, totalArea, hd::openPolys)
  12:     
  13:     |> (fun polyInfo -> Loop polyInfo tl)
  14:  
  15: //Start the loop
  16: Loop (0.0, 0.0, []) inputList

 

On the first pass I coded this with an if..then expression.  I then started thinking about creating further conditionals to filter the polylines and perform different actions based on those filters.  For instance, maybe we only want to count length for polys, open or closed, on layer A-Trim, without adding the open ones to the open poly list, count area and length for closed polys on layer A-Areas, and count length only for open polys on layer A-Areas.  Whew, that’s difficult to follow and it would result in a mess of confusing boolean comparisons, or even worse, nested if’s, if it was coded using imperative techniques.  Pattern matching, however, expresses it clearly and with very little code.

   1: match polyline.Closed, polyline.Layer with
   2: | _, "A-Trim" -> (totalLength + polyline.Length, totalArea, openPolys)
   3: | true, "A-Areas" -> (totalLength + polyline.Length, totalArea + polyline.Area, openPolys)
   4: | false, "A-Areas" -> (totalLength + polyline.Length, totalArea, hd::openPolys)
   5: | _ -> (totalLength, totalArea, openPolys)

Of course that was all hypothetical and our function doesn’t need all that functionality, but who knows, it might one day, and if it does, it will be very easy to add.

There were two things that I learned in this round of coding, keep your code generic to ensure it’s flexible and that it’s easier to change and maintain, and that pattern matching rocks!  In Part III, we will finalize the code and create a GUI, specifically a UserControl that we’ll host on a palette in AutoCAD.

Posted in Customization | Leave a comment

More F# Adventures: You win some; you lose some.

I just about have the second part of the manipulating polylines series complete.  Unfortunately I had to attend to some less than blog worthy aspects of the job the first part of the week and I haven’t quite got it finalized.  I did have one interesting item pop up.

A user came to me with an ACA project where the fields in the titleblocks would not update.  I checked a few things and discovered that the FIELDEVAL system variable was set to only update fields in the event of a category 5 tornado colliding with a 100 foot tidal wave on Friday the 13th.  The project had 60 some odd sheets that needed updating and was due at the end of the day, so I said I’d take care of it.  My first thought was to run a simple LISP routine to batch process the sheets.  But I had a little time and thought I’d try to write a quick F# command to do it.

It didn’t take long to write and it processed all the files in a matter of minutes.  I learned how to combine .NET Enums and I dipped into the Array module for the first time.  There was only one tiny problem.  The function didn’t update the FIELDEVAL variable.  It opened them all and saved them too.  It just didn’t make the change…

I’ll investigate why after I finish the polyline series, or when I start loosing sleep thinking about it.

One word of caution, there is no error checking in this.  It’s blindly opening and saving files and I don’t recommend this unless you have a completely controlled environment, and even then, user beware.

   1: #light
   2:  
   3: module BobbyCJones.BatchFieldUpdate
   4:  
   5: open Autodesk.AutoCAD.ApplicationServices
   6: open Autodesk.AutoCAD.Runtime
   7: open Autodesk.AutoCAD.Windows
   8: open System.Windows.Forms
   9:  
  10: [<CommandMethod("UpdateFields", CommandFlags.Modal ||| CommandFlags.Session)>]
  11: let UpdateFields () =
  12:   let fileDialog = new Autodesk.AutoCAD.Windows.OpenFileDialog("Files to update", 
  13:                                                                 "", 
  14:                                                                 "dwg", 
  15:                                                                 "fields", 
  16:                                                                 OpenFileDialog.OpenFileDialogFlags.AllowMultiple)
  17:   
  18:   match fileDialog.ShowDialog() with
  19:   | DialogResult.OK ->
  20:     let UpdateFile fileName =
  21:         let doc = Application.DocumentManager.Open(fileName, false)
  22:                         
  23:         Application.SetSystemVariable("FIELDEVAL", 31)
  24:                 
  25:         fileName |> doc.CloseAndSave
  26:         
  27:     Array.iter UpdateFile (fileDialog.GetFilenames())
  28:  
  29:   | _ -> ()
Posted in Uncategorized | Leave a comment