��diff --git a/Xbim.Geometry.Engine/XbimSolid.cpp b/Xbim.Geometry.Engine/XbimSolid.cpp index f61cd5cfe..147fa709e 100644 --- a/Xbim.Geometry.Engine/XbimSolid.cpp +++ b/Xbim.Geometry.Engine/XbimSolid.cpp @@ -75,6 +75,7 @@ #include <BRepBuilderAPI_Sewing.hxx> #include <Geom_Plane.hxx> #include <GeomAPI_ProjectPointOnSurf.hxx> +#include <GeomLib_Tool.hxx> #include <GC_MakeArcOfCircle.hxx> #include <BRepAdaptor_CompCurve.hxx> #include <Geom_SurfaceOfLinearExtrusion.hxx> @@ -82,6 +83,7 @@ #include <ShapeUpgrade_UnifySameDomain.hxx> #include <BRepGProp_Face.hxx> #include <BRepAlgo_Section.hxx> +#include <BRepBuilderAPI_MakePolygon.hxx> #include <GeomProjLib.hxx> #include <BRepOffsetAPI_NormalProjection.hxx> #include <TopTools_Array1OfShape.hxx> @@ -1504,6 +1506,231 @@ namespace Xbim return 1; } + TColgp_Array1OfPnt GetIndexedPolyCurvePointsArray(IIfcIndexedPolyCurve^ polyCurve, ILogger^ logger) + { + IItemSet<IItemSet<Ifc4::MeasureResource::IfcLengthMeasure>^>^ coordList; + IIfcCartesianPointList3D^ points3D = dynamic_cast<IIfcCartesianPointList3D^>(polyCurve->Points); + IIfcCartesianPointList2D^ points2D = dynamic_cast<IIfcCartesianPointList2D^>(polyCurve->Points); + int dim; + if (points3D != nullptr) + { + coordList = points3D->CoordList; + dim = 3; + } + else if (points2D != nullptr) + { + coordList = points2D->CoordList; + dim = 2; + } + else + { + XbimGeometryCreator::LogError(logger, polyCurve, "Unsupported type of Coordinate List"); + return TColgp_Array1OfPnt(); + } + + int pointCount = coordList->Count; + TColgp_Array1OfPnt poles(1, pointCount); + int n = 1; + for each (IItemSet<Ifc4::MeasureResource::IfcLengthMeasure> ^ coll in coordList) + { + IEnumerator<Ifc4::MeasureResource::IfcLengthMeasure>^ enumer = coll->GetEnumerator(); + enumer->MoveNext(); + gp_Pnt p; + p.SetX((double)enumer->Current); + enumer->MoveNext(); + p.SetY((double)enumer->Current); + if (dim == 3) + { + enumer->MoveNext(); + p.SetZ((double)enumer->Current); + + } + else + p.SetZ(0); + poles.SetValue(n, p); + n++; + } + + return poles; + } + + void AppendSegmentsLengthsFromPolylinePoints( + List<System::ValueTuple<double, double>>^ resultLengths, + TColgp_SequenceOfPnt pointSeq, + IIfcIndexedPolyCurve^ polyCurve, + ILogger^ logger) + { + double tolerance = polyCurve->Model->ModelFactors->Precision; + + int originalCount = pointSeq.Length(); + + bool isClosed = XbimFace::RemoveDuplicatePoints(pointSeq, false, tolerance); //don't assume it is closed + + if (pointSeq.Length() != originalCount) + { + XbimGeometryCreator::LogInfo(logger, polyCurve, "IfcIndexedPolyCurve with duplicate points. Duplicate has been removed"); + } + + if (pointSeq.Length() < 2) + { + XbimGeometryCreator::LogWarning(logger, polyCurve, "IfcIndexedPolyCurve with less than 2 points is an empty line. It has been ignored"); + return; + } + + for (int i = 1; i <= pointSeq.Length() - 1; ++i) + { + gp_Pnt start = pointSeq.Value(i); + gp_Pnt end = pointSeq.Value(i + 1); + + BRepBuilderAPI_MakePolygon polyMaker; + polyMaker.Add(start); + polyMaker.Add(end); + if (polyMaker.IsDone()) + { + auto wire = polyMaker.Wire(); + double occLength = (gcnew XbimWire(wire))->Length; + double ifcLength = 1.0; + resultLengths->Add(System::ValueTuple<double, double>(occLength, ifcLength)); + } + else + { + // failed to build edge, use 1.0 as occLength + resultLengths->Add(System::ValueTuple<double, double>(1.0, 1.0)); + } + } + + if (isClosed) + { + gp_Pnt start = pointSeq.Last(); + gp_Pnt end = pointSeq.First(); + + BRepBuilderAPI_MakePolygon polyMaker; + polyMaker.Add(start); + polyMaker.Add(end); + + if (polyMaker.IsDone()) + { + auto wire = polyMaker.Wire(); + double occLength = (gcnew XbimWire(wire))->Length; + double ifcLength = 1.0; + resultLengths->Add(System::ValueTuple<double, double>(occLength, ifcLength)); + } + else + { + // failed to build edge, most likely points coincide + } + } + } + + /// <summary> + /// Computes parameter lengths of polycurve segments. Returns collections of length pairs with first being OCC length and second IFC length. + /// </summary> + List<System::ValueTuple<double, double>>^ OccIndexedPolyCurveSegmentsLengths(IIfcIndexedPolyCurve^ polyCurve, ILogger^ logger) + { + auto resultLengths = gcnew List<System::ValueTuple<double, double>>(); + + auto poles = GetIndexedPolyCurvePointsArray(polyCurve, logger); + + if (poles.Length() < 2) + { + XbimGeometryCreator::LogWarning(logger, polyCurve, "There should be at least two indices in an IfcIndexedPolyCurve"); + return nullptr; + } + + double tolerance = polyCurve->Model->ModelFactors->Precision; + + if (Enumerable::Any(polyCurve->Segments)) + { + for each (IIfcSegmentIndexSelect ^ segment in polyCurve->Segments) + { + Ifc4::GeometryResource::IfcArcIndex^ arcIndex = dynamic_cast<Ifc4::GeometryResource::IfcArcIndex^>(segment); + Ifc4::GeometryResource::IfcLineIndex^ lineIndex = dynamic_cast<Ifc4::GeometryResource::IfcLineIndex^>(segment); + if (arcIndex != nullptr) + { + List<Ifc4::MeasureResource::IfcPositiveInteger>^ indices = (List<Ifc4::MeasureResource::IfcPositiveInteger>^)arcIndex->Value; + if (indices->Count != 3) + { + XbimGeometryCreator::LogWarning(logger, segment, "There should be three indices in an arc segment"); + continue; + } + + gp_Pnt start = poles.Value((int)indices[0]); + gp_Pnt mid = poles.Value((int)indices[1]); + gp_Pnt end = poles.Value((int)indices[2]); + GC_MakeCircle circleMaker(start, mid, end); + + if (circleMaker.IsDone()) //it is a valid arc + { + const Handle(Geom_Circle)& curve = circleMaker.Value(); + double u1, u2; + GeomLib_Tool::Parameter(curve, start, tolerance, u1); + GeomLib_Tool::Parameter(curve, end, tolerance, u2); + + double occLengthRadians = Math::Min(u2 - u1, M_PI * 2); + if (occLengthRadians < 0) + { + XbimGeometryCreator::LogWarning(logger, segment, "Negative arc segment length value. Using absolute value."); + occLengthRadians = Math::Abs(occLengthRadians); + } + + double radiansToAngle = 1.0 / polyCurve->Model->ModelFactors->AngleToRadiansConversionFactor; + double occLength = occLengthRadians * radiansToAngle; // OCC circle parameters are in radians, and in IFC it depends on model plane angle measurement unit + double ifcLength = occLength; // arcs are parameterized the same way in IFC and OCC + resultLengths->Add(System::ValueTuple<double, double>(occLength, ifcLength)); + } + else //most likley the three points are in a line it should be treated as a polyline segment according the the docs + { + TColgp_SequenceOfPnt pointSeq; + pointSeq.Append(start); + pointSeq.Append(end); + + AppendSegmentsLengthsFromPolylinePoints(resultLengths, pointSeq, polyCurve, logger); + } + } + else if (lineIndex != nullptr) + { + List<Ifc4::MeasureResource::IfcPositiveInteger>^ indices = (List<Ifc4::MeasureResource::IfcPositiveInteger>^)lineIndex->Value; + if (indices->Count < 2) + { + XbimGeometryCreator::LogWarning(logger, segment, "There should be at least two indices in an line index segment"); + continue; + } + + TColgp_SequenceOfPnt pointSeq; + for (Standard_Integer p = 1; p <= indices->Count; p++) + { + pointSeq.Append(poles.Value((int)indices[p - 1])); + } + + AppendSegmentsLengthsFromPolylinePoints(resultLengths, pointSeq, polyCurve, logger); + } + else + { + //most probably the start and end are the same point + XbimGeometryCreator::LogWarning(logger, segment, "Could not create line index segment as a polyline to IfcIndexedPolyCurve"); + } + } + } + else + { + // To be compliant with: + // "In the case that the list of Segments is not provided, all points in the IfcCartesianPointList are connected by straight line segments in the order they appear in the IfcCartesianPointList." + // http://www.buildingsmart-tech.org/ifc/IFC4/Add1/html/schema/ifcgeometryresource/lexical/ifcindexedpolycurve.htm + + int originalCount = poles.Length(); + + TColgp_SequenceOfPnt pointSeq; + for (Standard_Integer p = 1; p <= originalCount; p++) + { + pointSeq.Append(poles.Value(p)); + } + + AppendSegmentsLengthsFromPolylinePoints(resultLengths, pointSeq, polyCurve, logger); + } + + return resultLengths; + } + void XbimSolid::Init(IIfcSweptDiskSolid^ repItem, ILogger^ logger) { @@ -1733,6 +1960,57 @@ namespace Xbim else return wire; } + else if (dynamic_cast<IIfcIndexedPolyCurve^>(basisCurve)) + { + if (startParam.HasValue) + startPar = startParam.Value; + if (endParam.HasValue) + endPar = endParam.Value; + + double occStart = 0; + double occEnd = 0; + double totCurveLen = 0; + + IIfcIndexedPolyCurve^ polyCurve = (IIfcIndexedPolyCurve^)basisCurve; + auto lengths = OccIndexedPolyCurveSegmentsLengths(polyCurve, logger); + if (lengths == nullptr) + { + // failed to calculate segments lengths, use default behaviour + return (XbimWire^)wire->Trim(startPar, endPar, directrix->Model->ModelFactors->Precision, logger); + } + + for each (auto segment in lengths) + { + double occSegmentLen = segment.Item1; // this is the length to add to the OCC command if we use all of the segment + double ifcSegmentLen = segment.Item2; // this is the IFC size of the segment + totCurveLen += occSegmentLen; + + if (startPar > 0) + { + double ratio = Math::Min(startPar / ifcSegmentLen, 1.0); + startPar -= ratio * ifcSegmentLen; // reduce the outstanding amount (since it's been accounted for in the segment just processed) + occStart += ratio * occSegmentLen; // progress the occ amount by the ratio of the lenght + } + + if (endPar > 0) + { + double ratio = Math::Min(endPar / ifcSegmentLen, 1.0); + endPar -= ratio * ifcSegmentLen; // reduce the outstanding amount (since it's been accounted for in the segment just processed) + occEnd += ratio * occSegmentLen; // progress the occ amount by the ratio of the lenght + } + } + + double precision = directrix->Model->ModelFactors->Precision; + // only trim if needed either from start or end + if ((occStart > 0 && Math::Abs(occStart - 0.0) > precision) || (occEnd < totCurveLen && Math::Abs(occEnd - totCurveLen) > precision)) + { + return (XbimWire^)wire->Trim(occStart, occEnd, precision, logger); + } + else + { + return wire; + } + } else if (dynamic_cast<IIfcPolyline^>(basisCurve) && (double)startParam.Value == 0. && (double)endParam.Value == 1. && @@ -1777,8 +2055,6 @@ namespace Xbim return (XbimWire^)wire->Trim(startPar, endPar, directrix->Model->ModelFactors->Precision, logger); } - - void XbimSolid::Init(IIfcBoundingBox^ box, ILogger^ /*logger*/) { double precision = box->Model->ModelFactors->Precision;