My internet connection died last night. After worrying and testing quite a bit last night and this morning, I laid a temporary cable from my neighbour's router. Shortly after I finished, my own connection came back up again, after about 14 hours downtime. Murphy's law remains valid, as always. Very reassuring :-) There is lots of building work going on nearby, so probably they broke it and fixed it with no explanations given. Anyway, I am back to normal now and keeping my fingers crossed.
As an experiment and for comparison purposes, Piotr decided to port this to F# as well. In his own words:
Here are some of the lessons learned:
Let Not Use
Initially, the F# version of the command ran fine only the first time I launched it, and failed in the call to PickObject with an InvalidOperationException saying "The managed object is not valid" on the second call.
After isolating the call to PickObject, I changed the following four initialisation statements:
use uiApp = commandData.Application use app = uiApp.Application use uiDoc = uiApp.ActiveUIDocument use doc = uiDoc.Document
It turns out that this is wrong, since the F# 'use' statement is just like 'using' in C#.
So when these objects fall out of scope, Dispose will be called on them. Since they belong to Revit, they don't like being disposed of by external code. Changing 'use' to 'let' fixed that problem:
let uiApp = commandData.Application let app = uiApp.Application let uiDoc = uiApp.ActiveUIDocument let doc = uiDoc.Document
Thanks to Kean Walmsley for this explanation.
Pattern Matching Versus :?
At one point, I made a little bit of use of F# pattern matching to implement the WallSelectionFilter AllowElement method like this:
member x.AllowElement(element) = match element with | :? Wall as element -> true | _ -> false
However, on further consideration, it is clearer and more succinct to use the F# ':?' operator, which is an equivalent of 'is' in C#, like this:
type WallSelectionFilter() = class interface ISelectionFilter with member x.AllowElement(element) = element :? Wall member x.AllowReference(reference, xyz) = false end
Implementing an Exception Handler
I initially removed the 'try-catch' exception handler around the call to PickObject to handle a user cancelling the pick operation. On consideration, I found that it translates to 'try-with' in F#. The piping symbol '|' means pretty much 'data received from the last operation':
let reference = try sel.PickObject( ObjectType.Element, new WallSelectionFilter(), "Select a wall to split into panels") with | :? Autodesk.Revit.Exceptions. OperationCanceledException -> null
Exiting the mainline execution requires an if-else statement as well: if reference is null, return Result.Canceled. The best solution is probably more functional, though.
Here is the final complete F# implementation of this external command:
namespace FSharpPanelBuilder open System open System.Collections.Generic open Autodesk.Revit open Autodesk.Revit.UI open Autodesk.Revit.Attributes open Autodesk.Revit.DB open Autodesk.Revit.UI.Selection type WallSelectionFilter() = class interface ISelectionFilter with member x.AllowElement(element) = element :? Wall member x.AllowReference(reference, xyz) = false end [<Transaction(TransactionMode.Manual)>] type Command() = class interface IExternalCommand with member this.Execute(commandData, message : string byref, elements ) = try let uiApp = commandData.Application let app = uiApp.Application let uiDoc = uiApp.ActiveUIDocument let doc = uiDoc.Document let sel = uiDoc.Selection let reference = try sel.PickObject( ObjectType.Element, new WallSelectionFilter(), "Select a wall to split into panels") with | :? Autodesk.Revit.Exceptions. OperationCanceledException -> null if reference = null then TaskDialog.Show( "F# Panel Builder", "Operation was canceled") |> ignore Autodesk.Revit.UI.Result.Cancelled else let wall = doc.GetElement(reference.ElementId) :?> Wall let locationCurve = wall.Location :?> LocationCurve let line = locationCurve.Curve :?> Line use transaction = new Transaction(doc) let status = transaction.Start("Building panels") let wallList = new List<ElementId>(1) wallList.Add(reference.ElementId) PartUtils.CreateParts(doc, wallList) doc.Regenerate() let parts = PartUtils.GetAssociatedParts( doc, wall.Id, false, false) let divisions = 15 let origin = line.Origin let delta = line.Direction.Multiply( line.Length / (float)divisions) let shiftDelta = Transform.get_Translation(delta) let rotation = Transform.get_Rotation( origin, XYZ.BasisZ, 0.5 * Math.PI) let wallWidthVector = rotation.OfVector( line.Direction.Multiply(2. * wall.Width)) let mutable intersectionLine = app.Create.NewLineBound( origin + wallWidthVector, origin - wallWidthVector) let curveArray = new List<Curve>() for i = 1 to divisions do intersectionLine <- intersectionLine.get_Transformed( shiftDelta) :?> Line curveArray.Add(intersectionLine) let divisionSketchPlane = doc.Create.NewSketchPlane( new Plane(XYZ.BasisZ, line.Origin)) let intersectionElementsIds = new List<ElementId>() let partMaker = PartUtils.DivideParts( doc, parts, intersectionElementsIds, curveArray, divisionSketchPlane.Id) doc.ActiveView.PartsVisibility <- PartsVisibility.ShowPartsOnly transaction.Commit() |> ignore Result.Succeeded with ex -> TaskDialog.Show( "F# Panel Builder", ex.Message) |> ignore Result.Failed end
The code is a straight translation from C# into F#, almost without making any use of F# specific concepts.
Later on, it would be nice to rewrite it to be a bit more functional.
Git and GitHub Source Code Management
I placed the entire project on GitHub:
GitHub a source code collaboration platform based on the version control system git, a fast, efficient, distributed system for collaborative software development. It enables you to easily fork, send pull requests and manage all your public and private git repositories.
I love it. Here at CADPRO I don't get too much chance to use it collaboratively, since I am usually the only person working on a project.
When playing with open source projects, that's another story and git is a brilliant tool for it. Some people claim that is it hard and too complicated, but for me that was the first distributed source control system I used and I didn't find it too hard to grasp.
The thing that makes Git amazing is GitHub. They just put a nice desktop and web interface on it and make it nice and easy to use.
GitHub also provides a little tool that help with publishing source code on the Internet. Gist.github.com allows you to create snippets of code and then embed them in other websites.
As an example, I pasted the command.fs file from the F# sample.
Retrieving Line Styles
Mikako Harada published sample code showing how to retrieve all line styles through the CurveElement GetLineStyleIds property.
If you don't have a curve element handy to invoke the property on, you can simply create a temporary one, for instance a detail line in the currently active view. Created in a separate transaction which is rolled back afterwards, it enables access to the line styles with no effect on the database.