/// <summary> /// Returns a handle for creation of an AdvancedBrep with AdvancedFace and assigns it to the file /// </summary> /// <param name="exporterIFC">exporter IFC</param> /// <param name="element">the element</param> /// <param name="options">exporter option</param> /// <param name="geomObject">the geometry object</param> /// <returns>the handle</returns> public static IFCAnyHandle ExportBodyAsAdvancedBrep(ExporterIFC exporterIFC, Element element, BodyExporterOptions options, GeometryObject geomObject) { IFCFile file = exporterIFC.GetFile(); Document document = element.Document; // IFCFuzzyXYZ will be used in this routine to compare 2 XYZs, we consider two points are the same if their distance // is within this tolerance IFCFuzzyXYZ.IFCFuzzyXYZEpsilon = document.Application.VertexTolerance; IFCAnyHandle advancedBrep = null; using (IFCTransaction tr = new IFCTransaction(file)) { try { if (!(geomObject is Solid)) { return null; } HashSet<IFCAnyHandle> cfsFaces = new HashSet<IFCAnyHandle>(); Solid geomSolid = geomObject as Solid; FaceArray faces = geomSolid.Faces; // Check for supported curve and face types before creating an advanced BRep. IList<KeyValuePair<Edge, Curve>> edgesAndCurves = new List<KeyValuePair<Edge, Curve>>(); foreach (Edge edge in geomSolid.Edges) { Curve currCurve = edge.AsCurve(); if (currCurve == null) return null; bool isValidCurve = !(currCurve is CylindricalHelix); if (!isValidCurve) return null; // based on the definition of IfcAdvancedBrep in IFC 4 specification, an IfcAdvancedBrep must contain a closed shell, so we // have a test to reject all open shells here. // we check that geomSolid is an actual solid and not an open shell by verifying that each edge is shared by exactly 2 faces. for (int ii = 0; ii < 2; ii++) { if (edge.GetFace(ii) == null) return null; } edgesAndCurves.Add(new KeyValuePair<Edge, Curve>(edge, currCurve)); } foreach (Face face in geomSolid.Faces) { bool isValidFace = (face is PlanarFace) || (face is CylindricalFace) || (face is RuledFace) || (face is HermiteFace) || (face is RevolvedFace) || (face is ConicalFace); if (!isValidFace) { return null; } } Dictionary<Face, IList<Edge>> faceToEdges = new Dictionary<Face, IList<Edge>>(); Dictionary<Edge, IFCAnyHandle> edgeToIfcEdgeCurve = new Dictionary<Edge, IFCAnyHandle>(); // A map of already created IfcCartesianPoints, to avoid duplication. This is used for vertex points and other geometry in the BRep. // We do not share IfcCartesianPoints across BReps. IDictionary<IFCFuzzyXYZ, IFCAnyHandle> cartesianPoints = new SortedDictionary<IFCFuzzyXYZ, IFCAnyHandle>(); // A map of already created IfcVertexPoints, to avoid duplication. IDictionary<IFCFuzzyXYZ, IFCAnyHandle> vertices = new SortedDictionary<IFCFuzzyXYZ, IFCAnyHandle>(); // First phase: get all the vertices: foreach (KeyValuePair<Edge, Curve> edgeAndCurve in edgesAndCurves) { Edge edge = edgeAndCurve.Key; Curve currCurve = edgeAndCurve.Value; XYZ startPoint = currCurve.GetEndPoint(0); XYZ endPoint = currCurve.GetEndPoint(1); IFCFuzzyXYZ fuzzyStartPoint = new IFCFuzzyXYZ(startPoint); IFCFuzzyXYZ fuzzyEndPoint = new IFCFuzzyXYZ(endPoint); IFCAnyHandle edgeStart = null; IFCAnyHandle edgeEnd = null; if (vertices.ContainsKey(fuzzyStartPoint)) { edgeStart = vertices[fuzzyStartPoint]; } else { IFCAnyHandle edgeStartCP = GeometryUtil.XYZtoIfcCartesianPoint(exporterIFC, currCurve.GetEndPoint(0), cartesianPoints); edgeStart = IFCInstanceExporter.CreateVertexPoint(file, edgeStartCP); vertices.Add(fuzzyStartPoint, edgeStart); } if (vertices.ContainsKey(fuzzyEndPoint)) { edgeEnd = vertices[fuzzyEndPoint]; } else { IFCAnyHandle edgeEndCP = GeometryUtil.XYZtoIfcCartesianPoint(exporterIFC, currCurve.GetEndPoint(1), cartesianPoints); edgeEnd = IFCInstanceExporter.CreateVertexPoint(file, edgeEndCP); vertices.Add(fuzzyEndPoint, edgeEnd); } IFCAnyHandle edgeCurve = CreateEdgeCurveFromCurve(file, exporterIFC, currCurve, edgeStart, edgeEnd, true, cartesianPoints); edgeToIfcEdgeCurve.Add(edge, edgeCurve); Face face = null; for (int ii = 0; ii < 2; ii++) { face = edge.GetFace(ii); if (!faceToEdges.ContainsKey(face)) { faceToEdges.Add(face, new List<Edge>()); } IList<Edge> edges = faceToEdges[face]; edges.Add(edge); } } // Second phase: create IfcFaceOuterBound, IfcFaceInnerBound, IfcAdvancedFace and IfcAdvancedBrep foreach (Face face in geomSolid.Faces) { // List of created IfcEdgeLoops of this face IList<IFCAnyHandle> edgeLoopList = new List<IFCAnyHandle>(); // List of created IfcOrientedEdge in one loop IList<IFCAnyHandle> orientedEdgeList = new List<IFCAnyHandle>(); IFCAnyHandle surface = null; IList<HashSet<IFCAnyHandle>> boundsCollection = new List<HashSet<IFCAnyHandle>>(); // calculate sameSense by getting a point on the surface and compute the normal of the face and the surface at that point // if these two vectors agree, then sameSense is true. // The point we use to test will be the start point of the first edge in the first loop of this face bool sameSenseAF = true; EdgeArrayArray faceEdgeLoops = face.EdgeLoops; if (faceEdgeLoops == null || faceEdgeLoops.Size == 0) { return null; } EdgeArray firstEdgeLoop = faceEdgeLoops.get_Item(0); if (firstEdgeLoop == null) { return null; } Edge firstEdge = firstEdgeLoop.get_Item(0); UV pointToTest = firstEdge.EvaluateOnFace(0, face); // Compute the normal of the FACE at pointToTest XYZ faceNormal = face.ComputeNormal(pointToTest); // Compute the normal of the SURFACE at pointToTest Transform testPointDerivatives = face.ComputeDerivatives(pointToTest); XYZ surfaceNormal = testPointDerivatives.BasisZ; if (!faceNormal.IsAlmostEqualTo(surfaceNormal)) { sameSenseAF = false; } Dictionary<EdgeArray, IList<EdgeArray>> sortedEdgeLoop = GeometryUtil.SortEdgeLoop(faceEdgeLoops, face); // check that we get back the same number of edgeloop int numberOfSortedEdgeLoop = 0; foreach (KeyValuePair<EdgeArray, IList<EdgeArray>> pair in sortedEdgeLoop) { numberOfSortedEdgeLoop += 1 + pair.Value.Count; } if (numberOfSortedEdgeLoop != face.EdgeLoops.Size) { return null; } foreach (KeyValuePair<EdgeArray, IList<EdgeArray>> pair in sortedEdgeLoop) { if (pair.Key == null || pair.Value == null) return null; HashSet<IFCAnyHandle> bounds = new HashSet<IFCAnyHandle>(); // Append the outerloop at the beginning of the list of inner loop pair.Value.Insert(0, pair.Key); // Process each inner loop foreach (EdgeArray edgeArray in pair.Value) { // Map each edge in this loop back to its corresponding edge curve and then calculate its orientation to create IfcOrientedEdge foreach (Edge edge in edgeArray) { // The reason why edgeToIfcEdgeCurve cannot find edge is that either we haven't created the IfcOrientedEdge // corresponding to that edge OR we already have but the dictionary cannot find the edge as its key because // Face.EdgeLoop and geomSolid.Edges return different pointers for the same edge. This can be avoided if // Equals() method is implemented for Edge if (!edgeToIfcEdgeCurve.ContainsKey(edge)) return null; IFCAnyHandle edgeCurve = edgeToIfcEdgeCurve[edge]; Curve currCurve = edge.AsCurve(); Curve curveInCurrentFace = edge.AsCurveFollowingFace(face); if (currCurve == null || curveInCurrentFace == null) return null; // if the curve length is 0, ignore it. if (MathUtil.IsAlmostZero(currCurve.ApproximateLength)) continue; // if the curve is unbound, it means that the solid may be corrupted, we shouldn't process it anymore if (!currCurve.IsBound) return null; bool orientation = currCurve.GetEndPoint(0).IsAlmostEqualTo(curveInCurrentFace.GetEndPoint(0)); IFCAnyHandle orientedEdge = IFCInstanceExporter.CreateOrientedEdge(file, edgeCurve, orientation); orientedEdgeList.Add(orientedEdge); } IFCAnyHandle edgeLoop = IFCInstanceExporter.CreateEdgeLoop(file, orientedEdgeList); edgeLoopList.Add(edgeLoop); IFCAnyHandle faceBound = null; // EdgeLoopList has only 1 element indicates that this is the outer loop if (edgeLoopList.Count == 1) faceBound = IFCInstanceExporter.CreateFaceOuterBound(file, edgeLoop, true); else faceBound = IFCInstanceExporter.CreateFaceBound(file, edgeLoop, false); bounds.Add(faceBound); // After finishing processing one loop, clear orientedEdgeList orientedEdgeList.Clear(); } boundsCollection.Add(bounds); } edgeLoopList.Clear(); // TODO: create a new face processing method to factor out this code // process the face now if (face is PlanarFace) { PlanarFace plFace = face as PlanarFace; IFCAnyHandle location = GeometryUtil.XYZtoIfcCartesianPoint(exporterIFC, plFace.Origin, cartesianPoints); // Since there is no easy way to get the surface normal, we will calculate the face's normal and negate it if we know // the surface normal doesn't agree with the face normal based on the sameSense attribute that we calculate above XYZ zdir = plFace.FaceNormal; if (!sameSenseAF) zdir = zdir.Negate(); XYZ xdir = plFace.XVector; IFCAnyHandle position = CreatePositionForFace(exporterIFC, location, zdir, xdir); surface = IFCInstanceExporter.CreatePlane(file, position); } else if (face is CylindricalFace) { CylindricalFace cylFace = face as CylindricalFace; IFCAnyHandle location = GeometryUtil.XYZtoIfcCartesianPoint(exporterIFC, cylFace.Origin, cartesianPoints); // get radius-x and radius-y and the position of the origin XYZ rad = UnitUtil.ScaleLength(cylFace.get_Radius(0)); double radius = rad.GetLength(); XYZ zdir = cylFace.Axis; XYZ xdir = rad.Normalize(); IFCAnyHandle position = CreatePositionForFace(exporterIFC, location, zdir, xdir); surface = IFCInstanceExporter.CreateCylindricalSurface(file, position, radius); } else if (face is ConicalFace) { ConicalFace conicalFace = face as ConicalFace; IFCAnyHandle location = GeometryUtil.XYZtoIfcCartesianPoint(exporterIFC, conicalFace.Origin, cartesianPoints); XYZ zdir = conicalFace.Axis; if (zdir == null) return null; // Create a finite profile curve for the cone based on the bounding box. BoundingBoxUV coneUV = conicalFace.GetBoundingBox(); if (coneUV == null) return null; XYZ startPoint = conicalFace.Evaluate(new UV(0, coneUV.Min.V)); XYZ endPoint = conicalFace.Evaluate(new UV(0, coneUV.Max.V)); Curve profileCurve = Line.CreateBound(startPoint, endPoint); IFCAnyHandle axis = GeometryUtil.VectorToIfcDirection(exporterIFC, zdir); IFCAnyHandle axisPosition = IFCInstanceExporter.CreateAxis1Placement(file, location, axis); IFCAnyHandle sweptCurve = CreateProfileCurveFromCurve(file, exporterIFC, profileCurve, Resources.ConicalFaceProfileCurve, cartesianPoints); // The profile position is optional in IFC4+. surface = IFCInstanceExporter.CreateSurfaceOfRevolution(file, sweptCurve, null, axisPosition); } else if (face is RevolvedFace) { RevolvedFace revFace = face as RevolvedFace; IFCAnyHandle location = GeometryUtil.XYZtoIfcCartesianPoint(exporterIFC, revFace.Origin, cartesianPoints); XYZ zdir = revFace.Axis; if (zdir == null) return null; // Note that the returned curve is in the coordinate system of the face. Curve curve = revFace.Curve; if (curve == null) return null; // Create arbitrary plane with z direction as normal. Plane arbitraryPlane = new Plane(zdir, XYZ.Zero); Transform revitTransform = Transform.Identity; revitTransform.BasisX = arbitraryPlane.XVec; revitTransform.BasisY = arbitraryPlane.YVec; revitTransform.BasisZ = zdir; revitTransform.Origin = revFace.Origin; Curve profileCurve = curve.CreateTransformed(revitTransform); IFCAnyHandle axis = GeometryUtil.VectorToIfcDirection(exporterIFC, zdir); IFCAnyHandle axisPosition = IFCInstanceExporter.CreateAxis1Placement(file, location, axis); IFCAnyHandle sweptCurve = CreateProfileCurveFromCurve(file, exporterIFC, profileCurve, Resources.RevolvedFaceProfileCurve, cartesianPoints); // The profile position is optional in IFC4+. surface = IFCInstanceExporter.CreateSurfaceOfRevolution(file, sweptCurve, null, axisPosition); } else if (face is RuledFace) { return null; // currently does not support this type of face } else if (face is HermiteFace) { return null; // currently does not support this type of face } else { return null; } // If we had trouble creating a surface, stop trying. if (IFCAnyHandleUtil.IsNullOrHasNoValue(surface)) return null; foreach (HashSet<IFCAnyHandle> faceBound in boundsCollection) { IFCAnyHandle advancedFace = IFCInstanceExporter.CreateAdvancedFace(file, faceBound, surface, sameSenseAF); cfsFaces.Add(advancedFace); } } // create advancedBrep IFCAnyHandle closedShell = IFCInstanceExporter.CreateClosedShell(file, cfsFaces); advancedBrep = IFCInstanceExporter.CreateAdvancedBrep(file, closedShell); if (IFCAnyHandleUtil.IsNullOrHasNoValue(advancedBrep)) tr.RollBack(); else tr.Commit(); return advancedBrep; } catch { return null; } } }
/// <summary> /// Add one loop of vertices that will define a boundary loop of the current face. /// </summary> public void AddLoopVertices(IList<XYZ> loopVertices) { int vertexCount = (loopVertices == null) ? 0 : loopVertices.Count; if (vertexCount < 3) throw new InvalidOperationException("Too few distinct loop vertices, ignoring."); IList<XYZ> adjustedLoopVertices = new List<XYZ>(); IDictionary<IFCFuzzyXYZ, int> createdVertices = new SortedDictionary<IFCFuzzyXYZ, int>(); int numCreated = 0; for (int ii = 0; ii < vertexCount; ii++) { IFCFuzzyXYZ fuzzyXYZ = new IFCFuzzyXYZ(loopVertices[ii]); int createdVertexIndex = -1; if (createdVertices.TryGetValue(fuzzyXYZ, out createdVertexIndex)) { // We will allow the first and last point to be equivalent, or the current and last point. Otherwise we will throw. if (((createdVertexIndex == 0) && (ii == vertexCount - 1)) || (createdVertexIndex == numCreated-1)) continue; throw new InvalidOperationException("Loop is self-intersecting, ignoring."); } XYZ adjustedXYZ; if (!m_TessellatedFaceVertices.TryGetValue(fuzzyXYZ, out adjustedXYZ)) adjustedXYZ = m_TessellatedFaceVertices[fuzzyXYZ] = loopVertices[ii]; adjustedLoopVertices.Add(adjustedXYZ); createdVertices[new IFCFuzzyXYZ(adjustedXYZ)] = numCreated; numCreated++; } // Checking start and end points should be covered above. if (numCreated < 3) throw new InvalidOperationException("Loop has less than 3 distinct vertices, ignoring."); m_TessellatedFaceBoundary.Add(adjustedLoopVertices); }