// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; using Autodesk.Revit.DB; using Autodesk.Revit.DB.Architecture; using Autodesk.Revit.DB.Mechanical; using Autodesk.Revit.DB.Plumbing; using Autodesk.Revit.DB.Structure; using Autodesk.Revit.DB.Visual; namespace DatasmithRevitExporter { public class OrientatedBoundingBox { int tlf = 0; int trf = 1; int blf = 2; int brf = 3; int tlb = 4; int trb = 5; int blb = 6; int brb = 7; double Epsilon = 0.000001; // Z // | Y // |/ // o---X // // tlb-------MAX // /| /| // / | / | // tlf-------trf | // | | | | // | blb-----|-brb // | / | / // |/ |/ // MIN-------brf // // Vertices[0] := tlf: Top Left Front // Vertices[1] := trf: Top Right Front // Vertices[2] := blf: Bottom Left Front => min // Vertices[3] := brf: Bottom Right Front // Vertices[4] := tlb: Top Left Back // Vertices[5] := trb: Top Right Back => max // Vertices[6] := blb: Bottom Left Back // Vertices[7] := brb: Bottom Right Back public XYZ[] Vertices = new XYZ[8]; //8 vertices // corners double SideXDistance; double SideYDistance; double SideZDistance; public XYZ AxisAllignedMin; public XYZ AxisAllignedMax; Plane SideX0, SideX1; Plane SideY0, SideY1; Plane SideZ0, SideZ1; public bool bIsValidData; public OrientatedBoundingBox(Transform InTransform, XYZ MIN, XYZ MAX, bool InCalculatePlanes = false) { SideXDistance = (MAX.X - MIN.X) + Epsilon; SideYDistance = (MAX.Y - MIN.Y) + Epsilon; SideZDistance = (MAX.Z - MIN.Z) + Epsilon; bIsValidData = SideXDistance > Epsilon && SideYDistance > Epsilon && SideZDistance > Epsilon; if (!bIsValidData) { return; } Vertices[tlf] = new XYZ(MIN.X, MIN.Y, MAX.Z); Vertices[trf] = new XYZ(MAX.X, MIN.Y, MAX.Z); Vertices[brf] = new XYZ(MAX.X, MIN.Y, MIN.Z); Vertices[blf] = MIN; Vertices[tlb] = new XYZ(MIN.X, MAX.Y, MAX.Z); Vertices[trb] = MAX; Vertices[brb] = new XYZ(MAX.X, MAX.Y, MIN.Z); Vertices[blb] = new XYZ(MIN.X, MAX.Y, MIN.Z); double MinX = double.MaxValue; double MinY = double.MaxValue; double MinZ = double.MaxValue; double MaxX = -double.MaxValue; double MaxY = -double.MaxValue; double MaxZ = -double.MaxValue; for (int Index = 0; Index <= brb; Index++) { Vertices[Index] = InTransform.OfPoint(Vertices[Index]); if (Vertices[Index].X < MinX) MinX = Vertices[Index].X; if (Vertices[Index].Y < MinY) MinY = Vertices[Index].Y; if (Vertices[Index].Z < MinZ) MinZ = Vertices[Index].Z; if (Vertices[Index].X > MaxX) MaxX = Vertices[Index].X; if (Vertices[Index].Y > MaxY) MaxY = Vertices[Index].Y; if (Vertices[Index].Z > MaxZ) MaxZ = Vertices[Index].Z; } AxisAllignedMax = new XYZ(MaxX, MaxY, MaxZ); AxisAllignedMin = new XYZ(MinX, MinY, MinZ); if (InCalculatePlanes) { //We only need the planes for the SectionBox: CalculatePlanes(); } } private void CalculatePlanes() { SideX0 = Plane.CreateByThreePoints(Vertices[tlf], Vertices[blb], Vertices[tlb]); SideX1 = Plane.CreateByThreePoints(Vertices[trf], Vertices[trb], Vertices[brb]); SideY0 = Plane.CreateByThreePoints(Vertices[tlf], Vertices[trf], Vertices[brf]); SideY1 = Plane.CreateByThreePoints(Vertices[tlb], Vertices[brb], Vertices[trb]); SideZ0 = Plane.CreateByThreePoints(Vertices[blf], Vertices[brb], Vertices[blb]); SideZ1 = Plane.CreateByThreePoints(Vertices[tlf], Vertices[tlb], Vertices[trb]); } private bool IsPointWithin(Plane Plane1, Plane Plane2, double Distance, XYZ Point) { UV Uv; double Distance1; double Distance2; Plane1.Project(Point, out Uv, out Distance1); if (Distance1 > Distance) { return false; } Plane2.Project(Point, out Uv, out Distance2); if (Distance2 > Distance) { return false; } return true; } private bool IsPointInside(XYZ Point) { return IsPointWithin(SideX0, SideX1, SideXDistance, Point) && IsPointWithin(SideY0, SideY1, SideYDistance, Point) && IsPointWithin(SideZ0, SideZ1, SideZDistance, Point); } // If self contains Candidate it returns false: public bool DoesIntersect(OrientatedBoundingBox Candidate) { int VertexWithinCounter = 0; int VertexOutsideCounter = 0; for (int Index = 0; Index < Candidate.Vertices.Length; Index++) { if (IsPointInside(Candidate.Vertices[Index])) { VertexWithinCounter++; } else { VertexOutsideCounter++; } if (VertexOutsideCounter > 0 && VertexWithinCounter > 0) { return true; } } //It returns true only if we have at least 1 vertex within and at least 1 vertex outside //every other scenario should return false //as in: // - all vertex outside := false // - all vertex inside := false => geometries with completely contained bounding boxes won't get clipped. return false; } } public class FDocumentData { // This class reflects the child -> super component relationship in Revit into the exported hierarchy (children under super components actors). class FSuperComponentOptimizer { private Dictionary ElementDataToElementMap = new Dictionary(); private Dictionary ElementIdToElementDataMap = new Dictionary(); public void UpdateCache(FBaseElementData ParentElement, FBaseElementData ChildElement) { if (!ElementDataToElementMap.ContainsKey(ParentElement)) { Element Elem = null; if (ParentElement.GetType() == typeof(FElementData)) { Elem = ((FElementData)ParentElement).CurrentElement; } else if (ChildElement.GetType() == typeof(FElementData)) { Elem = ((FElementData)ChildElement).CurrentElement; } if (Elem != null) { ElementDataToElementMap[ParentElement] = Elem; ElementIdToElementDataMap[Elem.Id] = ParentElement; } } } public void Optimize() { foreach (var KV in ElementDataToElementMap) { FBaseElementData ElemData = KV.Key; Element Elem = KV.Value; if ((Elem as FamilyInstance) != null) { Element Parent = (Elem as FamilyInstance).SuperComponent; if (Parent != null) { FBaseElementData SuperParent = null; bool bGot = ElementIdToElementDataMap.TryGetValue(Parent.Id, out SuperParent); if (bGot && SuperParent != ElemData.Parent) { if (ElemData.Parent != null) { ElemData.Parent.ElementActor.RemoveChild(ElemData.ElementActor); ElemData.Parent.ChildElements.Remove(ElemData); } SuperParent.ChildElements.Add(ElemData); SuperParent.ElementActor.AddChild(ElemData.ElementActor); } } } } } }; public struct FPolymeshFace { public int V1; public int V2; public int V3; public int MaterialIndex; public FPolymeshFace(int InVertex1, int InVertex2, int InVertex3, int InMaterialIndex = 0) { V1 = InVertex1; V2 = InVertex2; V3 = InVertex3; MaterialIndex = InMaterialIndex; } } public class FDatasmithPolymesh { public List Vertices = new List(); public List Normals = new List(); public List Faces = new List(); public List UVs = new List(); } public class FBaseElementData { public ElementType BaseElementType; public FDatasmithPolymesh DatasmithPolymesh = null; public FDatasmithFacadeMeshElement DatasmithMeshElement = null; public FDatasmithFacadeActor ElementActor = null; public FDatasmithFacadeMetaData ElementMetaData = null; public FDocumentData DocumentData = null; public bool bOptimizeHierarchy = true; public bool bIsModified = true; public bool bAllowMeshInstancing = true; public bool bIsDecalElement = false; public Dictionary MeshMaterialsMap = new Dictionary(); public Transform WorldTransform; public List ChildElements = new List(); public FBaseElementData Parent = null; public bool bOwnedByParent = false; // Lifetime is controlled by parent Element public FBaseElementData( ElementType InElementType, FDocumentData InDocumentData ) { BaseElementType = InElementType; DocumentData = InDocumentData; } public FBaseElementData(FDatasmithFacadeActor InElementActor, FDatasmithFacadeMetaData InElementMetaData, FDocumentData InDocumentData) { ElementActor = InElementActor; ElementMetaData = InElementMetaData; DocumentData = InDocumentData; } void CopyActorData(FDatasmithFacadeActor InFromActor, FDatasmithFacadeActor InToActor) { InToActor.SetLabel(InFromActor.GetLabel()); double X, Y, Z, W; InFromActor.GetTranslation(out X, out Y, out Z); InToActor.SetTranslation(X, Y, Z); InFromActor.GetScale(out X, out Y, out Z); InToActor.SetScale(X, Y, Z); InFromActor.GetRotation(out X, out Y, out Z, out W); InToActor.SetRotation(X, Y, Z, W); InToActor.SetLayer(InFromActor.GetLayer()); for (int TagIndex = 0; TagIndex < InFromActor.GetTagsCount(); ++TagIndex) { InToActor.AddTag(InFromActor.GetTag(TagIndex)); } InToActor.SetIsComponent(InFromActor.IsComponent()); InToActor.SetVisibility(InFromActor.GetVisibility()); for (int ChildIndex = 0; ChildIndex < InFromActor.GetChildrenCount(); ++ChildIndex) { InToActor.AddChild(InFromActor.GetChild(ChildIndex)); } ElementMetaData?.SetAssociatedElement(InToActor); } // Return element when this 'ElementData' is associated with an Element // todo: Probably worth separating FBaseElementData into different entity, calling it now "Element" data is confusing as it's also used to hold for non-Element datasmith actors protected virtual Element GetElement() { return null; } public void AddToScene(FDatasmithFacadeScene InScene, FBaseElementData InParent, bool bInSkipChildren, bool bInForceAdd = false) { Element ThisElement = GetElement(); if (!bInSkipChildren) { foreach (FBaseElementData CurrentChild in ChildElements) { // Stairs get special treatment: elements of stairs (strings, landings etc.) can be duplicated, // meaning that the same element id can exist multiple times under the same parent. bool bIsStairsElement = (ThisElement != null) && (ThisElement.GetType() == typeof(Stairs)); bool bIsInstance = (ThisElement == null); bool bForceAdd = (bIsInstance && bIsModified) || (bIsStairsElement && bIsModified); CurrentChild.AddToScene(InScene, this, false, bForceAdd); } } bool bIsCached = ThisElement != null && ThisElement.IsValidObject && (DocumentData.DirectLink?.IsElementCached(ThisElement) ?? false); // Check if actor type has changed for this element (f.e. static mesh actor -> regular actor), // and re-created if needed. if (bIsCached && bIsModified && ElementActor != null) { FDatasmithFacadeActor CachedActor = DocumentData.DirectLink.GetCachedActor(ElementActor.GetName()); if (CachedActor != null && CachedActor.GetType() != ElementActor.GetType()) { InScene.RemoveActor(CachedActor); DocumentData.DirectLink.CacheActorType(ElementActor); bIsCached = false; } } if ((!bIsCached && bIsModified) || bInForceAdd) { if (InParent == null) { InScene.AddActor(ElementActor); } else { InParent.ElementActor.AddChild(ElementActor); } if (!DocumentData.bSkipMetadataExport && ElementMetaData != null) { InScene.AddMetaData(ElementMetaData); } if (ThisElement != null) { DocumentData.DirectLink?.CacheElement(DocumentData.CurrentDocument, ThisElement, this); } } bIsModified = false; } // Returns true if element's actor is simple(plain actor without descendants) public bool Optimize() { // Replace MeshActor without geometry by a dummy Actor if (ElementActor is FDatasmithFacadeActorMesh MeshActor && MeshActor.GetMeshName().Length == 0) { ElementActor = new FDatasmithFacadeActor(MeshActor.GetName()); CopyActorData(MeshActor, ElementActor); } // Optimize and remove children whose actors are simple List SimpleChildren = ChildElements.Where(Child => Child.Optimize()).ToList(); // Build a list of elements to remove after enumeration foreach (FBaseElementData Child in SimpleChildren) { ChildElements.Remove(Child); } bool bIsSimpleActor = !(ElementActor is FDatasmithFacadeActorMesh || ElementActor is FDatasmithFacadeActorLight || ElementActor is FDatasmithFacadeActorCamera); return bIsSimpleActor && (ChildElements.Count == 0) && bOptimizeHierarchy; } public void UpdateMeshName() { FDatasmithFacadeActorMesh MeshActor = ElementActor as FDatasmithFacadeActorMesh; if (MeshActor == null && DocumentData.DirectLink != null) { // We have valid mesh but the actor is not a mesh actor -- the type of element has changed (DirectLink). MeshActor = new FDatasmithFacadeActorMesh(ElementActor.GetName()); CopyActorData(ElementActor, MeshActor); ElementActor = MeshActor; } MeshActor?.SetMesh(DatasmithMeshElement.GetName()); bOptimizeHierarchy = false; } } public class FElementData : FBaseElementData { public Element CurrentElement = null; public Transform MeshPointsTransform = null; public Stack InstanceDataStack = new Stack(); public FElementData( Element InElement, Transform InWorldTransform, FDocumentData InDocumentData ) : base(InElement.Document.GetElement(InElement.GetTypeId()) as ElementType, InDocumentData) { CurrentElement = InElement; } protected override Element GetElement() { return CurrentElement; } public void InitializePivotPlacement(ref Transform InOutWorldTransform) { // If element has location, use it as a transform in order to have better pivot placement. Transform PivotTransform = GetPivotTransform(CurrentElement); if (PivotTransform != null) { if (!InOutWorldTransform.IsIdentity) { InOutWorldTransform = InOutWorldTransform * PivotTransform; } else { InOutWorldTransform = PivotTransform; } if (CurrentElement.GetType() == typeof(Wall) || CurrentElement.GetType() == typeof(ModelText) || CurrentElement.GetType().IsSubclassOf(typeof(MEPCurve)) || CurrentElement.GetType() == typeof(StructuralConnectionHandler) || CurrentElement.GetType() == typeof(Floor) || CurrentElement.GetType() == typeof(Ceiling) || CurrentElement.GetType() == typeof(RoofBase) || CurrentElement.GetType().IsSubclassOf(typeof(RoofBase))) { MeshPointsTransform = PivotTransform.Inverse; } } } // Compute orthonormal basis, given the X vector. static private void ComputeBasis(XYZ BasisX, ref XYZ BasisY, ref XYZ BasisZ) { BasisY = XYZ.BasisZ.CrossProduct(BasisX); if (BasisY.GetLength() < 0.0001) { // BasisX is aligned with Z, use dot product to take direction in account BasisY = BasisX.CrossProduct(BasisX.DotProduct(XYZ.BasisZ) * XYZ.BasisX).Normalize(); BasisZ = BasisX.CrossProduct(BasisY).Normalize(); } else { BasisY = BasisY.Normalize(); BasisZ = BasisX.CrossProduct(BasisY).Normalize(); } } private Transform GetPivotTransform(Element InElement) { if ((InElement as FamilyInstance) != null) { return null; } XYZ Translation = null; XYZ BasisX = new XYZ(); XYZ BasisY = new XYZ(); XYZ BasisZ = new XYZ(); // Get pivot translation if (InElement.GetType() == typeof(Railing)) { // Railings don't have valid location, so instead we need to get location from its path. IList Paths = (InElement as Railing).GetPath(); if (Paths.Count > 0 && Paths[0].IsBound) { Translation = Paths[0].GetEndPoint(0); } } else if (InElement.GetType() == typeof(StructuralConnectionHandler)) { Translation = (InElement as StructuralConnectionHandler).GetOrigin(); } else if (InElement.GetType() == typeof(Floor) || InElement.GetType() == typeof(Ceiling) || InElement.GetType() == typeof(RoofBase) || InElement.GetType().IsSubclassOf(typeof(RoofBase))) { BoundingBoxXYZ BoundingBox = InElement.get_BoundingBox(InElement.Document.ActiveView); if (BoundingBox != null) { Translation = BoundingBox.Min; } } else if (InElement.Location != null) { if (InElement.Location.GetType() == typeof(LocationCurve)) { LocationCurve CurveLocation = InElement.Location as LocationCurve; if (CurveLocation.Curve != null && CurveLocation.Curve.IsBound) { Translation = CurveLocation.Curve.GetEndPoint(0); } } else if (InElement.Location.GetType() == typeof(LocationPoint)) { Translation = (InElement.Location as LocationPoint).Point; } } if (Translation == null) { return null; // Cannot get valid translation } // Get pivot basis if (InElement.GetType() == typeof(Wall)) { // In rare cases, wall may not support orientation. // If this happens, we need to use the direction of its Curve property and // derive orientation from there. try { BasisY = (InElement as Wall).Orientation.Normalize(); BasisX = BasisY.CrossProduct(XYZ.BasisZ).Normalize(); BasisZ = BasisX.CrossProduct(BasisY).Normalize(); } catch { if (InElement.Location.GetType() == typeof(LocationCurve)) { LocationCurve CurveLocation = InElement.Location as LocationCurve; if (CurveLocation.Curve.GetType() == typeof(Line)) { BasisX = (CurveLocation.Curve as Line).Direction; ComputeBasis(BasisX, ref BasisY, ref BasisZ); } else if (CurveLocation.Curve.IsBound) { Transform Derivatives = CurveLocation.Curve.ComputeDerivatives(0f, true); BasisX = Derivatives.BasisX.Normalize(); BasisY = Derivatives.BasisY.Normalize(); BasisZ = Derivatives.BasisZ.Normalize(); } else { BasisX = XYZ.BasisX; BasisY = XYZ.BasisY; BasisZ = XYZ.BasisZ; } } } } else if (InElement.GetType() == typeof(Railing)) { IList Paths = (InElement as Railing).GetPath(); if (Paths.Count > 0) { Curve FirstPath = Paths[0]; if (FirstPath.GetType() == typeof(Line)) { BasisX = (FirstPath as Line).Direction.Normalize(); ComputeBasis(BasisX, ref BasisY, ref BasisZ); } else if (FirstPath.GetType() == typeof(Arc) && FirstPath.IsBound) { Transform Derivatives = (FirstPath as Arc).ComputeDerivatives(0f, true); BasisX = Derivatives.BasisX.Normalize(); BasisY = Derivatives.BasisY.Normalize(); BasisZ = Derivatives.BasisZ.Normalize(); } } } else if (InElement.GetType() == typeof(StructuralConnectionHandler)) { BasisX = XYZ.BasisX; BasisY = XYZ.BasisY; BasisZ = XYZ.BasisZ; } else if (InElement.GetType() == typeof(ModelText)) { // Model text has no direction information! BasisX = XYZ.BasisX; BasisY = XYZ.BasisY; BasisZ = XYZ.BasisZ; } else if (InElement.GetType() == typeof(FlexDuct)) { BasisX = (InElement as FlexDuct).StartTangent; ComputeBasis(BasisX, ref BasisY, ref BasisZ); } else if (InElement.GetType() == typeof(FlexPipe)) { BasisX = (InElement as FlexPipe).StartTangent; ComputeBasis(BasisX, ref BasisY, ref BasisZ); } else if (InElement.GetType() == typeof(Floor) || InElement.GetType() == typeof(Ceiling) || InElement.GetType() == typeof(RoofBase) || InElement.GetType().IsSubclassOf(typeof(RoofBase))) { BasisX = XYZ.BasisX; BasisY = XYZ.BasisY; BasisZ = XYZ.BasisZ; } else if (InElement.Location.GetType() == typeof(LocationCurve)) { LocationCurve CurveLocation = InElement.Location as LocationCurve; if (CurveLocation.Curve.GetType() == typeof(Line)) { BasisX = (CurveLocation.Curve as Line).Direction; ComputeBasis(BasisX, ref BasisY, ref BasisZ); } else if (CurveLocation.Curve.IsBound) { Transform Derivatives = CurveLocation.Curve.ComputeDerivatives(0f, true); BasisX = Derivatives.BasisX.Normalize(); BasisY = Derivatives.BasisY.Normalize(); BasisZ = Derivatives.BasisZ.Normalize(); } else { return null; } } else { return null; // Cannot get valid basis } Transform PivotTransform = Transform.CreateTranslation(Translation); PivotTransform.BasisX = BasisX; PivotTransform.BasisY = BasisY; PivotTransform.BasisZ = BasisZ; return PivotTransform; } public FBaseElementData PushInstance( ElementType InInstanceType, Transform InWorldTransform, bool bInAllowMeshInstancing ) { FBaseElementData InstanceData = new FBaseElementData(InInstanceType, DocumentData); InstanceData.bOwnedByParent = true; FamilyInstance CurrentFamilyInstance = CurrentElement as FamilyInstance; if (CurrentFamilyInstance != null && CurrentFamilyInstance.HasModifiedGeometry()) { //In case the FamilyInstance has a modified geometry, then don't instantiate the original mesh, //the onPolymesh will provide the customized geometry instead. bInAllowMeshInstancing = false; } InstanceData.bAllowMeshInstancing = bInAllowMeshInstancing; InstanceDataStack.Push(InstanceData); InitializeElement(InWorldTransform, InstanceData); // The Datasmith instance actor is a component in the hierarchy. InstanceData.ElementActor.SetIsComponent(true); return InstanceData; } public FBaseElementData PopInstance() { FBaseElementData Instance = InstanceDataStack.Pop(); Instance.bIsModified = true; return Instance; } public FDatasmithFacadeMeshElement GetCurrentMeshElement() { if (InstanceDataStack.Count > 0) { return InstanceDataStack.Peek().DatasmithMeshElement; } return DatasmithMeshElement; } public void AddLightActor( Transform InWorldTransform, Asset InLightAsset ) { // Create a new Datasmith light actor. // Hash the Datasmith light actor name to shorten it. string HashedActorName = FDatasmithFacadeElement.GetStringHash("L:" + GetActorName(true)); FDatasmithFacadeActorLight LightActor = FDatasmithRevitLight.CreateLightActor(CurrentElement, HashedActorName); LightActor.SetLabel(GetActorLabel()); // Set the world transform of the Datasmith light actor. DocumentData.SetActorTransform(InWorldTransform, LightActor); // Set the base properties of the Datasmith light actor. string LayerName = Category.GetCategory(CurrentElement.Document, BuiltInCategory.OST_LightingFixtureSource)?.Name ?? "Light Sources"; SetActorProperties(LayerName, LightActor); FDatasmithFacadeMetaData LightMetaData = null; if (!DocumentData.bSkipMetadataExport) { LightMetaData = GetActorMetaData(LightActor); } // Set the Datasmith light actor layer to its predefined name. string CategoryName = Category.GetCategory(CurrentElement.Document, BuiltInCategory.OST_LightingFixtureSource)?.Name ?? "Light Sources"; LightActor.SetLayer(CategoryName); // Set the specific properties of the Datasmith light actor. FDatasmithRevitLight.SetLightProperties(InLightAsset, CurrentElement, LightActor); // Add the light actor to the Datasmith actor hierarchy. AddChildActor(LightActor, LightMetaData, false, true); } public bool AddRPCActor( Transform InWorldTransform, Asset InRPCAsset, FMaterialData InMaterialData, out FDatasmithFacadeMesh OutDatasmithMesh, out FDatasmithFacadeMeshElement OutDatasmithMeshElement ) { // Create a new Datasmith RPC mesh. // Hash the Datasmith RPC mesh name to shorten it. string HashedName = FDatasmithFacadeElement.GetStringHash("RPCM:" + GetActorName(false)); FDatasmithFacadeMesh RPCMesh = new FDatasmithFacadeMesh(); RPCMesh.SetName(HashedName); Transform AffineTransform = Transform.Identity; LocationPoint RPCLocationPoint = CurrentElement.Location as LocationPoint; if (RPCLocationPoint != null) { if (RPCLocationPoint.Rotation != 0.0) { AffineTransform = AffineTransform.Multiply(Transform.CreateRotation(XYZ.BasisZ, -RPCLocationPoint.Rotation)); AffineTransform = AffineTransform.Multiply(Transform.CreateTranslation(RPCLocationPoint.Point.Negate())); } else { AffineTransform = Transform.CreateTranslation(RPCLocationPoint.Point.Negate()); } } int TotalVertexCount = 0; int TotalTriangleCount = 0; List GeometryObjectList = new List(); foreach (GeometryObject RPCGeometryObject in CurrentElement.get_Geometry(new Options())) { GeometryInstance RPCGeometryInstance = RPCGeometryObject as GeometryInstance; if (RPCGeometryInstance == null) { continue; } foreach (GeometryObject RPCInstanceGeometryObject in RPCGeometryInstance.GetInstanceGeometry()) { Mesh RPCInstanceGeometryMesh = RPCInstanceGeometryObject as Mesh; if (RPCInstanceGeometryMesh == null || RPCInstanceGeometryMesh.NumTriangles < 1) { continue; } TotalVertexCount += RPCInstanceGeometryMesh.Vertices.Count; TotalTriangleCount += RPCInstanceGeometryMesh.NumTriangles; GeometryObjectList.Add(RPCInstanceGeometryMesh); } } RPCMesh.SetVerticesCount(TotalVertexCount); RPCMesh.SetFacesCount(TotalTriangleCount * 2); // Triangles are added twice for RPC meshes: CW & CCW int MeshMaterialIndex = 0; int VertexIndexOffset = 0; int TriangleIndexOffset = 0; foreach (Mesh RPCInstanceGeometryMesh in GeometryObjectList) { // RPC geometry does not have normals nor UVs available through the Revit Mesh interface. int VertexCount = RPCInstanceGeometryMesh.Vertices.Count; int TriangleCount = RPCInstanceGeometryMesh.NumTriangles; // Add the RPC geometry vertices to the Datasmith RPC mesh. for (int VertexIndex = 0; VertexIndex < RPCInstanceGeometryMesh.Vertices.Count; ++VertexIndex) { XYZ PositionedVertex = AffineTransform.OfPoint(RPCInstanceGeometryMesh.Vertices[VertexIndex]); RPCMesh.SetVertex(VertexIndexOffset + VertexIndex, (float)PositionedVertex.X, (float)PositionedVertex.Y, (float)PositionedVertex.Z); } // Add the RPC geometry triangles to the Datasmith RPC mesh. for (int TriangleNo = 0, BaseTriangleIndex = 0; TriangleNo < TriangleCount; TriangleNo++, BaseTriangleIndex += 2) { MeshTriangle Triangle = RPCInstanceGeometryMesh.get_Triangle(TriangleNo); try { int Index0 = VertexIndexOffset + Convert.ToInt32(Triangle.get_Index(0)); int Index1 = VertexIndexOffset + Convert.ToInt32(Triangle.get_Index(1)); int Index2 = VertexIndexOffset + Convert.ToInt32(Triangle.get_Index(2)); // Add triangles for both the front and back faces. RPCMesh.SetFace(TriangleIndexOffset + BaseTriangleIndex, Index0, Index1, Index2, MeshMaterialIndex); RPCMesh.SetFace(TriangleIndexOffset + BaseTriangleIndex + 1, Index2, Index1, Index0, MeshMaterialIndex); } catch (OverflowException) { continue; } } VertexIndexOffset += VertexCount; TriangleIndexOffset += TriangleCount * 2; } // Create a new Datasmith RPC mesh actor. // Hash the Datasmith RPC mesh actor name to shorten it. string HashedActorName = FDatasmithFacadeElement.GetStringHash("RPC:" + GetActorName(true)); FDatasmithFacadeActor FacadeActor; if (RPCMesh.GetVerticesCount() > 0 && RPCMesh.GetFacesCount() > 0) { FDatasmithFacadeActorMesh RPCMeshActor = new FDatasmithFacadeActorMesh(HashedActorName); RPCMeshActor.SetMesh(RPCMesh.GetName()); FacadeActor = RPCMeshActor; OutDatasmithMesh = RPCMesh; OutDatasmithMeshElement = new FDatasmithFacadeMeshElement(HashedName); OutDatasmithMeshElement.SetLabel(GetActorLabel()); OutDatasmithMeshElement.SetMaterial(InMaterialData.MaterialInstance.GetName(), MeshMaterialIndex); } else { //Create an empty actor instead of a static mesh actor with no mesh. FacadeActor = new FDatasmithFacadeActor(HashedActorName); OutDatasmithMesh = null; OutDatasmithMeshElement = null; } FacadeActor.SetLabel(GetActorLabel()); // Set the world transform of the Datasmith RPC mesh actor. DocumentData.SetActorTransform(InWorldTransform, FacadeActor); // Set the base properties of the Datasmith RPC mesh actor. string LayerName = GetCategoryName(); SetActorProperties(LayerName, FacadeActor); // Add a Revit element RPC tag to the Datasmith RPC mesh actor. FacadeActor.AddTag("Revit.Element.RPC"); // Add some Revit element RPC metadata to the Datasmith RPC mesh actor. AssetProperty RPCTypeId = InRPCAsset.FindByName("RPCTypeId"); AssetProperty RPCFilePath = InRPCAsset.FindByName("RPCFilePath"); FDatasmithFacadeMetaData ElementMetaData = new FDatasmithFacadeMetaData(FacadeActor.GetName() + "_DATA"); ElementMetaData.SetLabel(FacadeActor.GetLabel()); ElementMetaData.SetAssociatedElement(FacadeActor); if (RPCTypeId != null) { ElementMetaData.AddPropertyString("Type*RPCTypeId", (RPCTypeId as AssetPropertyString).Value); } if (RPCFilePath != null) { ElementMetaData.AddPropertyString("Type*RPCFilePath", (RPCFilePath as AssetPropertyString).Value); } // Add the RPC mesh actor to the Datasmith actor hierarchy. AddChildActor(FacadeActor, ElementMetaData, false, true); return OutDatasmithMesh != null; } public void AddChildActor( FBaseElementData InChildActor ) { FBaseElementData ParentElement = (InstanceDataStack.Count == 0) ? this : InstanceDataStack.Peek(); ParentElement.ChildElements.Add(InChildActor); InChildActor.Parent = ParentElement; } public void AddChildActor( FDatasmithFacadeActor ChildActor, FDatasmithFacadeMetaData MetaData, bool bOptimizeHierarchy, bool bOwned // Make its lifetime controlled by this ElementData ) { FBaseElementData ElementData = new FBaseElementData(ChildActor, MetaData, DocumentData); ElementData.bOptimizeHierarchy = bOptimizeHierarchy; FBaseElementData Parent = (InstanceDataStack.Count == 0) ? this : InstanceDataStack.Peek(); Parent.ChildElements.Add(ElementData); ElementData.Parent = Parent; ElementData.bOwnedByParent = bOwned; } public void InitializeElement( Transform InWorldTransform, FBaseElementData InElement ) { InElement.WorldTransform = InWorldTransform; // Create a new Datasmith mesh. // Hash the Datasmith mesh name to shorten it. string HashedMeshName = FDatasmithFacadeElement.GetStringHash("M:" + GetMeshName()); InElement.DatasmithPolymesh = new FDatasmithPolymesh(); InElement.DatasmithMeshElement = new FDatasmithFacadeMeshElement(HashedMeshName); InElement.DatasmithMeshElement.SetLabel(GetActorLabel()); if (InElement.ElementActor == null) { // Create a new Datasmith mesh actor. // Hash the Datasmith mesh actor name to shorten it. string HashedActorName = FDatasmithFacadeElement.GetStringHash("A:" + GetActorName(true)); if (BaseElementType != null && BaseElementType.FamilyName == "Decal") { bOptimizeHierarchy = false; bIsDecalElement = true; InElement.ElementActor = new FDatasmithFacadeActorDecal(HashedActorName); } else { InElement.ElementActor = new FDatasmithFacadeActorMesh(HashedActorName); } InElement.ElementActor.SetLabel(GetActorLabel()); } // Set the world transform of the Datasmith mesh actor. DocumentData.SetActorTransform(InWorldTransform, InElement.ElementActor); // Set the base properties of the Datasmith mesh actor. string LayerName = GetCategoryName(); SetActorProperties(LayerName, InElement.ElementActor); if (!DocumentData.bSkipMetadataExport) { InElement.ElementMetaData = GetActorMetaData(InElement.ElementActor); } } public string GetCategoryName() { return BaseElementType?.Category?.Name ?? CurrentElement.Category?.Name; } public bool IgnoreElementGeometry() { // Ignore elements that have unwanted geometry, such as level symbols. return (BaseElementType as LevelType) != null; } public FDatasmithPolymesh GetCurrentPolymesh() { if (InstanceDataStack.Count == 0) { return DatasmithPolymesh; } else { return InstanceDataStack.Peek().DatasmithPolymesh; } } public FBaseElementData PeekInstance() { return InstanceDataStack.Count > 0 ? InstanceDataStack.Peek() : null; } public FBaseElementData GetCurrentActor() { if (InstanceDataStack.Count == 0) { return this; } else { return InstanceDataStack.Peek(); } } public void Log( FDatasmithFacadeLog InDebugLog, string InLinePrefix, int InLineIndentation ) { if (InDebugLog != null) { if (InLineIndentation < 0) { InDebugLog.LessIndentation(); } Element SourceElement = (InstanceDataStack.Count == 0) ? CurrentElement : InstanceDataStack.Peek().BaseElementType; InDebugLog.AddLine($"{InLinePrefix} {SourceElement.Id.IntegerValue} '{SourceElement.Name}' {SourceElement.GetType()}: '{GetActorLabel()}'"); if (InLineIndentation > 0) { InDebugLog.MoreIndentation(); } } } private string GetActorName(bool bEnsureUnique) { return DocumentData.GetActorName(this); } public string GenerateUniqueInstanceNameSuffix() { // GenerateUniqueInstanceName is being called when generating a name for instance. // After the call, the intance is added as a child to its parent. // Next time the method gets called for the next instance, ChildElements.Count will be different/incremented. // To add uniqueness to the generated name, we construct a string with child counts from // current parent instance, up to the root: // Elem->Instance->Instance->Instance can produce something like: "1:5:3" for example. // However, this is not enough because elsewhere we might encounter the same sequence in terms of child counts, // but adding the CurrentElement unique id ensures we get unique name string in the end. StringBuilder ChildCounts = new StringBuilder(); for (int ElemIndex = 1; ElemIndex < InstanceDataStack.Count; ++ElemIndex) { FBaseElementData Elem = InstanceDataStack.ElementAt(ElemIndex); ChildCounts.AppendFormat(":{0}", Elem.ChildElements.Count); } // Add child count for the root element (parent of all instances) ChildCounts.AppendFormat(":{0}", ChildElements.Count); return $"I:{ChildCounts}"; } private string GetMeshName() { if (InstanceDataStack.Count == 0) { return $"{DocumentData.DocumentId}:{CurrentElement.UniqueId}"; } FBaseElementData Instance = InstanceDataStack.Peek(); if (Instance.bAllowMeshInstancing) { // Generate instanced mesh name using Instance geometry UniqueId return $"{DocumentData.DocumentId}:{Instance.BaseElementType.UniqueId}"; } return GetActorName(true); // Use unique element's actor name to base mesh name on } private string GetActorLabel() { string CategoryName = GetCategoryName(); string FamilyName = BaseElementType?.FamilyName; string TypeName = BaseElementType?.Name; string InstanceName = (InstanceDataStack.Count > 1) ? InstanceDataStack.Peek().BaseElementType?.Name : null; string ActorLabel = ""; if (CurrentElement as Level != null) { ActorLabel += string.IsNullOrEmpty(FamilyName) ? "" : FamilyName + "*"; ActorLabel += string.IsNullOrEmpty(TypeName) ? "" : TypeName + "*"; ActorLabel += CurrentElement.Name; } else { ActorLabel += string.IsNullOrEmpty(CategoryName) ? "" : CategoryName + "*"; ActorLabel += string.IsNullOrEmpty(FamilyName) ? "" : FamilyName + "*"; ActorLabel += string.IsNullOrEmpty(TypeName) ? CurrentElement.Name : TypeName; ActorLabel += string.IsNullOrEmpty(InstanceName) ? "" : "*" + InstanceName; } DocumentData.Context.LogDebug($"GetActorLabel(CategoryName='{CategoryName}', FamilyName='{FamilyName}', TypeName='{TypeName}', InstanceName='{InstanceName}') -> '{ActorLabel}'"); return ActorLabel; } private void SetActorProperties( string InLayerName, FDatasmithFacadeActor IOActor ) { // Set the Datasmith actor layer to the element type category name. IOActor.SetLayer(InLayerName); // Add the Revit element ID and Unique ID tags to the Datasmith actor. IOActor.AddTag($"Revit.Element.Id.{CurrentElement.Id.IntegerValue}"); IOActor.AddTag($"Revit.Element.UniqueId.{CurrentElement.UniqueId}"); // For an hosted Revit family instance, add the host ID, Unique ID and Mirrored/Flipped flags as tags to the Datasmith actor. FamilyInstance CurrentFamilyInstance = CurrentElement as FamilyInstance; if (CurrentFamilyInstance != null) { IOActor.AddTag($"Revit.DB.FamilyInstance.Mirrored.{CurrentFamilyInstance.Mirrored}"); IOActor.AddTag($"Revit.DB.FamilyInstance.HandFlipped.{CurrentFamilyInstance.HandFlipped}"); IOActor.AddTag($"Revit.DB.FamilyInstance.FaceFlipped.{CurrentFamilyInstance.FacingFlipped}"); if (CurrentFamilyInstance.Host != null) { IOActor.AddTag($"Revit.Host.Id.{CurrentFamilyInstance.Host.Id.IntegerValue}"); IOActor.AddTag($"Revit.Host.UniqueId.{CurrentFamilyInstance.Host.UniqueId}"); } } } private FDatasmithFacadeMetaData GetActorMetaData(FDatasmithFacadeActor IOActor) { FDatasmithFacadeMetaData ElementMetaData = new FDatasmithFacadeMetaData(IOActor.GetName() + "_DATA"); ElementMetaData.SetLabel(IOActor.GetLabel()); ElementMetaData.SetAssociatedElement(IOActor); // Add the Revit element category name metadata to the Datasmith actor. string CategoryName = GetCategoryName(); if (!string.IsNullOrEmpty(CategoryName)) { ElementMetaData.AddPropertyString("Element*Category", CategoryName); } // Add the Revit element family name metadata to the Datasmith actor. string FamilyName = BaseElementType?.FamilyName; if (!string.IsNullOrEmpty(FamilyName)) { ElementMetaData.AddPropertyString("Element*Family", FamilyName); } // Add the Revit element type name metadata to the Datasmith actor. string TypeName = BaseElementType?.Name; if (!string.IsNullOrEmpty(TypeName)) { ElementMetaData.AddPropertyString("Element*Type", TypeName); } // Add Revit element metadata to the Datasmith actor. FUtils.AddActorMetadata(CurrentElement, "Element*", ElementMetaData, DocumentData.CurrentSettings); if (BaseElementType != null) { // Add Revit element type metadata to the Datasmith actor. FUtils.AddActorMetadata(BaseElementType, "Type*", ElementMetaData, DocumentData.CurrentSettings); } return ElementMetaData; } } private string GetActorName(FElementData InElementData) { // GetActorName should be called only for the current processed element // so InElementData serves internally the only purpose to validate the call Debug.Assert(InElementData == ElementDataStack.Peek()); string ActorName = Context.GetActorName(this); if (InElementData.InstanceDataStack.Count == 0) { return ActorName; } // Instance actor name (when InstanceDataStack is not empty), using current element's encountered instance count as instance's identification // (to to OnElementBegin could follow multiple OnInstanceBegin/End calls, meaning an element can have multiple instances nested right under it) // And each instance should have a separate unique name name identified by element path from root plus instances index(calling GetActorName for next // instance in the same element will have InstanceDataStack.Count increased) return ActorName + InElementData.GenerateUniqueInstanceNameSuffix(); } public Dictionary>> MeshMap = new Dictionary>>(); public Dictionary ActorMap = new Dictionary(); public Dictionary MaterialDataMap = null; public Dictionary NewMaterialsMap = new Dictionary(); public Dictionary DecalElementsMap = new Dictionary(); public Dictionary DecalMaterialsMap = new Dictionary(); private Stack ElementDataStack = new Stack(); private string CurrentMaterialName = null; private List MessageList = null; private FSettings CurrentSettings = null; // Apply world offset to elements public FSettings.EInsertionPoint InsertionPoint { get; set; } = FSettings.EInsertionPoint.Default; private XYZ ProjectSurveyPoint = null; private XYZ ProjectBasePoint = null; public FDatasmithRevitExportContext Context = null; public string DocumentId { get; private set; } = ""; public bool bSkipMetadataExport { get; private set; } = false; public Document CurrentDocument { get; private set; } = null; public FDirectLink DirectLink { get; private set; } = null; public Outline SectionBoxOutline = null; private OrientatedBoundingBox SectionBox = null; public FDocumentData( Document InDocument, FSettings InSettings, ref List InMessageList, FDirectLink InDirectLink, string InDocumentId ) { CurrentSettings = InSettings; DirectLink = InDirectLink; CurrentDocument = InDocument; MessageList = InMessageList; // With DirectLink, we delay export of metadata for a faster initial export. bSkipMetadataExport = (DirectLink != null); DocumentId = InDocumentId; } public void Reset(FDatasmithRevitExportContext InContext) { Context = InContext; // Make currently processing document aware of the current export context (currently context is recreated on each Sync) MeshMap = new Dictionary>>(); ActorMap = new Dictionary(); MaterialDataMap = null; NewMaterialsMap = new Dictionary(); DecalElementsMap = new Dictionary(); DecalMaterialsMap = new Dictionary(); ElementDataStack = new Stack(); CurrentMaterialName = null; InsertionPoint = FSettings.EInsertionPoint.Default; ProjectSurveyPoint = null; ProjectBasePoint = null; SectionBoxOutline = null; SectionBox = null; if (DirectLink != null) { MaterialDataMap = DirectLink.MaterialDataMap; } else { MaterialDataMap = new Dictionary(); } InsertionPoint = CurrentSettings?.InsertionPoint ?? FSettings.EInsertionPoint.Default; // Cache document section boxes if (CurrentDocument.ActiveView != null) { View3D CurrentView3d = (GetElement(CurrentDocument.ActiveView.Id) as View3D); SectionBox = null; SectionBoxOutline = null; if (CurrentView3d != null && CurrentView3d.IsSectionBoxActive) { BoundingBoxXYZ BBox = CurrentView3d.GetSectionBox(); #if REVIT_API_2023 if (BBox.IsSet && BBox.Enabled) #else if (BBox.Enabled) #endif { SectionBox = new OrientatedBoundingBox(BBox.Transform, BBox.Min, BBox.Max, true); if (SectionBox.bIsValidData) { SectionBoxOutline = new Outline(SectionBox.AxisAllignedMin, SectionBox.AxisAllignedMax); } else { SectionBox = null; } } } } } public Element GetElement( ElementId InElementId ) { return (InElementId != ElementId.InvalidElementId) ? CurrentDocument.GetElement(InElementId) : null; } public bool ContainsMesh(string MeshName) { return MeshMap.ContainsKey(MeshName); } public bool PushElement( Element InElement, Transform InWorldTransform ) { #if REVIT_API_2023 if (DirectLink != null && InElement.Category != null && InElement.Category.BuiltInCategory != BuiltInCategory.OST_Levels) #else if (DirectLink != null && InElement.Category != null && (BuiltInCategory)InElement.Category.Id.IntegerValue != BuiltInCategory.OST_Levels) #endif { //Check if any of its children is Decal: //If so track them, so that we can use the owner object elementId to update the Decals (when modified, for ep: changing its location) foreach (ElementId DependentElementId in FUtils.GetAllDependentElements(InElement)) { if (FUtils.IsElementIdDecal(InElement.Document, DependentElementId)) { if (DependentElementId != InElement.Id && !DirectLink.DecalIdToOwnerObjectIdMap.ContainsKey(DependentElementId)) { DirectLink.DecalIdToOwnerObjectIdMap.Add(DependentElementId, InElement.Id); } } } } DirectLink?.MarkForExport(InElement); FElementData ElementData = null; if (ActorMap.ContainsKey(InElement.Id)) { if (DirectLink != null && DirectLink.IsElementCached(InElement)) { return false; } ElementData = ActorMap[InElement.Id] as FElementData; } if (ElementData == null) { if (DirectLink?.IsElementCached(InElement) ?? false) { ElementData = (FElementData)DirectLink.GetCachedElement(InElement); bool bIsLinkedDocument = InElement.GetType() == typeof(RevitLinkInstance); if (bIsLinkedDocument || DirectLink.IsElementModified(InElement)) { FDatasmithFacadeActor ExistingActor = ElementData.ElementActor; // Remove children that are instances: they will be re-created; // The reason is that we cannot uniquely identify family instances (no id) and when element changes, // we need to export all of its child instances anew. if (ExistingActor != null) { if (ElementData.ChildElements.Count > 0) { List ChildrenToRemove = new List(); for(int ChildIndex = 0; ChildIndex < ElementData.ChildElements.Count; ++ChildIndex) { FBaseElementData ChildElement = ElementData.ChildElements[ChildIndex]; bool bIsFamilyIntance = ((ChildElement as FElementData) == null) && ChildElement.ElementActor.IsComponent(); if (bIsFamilyIntance) { ChildrenToRemove.Add(ChildElement); } } foreach (FBaseElementData Child in ChildrenToRemove) { ExistingActor.RemoveChild(Child.ElementActor); ElementData.ChildElements.Remove(Child); } } ExistingActor.ResetTags(); (ExistingActor as FDatasmithFacadeActorMesh)?.SetMesh(null); } ElementData.BaseElementType = InElement.Document.GetElement(InElement.GetTypeId()) as ElementType; ElementData.CurrentElement = InElement; ElementData.MeshMaterialsMap.Clear(); } else { ActorMap[InElement.Id] = ElementData; return false; // We have up to date cache for this element. } } else { ElementData = new FElementData(InElement, InWorldTransform, this); } ElementDataStack.Push(ElementData); // Initialize element after pushing it on the stack(to unify this with other calls to element's methods) // GetActorName depends on this ElementData.InitializePivotPlacement(ref InWorldTransform); ElementData.InitializeElement(InWorldTransform, ElementData); if (ElementData.bIsDecalElement) { if (!DecalElementsMap.ContainsKey(InElement.Id)) { DecalElementsMap.Add(InElement.Id, ElementData); } } } else { ElementDataStack.Push(ElementData); } ElementDataStack.Peek().ElementActor.AddTag("IsElement"); return true; } public void PopElement(FDatasmithFacadeScene InDatasmithScene) { FElementData ElementData = ElementDataStack.Pop(); FDatasmithPolymesh DatasmithPolymesh = ElementData.DatasmithPolymesh; if(DatasmithPolymesh.Vertices.Count > 0 && DatasmithPolymesh.Faces.Count > 0) { ElementData.UpdateMeshName(); } CollectMesh(ElementData.DatasmithPolymesh, ElementData.DatasmithMeshElement, InDatasmithScene); DirectLink?.ClearModified(ElementData.CurrentElement); ElementData.bIsModified = true; ElementId ElemId = ElementData.CurrentElement.Id; if (ElementDataStack.Count == 0) { if (ActorMap.ContainsKey(ElemId) && ActorMap[ElemId] != ElementData) { // Handle the spurious case of Revit Custom Exporter calling back more than once for the same element. // These extra empty actors will be cleaned up later by the Datasmith actor hierarchy optimization. ActorMap[ElemId].ChildElements.Add(ElementData); ElementData.Parent = ActorMap[ElemId]; } else { // Collect the element mesh actor into the Datasmith actor dictionary. ActorMap[ElemId] = ElementData; } } else { if (!ActorMap.ContainsKey(ElemId)) { // Add the element mesh actor to the Datasmith actor hierarchy. ElementDataStack.Peek().AddChildActor(ElementData); } } } private static FDatasmithFacadeActor DuplicateBaseActor(FDatasmithFacadeActor SourceActor) { FDatasmithFacadeActor CloneActor = new FDatasmithFacadeActor(SourceActor.GetName()); CloneActor.SetLabel(SourceActor.GetLabel()); double X, Y, Z, W; SourceActor.GetTranslation(out X, out Y, out Z); CloneActor.SetTranslation(X, Y, Z); SourceActor.GetScale(out X, out Y, out Z); CloneActor.SetScale(X, Y, Z); SourceActor.GetRotation(out X, out Y, out Z, out W); CloneActor.SetRotation(X, Y, Z, W); CloneActor.SetLayer(SourceActor.GetLayer()); for (int TagIndex = 0; TagIndex < SourceActor.GetTagsCount(); ++TagIndex) { CloneActor.AddTag(SourceActor.GetTag(TagIndex)); } CloneActor.SetIsComponent(SourceActor.IsComponent()); CloneActor.SetVisibility(SourceActor.GetVisibility()); for (int ChildIndex = 0; ChildIndex < SourceActor.GetChildrenCount(); ++ChildIndex) { CloneActor.AddChild(SourceActor.GetChild(ChildIndex)); } return CloneActor; } public void PushInstance( ElementType InInstanceType, Transform InWorldTransform ) { // Check if this instance intersects any section box. // If so, we can't instance its mesh--will be considered unique. bool bIntersectedBySectionBox = false; if (SectionBox != null) { BoundingBoxXYZ InstanceBoundingBox = InInstanceType.get_BoundingBox(CurrentDocument.ActiveView); if (InstanceBoundingBox != null) { OrientatedBoundingBox InstanceOrientatedBoundingBox = new OrientatedBoundingBox(InWorldTransform, InstanceBoundingBox.Min, InstanceBoundingBox.Max); if (InstanceOrientatedBoundingBox.bIsValidData) { Outline InstanceOutline = new Outline(InstanceOrientatedBoundingBox.AxisAllignedMin, InstanceOrientatedBoundingBox.AxisAllignedMax); bIntersectedBySectionBox = SectionBox.DoesIntersect(InstanceOrientatedBoundingBox); } } } FElementData CurrentElementData = ElementDataStack.Peek(); FBaseElementData NewInstance = CurrentElementData.PushInstance(InInstanceType, InWorldTransform, !bIntersectedBySectionBox); } public void PopInstance(FDatasmithFacadeScene InDatasmithScene) { FElementData CurrentElement = ElementDataStack.Peek(); FBaseElementData InstanceData = CurrentElement.PopInstance(); FDatasmithPolymesh DatasmithPolymesh = InstanceData.DatasmithPolymesh; if (ContainsMesh(InstanceData.DatasmithMeshElement.GetName()) || (DatasmithPolymesh.Vertices.Count > 0 && DatasmithPolymesh.Faces.Count > 0)) { InstanceData.UpdateMeshName(); } else { /* Instance has no mesh. * Handle the case where instance has valid transform, but parent element has valid mesh (exported after instance gets finished), * in which case we want to apply the instance transform as a pivot transform. * This is a case currently encountered for steel beams. */ bool bElementHasMesh = ContainsMesh(CurrentElement.DatasmithMeshElement.GetName()) || (CurrentElement.DatasmithPolymesh.Vertices.Count > 0 && CurrentElement.DatasmithPolymesh.Faces.Count > 0); if (CurrentElement.CurrentElement.GetType() == typeof(FamilyInstance) && !bElementHasMesh) { if (!CurrentElement.WorldTransform.IsIdentity) { CurrentElement.MeshPointsTransform = (CurrentElement.WorldTransform.Inverse * InstanceData.WorldTransform).Inverse; } else { CurrentElement.MeshPointsTransform = InstanceData.WorldTransform.Inverse; } CurrentElement.WorldTransform = InstanceData.WorldTransform; SetActorTransform(CurrentElement.WorldTransform, CurrentElement.ElementActor); } } // Collect the element Datasmith mesh into the mesh dictionary. CollectMesh(InstanceData.DatasmithPolymesh, InstanceData.DatasmithMeshElement, InDatasmithScene); // Add the instance mesh actor to the Datasmith actor hierarchy. CurrentElement.AddChildActor(InstanceData); } public void AddLocationActors( Transform InWorldTransform ) { // Add a new Datasmith placeholder actor for this document site location. AddSiteLocation(CurrentDocument.SiteLocation); // Add new Datasmith placeholder actors for the project base point and survey points. // A project has one base point and at least one survey point. Linked documents also have their own points. AddPointLocations(InWorldTransform); } public void AddLightActor( Transform InWorldTransform, Asset InLightAsset ) { ElementDataStack.Peek().AddLightActor(InWorldTransform, InLightAsset); } public void AddRPCActor( Transform InWorldTransform, Asset InRPCAsset, FDatasmithFacadeScene InDatasmithScene ) { // Create a simple fallback material for the RPC mesh. string RPCCategoryName = ElementDataStack.Peek().GetCategoryName(); bool isRPCPlant = !string.IsNullOrEmpty(RPCCategoryName) && RPCCategoryName == Category.GetCategory(CurrentDocument, BuiltInCategory.OST_Planting)?.Name; string RPCMaterialName = isRPCPlant ? "RPC_Plant" : "RPC_Material"; string RPCHashedMaterialName = FDatasmithFacadeElement.GetStringHash(RPCMaterialName); if (!MaterialDataMap.ContainsKey(RPCHashedMaterialName)) { // Color reference: https://www.color-hex.com/color-palette/70002 Color RPCColor = isRPCPlant ? /* green */ new Color(88, 126, 96) : /* gray */ new Color(128, 128, 128); // Keep track of a new RPC master material. MaterialDataMap[RPCHashedMaterialName] = new FMaterialData(RPCHashedMaterialName, RPCMaterialName, RPCColor); NewMaterialsMap[RPCHashedMaterialName] = MaterialDataMap[RPCHashedMaterialName]; } FMaterialData RPCMaterialData = MaterialDataMap[RPCHashedMaterialName]; if (ElementDataStack.Peek().AddRPCActor(InWorldTransform, InRPCAsset, RPCMaterialData, out FDatasmithFacadeMesh RPCMesh, out FDatasmithFacadeMeshElement RPCMeshElement)) { // Collect the RPC mesh into the Datasmith mesh dictionary. CollectMesh(RPCMesh, RPCMeshElement, InDatasmithScene); } } public bool SetMaterial( MaterialNode InMaterialNode, IList InExtraTexturePaths ) { Material CurrentMaterial = GetElement(InMaterialNode.MaterialId) as Material; if (InMaterialNode.HasOverriddenAppearance) { IList> ReferencingDecalIdAndAssetPairs = FDecalMaterial.GetDecalElementIdAndAppearancePairList(InMaterialNode); //Assets in on the .Value seem to be always unique, regardless if they are "instances" of the same DecalType: if (ReferencingDecalIdAndAssetPairs.Count > 0) { IList DecalMaterials = new List(); foreach (KeyValuePair DecalIdAndAssetPair in ReferencingDecalIdAndAssetPairs) { if (DecalMaterialsMap.ContainsKey(DecalIdAndAssetPair.Key)) { continue; } FDecalMaterial DecalMaterial = FDecalMaterial.Create(InMaterialNode, CurrentMaterial, DecalMaterials.Count, DecalIdAndAssetPair.Value); if (DecalMaterial != null) { //unique DecalMaterial: FDecalMaterial ExistingDecalMaterial = null; foreach (FDecalMaterial ExistingDecalMaterialCandidate in DecalMaterials) { if (ExistingDecalMaterialCandidate.CheckRenderValueEquiality(DecalMaterial)) { ExistingDecalMaterial = ExistingDecalMaterialCandidate; break; } } if (ExistingDecalMaterial != null) { DecalMaterialsMap.Add(DecalIdAndAssetPair.Key, ExistingDecalMaterial); } else { DecalMaterialsMap.Add(DecalIdAndAssetPair.Key, DecalMaterial); DecalMaterials.Add(DecalMaterial); } } } } } CurrentMaterialName = FMaterialData.GetMaterialName(InMaterialNode, CurrentMaterial); if (!MaterialDataMap.ContainsKey(CurrentMaterialName) || (CurrentMaterial != null && DirectLink != null && DirectLink.IsMaterialDirty(CurrentMaterial))) { // Keep track of a new Datasmith master material. MaterialDataMap[CurrentMaterialName] = new FMaterialData(InMaterialNode, CurrentMaterial, InExtraTexturePaths); NewMaterialsMap[CurrentMaterialName] = MaterialDataMap[CurrentMaterialName]; DirectLink?.SetMaterialClean(CurrentMaterial); // A new Datasmith master material was created. return true; } // No new Datasmith master material created. return false; } public bool IgnoreElementGeometry() { bool bIgnore = ElementDataStack.Peek().IgnoreElementGeometry(); if (!bIgnore) { // Check for instanced meshes. // For mesh to be reused, it must not be cutoff by a section box. FBaseElementData CurrentInstance = ElementDataStack.Peek().PeekInstance(); if (CurrentInstance != null && CurrentInstance.bAllowMeshInstancing && CurrentInstance.DatasmithMeshElement != null) { bIgnore = MeshMap.ContainsKey(CurrentInstance.DatasmithMeshElement.GetName()); } } return bIgnore; } public FDatasmithPolymesh GetCurrentPolymesh() { return ElementDataStack.Peek().GetCurrentPolymesh(); } public FDatasmithFacadeMeshElement GetCurrentMeshElement() { return ElementDataStack.Peek().GetCurrentMeshElement(); } public Transform GetCurrentMeshPointsTransform() { return ElementDataStack.Peek().MeshPointsTransform; } public int GetCurrentMaterialIndex() { FElementData ElemData = ElementDataStack.Peek(); FBaseElementData InstanceData = ElemData.PeekInstance(); FBaseElementData CurrentElement = InstanceData != null ? InstanceData : ElemData; if (!CurrentElement.MeshMaterialsMap.ContainsKey(CurrentMaterialName)) { int NewMaterialIndex = CurrentElement.MeshMaterialsMap.Count; CurrentElement.MeshMaterialsMap[CurrentMaterialName] = NewMaterialIndex; CurrentElement.DatasmithMeshElement.SetMaterial(CurrentMaterialName, NewMaterialIndex); } return CurrentElement.MeshMaterialsMap[CurrentMaterialName]; } public FBaseElementData GetCurrentActor() { return ElementDataStack.Peek().GetCurrentActor(); } public Element GetCurrentElement() { return ElementDataStack.Count > 0 ? ElementDataStack.Peek().CurrentElement : null; } private FBaseElementData OptimizeElementRecursive(FBaseElementData InElementData, FDatasmithFacadeScene InDatasmithScene, FSuperComponentOptimizer SuperComponentOptimizer) { List RemoveChildren = new List(); List AddChildren = new List(); for (int ChildIndex = 0; ChildIndex < InElementData.ChildElements.Count; ChildIndex++) { FBaseElementData ChildElement = InElementData.ChildElements[ChildIndex]; // Optimize the Datasmith child actor. FBaseElementData ResultElement = OptimizeElementRecursive(ChildElement, InDatasmithScene, SuperComponentOptimizer); if (ChildElement != ResultElement) { RemoveChildren.Add(ChildElement); if (ResultElement != null) { AddChildren.Add(ResultElement); SuperComponentOptimizer.UpdateCache(ResultElement, ChildElement); } } } foreach (FBaseElementData Child in RemoveChildren) { Child.Parent = null; InElementData.ChildElements.Remove(Child); InElementData.ElementActor.RemoveChild(Child.ElementActor); } foreach (FBaseElementData Child in AddChildren) { Child.Parent = InElementData; InElementData.ChildElements.Add(Child); InElementData.ElementActor.AddChild(Child.ElementActor); } if (InElementData.bOptimizeHierarchy) { int ChildrenCount = InElementData.ElementActor.GetChildrenCount(); if (ChildrenCount == 0) { // This Datasmith actor can be removed by optimization. return null; } if (ChildrenCount == 1) { Debug.Assert(InElementData.ChildElements.Count == 1); // This intermediate Datasmith actor can be removed while keeping its single child actor. FBaseElementData SingleChild = InElementData.ChildElements[0]; // Make sure the single child actor will not become a dangling component in the actor hierarchy. if (!InElementData.ElementActor.IsComponent() && SingleChild.ElementActor.IsComponent()) { SingleChild.ElementActor.SetIsComponent(false); } return SingleChild; } } return InElementData; } public void OptimizeActorHierarchy(FDatasmithFacadeScene InDatasmithScene) { FSuperComponentOptimizer SuperComponentOptimizer = new FSuperComponentOptimizer(); foreach (var ElementEntry in ActorMap) { FBaseElementData ElementData = ElementEntry.Value; FBaseElementData ResultElementData = OptimizeElementRecursive(ElementData, InDatasmithScene, SuperComponentOptimizer); if (ResultElementData != ElementData) { if (ResultElementData == null) { InDatasmithScene.RemoveActor(ElementData.ElementActor, FDatasmithFacadeScene.EActorRemovalRule.RemoveChildren); } else { InDatasmithScene.RemoveActor(ElementData.ElementActor, FDatasmithFacadeScene.EActorRemovalRule.KeepChildrenAndKeepRelativeTransform); if (ElementData.ChildElements.Count == 1) { SuperComponentOptimizer.UpdateCache(ElementData.ChildElements[0], ElementData); } } } } SuperComponentOptimizer.Optimize(); } public void WrapupLink( FDatasmithFacadeScene InDatasmithScene, FBaseElementData InLinkActor, HashSet UniqueTextureNameSet ) { // Add the collected meshes from the Datasmith mesh dictionary to the Datasmith scene. AddCollectedMeshes(InDatasmithScene); // Factor in the Datasmith actor hierarchy the Revit document host hierarchy. AddHostHierarchy(); // Factor in the Datasmith actor hierarchy the Revit document level hierarchy. AddLevelHierarchy(); if (ActorMap.Count > 0) { // Prevent the Datasmith link actor from being removed by optimization. InLinkActor.bOptimizeHierarchy = false; // Add the collected actors from the Datasmith actor dictionary as children of the Datasmith link actor. foreach (var Actor in ActorMap.Values) { InLinkActor.ChildElements.Add(Actor); Actor.Parent = InLinkActor; } } // Add the collected master materials from the material data dictionary to the Datasmith scene. AddCollectedMaterials(InDatasmithScene, UniqueTextureNameSet); } public void WrapupScene( FDatasmithFacadeScene InDatasmithScene, HashSet UniqueTextureNameSet ) { AddCollectedDecals(InDatasmithScene); // Add the collected meshes from the Datasmith mesh dictionary to the Datasmith scene. AddCollectedMeshes(InDatasmithScene); // Factor in the Datasmith actor hierarchy the Revit document host hierarchy. AddHostHierarchy(); // Factor in the Datasmith actor hierarchy the Revit document level hierarchy. AddLevelHierarchy(); List OptimizedAwayElements = new List(); foreach (var CollectedActor in ActorMap) { if (CollectedActor.Value.Optimize()) { OptimizedAwayElements.Add(CollectedActor.Key); } } foreach (ElementId Element in OptimizedAwayElements) { ActorMap.Remove(Element); } // Add the collected actors from the Datasmith actor dictionary to the Datasmith scene. foreach (var ActorEntry in ActorMap) { Element CollectedElement = CurrentDocument.GetElement(ActorEntry.Key); if (DirectLink != null && CollectedElement.GetType() == typeof(RevitLinkInstance)) { Document LinkedDoc = (CollectedElement as RevitLinkInstance).GetLinkDocument(); if (LinkedDoc != null) { DirectLink.OnBeginLinkedDocument(CollectedElement); foreach (FBaseElementData CurrentChild in ActorEntry.Value.ChildElements) { CurrentChild.AddToScene(InDatasmithScene, ActorEntry.Value, false); } DirectLink.OnEndLinkedDocument(); ActorEntry.Value.AddToScene(InDatasmithScene, null, true); } else { ActorEntry.Value.AddToScene(InDatasmithScene, null, false); } } else { ActorEntry.Value.AddToScene(InDatasmithScene, null, false); } } // Add the collected master materials from the material data dictionary to the Datasmith scene. AddCollectedMaterials(InDatasmithScene, UniqueTextureNameSet); } public void LogElement( FDatasmithFacadeLog InDebugLog, string InLinePrefix, int InLineIndentation ) { ElementDataStack.Peek().Log(InDebugLog, InLinePrefix, InLineIndentation); } public void LogMaterial( MaterialNode InMaterialNode, FDatasmithFacadeLog InDebugLog, string InLinePrefix ) { if (MaterialDataMap.ContainsKey(CurrentMaterialName)) { MaterialDataMap[CurrentMaterialName].Log(InMaterialNode, InDebugLog, InLinePrefix); } } private void AddSiteLocation( SiteLocation InSiteLocation ) { if (InSiteLocation == null || !InSiteLocation.IsValidObject) { return; } FDatasmithFacadeActor SiteLocationActor = null; FBaseElementData ElementData = null; DirectLink?.MarkForExport(InSiteLocation); if (DirectLink?.IsElementCached(InSiteLocation) ?? false) { if (!DirectLink.IsElementModified(InSiteLocation)) { return; } ElementData = DirectLink.GetCachedElement(InSiteLocation); SiteLocationActor = ElementData.ElementActor; SiteLocationActor.ResetTags(); } else { // Create a new Datasmith placeholder actor for the site location. // Hash the Datasmith placeholder actor name to shorten it. string NameHash = FDatasmithFacadeElement.GetStringHash("SiteLocation"); SiteLocationActor = new FDatasmithFacadeActor(NameHash); SiteLocationActor.SetLabel("Site Location"); } // Set the Datasmith placeholder actor layer to the site location category name. SiteLocationActor.SetLayer(InSiteLocation.Category.Name); // Add the Revit element ID and Unique ID tags to the Datasmith placeholder actor. SiteLocationActor.AddTag($"Revit.Element.Id.{InSiteLocation.Id.IntegerValue}"); SiteLocationActor.AddTag($"Revit.Element.UniqueId.{InSiteLocation.UniqueId}"); // Add a Revit element site location tag to the Datasmith placeholder actor. SiteLocationActor.AddTag("Revit.Element.SiteLocation"); FDatasmithFacadeMetaData SiteLocationMetaData = new FDatasmithFacadeMetaData(SiteLocationActor.GetName() + "_DATA"); SiteLocationMetaData.SetLabel(SiteLocationActor.GetLabel()); SiteLocationMetaData.SetAssociatedElement(SiteLocationActor); // Add site location metadata to the Datasmith placeholder actor. const double RadiansToDegrees = 180.0 / Math.PI; SiteLocationMetaData.AddPropertyFloat("SiteLocation*Latitude", (float)(InSiteLocation.Latitude * RadiansToDegrees)); SiteLocationMetaData.AddPropertyFloat("SiteLocation*Longitude", (float)(InSiteLocation.Longitude * RadiansToDegrees)); SiteLocationMetaData.AddPropertyFloat("SiteLocation*Elevation", (float)InSiteLocation.Elevation); SiteLocationMetaData.AddPropertyFloat("SiteLocation*TimeZone", (float)InSiteLocation.TimeZone); SiteLocationMetaData.AddPropertyString("SiteLocation*Place", InSiteLocation.PlaceName); // Collect the site location placeholder actor into the Datasmith actor dictionary. if (ElementData == null) { ElementData = new FBaseElementData(SiteLocationActor, null, this); // Prevent the Datasmith placeholder actor from being removed by optimization. ElementData.bOptimizeHierarchy = false; } else { ElementData.ElementMetaData = SiteLocationMetaData; } ActorMap[InSiteLocation.Id] = ElementData; DirectLink?.CacheElement(CurrentDocument, InSiteLocation, ElementData); } private void AddPointLocations( Transform InWorldTransform ) { FilteredElementCollector Collector = new FilteredElementCollector(CurrentDocument); ICollection PointLocations = Collector.OfClass(typeof(BasePoint)).ToElements(); foreach (Element PointLocation in PointLocations) { BasePoint BasePointLocation = PointLocation as BasePoint; if (BasePointLocation != null) { // Since BasePoint.Location is not a location point we cannot get a position from it; so we use a bounding box approach. // Note that, as of Revit 2020, BasePoint has 2 new properties: Position for base point and SharedPosition for survey point. BoundingBoxXYZ BasePointBoundingBox = BasePointLocation.get_BoundingBox(CurrentDocument.ActiveView); if (BasePointBoundingBox == null) { continue; } string ActorName = BasePointLocation.IsShared ? "SurveyPoint" : "BasePoint"; string ActorLabel = BasePointLocation.IsShared ? "Survey Point" : "Base Point"; FDatasmithFacadeActor BasePointActor = null; FBaseElementData BasePointElement = null; DirectLink?.MarkForExport(PointLocation); if (DirectLink?.IsElementCached(PointLocation) ?? false) { if (!DirectLink.IsElementModified(PointLocation)) { continue; } BasePointElement = DirectLink.GetCachedElement(PointLocation); BasePointActor = BasePointElement.ElementActor; BasePointActor.ResetTags(); } else { // Create a new Datasmith placeholder actor for the base point. // Hash the Datasmith placeholder actor name to shorten it. string HashedActorName = FDatasmithFacadeElement.GetStringHash(ActorName); BasePointActor = new FDatasmithFacadeActor(HashedActorName); BasePointActor.SetLabel(ActorLabel); } // Set the world transform of the Datasmith placeholder actor. XYZ BasePointPosition = BasePointBoundingBox.Min; if (BasePointLocation.IsShared) { ProjectSurveyPoint = BasePointPosition; } else { ProjectBasePoint = BasePointPosition; } Transform TranslationMatrix = Transform.CreateTranslation(BasePointPosition); // Don't apply offset since basepoints aren't yet initialized SetActorTransform(TranslationMatrix.Multiply(InWorldTransform), BasePointActor, false); // Set the Datasmith placeholder actor layer to the base point category name. BasePointActor.SetLayer(BasePointLocation.Category.Name); // Add the Revit element ID and Unique ID tags to the Datasmith placeholder actor. BasePointActor.AddTag($"Revit.Element.Id.{BasePointLocation.Id.IntegerValue}"); BasePointActor.AddTag($"Revit.Element.UniqueId.{BasePointLocation.UniqueId}"); // Add a Revit element base point tag to the Datasmith placeholder actor. BasePointActor.AddTag("Revit.Element." + ActorName); // Add base point metadata to the Datasmith actor. string MetadataPrefix = BasePointLocation.IsShared ? "SurveyPointLocation*" : "BasePointLocation*"; FDatasmithFacadeMetaData BasePointMetaData = new FDatasmithFacadeMetaData(BasePointActor.GetName() + "_DATA"); BasePointMetaData.SetLabel(BasePointActor.GetLabel()); BasePointMetaData.SetAssociatedElement(BasePointActor); BasePointMetaData.AddPropertyVector(MetadataPrefix + "Location", $"{BasePointPosition.X} {BasePointPosition.Y} {BasePointPosition.Z}"); if (!bSkipMetadataExport) { FUtils.AddActorMetadata(BasePointLocation, MetadataPrefix, BasePointMetaData, CurrentSettings); } if (BasePointElement == null) { // Collect the base point placeholder actor into the Datasmith actor dictionary. BasePointElement = new FBaseElementData(BasePointActor, BasePointMetaData, this); BasePointElement.bOptimizeHierarchy = false; } else { BasePointElement.ElementMetaData = BasePointMetaData; } ActorMap[BasePointLocation.Id] = BasePointElement; DirectLink?.CacheElement(CurrentDocument, PointLocation, BasePointElement); } } } private void CollectMesh( FDatasmithFacadeMesh InMesh, FDatasmithFacadeMeshElement InMeshElement, FDatasmithFacadeScene InDatasmithScene ) { if (InDatasmithScene != null && InMesh.GetVerticesCount() > 0 && InMesh.GetFacesCount() > 0) { string MeshName = InMesh.GetName(); if (!MeshMap.ContainsKey(MeshName)) { // Export the DatasmithMesh in a task while we parse the rest of the document. // The task result indicates if the export was successful and if the associated FDatasmithFacadeMeshElement can be added to the scene. MeshMap[MeshName] = new Tuple>(InMeshElement, Task.Run(() => InDatasmithScene.ExportDatasmithMesh(InMeshElement, InMesh))); } } } private void CollectMesh( FDatasmithPolymesh InPolymesh, FDatasmithFacadeMeshElement InMeshElement, FDatasmithFacadeScene InDatasmithScene ) { if (InDatasmithScene != null && InPolymesh.Vertices.Count > 0 && InPolymesh.Faces.Count > 0) { string MeshName = InMeshElement.GetName(); if (!MeshMap.ContainsKey(MeshName)) { // Export the DatasmithMesh in a task while we parse the rest of the document. // The task result indicates if the export was successful and if the associated FDatasmithFacadeMeshElement can be added to the scene. MeshMap[MeshName] = new Tuple>(InMeshElement, Task.Run( () => { using (FDatasmithFacadeMesh DatasmithMesh = ParsePolymesh(InPolymesh, MeshName)) { return InDatasmithScene.ExportDatasmithMesh(InMeshElement, DatasmithMesh); } } )); } } } private FDatasmithFacadeMesh ParsePolymesh(FDatasmithPolymesh InPolymesh, string MeshName) { FDatasmithFacadeMesh DatasmithMesh = new FDatasmithFacadeMesh(); DatasmithMesh.SetName(MeshName); DatasmithMesh.SetVerticesCount(InPolymesh.Vertices.Count); DatasmithMesh.SetFacesCount(InPolymesh.Faces.Count); DatasmithMesh.SetUVChannelsCount(1); DatasmithMesh.SetUVCount(0, InPolymesh.UVs.Count); const int UVChannelIndex = 0; // Add the vertex points (in right-handed Z-up coordinates) to the Datasmith mesh. for (int VertexIndex = 0; VertexIndex < InPolymesh.Vertices.Count; ++VertexIndex) { XYZ Point = InPolymesh.Vertices[VertexIndex]; DatasmithMesh.SetVertex(VertexIndex, (float)Point.X, (float)Point.Y, (float)Point.Z); } // Add the vertex UV texture coordinates to the Datasmith mesh. for (int UVIndex = 0; UVIndex < InPolymesh.UVs.Count; ++UVIndex) { UV CurrentUV = InPolymesh.UVs[UVIndex]; DatasmithMesh.SetUV(UVChannelIndex, UVIndex, CurrentUV.U, CurrentUV.V); } // Add the triangle vertex indexes to the Datasmith mesh. for (int FacetIndex = 0; FacetIndex < InPolymesh.Faces.Count; ++FacetIndex) { FDocumentData.FPolymeshFace Face = InPolymesh.Faces[FacetIndex]; DatasmithMesh.SetFace(FacetIndex, Face.V1, Face.V2, Face.V3, Face.MaterialIndex); DatasmithMesh.SetFaceUV(FacetIndex, UVChannelIndex, Face.V1, Face.V2, Face.V3); } for (int NormalIndex = 0; NormalIndex < InPolymesh.Normals.Count; ++NormalIndex) { XYZ Normal = InPolymesh.Normals[NormalIndex]; DatasmithMesh.SetNormal(NormalIndex, (float)Normal.X, (float)Normal.Y, (float)Normal.Z); } return DatasmithMesh; } private void AddCollectedDecals(FDatasmithFacadeScene InDatasmithScene) { foreach (KeyValuePair DecalMaterialPair in DecalMaterialsMap) { FElementData DecalElement = null; if (!DecalElementsMap.TryGetValue(DecalMaterialPair.Key, out DecalElement)) { continue; } FDecalMaterial DecalMaterial = DecalMaterialPair.Value; FDatasmithFacadeMaterialInstance DatasmithMaterial = new FDatasmithFacadeMaterialInstance(DecalMaterial.MaterialName); DatasmithMaterial.SetMaterialType(FDatasmithFacadeMaterialInstance.EMaterialInstanceType.Decal); if (!string.IsNullOrEmpty(DecalMaterial.DiffuseTexturePath)) { FDatasmithFacadeTexture DiffuseTexture = FDatasmithFacadeMaterialsUtils.CreateSimpleTextureElement(DecalMaterial.DiffuseTexturePath); DiffuseTexture.SetSRGB(FDatasmithFacadeTexture.EColorSpace.sRGB); DiffuseTexture.SetTextureMode(FDatasmithFacadeTexture.ETextureMode.Diffuse); DiffuseTexture.SetFile(DecalMaterial.DiffuseTexturePath); DatasmithMaterial.AddTexture("ColorMap", DiffuseTexture); InDatasmithScene.AddTexture(DiffuseTexture); } if (!string.IsNullOrEmpty(DecalMaterial.BumpTexturePath)) { FDatasmithFacadeTexture BumpTexture = FDatasmithFacadeMaterialsUtils.CreateSimpleTextureElement(DecalMaterial.BumpTexturePath); BumpTexture.SetSRGB(FDatasmithFacadeTexture.EColorSpace.sRGB); BumpTexture.SetTextureMode(FDatasmithFacadeTexture.ETextureMode.Bump); BumpTexture.SetFile(DecalMaterial.BumpTexturePath); DatasmithMaterial.AddTexture("NormalMap", BumpTexture); DatasmithMaterial.AddFloat("NormalMapAmount", (float)DecalMaterial.BumpAmount); InDatasmithScene.AddTexture(BumpTexture); } if (!string.IsNullOrEmpty(DecalMaterial.CutoutTexturePath)) { FDatasmithFacadeTexture CutoutTexture = FDatasmithFacadeMaterialsUtils.CreateSimpleTextureElement(DecalMaterial.CutoutTexturePath); CutoutTexture.SetSRGB(FDatasmithFacadeTexture.EColorSpace.sRGB); CutoutTexture.SetTextureMode(FDatasmithFacadeTexture.ETextureMode.Other); CutoutTexture.SetFile(DecalMaterial.CutoutTexturePath); DatasmithMaterial.AddBoolean("UseCustomOpacityMap", true); DatasmithMaterial.AddTexture("OpacityMap", CutoutTexture); InDatasmithScene.AddTexture(CutoutTexture); } if (DecalMaterial.Luminance > 0f) { DatasmithMaterial.AddFloat("LuminanceAmount", (float)DecalMaterial.Luminance); } if (DecalMaterial.Transparency > 0f) { DatasmithMaterial.AddFloat("Opacity", (float)DecalMaterial.Transparency); } InDatasmithScene.AddMaterial(DatasmithMaterial); Transform DecalTransform = null; XYZ DecalDimensions = null; FUtils.GetDecalSpatialParams(DecalElement.CurrentElement, ref DecalTransform, ref DecalDimensions); if (DecalTransform == null || DecalDimensions == null) { continue; } FDatasmithFacadeActorDecal DecalActor = DecalElement.ElementActor as FDatasmithFacadeActorDecal; DecalActor.SetDimensions(DecalDimensions.Z, DecalDimensions.X, DecalDimensions.Y); DecalActor.SetDecalMaterialPathName(DatasmithMaterial.GetName()); SetActorTransform(DecalTransform, DecalActor); } } private void AddCollectedMeshes( FDatasmithFacadeScene InDatasmithScene ) { // Add the collected meshes from the Datasmith mesh dictionary to the Datasmith scene. foreach (var MeshElementExportResultTuple in MeshMap.Values) { // Wait for the export to complete and add the Mesh element on success. if (MeshElementExportResultTuple.Item2.Result) { InDatasmithScene.AddMesh(MeshElementExportResultTuple.Item1); } } } private void AddHostHierarchy() { AddParentElementHierarchy(GetHostElement); } private void AddLevelHierarchy() { AddParentElementHierarchy(GetLevelElement); } private void AddCollectedMaterials( FDatasmithFacadeScene InDatasmithScene, HashSet UniqueTextureNameSet ) { UniqueTextureNameSet = (DirectLink != null) ? DirectLink.UniqueTextureNameSet : UniqueTextureNameSet; // Add the collected master materials from the material data dictionary to the Datasmith scene. foreach (FMaterialData CollectedMaterialData in NewMaterialsMap.Values) { InDatasmithScene.AddMaterial(CollectedMaterialData.MaterialInstance); foreach(FDatasmithFacadeTexture CurrentTexture in CollectedMaterialData.CollectedTextures) { string TextureName = CurrentTexture.GetName(); if (!UniqueTextureNameSet.Contains(TextureName)) { UniqueTextureNameSet.Add(TextureName); InDatasmithScene.AddTexture(CurrentTexture); } } if (CollectedMaterialData.MessageList.Count > 0) { MessageList.AddRange(CollectedMaterialData.MessageList); } } } private Element GetHostElement( ElementId InElementId ) { Element SourceElement = CurrentDocument.GetElement(InElementId); Element HostElement = null; if (SourceElement as FamilyInstance != null) { HostElement = (SourceElement as FamilyInstance).Host; } else if (SourceElement as Wall != null) { HostElement = CurrentDocument.GetElement((SourceElement as Wall).StackedWallOwnerId); } else if (SourceElement as ContinuousRail != null) { HostElement = CurrentDocument.GetElement((SourceElement as ContinuousRail).HostRailingId); } else if (SourceElement.GetType().IsSubclassOf(typeof(InsulationLiningBase))) { HostElement = CurrentDocument.GetElement((SourceElement as InsulationLiningBase).HostElementId); } // DirectLink: if host is hidden, go up the hierarchy (NOTE this does not apply for linked documents) if (DirectLink != null && HostElement != null && CurrentDocument.ActiveView != null && HostElement.IsHidden(CurrentDocument.ActiveView)) { return GetHostElement(HostElement.Id); } return HostElement; } private Element GetLevelElement( ElementId InElementId ) { Element SourceElement = CurrentDocument.GetElement(InElementId); return (SourceElement == null) ? null : CurrentDocument.GetElement(SourceElement.LevelId); } private void AddParentElementHierarchy( Func InGetParentElement ) { Context.LogDebug("AddParentElementHierarchy"); Queue ElementIdQueue = new Queue(ActorMap.Keys); // Make sure the Datasmith actor dictionary contains actors for all the Revit parent elements. while (ElementIdQueue.Count > 0) { Element ParentElement = InGetParentElement(ElementIdQueue.Dequeue()); if (ParentElement == null) { continue; } ElementId ParentElementId = ParentElement.Id; if (ActorMap.ContainsKey(ParentElementId)) { continue; } if (DirectLink?.IsElementCached(ParentElement) ?? false) { // Move parent actor out of cache. DirectLink.MarkForExport(ParentElement); ActorMap[ParentElementId] = DirectLink.GetCachedElement(ParentElement); } else { Context.LogDebug(" Add element"); const FDatasmithFacadeScene NullScene = null; PushElement(ParentElement, Transform.Identity); PopElement(NullScene); } ElementIdQueue.Enqueue(ParentElementId); } // Add the parented actors as children of the parent Datasmith actors. foreach (ElementId ElemId in new List(ActorMap.Keys)) { Element ParentElement = InGetParentElement(ElemId); if (ParentElement == null) { continue; } Element SourceElement = CurrentDocument.GetElement(ElemId); if ((SourceElement as FamilyInstance != null && ParentElement as Truss != null) || (SourceElement as Mullion != null) || (SourceElement as Panel != null) || (SourceElement as ContinuousRail != null)) { // The Datasmith actor is a component in the hierarchy. ActorMap[ElemId].ElementActor.SetIsComponent(true); } ElementId ParentElementId = ParentElement.Id; // Add the parented actor as child of the parent Datasmith actor. FBaseElementData ElementData = ActorMap[ElemId]; FBaseElementData ParentElementData = ActorMap[ParentElementId]; if (!ParentElementData.ChildElements.Contains(ElementData)) { ParentElementData.ChildElements.Add(ElementData); ElementData.Parent = ParentElementData; } // Prevent the parent Datasmith actor from being removed by optimization. ParentElementData.bOptimizeHierarchy = false; } // Remove the parented child actors from the Datasmith actor dictionary. foreach (ElementId ElemId in new List(ActorMap.Keys)) { Element ParentElement = InGetParentElement(ElemId); if (ParentElement == null) { continue; } // Remove the parented child actor from the Datasmith actor dictionary. ActorMap.Remove(ElemId); } } private void SetActorTransform( Transform InWorldTransform, FDatasmithFacadeActor IOActor, bool bInApplyOffset = true ) { XYZ transformBasisX = InWorldTransform.BasisX; XYZ transformBasisY = InWorldTransform.BasisY; XYZ transformBasisZ = InWorldTransform.BasisZ; XYZ transformOrigin = InWorldTransform.Origin; // Check if need to apply world offset to element transform if (bInApplyOffset && InsertionPoint != FSettings.EInsertionPoint.Default) { switch (InsertionPoint) { case FSettings.EInsertionPoint.BasePoint: { if (ProjectBasePoint != null) { transformOrigin -= ProjectBasePoint; } } break; case FSettings.EInsertionPoint.SurveyPoint: { if (ProjectSurveyPoint != null) { transformOrigin -= ProjectSurveyPoint; } } break; } } double[] worldMatrix = new double[16]; worldMatrix[0] = transformBasisX.X; worldMatrix[1] = transformBasisX.Y; worldMatrix[2] = transformBasisX.Z; worldMatrix[3] = 0.0; worldMatrix[4] = transformBasisY.X; worldMatrix[5] = transformBasisY.Y; worldMatrix[6] = transformBasisY.Z; worldMatrix[7] = 0.0; worldMatrix[8] = transformBasisZ.X; worldMatrix[9] = transformBasisZ.Y; worldMatrix[10] = transformBasisZ.Z; worldMatrix[11] = 0.0; worldMatrix[12] = transformOrigin.X; worldMatrix[13] = transformOrigin.Y; worldMatrix[14] = transformOrigin.Z; worldMatrix[15] = 1.0; // Set the world transform of the Datasmith actor. IOActor.SetWorldTransform(worldMatrix); } /// /// Combine whole element stack in the document hierarchy to build unique path to the element in the document /// public string GetElementStackName() { return string.Join(", ", ElementDataStack.Select(Data => $"{Data.CurrentElement.UniqueId}")); } } }