NewFamilyInstance not setting level of family instance

NewFamilyInstance not setting level of family instance

grubdex
Enthusiast Enthusiast
3,050 Views
14 Replies
Message 1 of 15

NewFamilyInstance not setting level of family instance

grubdex
Enthusiast
Enthusiast

I'm placing a FamilySymbol into my Revit model and have encountered a strange error that only lead to more questions. Since my family is face-based, I'm using the `NewFamilyInstance Method (Face, XYZ, XYZ, FamilySymbol) ` function. However, this doesn't set the `LevelId` property of the new instance (it's set to -1, and appears blank in the UI - the property itself is present in the UI however).

If I query

instance.get_Parameter(BuiltInParameter.FAMILY_LEVEL_PARAM)

I get a very low ElementId of -1001352. If I check my document for that ID (doc.GetElement), I get null returned.

 

I tried setting the levelId parameter, but unfortunately `instance.levelId` is read-only.

 

I then started looking into other elements in my model and noticed that walls and rooms do have a levelId, but things like

wall.get_Parameter(BuiltInParameter.FAMILY_LEVEL_PARAM)

 

returns null? Shouldn't that and `wall.LevelId` be the same (where the latter has a valid levelId for me)?

 

I then tried to set the `FAMILY_LEVEL_PARAM`  for the wall (and other elements like the room) following this post https://thebuildingcoder.typepad.com/blog/2011/01/family-instance-missing-level-property.html,

but because

wall.get_Parameter(BuiltInParameter.FAMILY_LEVEL_PARAM)

returns `null`, I can't call `.Set()` on it, since it throws the `object reference not set to an instance of an object.` error (no idea why it worked in the referenced post by thebuildingcoder).

 

Anyway, I'm mainly looking for a way to successfully set the levelId of my inserted family, does anyone have any hints? Explanations on the weird difference between elements' FAMILY_LEVEL_PARAM and levelId would also be appreciated.

 

I'm on Revit 2023 and the model I used is the one from the "My First Plugin" tutorial here https://knowledge.autodesk.com/search-result/caas/simplecontent/content/my-first-revit-plug-overview...

0 Likes
3,051 Views
14 Replies
Replies (14)
Message 2 of 15

mhannonQ65N2
Collaborator
Collaborator

When you got the id from BuiltInParameter.FAMILY_LEVEL_PARAM, I think you must have used parameter.Id, which returns the Id of the parameter, which for BuiltInParameter.FAMILY_LEVEL_PARAM is -1001352. To get its value as an element Id call parameter.AsElementId().

0 Likes
Message 3 of 15

grubdex
Enthusiast
Enthusiast

Thanks for your reply! Unfortunately, that doesn't solve my main issue of setting the level of the newly placed elements. Does anyone know how to set that?

0 Likes
Message 4 of 15

grubdex
Enthusiast
Enthusiast

Does anyone have an answer for how to set the level of a family instance?

0 Likes
Message 5 of 15

rhanzlick
Advocate
Advocate

Unfortunately, your options are somewhat limited because RevitAPI content has such complex relationships to levels depending on object type. In my experience, you have a couple of options:

1. Use the Create.NewFamilyInstance overload that takes a Level.

2. Place per usual, then set the Level Parameter of your element - this, unfortunately, is not always possible and can be stored in many different parameters depending on what type of object you are placing, but for FamilyInstance,  I believe it is the ParameterTypeId.LevelParam, (or the BuiltinParameter.LEVEL_PARAM for older versions of Revit).

**Note that to do option 2 you will likely also need to update the Offset-from-level parameter after your element is placed to update the Z coordinate of your element**

 

Good Luck and Please post a solution if you manage to figure out a reliable way.

0 Likes
Message 6 of 15

grubdex
Enthusiast
Enthusiast

Thanks for the reply! I'm wondering which overload would take a level? I don't see an obvious candidate in the docs, at least not one that would also work with a face-based familySymbol.

 

For option 2, do you happen to have a short code sample as to how you'd set the parameters using ParameterTypeId.LevelParam or BuiltinParameter.LEVEL_PARAM? At least the latter seems to be readonly for me.

 

0 Likes
Message 7 of 15

rhanzlick
Advocate
Advocate

For Face-Based, you may have some luck with the SCHEDULE_LEVEL_PARAM, then adjusting INSTANCE_ELEVATION_PARAM accordingly.

Additionally, this may not be specifically discussing a face-based FamilyInstance, but may provide some useful info as well:

 

https://forums.autodesk.com/t5/revit-api-forum/change-the-reference-level-of-a-family-instance/td-p/...

0 Likes
Message 8 of 15

grubdex
Enthusiast
Enthusiast

Not sure why it worked for you, but for both the

SCHEDULE_LEVEL_PARAM

as well as the 

FAMILY_LEVEL_PARAM

I'm getting a InvalidOperationException: The parameter is read-only. Also tried regenerating the document before attepmting to set those parameters, but that didn't help either.

 

I also tried setting the element using the level, i.e. with

NewFamilyInstance Method (XYZ, FamilySymbol, XYZ, Element, StructuralType)

 

and the level as the Element input type, but that only set the host tot he level, the schedule level remained null and readonly (note that readonly refers to the API alone, I can change the Schedule Level as I want in the UI).

I'm on Revit 2023, maybe that is the issue, not sure.

 

 

0 Likes
Message 9 of 15

grubdex
Enthusiast
Enthusiast

Just as an udpate: So far, I've tried the following methods to create a new Family Instance

 

NewFamilyInstance Method (Face, XYZ, XYZ, FamilySymbol)

 

NewFamilyInstance Method (Reference, XYZ, XYZ, FamilySymbol)

 

NewFamilyInstance Method (XYZ, FamilySymbol, XYZ, Element, StructuralType)

 

None of them set the level (I passed the level explicitly to the last of the bunch as `element`, which set the level as host but not as, well, level). All of them had the Level and Schedule Level parameters as readonly, so I couldn't change them after creation. Pretty annoying if you ask me. If anyone knows a solution, please let me know

Message 10 of 15

rhanzlick
Advocate
Advocate

Thanks for the followup. It looks like you've exhausted many of the methods readily available. Perhaps @jeremytammik has some wisdom on whether or not this is specifically possible (or knows of a workaround) via the API?

Sorry I couldn't be more help!

Message 11 of 15

jeremy_tammik
Alumni
Alumni

Maybe you can find some additional hints in this recent post?

  

https://thebuildingcoder.typepad.com/blog/2022/10/element-level-and-ifc-properties-.html

  

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open
Message 12 of 15

grubdex
Enthusiast
Enthusiast

So as a byproduct of finding out that the NewFamilyInstance overloads exist in different places with only partial overlap for whatever reason, I finally found the overloads that take a level that people have been mentioning here and there in the forums. Here is an update on what I found out, also based on the link provided by Jeremy Tammik.

 

The heart of the code to try this is as follows, I'm also attaching the model in question

 

 

 

 

 

//these params are hardcoded for quick testing
FamilySymbol switchFamilySymbol = doc.GetElement(new ElementId(361776)); 
Element host = doc.GetElement(new ElementId(353023));
var level = doc.GetElement(new ElementId(30)) as Level;
var pos = new XYZ(0.910480147, 4.925404783, 3.937007874);
var orientation = new XYZ(-1, 0, 0);
// end hardcoded
using( var trans = new Transaction( doc ) )
{
    trans.Start( "test placement" );
    pos = new XYZ(pos.X-15, pos.Y, pos.Z);
    var lvlAndHost = doc.Create.NewFamilyInstance(pos,
        switchFamilySymbol, host, level, StructuralType.NonStructural);
    //-- see explanation below for why this exists --
    doc.Regenerate();
    var setPos = withLvl.Location as LocationPoint;
    setPos.Point = pos ;
    // -- end see explanation --
    // try with setting the level as host element, recommended in tammik's link.
    pos = new XYZ(pos.X+2, pos.Y, pos.Z);
    var lvlAsHost= doc.Create.NewFamilyInstance(pos, switchFamilySymbol, level,
        StructuralType.NonStructural);
    // try with using the level as host directly, plus orientation
    pos = new XYZ(pos.X+2, pos.Y, pos.Z);
    var lvlAsHostAndOrient = doc.Create.NewFamilyInstance(pos, switchFamilySymbol, orientation,
        level, StructuralType.NonStructural);
    // try with using the host element directly
    pos = new XYZ(pos.X+2, pos.Y, pos.Z);
    var hostElement = doc.Create.NewFamilyInstance(pos, switchFamilySymbol, orientation,
        host, StructuralType.NonStructural);  
    // try with using the faceref, as always
    pos = new XYZ(pos.X+2, pos.Y, pos.Z);
    // for the wall in question, the exterior and interior shell layers are mixed up, which is why we use .Exterior here
    var reference = HostObjectUtils.GetSideFaces(wall, ShellLayerType.Exterior);
    var faceRef =
        doc.Create.NewFamilyInstance(reference, pos, 
            orientation, switchFamilySymbol);
    // try with using the level as ref
    pos = new XYZ(pos.X+2, pos.Y, pos.Z);
    reference = new Reference(level);
    var levelRef =
        doc.Create.NewFamilyInstance(reference, pos, 
            orientation, switchFamilySymbol);
    trans.Commit(); 
}

 

 

 

 

 

And here is, again, where things get a bit weird. From the floor plan, everything seems as expected - all elements are aligned to the wall, though the two instances that didn't get passed the orientation are the wrong way around

grubdex_0-1667234357545.png

 

 

However, if we look at the cross-section, every single placement besides the one taking the face reference of the wall (2nd to last) places the element on the very bottom of the host. Even more interesting, I'm NOT able to change the elevation of any of these bottom-wall instances (no problem though with the one that has the correct height set). Since I'm not able to do it in the GUI, I'm obviously also not able to do it using the API (this is the "to-be-explained" block in the source code above)

grubdex_1-1667234433037.png

 

As for the host and level parameters, here's a quick overview of what the individual functions set (numbers correspond to the order of functions in the source code above)

 

  1. NewFamilyInstance(XYZ, FamilySymbol, Element, Level, StructuralType)
    1. Level set to Level 1, Host set to Wall, Elevation set to 0 and unchangeable
  2. NewFamilyInstance(XYZ, FamilySymbol, Level, StructuralType)
    1. Level set to Level 1, Host is None, Elevation set to 0 and unchangeable
  3. NewFamilyInstance(XYZ, FamilySymbol, XYZ, Element, StructuralType) (passing the level as host elemen...
    1. Level set to None, Host set to Level 1, Elevation set to 0 and unchangeable
  4. NewFamilyInstance(XYZ, FamilySymbol, XYZ, Element, StructuralType) (passing the wall as host element...
    1. Level set to None, Host set to Wall, Elevation set to 0 and unchangeable
  5. NewFamilyInstance(Reference, XYZ, XYZ, FamilySymbol) (passing a reference to the wall face as refere...
    1. Level set to None, Host set to Wall, Elevation set to 0 and unchangeable (but once setting the Schedule Level in the GUI, the elevation immediately shows the correct value and can also be altered in the properties, the instance itself has been at the correct spot height from the beginning in the actual model though, and could also be moved in Z-direction, which was not true for any of the other instances)
  6. NewFamilyInstance(Reference, XYZ, XYZ, FamilySymbol) (passing a reference to the level as reference)
    1. Level set to Level 1, Host set to Level 1, Elevation set to 0, and unchangeable.

 

Bonus weird stuff: Although a good chunk of these instances has the wall as their host, for some reason I'm able to move all elements freely in the XY-plane, except for the one instance that also happens to have the correct height. And no, they're not losing their association with their host (see screenshot below).

 

grubdex_2-1667235515168.png

Bonus Bonus weird stuff: I'm not able to pick a new host in the GUI for the first 4 options tried (works for the last two)

grubdex_0-1667236954147.png

 

Weirded out yet? Well, there's more (I keep editing these in as I find them): When I snoop these instances, all but the last two have their Host and HostFace set to None - even if in the GUI it shows a Host in the instance's properties

grubdex_1-1667237974569.png

 

 

So to summarise: Either I'm fundamentally misunderstanding the creation of new family instances, or the creation of new family instances is severely broken. Since this functionality really seems like a core functionality for developers to use the API in the first place, I'm quite surprised to find it in such a broken state.

0 Likes
Message 13 of 15

rhanzlick
Advocate
Advocate

Wow, thanks very much for the thorough exploration and documentation. Sorry if I was unclear earlier, I also did not realize the Create.NewFamilyInstance existed in multiple locations! I will test out these methods you've outlined myself and follow up if I find any workarounds, but at the moment I would guess that your understanding is more complete than most - and therefore highlights some potential bug fixes to be made in the API. In any case, I believe the "Pick New Workplane" (Pick New Host For Existing FamilyInstance) which is trivially possible through the UI is certainly (and sorely) missing via the API.

0 Likes
Message 14 of 15

jeremy_tammik
Alumni
Alumni

Thank you for your investigation and raising this. I passed it on to the development team for you, and they raised a ticket REVIT-198548 for further exploration (all links internal only, for my own reference). I keep you posted and hopefully be able to report something useful soon.

  

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open
Message 15 of 15

jeremy_tammik
Alumni
Alumni

Dear Grubdex,

 

Thank you for your patience.

 

The development team replied:

 

In the customer's RVT file, they are placing the Lighting Switches / Two Way Switch family. They noticed that when placing this family via the API, the "Schedule Level" instance parameter is present but blank, whereas when the family is placed via the UI the "Schedule Level" Instance parameter is set to the host level of the host element. (So, levelId 30 for a wall on Level 1.)

 

I tried placing the switch via the UI. I chose Placement / Place on Face in the placement editor ribbon. When clicking on a face of a wall, I can see the "Schedule Level" ID getting set from the FamilyInstance constructor.

 

When placing via the API, NewFamilyInstance(Face, XYZ, XYZ, FamilySymbol) is the overload that lets me place the switch on the face at a particular location/height.

 

However, the FamilyInstance constructor's callstack does not pass along the valid Level ID to be set in the new family instance.

 

The Schedule Level is left blank.

 

Using a modified version of Grubdex's sample code, I was able to place the "Lighting Switches / Two Way Switch" family on levels and walls in HOTEL_2.rvt. I tested in Revit development version and in Revit 2023.

 

I had to manually place one "Two Way Switch" before the API could place additional switches, too.

 

I can confirm that the "Schedule Level" parameter is left empty (no level selected) when using the NewFamilyInstance APIs that Grubdex tried in the sample code. However, all new family instances I created that had "Schedule Level" parameter editable in the UI could also be edited by the API. To set "Schedule Level" via the API, I just needed to get/set parameter "BuiltInParameter.INSTANCE_SCHEDULE_ONLY_LEVEL_PARAM" with a valid level ID.

 

newFamilyInstance.get_Parameter(BuiltInParameter.INSTANCE_SCHEDULE_ONLY_LEVEL_PARAM).Set(level2id);

 

In the forum post from Grubdex there was some questioning about which NewFamilyInstance API was the right for placing switches in walls. I believe that NewFamilyInstance Method (Face, XYZ, XYZ, FamilySymbol) makes the most sense in this context, as the Two Way Switch family is face-based. To get the wall face that would host the switch, I used wall.get_Geometry() like this:

 

  List<Face> faces = new List<Face>();
  Options geomOptions = new Options();
  geomOptions.ComputeReferences = true;
  GeometryElement wallGeom = wall.get_Geometry(geomOptions);
  foreach (GeometryObject geomObj in wallGeom)
  {
    Solid geomSolid = geomObj as Solid;
    if (null != geomSolid)
    {
      foreach (Face geomFace in geomSolid.Faces)
      {
        faces.Add(geomFace);
      }
      break;
    }
  }			    
  var faceRef = doc.Create.NewFamilyInstance(faces[0], pos, orientation, switchFamilySymbol);
  
  // Set the "Schedule Level" parameter to Level 2
  faceRef.get_Parameter(BuiltInParameter.INSTANCE_SCHEDULE_ONLY_LEVEL_PARAM).Set(level2id);

   

I did not have success using HostObjectUtils.GetSideFaces() to get the wall face.

 

Here is my C# macro which places a Two Way Switch in a wall from HOTEL_2.rvt. With Revit 2023, you can open the attached hotel_2_macro_r2023.rvt to play with it.

 

  public void PlaceSwitchAndUpdateScheduleLevel()
  {
    Document doc = Document;
    
    //these params are hardcoded for quick testing
    FamilySymbol switchFamilySymbol = doc.GetElement(new ElementId(361776)) as FamilySymbol; 
    Wall wall = doc.GetElement(new ElementId(353023)) as Wall;;
    ElementId level2id = new ElementId(9946);
    var pos = new XYZ(-14.0, 4.925404783, 3.937007874);
    var orientation = new XYZ(-1, 0, 0);
    // end hardcoded
    
    using( var trans = new Transaction( doc ) )
    {
      trans.Start( "test placement" );
      
      List<Face> faces = new List<Face>();
      Options geomOptions = new Options();
      geomOptions.ComputeReferences = true;
      GeometryElement wallGeom = wall.get_Geometry(geomOptions);
      foreach (GeometryObject geomObj in wallGeom)
      {
        Solid geomSolid = geomObj as Solid;
        if (null != geomSolid)
        {
          foreach (Face geomFace in geomSolid.Faces)
          {
            faces.Add(geomFace);
          }
          break;
        }
      }			    
      var faceRef = doc.Create.NewFamilyInstance(faces[0], pos, orientation, switchFamilySymbol);
      
      // Set the "Schedule Level" parameter to Level 2
      faceRef.get_Parameter(BuiltInParameter.INSTANCE_SCHEDULE_ONLY_LEVEL_PARAM).Set(level2id);
      
      trans.Commit(); 
    }
  }

 

Please see the responses above regarding placing a light switch family on a wall face and setting the "Schedule Level" parameter. I only focused on the simple goal of placing a switch on a wall, and did not dig into the other "tricky issues."

 

I hope this helps.

 

Best regards,

 

Jeremy

 

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open