Question

I'm trying to create my first Revit plugin.

I'm using Revit 2014 and what I want is to place a SINGLE instance of a family loaded from a file. I'm actually using this code:

[TransactionAttribute(TransactionMode.Manual)]
[RegenerationAttribute(RegenerationOption.Manual)]
public class InsertFamily : IExternalCommand
{
    readonly List<ElementId> _addedElementIds = new List<ElementId>();

    public Result Execute(
        ExternalCommandData commandData,
        ref string message,
        ElementSet elements)
    {
        UIApplication uiApp = commandData.Application;
        Document document = uiApp.ActiveUIDocument.Document;

        FamilySymbol family = null;
        bool good = false;
        using (var trans = new Transaction(document, "inserting family"))
        {
            trans.Start();
            good = document.LoadFamilySymbol(@"my file path.rfa", "my type", new FamilyLoadingOverwriteOption(), out family);
            trans.Commit();
        }
        if (good && family != null)
        {

            _addedElementIds.Clear();

            uiApp.Application.DocumentChanged += applicationOnDocumentChanged;

            uiApp.ActiveUIDocument.PromptForFamilyInstancePlacement(family);

            uiApp.Application.DocumentChanged -= applicationOnDocumentChanged;

        }
        return Result.Succeeded;
    }

    private void applicationOnDocumentChanged(object sender, DocumentChangedEventArgs documentChangedEventArgs)
    {
        _addedElementIds.AddRange(documentChangedEventArgs.GetAddedElementIds());        
    }
}



class FamilyLoadingOverwriteOption : IFamilyLoadOptions
{
    public bool OnFamilyFound(bool familyInUse, out bool overwriteParameterValues)
    {
        overwriteParameterValues = true;
        return true;
    }

    public bool OnSharedFamilyFound(Family sharedFamily, bool familyInUse, out FamilySource source, out bool overwriteParameterValues)
    {
        source = FamilySource.Family;
        overwriteParameterValues = true;
        return true;
    }
}

The problem is that the method PromptForFamilyInstancePlacement allows user to insert multiple instances of the family. I want that the user can insert only ONE instance into the project. I write also the code to have back the inserted instance (using the DocumentChanged event as you can see), so maybe that handler can be useful in some ways..

Was it helpful?

Solution

Finally I found my own solution (thanks to Jeremy Tammik blog): the only way seems to send the "Esc" + "Esc" key combination to Windows while the command is executing:

I've done a class that handles the low levels messages:

public class Press
{
    [DllImport("USER32.DLL")]
    public static extern bool PostMessage(
      IntPtr hWnd, uint msg, uint wParam, uint lParam);

    [DllImport("user32.dll")]
    static extern uint MapVirtualKey(
      uint uCode, uint uMapType);

    enum WH_KEYBOARD_LPARAM : uint
    {
        KEYDOWN = 0x00000001,
        KEYUP = 0xC0000001
    }

    enum KEYBOARD_MSG : uint
    {
        WM_KEYDOWN = 0x100,
        WM_KEYUP = 0x101
    }

    enum MVK_MAP_TYPE : uint
    {
        VKEY_TO_SCANCODE = 0,
        SCANCODE_TO_VKEY = 1,
        VKEY_TO_CHAR = 2,
        SCANCODE_TO_LR_VKEY = 3
    }

    /// <summary>
    /// Post one single keystroke.
    /// </summary>
    static void OneKey(IntPtr handle, char letter)
    {
        uint scanCode = MapVirtualKey(letter,
          (uint)MVK_MAP_TYPE.VKEY_TO_SCANCODE);

        uint keyDownCode = (uint)
          WH_KEYBOARD_LPARAM.KEYDOWN
          | (scanCode << 16);

        uint keyUpCode = (uint)
          WH_KEYBOARD_LPARAM.KEYUP
          | (scanCode << 16);

        PostMessage(handle,
          (uint)KEYBOARD_MSG.WM_KEYDOWN,
          letter, keyDownCode);

        PostMessage(handle,
          (uint)KEYBOARD_MSG.WM_KEYUP,
          letter, keyUpCode);
    }

    /// <summary>
    /// Post a sequence of keystrokes.
    /// </summary>
    public static void Keys(string command)
    {
        IntPtr revitHandle = System.Diagnostics.Process
          .GetCurrentProcess().MainWindowHandle;

        foreach (char letter in command)
        {
            OneKey(revitHandle, letter);
        }
    }
}

The main code is the following:

{ 
...
_uiApp.Application.DocumentChanged += applicationOnDocumentChanged;
_uiApp.ActiveUIDocument.PromptForFamilyInstancePlacement(family);
_uiApp.Application.DocumentChanged -= applicationOnDocumentChanged;
var el = document.GetElement(_addedElementIds[0]);
...
}


private void applicationOnDocumentChanged(object sender, DocumentChangedEventArgs documentChangedEventArgs)
{
    if (documentChangedEventArgs.GetTransactionNames().Contains("Component"))
    {
        _addedElementIds.AddRange(documentChangedEventArgs.GetAddedElementIds());
        Press.Keys("" + (char)(int)Keys.Escape + (char)(int)Keys.Escape);
    }
}

In this way only one element is placed and I've it's reference into el variable.

OTHER TIPS

Do you need the user to be able to select the location of the family instance?

If not, then you should use the Document.NewFamilyInstance method

These posts should help clarify which overload to use:

http://thebuildingcoder.typepad.com/blog/2011/01/newfamilyinstance-overloads.html

http://thebuildingcoder.typepad.com/blog/2013/09/family-instance-placement.html

If you do need the user to select the location to place the family instance, you could potentially use the Selection.PickPoint method to first get a location point, and then pass that location to the NewFamilyInstance method.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top