// // (C) Copyright 2003-2023 by Autodesk, Inc. // // Permission to use, copy, modify, and distribute this software in // object code form for any purpose and without fee is hereby granted, // provided that the above copyright notice appears in all copies and // that both that copyright notice and the limited warranty and // restricted rights notice below appear in all supporting // documentation. // // AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. // AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF // MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. // DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE // UNINTERRUPTED OR ERROR FREE. // // Use, duplication, or disclosure by the U.S. Government is subject to // restrictions set forth in FAR 52.227-19 (Commercial Computer // Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) // (Rights in Technical Data and Computer Software), as applicable. // using System; using System.Collections.Generic; using System.Windows.Forms; using Autodesk.Revit; using Autodesk.Revit.DB; using Autodesk.Revit.UI; using TaskDialog = Autodesk.Revit.UI.TaskDialog; namespace Revit.SDK.Samples.PanelEdgeLengthAngle.CS { /// /// A class inherits IExternalCommand interface. /// This class shows how to compute the length and angle data of curtain panels /// [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)] [Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)] [Autodesk.Revit.Attributes.Journaling(Autodesk.Revit.Attributes.JournalingMode.NoCommandData)] public class SetLengthAngleParams : IExternalCommand { /// /// The Revit application instance /// Autodesk.Revit.ApplicationServices.Application m_app; /// /// The active Revit document /// Autodesk.Revit.DB.Document m_doc; /// /// Implement this method as an external command for Revit. /// /// An object that is passed to the external application /// which contains data related to the command, /// such as the application object and active view. /// A message that can be set by the external application /// which will be displayed if a failure or cancellation is returned by /// the external command. /// A set of elements to which the external application /// can add elements that are to be highlighted in case of failure or cancellation. /// Return the status of the external command. /// A result of Succeeded means that the API external method functioned as expected. /// Cancelled can be used to signify that the user cancelled the external operation /// at some point. Failure should be returned if the application is unable to proceed with /// the operation. public Autodesk.Revit.UI.Result Execute(ExternalCommandData commandData, ref string message, Autodesk.Revit.DB.ElementSet elements) { m_app = commandData.Application.Application; m_doc = commandData.Application.ActiveUIDocument.Document; // step 1: get all the divided surfaces in the Revit document List dsList = GetElements(); foreach (DividedSurface ds in dsList) { // step 2: get the panel instances from the divided surface List fiList = GetFamilyInstances(ds); foreach (FamilyInstance inst in fiList) { // step 3: compute the length and angle and set them to the parameters InstParameters instParams = GetParams(inst); EdgeArray edges = GetEdges(inst); SetParams(m_doc, edges, instParams); } } return Autodesk.Revit.UI.Result.Succeeded; } /// /// Get all the panel instances from a divided surface /// /// The divided surface with some panels /// A list containing all the panel instances private List GetFamilyInstances(DividedSurface ds) { List fiList = new List(); for (int u = 0; u < ds.NumberOfUGridlines; ++u) { for (int v = 0; v < ds.NumberOfVGridlines; ++v) { GridNode gn = new GridNode(u, v); FamilyInstance familyInstance = ds.GetTileFamilyInstance(gn, 0); if (familyInstance != null) { fiList.Add(familyInstance); } } } return fiList; } /// /// Get all the edges from the given family instance /// /// The family instance with some edges /// Edges of the family instance private EdgeArray GetEdges(FamilyInstance familyInstance) { Autodesk.Revit.DB.Options opt = m_app.Create.NewGeometryOptions(); opt.ComputeReferences = true; Autodesk.Revit.DB.GeometryElement geomElem = familyInstance.get_Geometry(opt); //foreach (GeometryObject geomObject1 in geomElem.Objects) IEnumerator Objects = geomElem.GetEnumerator(); while (Objects.MoveNext()) { GeometryObject geomObject1 = Objects.Current; Solid solid = null; // partial panels if (geomObject1 is Solid) { solid = (Solid)geomObject1; if (null == solid) continue; } // non-partial panels else if (geomObject1 is Autodesk.Revit.DB.GeometryInstance) { GeometryInstance geomInst = geomObject1 as GeometryInstance; //foreach (Object geomObj in geomInst.SymbolGeometry.Objects) IEnumerator Objects1 = geomInst.SymbolGeometry.GetEnumerator(); while (Objects1.MoveNext()) { Object geomObj = Objects1.Current; solid = geomObj as Solid; if (solid != null) break; } } if (null == solid || // the solid can't be null null == solid.Faces || 0 == solid.Faces.Size || // the solid must have 1 or more faces null == solid.Faces.get_Item(0) || // the solid must have a NOT-null face null == solid.Faces.get_Item(0).EdgeLoops || 0 == solid.Faces.get_Item(0).EdgeLoops.Size) // the face must have some edges continue; return solid.Faces.get_Item(0).EdgeLoops.get_Item(0); } return null; } /// /// Compute the length and angle data of the edges, then update the parameters with these values /// /// The active document. /// The edges of the curtain panel /// The parameters which records the length and angle data private void SetParams(Document document, EdgeArray edge_ar, InstParameters instParams) { double length4 = 0d; double angle3 = 0d; double angle4 = 0d; Edge edge1 = edge_ar.get_Item(0); Edge edge2 = edge_ar.get_Item(1); Edge edge3 = edge_ar.get_Item(2); double length1 = edge1.ApproximateLength; double length2 = edge2.ApproximateLength; double length3 = edge3.ApproximateLength; double angle1 = AngleBetweenEdges(edge1, edge2); double angle2 = AngleBetweenEdges(edge2, edge3); if (edge_ar.Size == 3) { angle3 = AngleBetweenEdges(edge3, edge1); } else if (edge_ar.Size > 3) { Edge edge4 = edge_ar.get_Item(3); length4 = edge4.ApproximateLength; angle3 = AngleBetweenEdges(edge3, edge4); angle4 = AngleBetweenEdges(edge4, edge1); } using (Transaction transaction = new Transaction(document, "Update family instance parameters")) { transaction.Start(); instParams["Length1"].Set(length1); instParams["Length2"].Set(length2); instParams["Length3"].Set(length3); instParams["Length4"].Set(length4); instParams["Angle1"].Set(angle1); instParams["Angle2"].Set(angle2); instParams["Angle3"].Set(angle3); instParams["Angle4"].Set(angle4); transaction.Commit(); } } /// /// Compute the angle between two edges /// /// The 1st edge /// The 2nd edge /// The angle of the 2 edges private double AngleBetweenEdges(Edge edgeA, Edge edgeB) { Autodesk.Revit.DB.XYZ vectorA = null; Autodesk.Revit.DB.XYZ vectorB = null; // find coincident vertices Autodesk.Revit.DB.XYZ A_0 = edgeA.Evaluate(0); Autodesk.Revit.DB.XYZ A_1 = edgeA.Evaluate(1); Autodesk.Revit.DB.XYZ B_0 = edgeB.Evaluate(0); Autodesk.Revit.DB.XYZ B_1 = edgeB.Evaluate(1); if (A_0.IsAlmostEqualTo(B_0)) { vectorA = edgeA.ComputeDerivatives(0).BasisX.Normalize(); vectorB = edgeA.ComputeDerivatives(0).BasisX.Normalize(); } else if (A_0.IsAlmostEqualTo(B_1)) { vectorA = edgeA.ComputeDerivatives(0).BasisX.Normalize(); vectorB = edgeB.ComputeDerivatives(1).BasisX.Normalize(); } else if (A_1.IsAlmostEqualTo(B_0)) { vectorA = edgeA.ComputeDerivatives(1).BasisX.Normalize(); vectorB = edgeB.ComputeDerivatives(0).BasisX.Normalize(); } else if (A_1.IsAlmostEqualTo(B_1)) { vectorA = edgeA.ComputeDerivatives(1).BasisX.Normalize(); vectorB = edgeB.ComputeDerivatives(1).BasisX.Normalize(); } if (A_1.IsAlmostEqualTo(B_0) || A_0.IsAlmostEqualTo(B_1)) vectorA = vectorA.Negate(); if (null == vectorA || null == vectorB) { return 0d; } double angle = Math.Acos(vectorA.DotProduct(vectorB)); return angle; } /// /// Get all the parameters and store them into a list /// /// The instance of a curtain panel /// A list containing all the required parameters private InstParameters GetParams(FamilyInstance familyInstance) { InstParameters iParams = new InstParameters(); Parameter L1 = familyInstance.LookupParameter("Length1"); Parameter L2 = familyInstance.LookupParameter("Length2"); Parameter L3 = familyInstance.LookupParameter("Length3"); Parameter L4 = familyInstance.LookupParameter("Length4"); Parameter A1 = familyInstance.LookupParameter("Angle1"); Parameter A2 = familyInstance.LookupParameter("Angle2"); Parameter A3 = familyInstance.LookupParameter("Angle3"); Parameter A4 = familyInstance.LookupParameter("Angle4"); if (L1 == null || L2 == null || L3 == null || L4 == null || A1 == null || A2 == null || A3 == null || A4 == null) { string errorstring = "Panel family: " + familyInstance.Id.ToString() + " '" + familyInstance.Symbol.Family.Name + "' must have instance parameters Length1, Length2, Length3, Length4, Angle1, Angle2, Angle3, and Angle4"; TaskDialog.Show("Revit", errorstring); // throw new ArgumentException(errorstring); } iParams["Length1"] = L1; iParams["Length2"] = L2; iParams["Length3"] = L3; iParams["Length4"] = L4; iParams["Angle1"] = A1; iParams["Angle2"] = A2; iParams["Angle3"] = A3; iParams["Angle4"] = A4; return iParams; } protected List GetElements() where T : Element { List returns = new List(); FilteredElementCollector collector = new FilteredElementCollector(m_doc); ICollection founds = collector.OfClass(typeof(T)).ToElements(); foreach (Element elem in founds) { returns.Add(elem as T); } return returns; } } /// /// This class contains a dictionary which stores the parameter and parameter name pairs /// class InstParameters { private Dictionary m_parameters = new Dictionary(8); /// /// Get/Set the parameter by its name /// /// the name of the parameter /// The parameter which matches the name public Parameter this[string index] { get { return m_parameters[index]; } set { m_parameters[index] = value; } } } }