2654 lines
86 KiB
C#
2654 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;
|
|
}
|
|
|
|
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<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)
|
|
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<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();
|
|
}
|
|
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<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
|
|
)
|
|
{
|
|
Context.LogDebug("AddParentElementHierarchy");
|
|
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
|
|
{
|
|
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<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}"));
|
|
}
|
|
|
|
}
|
|
}
|