Paint Stair Faces

Paint Stair Faces

Anonymous
3,553 Views
15 Replies
Message 1 of 16

Paint Stair Faces

Anonymous
Not applicable

Hi,

 

I'm trying to paint some of the faces of a stair (a monolithic stair) through the 2015 revit API. It appears that it cannot be done programmatically (i get an error message that "the element faces cannot be painted") even if i can do this stair face painting manually in revit. Is there a way to paint automatically those faces? An excerpt of my code below. 

 

Thanks

 

FilteredElementCollector collectorStairs = new FilteredElementCollector(doc);
ElementCategoryFilter FilterStairs = new ElementCategoryFilter(BuiltInCategory.OST_Stairs, false);
collectorStairs.WherePasses(FilterStairs);
collectorStairs.WhereElementIsNotElementType();



foreach (Stairs stair in collectorStairs)
{

ICollection<PlanarFace> Vertfaces = Get_Vertical_Faces_Of_Element(doc, stair);
foreach (PlanarFace planarface in Vertfaces)
{

doc.Paint(stair.Id, planarface, Mat.Id);

}
}

0 Likes
Accepted solutions (1)
3,554 Views
15 Replies
Replies (15)
Message 2 of 16

jeremytammik
Autodesk
Autodesk

Hi,

 

Can you please provide a full reproducible case including a minimal sample model (a single stair element?) and a complete Visual Studio solution with exact step-by-step instructuions to demonstrate both the successful manual process in the user interface and the complete add-in that you think should achieve the same programmatically and fails?

 

http://thebuildingcoder.typepad.com/blog/about-the-author.html#1b

 

Thank you!

 

Cheers,

 

Jeremy



Jeremy Tammik
Developer Technical Services
Autodesk Developer Network, ADN Open
The Building Coder

0 Likes
Message 3 of 16

kim_sivonen
Explorer
Explorer

Hi Jeremy,

 

I ran into the exact same problem. This post seems to be a bit old, but since I didn't find anything else online, I'll give it a try...

 

VS solution attached.

 

How to do it in plain Revit UI:

https://www.prodlib.com/downloads/video/PaintingWithRevitPaintTool.mp4

 

How (trying) to do it through the API:

https://www.prodlib.com/downloads/video/PaintingWithAPI.mp4

 

Any ideas to make it work?

 

Best Regards

Kim Sivonen

ProdLib Oy

0 Likes
Message 4 of 16

canyon.des
Contributor
Contributor

It's 2021 now. Any solution for this problem? It still exists in Revit 2022.

0 Likes
Message 5 of 16

jeremy_tammik
Autodesk
Autodesk

Sorry for the long delay. I asked the development team for you what might be causing this and will raise a ticket with them if needed.

 

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

canyon.des
Contributor
Contributor
Accepted solution

Hi Jememy,

I found the answer. For Stairs, you need to use GetStairsLandings() and GetStairsRuns(), to get those ElementId to paint landings or runs. It's not intuitive, but it actually works. It's same to find out those landings' or runs' face are painted or not.

 

 

 

Material Mat;
Selection sel = UIDoc.Selection;
Reference pickedRef = null;
Face fc = null;
SurfaceSelectionFilter filter = new SurfaceSelectionFilter();
pickedRef = sel.PickObject(Autodesk.Revit.UI.Selection.ObjectType.PointOnElement, filter, "please select a Face");
GeometryObject geoObject = doc.GetElement(pickedRef).GetGeometryObjectFromReference(pickedRef);
Element elem = doc.GetElement(pickedRef);
fc = geoObject as Face;
if (elem.Category.Id.IntegerValue == -2000120)//Stairs;
{
bool flag =false;
 Stairs str = elem as Stairs;
 List<ElementId> StarLands =str.GetStairsLandings().ToList();
 List<ElementId> StarRuns = = str.GetStairsLandings().ToList();
 using (Transaction transaction = new Transaction(doc, "Paint Material"))
{
transaction.Start();
  foreach (ElementId Lid in StarLands)
{
try
{
  doc.Paint(Lid, fc, Mat.Id);
flag=true;
break;
}
catch{}
}
if(!flag)
  foreach (ElementId Lid in StarRuns)
{
try
{
  doc.Paint(Lid, fc, Mat.Id);
break;
}
catch{}
}
  transaction.Commit();
}

 

 

 

Message 7 of 16

jeremy_tammik
Autodesk
Autodesk

Congratulations on solving this and thank you very much for letting us know the solution!

 

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

canyon.des
Contributor
Contributor

You're welcome. Just happened to work on the same issue here.

0 Likes
Message 9 of 16

jeremy_tammik
Autodesk
Autodesk

I edited your code a bit, eliminating the ToList calls, try/catch, etc.:

 

    void PaintStairs( UIDocument uidoc, Material mat )
    {
      Document doc = uidoc.Document;
      Selection sel = uidoc.Selection;

      //SurfaceSelectionFilter filter = new SurfaceSelectionFilter();
      Reference pickedRef = sel.PickObject(
        ObjectType.PointOnElement,
        //filter, 
        "Please select a Face" );

      Element elem = doc.GetElement( pickedRef );

      GeometryObject geoObject = elem
        .GetGeometryObjectFromReference( pickedRef );

      Face fc = geoObject as Face;

      if( elem.Category.Id.IntegerValue == -2000120 ) // Stairs
      {
        bool flag = false;
        Stairs str = elem as Stairs;
        ICollection<ElementId> landings = str.GetStairsLandings();
        ICollection<ElementId> runs = str.GetStairsLandings();
        using( Transaction transaction = new Transaction( doc ) )
        {
          transaction.Start( "Paint Material" );
          foreach( ElementId id in landings )
          {
            doc.Paint( id, fc, mat.Id );
            flag = true;
            break;
          }
          if( !flag )
          {
            foreach( ElementId id in runs )
            {
              doc.Paint( id, fc, mat.Id );
              break;
            }
          }
          transaction.Commit();
        }
      }
    }
  }

 

Question: why do you break out of the two loops after painting the first landing and the first run?

  

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

canyon.des
Contributor
Contributor

Hi Jeremy,

That post was a quick response. I didn't test too much. The reason I break the loop, is because I thought only one face is able to be painted. But I was wrong. After few test, I found that's only true for stairs runs between landing and the last run. Both faces on landing and last run can be painted in both loops. It's weird. I guess it's due to some internal face joined issue for those place. One other thing I found is that the same methodology doesn't work on Railings, which is also can't be painted by Revit API, but can be done using native paint tool. Any thought about that? By the way, you may need to use try catch for the loops. Otherwise, an exception will be throw before hitting next run.

0 Likes
Message 11 of 16

jeremy_tammik
Autodesk
Autodesk

Dear Bruce,

  

Thank you for the update and clarification.

 

What exception is thrown? 

 

You should never catch all exceptions:

 

  

If the railing can be painted in the UI and not in the API, that may be something for the development team to take a look at.

  

You can submit a minimal reproducible case for that, if you like, and I can pass it on to them for analysis:

  

   

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

canyon.des
Contributor
Contributor

Dear Jeremy,

You are right. All exceptions should be handled properly. The code snap above just for a quick test run, not for real production. The exception thrown will be the one on the very top of this thread mentioned "the element faces cannot be painted". Seems not very serious exception, so I just catch it for quick test of paint function. A minimal reproducible case seems too much work for me now, kind'a of busy these days. Sorry.

0 Likes
Message 13 of 16

jeremy_tammik
Autodesk
Autodesk

That's cool. Would you like to share your final working version once you have settled on how to handle it, and maybe a screen snapshot of the model before and after painting? Thank you!

  

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

canyon.des
Contributor
Contributor

2021-06-18_092530.png2021-06-18_092608.png2021-06-18_092702.png2021-06-18_092752.png2021-06-18_092824.pngHi Jeremy,

You are right. Somehow, painting sub elements like stair's landing or runs, there is no exception be thrown like painting elements does. It's interesting.  I have modified the code to be able to paint stair's landings or runs correctly without error. I added a function to test which element the picked face is belong to, so it doesn't need to run both loops to paint. The function  is set to paint any face, so it's also a sample to show how railings' face can't be painted like UI does. I use Revit sample project for demo, so you can easy reproduce same scenario at your side. The last picture is showing error message using this function trying to paint Railings' top rail. The second to last picture is showing UI successfully painting same place.

 

 

 

 

       private void PaintFace(UIDocument uidoc, Material mat)
        {
            Document doc = uidoc.Document;
            Selection sel = uidoc.Selection;
            List<String> errors = new List<string>();
            //SurfaceSelectionFilter filter = new SurfaceSelectionFilter();
            Reference pickedRef = sel.PickObject(
              ObjectType.PointOnElement,
              //filter, 
              "Please select a Face");

            Element elem = doc.GetElement(pickedRef);

            GeometryObject geoObject = elem
              .GetGeometryObjectFromReference(pickedRef);

            Face fc = geoObject as Face;

            using (Transaction transaction = new Transaction(doc))
            {
                transaction.Start("Paint Material");
                if (elem.Category.Id.IntegerValue == -2000120) // Stairs
                {
                    Stairs str = elem as Stairs;
                    bool IsLand = false;

                    ICollection<ElementId> landings = str.GetStairsLandings();
                    ICollection<ElementId> runs = str.GetStairsRuns();
                    foreach (ElementId id in landings)
                    {

                        Element land = doc.GetElement(id);
                        IsLand = FaceTest(land, fc);
                        if (IsLand)
                        {
                            break;
                        }
                    }

                    if (IsLand)
                    {
                        foreach (ElementId id in landings)
                        {
                            doc.Paint(id, fc, mat.Id);
                            break;
                        }
                    }
                    else
                    {
                        foreach (ElementId id in runs)
                        {
                            doc.Paint(id, fc, mat.Id);
                            break;

                        }
                    }

                }
                else
                {
                    try
                    {
                        doc.Paint(elem.Id, fc, mat.Id);
                    }
                    catch(Exception ex)
                    {
                        TaskDialog.Show("Error", ex.Message);
                    }
                }
                transaction.Commit();
            }
        }
 private bool FaceTest(Element elem, Face fc)
        {
            bool find = false;
            List<Solid> solids = GetElemSolids(elem.get_Geometry(new Options()));
            foreach (Solid s in solids)
            {
                if (s != null)
                {
                    if (s.Volume > 0)
                    {

                            foreach (Face fs in s.Faces)
                            {

                                if (fs == fc)
                                {
                                    find = true;
                                    break;
                                }
                                if (fs.HasRegions)
                                {
                                    for (int i = 0; i < fs.GetRegions().Count; i++)
                                    {
                                        Face reg = fs.GetRegions()[i];

                                        if (reg == fc)
                                        {
                                            find = true;
                                            break;
                                        }

                                    }
                                }
                            }
                            if (find)
                            {
                                break;
                            }

                    }

                }
            }
            return find;
        }
        private List<Solid> GetElemSolids(GeometryElement geomElem)
        {
            if (geomElem == null)
                return new List<Solid>();

            List<Solid> solids = new List<Solid>();
            foreach (GeometryObject geomObj in geomElem)
            {
                if (geomObj is Solid solid)
                {
                    if (solid.Faces.Size > 0)
                    {
                        solids.Add(solid);
                        continue;
                    }
                }
                if (geomObj is GeometryInstance geomInst)
                {
                    solids.AddRange(GetElementSolids(geomInst.GetInstanceGeometry()));
                }
            }
            return solids;
        }

 

 

 

 

0 Likes
Message 15 of 16

jeremy_tammik
Autodesk
Autodesk

Thank you for the screen snapshots, explanation and updated code.

 

Do the stair railings have a different category than OST_Stairs? Apparently so.

 

Can you share a minimal sample model to run this in that demonstrates the problem? 

  

Oh, and I still wonder: why do you break out of the loop after painting the first landing or run? Would you never want to paint two, or more, or all of them, if there are more than one?

 

Here is a cleaned up version of your code that I added to The Building Coder samples:

  

 

  /// <summary>
  /// Prompt user to pick a face and paint it 
  /// with the given material. If the face belongs
  /// to a stair run or landing, paint that part
  /// of the stair specifically.
  /// </summary>
  void PaintSelectedFace( UIDocument uidoc, Material mat )
  {
    Document doc = uidoc.Document;
    Selection sel = uidoc.Selection;
    List<String> errors = new List<string>();
    //SurfaceSelectionFilter filter = new SurfaceSelectionFilter();
    Reference pickedRef = sel.PickObject(
      ObjectType.PointOnElement,
      //filter, 
      "Please select a face to paint" );

    Element elem = doc.GetElement( pickedRef );

    GeometryObject geoObject = elem
      .GetGeometryObjectFromReference( pickedRef );

    Face selected_face = geoObject as Face;

    using( Transaction transaction = new Transaction( doc ) )
    {
      transaction.Start( "Paint Selected Face" );

      if( elem.Category.Id.IntegerValue.Equals(
        (int) BuiltInCategory.OST_Stairs ) )
      {
        Stairs str = elem as Stairs;
        bool IsLand = false;

        ICollection<ElementId> landings = str.GetStairsLandings();
        ICollection<ElementId> runs = str.GetStairsRuns();

        foreach( ElementId id in landings )
        {

          Element land = doc.GetElement( id );
          List<Solid> solids = GetElemSolids( 
            land.get_Geometry( new Options() ) );

          IsLand = SolidsContainFace( solids, selected_face );

          if( IsLand )
          {
            break;
          }
        }

        if( IsLand )
        {
          foreach( ElementId id in landings )
          {
            doc.Paint( id, selected_face, mat.Id );
            break;
          }
        }
        else
        {
          foreach( ElementId id in runs )
          {
            doc.Paint( id, selected_face, mat.Id );
            break;
          }
        }
      }
      else
      {
        try
        {
          doc.Paint( elem.Id, selected_face, mat.Id );
        }
        catch( Exception ex )
        {
          TaskDialog.Show( "Error painting selected face", 
            ex.Message );
        }
      }
      transaction.Commit();
    }
  }

  /// <summary>
  /// Does the given face belong to one of the given solids?
  /// </summary>
  private bool SolidsContainFace( List<Solid> solids, Face face )
  {
    foreach( Solid s in solids )
    {
      if( null != s
        && 0 < s.Volume )
      {
        foreach( Face f in s.Faces )
        {
          if( f == face )
          {
            return true;
          }
          else if( f.HasRegions )
          {
            foreach( Face f2 in f.GetRegions() )
            {
              if( f2 == face )
              {
                return true;
              }
            }
          }
        }
      }
    }
    return false;
  }

  /// <summary>
  /// Recursively collect all solids 
  /// contained in the given element geomety
  /// </summary>
  List<Solid> GetElemSolids( GeometryElement geomElem )
  {
    List<Solid> solids = new List<Solid>();

    if( null != geomElem )
    {
      foreach( GeometryObject geomObj in geomElem )
      {
        if( geomObj is Solid solid )
        {
          if( solid.Faces.Size > 0 )
          {
            solids.Add( solid );
            continue;
          }
        }
        if( geomObj is GeometryInstance geomInst )
        {
          solids.AddRange( GetElemSolids(
            geomInst.GetInstanceGeometry() ) );
        }
      }
    }
    return solids;
  }

 

 

Would you like to test that and check that I didn't break anything?

 

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

canyon.des
Contributor
Contributor

Hi Jeremy,

Thanks for cleaning my messy code. I have tested your code  and it worked fine. The reason for only painting one face is that I want to paint selected face like UI does. I just made a free app on Autodesk Exchange using this function to paint face without the need to open paint browser, called CopyPaint. It might take a couple days for publishing. For sample project, I use built in sample "rac_basic_sample_project.rvt ".

Demo of CopyPaint:https://www.youtube.com/watch?v=mN_XyFVpf48