Creating in-place families

Creating in-place families

stefanome
Collaborator Collaborator
4,689 Views
13 Replies
Message 1 of 14

Creating in-place families

stefanome
Collaborator
Collaborator

Is it possible to create an in-place family with the APIs?

 

I can use LoadFamily and NewFamilyInstance to load new families and create new instances, but I would like to create new in-place families instead.

 

The reason why I want in-place families is because the add-in will create one family on each selected face (mass or wall face). The in-place family created by the add-in will have some automatically generated grid pattern made by model lines.

 

Sometimes the automatically generated grids are correct, but often they require some user editing. The user will be able to double click the in-place family and edit it, then add, remove or edit the existing grid lines while using the model entities as references.

 

If I used normal families, the model would disappear when the user starts the family editor.

 

If there is no way to create in-place families via APIs, is there a way to allow the user to edit normal families in-place, as if they were in-place families?

 

An alternative would be for the add-in to generate the grid lines and put them in a group. Groups would be edited in place as I like, but... well, they are not families and if I could do something with families I think it's better to do it with families.

Accepted solutions (1)
4,690 Views
13 Replies
Replies (13)
Message 2 of 14

jeremy_tammik
Alumni
Alumni
Accepted solution

No, the Revit API does not support in-place families.

 

I believe there is a wish list item for that in the Revit Idea Station.

 

Whenever you require new or enhanced functionality, the Revit Idea Station is the place to go.

 

Please search there for a corresponding wish list entry for the suggested functionality and add your comments to it, or create a new one, if none already exists:

 

https://forums.autodesk.com/t5/revit-ideas/idb-p/302

 

Tag it as an API wish:

 

https://forums.autodesk.com/t5/revit-ideas/idb-p/302/tab/most-recent/label-name/api

 

Ensure it gets as many votes as possible to underline its importance to you and the rest of the developer community.

 

The Revit Idea Station is currently one of the main driving input forces for Revit API enhancements.

 

The Revit development team look there. Your comment here in the discussion forum might be overlooked.

 

Thank you!

 

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open
0 Likes
Message 3 of 14

stefanome
Collaborator
Collaborator

Thank you,

I will explore the alternative using groups rather than in-place families.

0 Likes
Message 4 of 14

arautio
Advocate
Advocate

It is now 2023,  is there any change to this lack of functionality of the API? I would really love to be able to create and in-place-family via the API.

0 Likes
Message 5 of 14

jeremy_tammik
Alumni
Alumni

I cannot tell off-hand. Have you checked the Revit Idea Station for the corresponding wish and its status (see above)? Have you checked the Revit public roadmap to see whether any changes in this area are already under consideration?

  

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open
0 Likes
Message 6 of 14

arautio
Advocate
Advocate

The idea station only had 6 votes,  7 now.  I am not familiar with the Revit public roadmap, I will take a peek.  For now I guess I'm generating a generic model family and will put my geo in that.   Making a tool to generate rebar in a compound sloped slab.

0 Likes
Message 7 of 14

jeremy_tammik
Alumni
Alumni

Yes, creating a family and placing an instance in one option. For many uses, the creation of a direct shape has proved simpler and more efficient, so you may want to consider that possibility as well:

  

  

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open
0 Likes
Message 8 of 14

arautio
Advocate
Advocate

I guess Great minds do think alike.

 

Just pulled the code for direct shape before your post,  it will be an option, but I may just go with the family so I can control the rebar as a group and assign materials etc.

 

For now I just drew model lines on each face of the compound floor and using routines to collect the lines into individual loops in a curve array array, then will sweep each curve array with the profile of the rebar number.

 

Eventually will have the user pick the floor and then the direction of the rebar, then the rebar number,  then the spacing, then the cover offset for sides and top, and will generate the linework over the floor via the api (cropping the ends by the side cover, then move the whole system down by the top cover amount at the end.

 

 

 

public void FloorRebarGenerate()
{        
    UIDocument uidoc = this.ActiveUIDocument;
    Document doc = uidoc.Document;
    Autodesk.Revit.DB.View activev = doc.ActiveView;
    
    FilteredElementCollector collector = new FilteredElementCollector(doc, doc.ActiveView.Id).WherePasses(new ElementClassFilter(typeof(CurveElement)));
            
    CurveArray carray = new CurveArray();
    CurveArrArray carrarray = new CurveArrArray();    
    ReferenceArray refArray = new ReferenceArray();
    
    foreach (CurveElement cele in collector)
    {            
        ModelLine ml = cele as ModelLine;        
        Curve cv = ml.GeometryCurve;
        carray.Append(cv);    
        Reference cvref = cv.Reference;
        refArray.Append(cvref);
    }
            
    CurveArrArray newcurarr = new CurveArrArray();
    carrarray.Append(carray);
    SortCurveLoops0(carrarray, doc);
    SortCurvesContiguousArray(carrarray);
    
    //Solid solid = null;    
    //solid = GeometryCreationUtilities.CreateSweptGeometry();
    //ElementId categoryId = new ElementId(BuiltInCategory.OST_GenericModel);
    //DirectShape ds = DirectShape.CreateElement(doc, Guid.NewGuid().ToString());
    //solids = new List<GeometryObject>() { solid };
    //ds.SetShape(solids);
    //ds.Name = "Rebar1";
            
    // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////

    Document docfamily;
    Family fam;
  
    string ftitle = doc.Title;
    string fpath = doc.PathName;
    int ftitlelen = ftitle.Length + 4;
    int fpathlen = fpath.Length;
    int finpathlen = fpathlen - ftitlelen;
    string sfinpath = fpath.Substring(0,finpathlen);
    string famname = "CompoundRebar";
    string fext = ".rfa";
    
    int counter = 1;
    while (counter < 100)
    {
        famname = ("CompoundRebar" + counter as String);
        Family family = FindElementByName(doc,typeof(Family),famname)as Family;
            ifnull == family )
            {
                sfinpath = (sfinpath + famname + fext);
                counter = 200;
            }
            counter += 1;
    }

    FilteredElementCollector collector0 = new FilteredElementCollector(doc);
    ICollection<Element> collection0 = collector0.WhereElementIsNotElementType().ToElements();
    List<FamilySymbol> fsym0 = new FilteredElementCollector(doc).OfClass(typeof(FamilySymbol)).Cast<FamilySymbol>().ToList();
    FamilySymbol famsymb0 = null;
    foreach (FamilySymbol symb in fsym0)
    {
        if (symb.Name == "CompoundRebarBase")
        {
             famsymb0 = symb as FamilySymbol;
        }
    }
            
    fam = famsymb0.Family;
      
    docfamily = doc.EditFamily(fam);                
    try
    {
        docfamily.SaveAs(@sfinpath);
    }
    catch
    {
        TaskDialog.Show("Revit""Could Not Save Rebar Family");
    }
           
    SketchPlane sketch;
    
    
    using (Autodesk.Revit.DB.Transaction trans = new Autodesk.Revit.DB.Transaction(docfamily))
    {
       trans.Start("Make Compound Rebar Family");
       
        ElementId delid = null;
        FamilySymbol profsymbol = null;

        // Get Profiles                      
        collector = new FilteredElementCollector( docfamily );
        foreach(Element element in collector.OfClass(typeof(FamilySymbol)))
        {
            if (element.Category.Name.ToString() == "Profiles")
            {
                //TaskDialog.Show("Revit", element.Name.ToString());
                if (element.Name.ToString().StartsWith("3/4"))
                    {
                        profsymbol = element as FamilySymbol;
                    }
            }
        }
            
        //TaskDialog.Show("Revit", profsymbol.ToString());
        
        collector = new FilteredElementCollector( docfamily );
        foreach(Element element in collector.OfClass(typeof(GenericForm)))
        {
            delid = element.Id;
        }
        
        try
        {
            docfamily.Delete(delid);            
        }
        catch
        {
            
        }
       
        FilteredElementCollector levcoll = new FilteredElementCollector(docfamily).WherePasses(new ElementClassFilter(typeof(Level), false));
        ElementId levid = null;
        foreach (Level le in levcoll)
        {
             //TaskDialog.Show("Revit LevName", le.Name.ToString());
             if (le.Name.ToString() == "Ref. Level" && levid == null)
             {
                 //TaskDialog.Show("Revit LevName is Ref Lev", le.Name.ToString());
                 levid = le.Id as ElementId;
             }
        }
         
        
               
        sketch = SketchPlane.Create(docfamily, levid);
    
        //Sweep sweep = familydoc.FamilyCreate.NewSweep(true, refArray, profile, 0, ProfilePlaneLocation.Start);
            
            
        trans.Commit();
      }
            
    docfamily.Save();
    docfamily.LoadFamily(doc, new CustomFamilyLoadOption());    
    docfamily.Close();
    File.Delete(sfinpath);
    
    // //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    
    
    //TaskDialog.Show("Revit", carrarray.Size.ToString());
    
    //ModelCurve.ChangeToReferenceLine()
    
// create a circle as bottom shape for the cable
//    IList<XYZ> points = sweepPath.GeometryCurve.Tessellate();
//    XYZ center = points[0];
//    Plane workingPlane = Plane.CreateByNormalAndOrigin(XYZ.BasisZ, center);
//    Arc bottomShape = Arc.Create(workingPlane, _radius, 0, 2 * Math.PI);
//    
//    // create profile
//    CurveArray curveArray = new CurveArray();
//    curveArray.Append(bottomShape);
//    CurveArrArray arrArray = new CurveArrArray();
//    arrArray.Append(curveArray);
//    SweepProfile profile = _rvApp.Create.NewCurveLoopsProfile(arrArray) as SweepProfile;
//    
//    // create path
//    XYZ sweepPathDirection = points[1] - points[0];
//    double angle = sweepPathDirection.AngleTo(XYZ.BasisZ);
//    XYZ direction = sweepPathDirection.CrossProduct(XYZ.BasisZ);
//    Line axis = Line.CreateUnbound(center, direction);
//    ElementTransformUtils.RotateElement(familydoc, sweepPath.Id, axis, angle);
//    CurveArray path = new CurveArray();
//    path.Append(sweepPath.GeometryCurve);
//    
//    // create sketch plane
//    Plane plane = Plane.CreateByNormalAndOrigin(new XYZ(10, 0, 0), refPointArray.get_Item(0).Position);
//    SketchPlane pathPlane = SketchPlane.Create(familydoc, plane);
//    
//    // create the cable
//    // Sweep sweep = familydoc.FamilyCreate.NewSweep(true, curveArray, pathPlane, profile, 0, ProfilePlaneLocation.Start);
//    ReferenceArray refArray = new ReferenceArray();
//    refArray.Append(sweepPath.GeometryCurve.Reference);
//    Sweep sweep = familydoc.FamilyCreate.NewSweep(true, refArray, profile, 0, ProfilePlaneLocation.Start);
    
        
}

 

Rebar on compound floor.jpg

 

 

 

The autodesk response for this functionality was that is was too complex for now,  dunno,  I'm not a programmer by trade or education, but I will have it in a day or so.

 

0 Likes
Message 9 of 14

jeremy_tammik
Alumni
Alumni

Looks like a good solid solution and a great approach to me. Looking forward to your final solution in a day or two. Best of luck with that! Thank you for sharing it!

  

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open
0 Likes
Message 10 of 14

arautio
Advocate
Advocate

Stuck on something that is probably simple.  Noticed  you needed references to generate a sweep and not curves

 

I get the length of the curve references in the test dialogue but then get invalid reference error when making the sweep.   Maybe I am improperly making the reference?    The test array is just pulling 3 connected model lines from the view.

 

          refArray.Clear();
          foreach (Curve cv in testarray)
          {
              TaskDialog.Show("Revit", cv.ApproximateLength.ToString());
              Reference cvref = cv.Reference;
              refArray.Append(cvref);
          }
          
        Sweep sweep = docfamily.FamilyCreate.NewSweep(true, refArray, fsp, 0, ProfilePlaneLocation.Start);

0 Likes
Message 11 of 14

jeremy_tammik
Alumni
Alumni

Afaik, a reference can only exist in the context of a Revit database. So, whether or not the Reference property of a Curve object will return a valid reference for your purposes probably depends on the context in which the curve is living. Is it part of a database-resident something or other? If not, I believe it cannot have a valid reference.

   

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

arautio
Advocate
Advocate

I created new curves manually to figure it out.  now it wants to sweep but I get a transaction error.  I only have 1 transaction and that is on the family document itself.  It says sub transaction was never started.  (course I have a regular transaction not a sub transaction?)

 

 

(code not shown, and not in a transaction) = all the stuff before hand,   grabbing the model lines making the family to edit and packing the arrays, etc...

 

 

 using (Autodesk.Revit.DB.Transaction trans = new Autodesk.Revit.DB.Transaction(docfamily))
    {
        trans.Start("Make Compound Rebar Family");
        
        ElementId delid = null;
        FamilySymbol profsymbol = null;

        // Get Profiles                      
        collector = new FilteredElementCollector( docfamily );
        foreach(Element element in collector.OfClass(typeof(FamilySymbol)))
        {
            if (element.Category.Name.ToString() == "Profiles")
            {
                //TaskDialog.Show("Revit", element.Name.ToString());
                if (element.Name.ToString().StartsWith("4"))
                    {
                        profsymbol = element as FamilySymbol;
                    }
            }
        }
            
        //TaskDialog.Show("Revit", profsymbol.ToString());
        
        collector = new FilteredElementCollector( docfamily );
        foreach(Element element in collector.OfClass(typeof(GenericForm)))
        {
            delid = element.Id;
        }
        
        try
        {
            docfamily.Delete(delid);            
        }
        catch
        {
            
        }
        
        //SketchPlane sketch;
        //FilteredElementCollector levcoll = new FilteredElementCollector(docfamily).WherePasses(new ElementClassFilter(typeof(Level), false));
        //ElementId levid = null;
        //foreach (Level le in levcoll)
        //{
             //TaskDialog.Show("Revit LevName", le.Name.ToString());
             //if (le.Name.ToString() == "Ref. Level" && levid == null)
             //{
                 //TaskDialog.Show("Revit LevName is Ref Lev", le.Name.ToString());
                 //levid = le.Id as ElementId;
             //}
        //}
                           
        //sketch = SketchPlane.Create(docfamily, levid);
            
        
        // Generate the family symbol profile:

          FamilySymbolProfile fsp = null;
    
          ifnull != profsymbol )
          {
              fsp = app.Create.NewFamilySymbolProfile( profsymbol );
          }
           
          refArray.Clear();
          foreach (Curve cv in testarray)
          {
              TaskDialog.Show("Revit", cv.ApproximateLength.ToString());
              Reference cvref = cv.Reference;
              refArray.Append(cvref);
              TaskDialog.Show("Revit ref type", cvref.ElementReferenceType.ToString());
          }
          
                  
        refArray.Clear();
        foreach (Curve cv in testarray)
        {
            XYZ pt1 = cv.GetEndPoint(0);
            XYZ pt2 = cv.GetEndPoint(1);
            Curve ncurv = Line.CreateBound(pt1,pt2);        
            refArray.Append(ncurv.Reference);
        }
                         
        Sweep sweep = docfamily.FamilyCreate.NewSweep(true, refArray, fsp, 0, ProfilePlaneLocation.Start);    

        trans.Commit();
    }

 

I get the logic down quick but there is alway some silly unintuitive thing that then bogs me down for hours.

0 Likes
Message 13 of 14

arautio
Advocate
Advocate

it appears it is seeing the sweep command as a subtransaction itself.

0 Likes
Message 14 of 14

arautio
Advocate
Advocate

So creating sweep from main doc in a family doc has issues with transactions being seen as not started.  However the code works fine if your in a family doc itself.   Anyway simple solution was to copy the model lines generated into the family, and then sweep them in a family transaction.

 

User can now select a floor, pick the faces to generate rebar for,  pick an edge to set rebar direction, input the rebar #,  input the rebar spacing, and set the rebar cover distance down from the top of concrete.

 

Result:

Rebar in Compound Sloped Floor.jpg

 

 

0 Likes