| Bobby's profileBobby C. JonesBlogListsGuestbook | Help |
|
Bobby C. JonesMusings of a Corporate CAD Manager June 10 F# Skins the Fibonacci Cat, or So Much To Learn, So Little Brain PowerIn 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.revJune 04 Revit Journal Scripting & F# – Unveiling the Black Arts Part IIIn 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.
Here are some examples of the desired replacement strings for elevations in 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,
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: journalTemplateText34: |> 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 groupsJune 03 Revit Journal Scripting & F# – Unveiling the Black ArtsF# 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.
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.
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.
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.
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. May 28 Manipulating Polylines In AutoCAD With F# – Part VIPart I 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: with15: | _ 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 = 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. Manipulating Polylines In AutoCAD With F# – Part VPart I 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. 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 with37: | 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 with54: | 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. May 27 Manipulating Polylines In AutoCAD With F# – Part IVPart I 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 with41: | DockSides.None -> BOMPaletteSet.Location <- new System.Drawing.Point(200, 200) 42: | _ -> () 43: 44: 45: let rec Print inputList = 46: match inputList with47: | [] -> 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: )May 20 May F# CTP Update Released Today 05-20-09I’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.
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. May 15 Manipulating Polylines In AutoCAD With F# – Part IIPart I 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().EditorThe 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 with10: | 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 with2: | _, "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. May 12 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: | _ -> ()May 06 Manipulating Polylines In AutoCAD With F# – Part IPart I I’ve made the decision to jump onto the functional bandwagon. I could hardly help it, what with all the recent hype around F#. At first I blew it off as a novelty language with little more use than selling more seats of Visual Studio, but eventually I decided to at least give it a look, and now I’m dangerously close to convincing myself that this is the language that I’ve been looking for, even though I didn’t know I was looking. As a CAD manager, many of my coding projects are targeted to tackle a specific task, or linked set of tasks, iterate and manipulate large data sets, and present multiple views of that data. And while C# can handle those projects, they can get verbose. I think this is the niche for F#. At least the niche that I see it filling for me. Let me illustrate with a concrete example. I have a small utility for our B.O.M. team that lets them select polylines. It then displays the sum of the lengths and the sum of the areas of those polylines. The tool reports to a palette and the values can be copied to the clipboard. The team requested some updates and while reviewing the code it really struck me just how much code I typed, and now have to manage, for this seemingly small applet; about 430 lines of code across 7 files. In this and in the next several posts I’m going to share my adventures in re-writing this application using Functional Programming, aka FP, with F#. My goals for this project are to:
Since I am still very much in the learning stages of FP & F#, I’m going to tackle this in small chunks. The first, get my feet wet, chunk is to get a selection of polylines from the user and report the sum of the lengths. Designing that chunk of code was my first test of one of my goals, “Can this problem be expressed in FP terms?” And the answer for me was a resounding yes. I wrote it in pseudo code that looked something like this:
Not once did I consider creating an object, and certainly not an object hierarchy; and there was no need, as this is a simple problem and can be easily solved with a few tasks, or functions, with the data flowing from one task to the other. I spent a total of 15 seconds reviewing my design and then jumped into the IDE, wiped the froth from my lips, and started hammering out code. Not necessarily the recommended method, but I was having fun. This is the header for the module. The only interesting thing here are the identifiers marked as mutable. These are scoped to the module so that all functions have access to them. From a functional point of view, this is scary stuff. Any function in the module can change the values and the other functions have no knowledge of that change happening. When they access the value, they get whatever one of the other function put there. It may be more common for FP programmers to pass these values as parameters from function to function. With that said, this is a very common pattern in an OO class to store state in private member variables, although it is a best practice to access them through a property, and I’ll leave my mutable identifiers until, or if, I find a better, more FP way. Maybe create functions that retrieve the current values, GetActiveDoc & GetCurrentEditor? I’m still undecided on that one. 1: #light 2: 3: module BobbyCJones.BOMTools 4: 5: open Autodesk.AutoCAD.Runtime 6: open Autodesk.AutoCAD.ApplicationServices 7: open Autodesk.AutoCAD.DatabaseServices 8: open Autodesk.AutoCAD.EditorInput 9: 10: //Declare some shared variables for the functions in the module 11: let mutable activeDoc = Application.DocumentManager.MdiActiveDocument 12: let mutable currentEditor = activeDoc.Editor
This is the first function in the module. I modified the name slightly from my original design in order to better reflect what the function returns. It’s pretty standard stuff, it takes unit for an argument and returns a list of ObjectId’s for the selected Polylines. In F# unit is an empty list, or (). Notice the bracket and bananas construct, [| |], to create an array for the SelectionFilter object. I’ve always called the | symbol a pipe, but on several of the sites I visited to research this project, it was called bananas. I’m sticking with bananas :-) At the end of the function I run a match on the ssResult.Status. I could have used an if..then expression, but remember, one of my goals is to shake imperative programming thinking while coding in F#, and pattern matching is key to FP. Also note the for loop to construct the output list. It can iterate over any IEnumerable object, passing each element to an expression, and build the list from the value returned from that expression. I simply return the ObjectId property of each selected entity, but any expression is valid, including functions, lambda or named. 1: //Get a selection of polylines from the user 2: let GetPolylineIdsFromUser () = 3: 4: let ssOpts = new PromptSelectionOptions() 5: ssOpts.MessageForAdding <- "Select polylines to count: " 6: ssOpts.AllowDuplicates <- false 7: 8: let ssFilter = new SelectionFilter [|new TypedValue((int)DxfCode.Start, "LWPOLYLINE")|] 9: 10: let ssResult = currentEditor.GetSelection(ssOpts, ssFilter) 11: 12: //Return a list of ObjectIds 13: match ssResult.Status with14: | PromptStatus.OK -> [for selectedEnt in ssResult.Value -> selectedEnt.ObjectId] 15: | _ -> []
This next function was my first attempt at calculating the sum of the Polyline lengths. It is certainly terse, which is one of the things that I was looking for with FP. I start a Transaction so I can open the Polylines, note the ‘use’ keyword to automatically dispose of the Transaction. As a side note, the transaction should be committed prior to being disposed. I’ll make that modification in the next post. After starting the transaction, I define a nested function to open the Polylines. I then map that function to the inputList to create a list of open Polylines, pipe that list to another map function which calls a lambda function to get a list of the lengths, and finally pipe that list to a function that calculates the sum of the lengths. I was planning my second pass to condense this code even more and it struck me just how inefficient this function was. It loops over the data three separate times, and that doesn’t include the loop in the previous function. And although I created the function to sum lengths, I knew that eventually it would need to sum the areas as well, which would likely create even more loops over the data. Instead of condensing it, I opted to scrap it and start over. In all honesty the multiple loops didn’t bother me much, considering how small the data set was going to be, a dozen or so would be at the top end. But this is a learning exercise and it’s better to start with best practices up front rather than try to unlearn bad habits later. 1: let OrigGetTotalPolylineLength inputList = 2: use trans = activeDoc.TransactionManager.StartTransaction() 3: 4: let openId id = 5: trans.GetObject(id, OpenMode.ForRead) :?> Polyline 6: 7: List.map openId inputList 8: |> List.map (fun ent -> ent.EndParam |> ent.GetDistanceAtParameter) 9: |> List.fold_left (+) 0.0
This is the function as it stands now. Certainly not as terse as the first version, but it only loops the data once and it’s primed for morphing into a function that calculates the sums of lengths as well as areas. It also demonstrates a couple of key patterns in functional programming, a tail recursive loop, and using an accumulator in a recursive loop. First let’s look at the accumulator and then we’ll talk about tail recursion. The nested Loop function is defined on lines 5 – 10. It’s defined as recursive with the rec keyword. It’s not fired up until line 13 where it’s started with a seed value of 0.0 for the totalLength and the inputList of ObjectId’s. On the first pass of the loop a match is run on the inputList. The first check, line 7, is to see if the inputList matches an empty list, []. This match will succeed on two conditions, if the initial inputList was empty, in which case the functions simply returns the seed value, or if the recursive loop has reached the end of the list, at which point it will return the accumulated totals of all the lengths. If the inputList is not empty, it will match the pattern on line 8, hd::tl. The pattern says, if the list consists of an element, hd, cons’d, ::, with the remainder of a list, tl, then it’s a match. In essence, this pattern will match any non-empty list. If you wanted to match a list with exactly one item in it you could use this pattern, hd::[]. Props goes to the first person that comments on how to match other patterns, like a two element list. The cool thing is that not only does pattern matching allow you to find matches on the structure of a list, but it allows you to assign identifiers to portions of the match and then write functions using those identifiers. On line 8, the pattern assigns the identifier hd to the first item in a list, it’s head, and tl to the remainder of the list, its tail. Just to clarify, the head of a list is the first element in the list whereas the tail is itself a list minus the first element. And this is just the very tip of the iceberg of pattern matching. But I digress and need to remind myself how to eat an elephant; one bite at a time. We’ll leave further discussions on pattern matching for another time. Now that line 8 has matched a list with something in it, line 9 opens the polyline and line 10 adds the polyline’s length to our running total, our accumulator, and recursively calls the Loop function with the running total and the remainder of the list, the tail. If you’re like me, you need a concrete example to visualize the process. Let’s imagine that the input list contains 3 polylines with lengths of 5, 1, and 3 respectively. In these examples I’m going to show a list with lengths, but remember that our actual list contains ObjectId’s not lengths. On the first call the totalLength is the seed value, 0.0.
The inputList is matched to the non-empty list pattern and the length 5 is extracted and added to totalLength, 0.0 + 5. Then the Loop function is called again with our new totalLength and the tail of the original list.
Again a non-empty list is matched and the length 1 is extracted from the head and added to the passed in totalLength, 5.0 + 1. The loop is called again with our new values.
The list still isn’t empty, so the match is made and 3 is added to totalLength. The loop is called again. This time there is nothing after the head, so the tail is an empty list.
Now the match is on the empty list pattern and the function simply returns our accumulated totalLength, 9.0. That’s a classic recursive accumulator. One other note on this recursive function. It’s tail recursive. That means that the very last action taken in the recursive section of code is the recursive call. In other words, no other work is done after the recursion. It doesn’t take much Google searching to find examples of recursion where it is difficult for the untrained eye to see if it’s tail recursive or not. My eye is certainly untrained and I hope to get better in this area with time. Be sure and check out Kean Walmsley’s blog for more information on this topic. 1: let GetTotalPolylineLength inputList = 2: use trans = activeDoc.TransactionManager.StartTransaction() 3: 4: //Recursive loop to accumulate total length 5: let rec Loop totalLength inputList = 6: match inputList with 7: | [] -> totalLength 8: | hd::tl -> 9: let polyline = trans.GetObject(hd, OpenMode.ForRead) :?> Polyline 10: Loop (totalLength + polyline.Length) tl 11: 12: //Start the loop 13: Loop 0.0 inputListHere’s something that really amazes me about the F# compiler. Below is a screen shot of the Visual Studio IDE. I’m hovering the cursor over the above function’s declaration. It’s telling me that this function accepts an ObjectId list and returns a float. Nowhere in this code have I specified type information, not in function declarations, nor in any identifier declarations. Similar C# and VB code would be peppered with type information. How does it know that the identifier ‘inputList’ refers to a list of ObjectId’s? How does it know it returns a float? It’s almost magical how F# can infer type information. My best guess is that it infers it’s a list because of the match patterns I’m performing on it and it infers that the list contains ObjectId’s because I pass the list elements as the first argument of the trans.GetObject() method and that argument requires an ObjectId. On occasion you’ll need to give the compiler a hint by declaring a type, but I haven’t had to do it yet in my admittedly simple application. And finally we have the CommandMethod function that brings all the others together. It also sets values for the mutable activeDoc and currentEditor identifiers. If we didn’t do this, then these identifiers would always refer to the document that was open when the assembly was first loaded; an apt illustration of the dangers of maintaining state. Ooh, I think I’ve finally scared myself into not using these. Yet another change for the next installment. The rest of the code flows pretty much with my original design, just calling the other functions and piping the results through until it prints the result at the end. 1: [<CommandMethod("GetPolys")>] 2: let Main () = 3: activeDoc <- Application.DocumentManager.MdiActiveDocument 4: currentEditor <- activeDoc.Editor 5: 6: GetPolylineIdsFromUser () 7: |> GetTotalPolylineLength 8: |> (fun length -> "\nTotal length: " + length.ToString()) 9: |> currentEditor.WriteMessage
In Part II I’ll refine these a little more and add the ability to report area as well as length. Technorati Tags: F#,AutoCAD Customization August 11 AutoCAD Top Ten ListI've used AutoCAD for a long time and just like any long term relationship with an inanimate pile of 1's and 0's it is deep love/hate relationship. Below is my list of the top 10 items that I love about AutoCAD. Some of them refer to CAD in general and some are more geared towards the vertical applications and the new B.I.M. applications, but they all originated with my use of AutoCAD. #10 - Vertical Applications #9 - Fields #8 - The 3rd dimension #7 - Multiple Undo #6 - The powers of association #5 - Style based objects Style based objects make it extremely easy to put in a window of one style and change it to another. Styles have been around for a while, even in straight AutoCAD. Text and dimension styles have been bread and butter tools for CAD Managers and power users for many years. I can't imagine drawing without them. #4 - Blocks #3 - Customization #2 - Ubiquity #1 - Users There's my list, how does it compare to yours? Next we'll look at the top 10 items that I hate about AutoCAD.
Technorati tags: CAD Management July 11 There's Power Under ThereBefore I get into the CAD stuff, I need to share a little something that you may find useful. A while back I read a book on speed reading. And guess what, despite the "As Seen on TV!" warning on the front cover, it really works. My casual reading speed has increased 100% without any loss of comprehension or retention. I'm now working on actually increasing comprehension and retention while maintaining my increased speed. One of the methods the book suggests to do that is to increase your vocabulary.
I thought that I had a fairly decent vocabulary, but I tried an exercize outlined in the book. Anytime I came across a word that I didn't fully understand, I stopped reading and looked it up. The theory is that eventually your vocabulary will increase, you'll start spending less time in the dictionary, and your reading speed will increase as your grasp of the language increases. No kidding, on the first page that I read after starting this exercize, I found three words that I either didn't know at all, or only had a vague or partial understanding of the meaning. The words were pique, balefully, and diatribe. And let me tell you, after looking up these words and fully grasping their meaning, that part of the story took on a whole new dimension. I fully encourage you to give this experiment a try, even if you believe that your vocabulary is already Brobdingnagian.
Just like a large vocabulary is a key to success with language, knowledge of system variables is a key to success with AutoCAD. One of my old AutoLISP books had a huge section in the back that listed all of the AutoCAD system variables. It was instrumental in my early success with AutoCAD. I still try to keep up with all of the new ones, but I do miss a few. Here's one that a co-worker taught me about today:
PUBLISHALLSHEETS
It controls whether or not the PUBLISH command brings in the layout tabs from just the current drawing or from all open drawings. That one created some monster plot jobs before we figured out what was going on!
If you're not familiar with system variables, start exploring, you'll like what you find. If you think that you're well versed in them, give them another look. I promise that you'll rediscover variables that you forgot about and you may even find one or two that you never knew existed. July 10 Before it's too lateLast week on the Autodesk discussion groups I read of the passing of Don Reichle. I didn't know Don. I had never interacted with him in the groups. But I could see from the replies to the news of his passing that he was very well respected and liked. Lots of people had lots of good things to say about him. It struck me as sad that he may not have known the impact that he had on the people he knew and the people he didn't. All those words of kindness were a little too late for Don.
I started thinking of the people that I've met online that have had an impact on my career and my life. While there are many, I could fill pages with their names, 3 come to the top.
So while you're still around to hear it, thank you! You've been teachers, mentors, and most importantly friends. July 07 A Blog in the OvenOwning a Blog is much harder than I originally thought. It sounds simple enough; throw your thoughts into the machine, mix in a few screen shots, stir, and viola, a Blog is born. Truth be told, I am very happy with my Blog. And why shouldn't I be? I made it exactly the way that I wanted it. The problem with authoring a Blog is the same problem that Chefs face; who's going to eat it? I like brownies. My motto is that if I'm going to add inches to my waistline, there will be chocolate involved. In particular I like my brownies extra gooey. I've found that taking them out of the oven 5 minutes early will achieve gooey perfection. No one else in my family, immediate or extended, likes them gooey. They prefer them more cake like. (I know, it doesn't make sense to me either.) Just remember, of all my family I only choose one, and I can forgive her the sin of liking cakey brownies. So when I make brownies for me, they are dense and gooey. When we take them to a family gathering, they are light and cakey. How do you like your Blogs, dense and gooey or light and cakey? My guess is that most readers prefer light and cakey. As the chef of this Blog, however, I sometimes like to cook and serve up something dense and gooey. Have you read my very first post? Most of my posts have been very light and cakey, mostly dealing with work and professional topics. But that's not me, at least not all of me. And if I try to stay completely within the light and cakey realm, I'll get bored and stop posting. (This is my first post since early March for crying out loud!) I promise that I'll keep the dense and gooey posts to a minimum, with the deepest left inside my journal, but realistically, if I'm going to keep this Blog active it's going to have some deep gooey places. So until next time, adios! Will the next time be dense or cakey? I don't know, but rest assured, there will be a next time. Thanks for visiting!
|
||||||||||||||||||||
|
|