Files
UnrealEngineUWP/Engine/Source/Programs/Enterprise/Datasmith/DatasmithRevitExporter/Private/DatasmithRevitDocumentData.cs
kerim borchaev c99ece33ff [Revit] Stable AND unique actor names
- Actor name for a Revit element is now composed from the 'path' through whole hierarchy of Revit elements(including linked documents). This makes every element that occurs multiple times in the scene(e.g. in Linked docs) to have different actor name

- This makes it unique(path to element occurrence is unique).

- This 'path' is stable because of not using any 'unstable' elements(like temporary geometry symbols created by Revit during export - only elements UniqueIds)

- additional: debug output for scene parsing(compile-time enabled using DatasmithRevitDebugOutput symbol)

#rb benoit.deschenes
#jira UE-169815
#preflight 6380f92b3377450900872239
#lockdown jeanmichel.dignard

[CL 23300209 by kerim borchaev in ue5-main branch]
2022-11-28 19:14:00 -05:00

2656 lines
86 KiB
C#

// 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<FBaseElementData, Element> ElementDataToElementMap = new Dictionary<FBaseElementData, Element>();
private Dictionary<ElementId, FBaseElementData> ElementIdToElementDataMap = new Dictionary<ElementId, FBaseElementData>();
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<XYZ> Vertices = new List<XYZ>();
public List<XYZ> Normals = new List<XYZ>();
public List<FPolymeshFace> Faces = new List<FPolymeshFace>();
public List<UV> UVs = new List<UV>();
}
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<string, int> MeshMaterialsMap = new Dictionary<string, int>();
public Transform WorldTransform;
public List<FBaseElementData> ChildElements = new List<FBaseElementData>();
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<FBaseElementData> 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<FBaseElementData> InstanceDataStack = new Stack<FBaseElementData>();
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<Curve> 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<Curve> 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<Mesh> GeometryObjectList = new List<Mesh>();
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;
}
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<string, Tuple<FDatasmithFacadeMeshElement, Task<bool>>>
MeshMap = new Dictionary<string, Tuple<FDatasmithFacadeMeshElement, Task<bool>>>();
public Dictionary<ElementId, FBaseElementData> ActorMap = new Dictionary<ElementId, FDocumentData.FBaseElementData>();
public Dictionary<string, FMaterialData> MaterialDataMap = null;
public Dictionary<string, FMaterialData> NewMaterialsMap = new Dictionary<string, FMaterialData>();
public Dictionary<ElementId, FElementData> DecalElementsMap = new Dictionary<ElementId, FElementData>();
public Dictionary<ElementId, FDecalMaterial> DecalMaterialsMap = new Dictionary<ElementId, FDecalMaterial>();
private Stack<FElementData> ElementDataStack = new Stack<FElementData>();
private string CurrentMaterialName = null;
private List<string> 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<string> 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<string, Tuple<FDatasmithFacadeMeshElement, Task<bool>>>();
ActorMap = new Dictionary<ElementId, FBaseElementData>();
MaterialDataMap = null;
NewMaterialsMap = new Dictionary<string, FMaterialData>();
DecalElementsMap = new Dictionary<ElementId, FElementData>();
DecalMaterialsMap = new Dictionary<ElementId, FDecalMaterial>();
ElementDataStack = new Stack<FElementData>();
CurrentMaterialName = null;
InsertionPoint = FSettings.EInsertionPoint.Default;
ProjectSurveyPoint = null;
ProjectBasePoint = null;
SectionBoxOutline = null;
SectionBox = null;
if (DirectLink != null)
{
MaterialDataMap = DirectLink.MaterialDataMap;
}
else
{
MaterialDataMap = new Dictionary<string, FMaterialData>();
}
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)
IList<ElementId> DependentElements = InElement.GetDependentElements(null);
foreach (ElementId DependentElementId in DependentElements)
{
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<FBaseElementData> ChildrenToRemove = new List<FBaseElementData>();
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();
if (ElementData.bIsDecalElement)
{
if (!DecalElementsMap.ContainsKey(InElement.Id))
{
DecalElementsMap.Add(InElement.Id, ElementData);
}
}
}
else
{
ActorMap[InElement.Id] = ElementData;
return false; // We have up to date cache for this element.
}
}
else
{
ElementData = new FElementData(InElement, InWorldTransform, this);
if (ElementData.bIsDecalElement)
{
DecalElementsMap.Add(InElement.Id, ElementData);
}
}
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);
}
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<string> InExtraTexturePaths
)
{
Material CurrentMaterial = GetElement(InMaterialNode.MaterialId) as Material;
if (InMaterialNode.HasOverriddenAppearance)
{
IList<KeyValuePair<ElementId, Asset>> 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<FDecalMaterial> DecalMaterials = new List<FDecalMaterial>();
foreach (KeyValuePair<ElementId, Asset> 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<FBaseElementData> RemoveChildren = new List<FBaseElementData>();
List<FBaseElementData> AddChildren = new List<FBaseElementData>();
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<string> 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<string> 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<ElementId> OptimizedAwayElements = new List<ElementId>();
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<Element> 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<FDatasmithFacadeMeshElement, Task<bool>>(InMeshElement, Task.Run<bool>(() => 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<FDatasmithFacadeMeshElement, Task<bool>>(InMeshElement, Task.Run<bool>(
() =>
{
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<ElementId, FDecalMaterial> 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<string> 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<ElementId, Element> InGetParentElement
)
{
Queue<ElementId> ElementIdQueue = new Queue<ElementId>(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
{
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<ElementId>(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<ElementId>(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);
}
/// <summary>
/// Combine whole element stack in the document hierarchy to build unique path to the element in the document
/// </summary>
public string GetElementStackName()
{
return string.Join(", ", ElementDataStack.Select(Data => $"{Data.CurrentElement.UniqueId}"));
}
}
}