/////////////////////////////////////////////////////////////////////////////// // Copyright (C) 2002-2025, Open Design Alliance (the "Alliance"). // All rights reserved. // // This software and its documentation and related materials are owned by // the Alliance. The software may only be incorporated into application // programs owned by members of the Alliance, subject to a signed // Membership Agreement and Supplemental Software License Agreement with the // Alliance. The structure and organization of this software are the valuable // trade secrets of the Alliance and its suppliers. The software is also // protected by copyright law and international treaty provisions. Application // programs incorporating this software must include the following statement // with their copyright notices: // // This application incorporates Open Design Alliance software pursuant to a license // agreement with Open Design Alliance. // Open Design Alliance Copyright (C) 2002-2025 by Open Design Alliance. // All rights reserved. // // By use of this software, its documentation or related materials, you // acknowledge and accept the above terms. /////////////////////////////////////////////////////////////////////////////// #include "IfcEntity.h" #include "IfcFile.h" #include "Ge/GePlane.h" #include "Ge/GePolyline3d.h" #include "IfcFaceLoopsOrientationValidationTask.h" using namespace OdDAI; using namespace OdIfc; ODRX_VALIDATION_CONS_DEFINE_MEMBERS(FaceLoopsOrientationValidationTask, OdDAI::ExtentValidationTask, RXIMPL_CONSTR); ODRX_CONS_DEFINE_MEMBERS(FaceLoopsOrientationValidationHealer, OdDAI::ValidationHealer, RXIMPL_CONSTR); namespace { struct LoopSignedArea { OdDAIObjectId idFaceBound; OdDAIObjectId idLoop; double signedArea; }; double loopDirection(OdIfcInstancePtr ifcLoop) { OdIfcFilePtr pFile = ifcLoop->owningStepFile(); OdDAIObjectIds polygon; if (!(ifcLoop->getAttr(kPolygon) >> polygon)) { //IFC_ERROR_REPORT(ifcLoop, GETATTR_FAIL); return eInvalidInput; } OdGePoint3dArray pts; for (const auto& pPntIt : polygon) { if (pPntIt.isValid()) { OdGePoint3d v3d = *OdIfcInstance::asPoint3d(pFile->get(pPntIt)); pts.append(v3d); } } OdGePolyline3d polyline3d(pts); OdGePlane plane; if (polyline3d.isPlanar(plane)) { OdGePoint3d origin; OdGeVector3d xAxis; OdGeVector3d yAxis; OdGeVector3d zAxis; plane.getCoordSystem(origin, xAxis, yAxis); OdGeMatrix3d planeInvMatrix; planeInvMatrix.setCoordSystem(origin, xAxis, yAxis, xAxis.crossProduct(yAxis)); planeInvMatrix.invert(); polyline3d.transformBy(planeInvMatrix); OdGePoint2dArray pts2d; for (auto& pt : pts) { pt.transformBy(planeInvMatrix); if (OdZero(pt.z, 1e-9) == false) return 0.; // Not plain loop pts2d.append(pt.convert2d()); } double signedArea = getSignedArea(pts2d); return signedArea; } return 0.; } std::vector collectSignedAreas(OdDAIObjectIds bounds) { std::vector loopSignedAreaMap; for (OdUInt32 loopNum = 0; loopNum < bounds.size(); ++loopNum) { OdDAIObjectId idBound = bounds.at(loopNum); OdIfc::OdIfcInstancePtr loop = idBound.openObject(); OdDAIObjectId idIfcLoop; if (loop->getAttr(kBound) >> idIfcLoop) { OdIfc::OdIfcInstancePtr ifcLoop = idIfcLoop.openObject(); if (ifcLoop->type() == kIfcPolyLoop) { double signedArea = loopDirection(ifcLoop); if (!OdZero(signedArea)) loopSignedAreaMap.push_back({ idBound, idIfcLoop, signedArea }); } } else { ODA_FAIL_M("Can not get IfcLoop from bound."); } } return loopSignedAreaMap; } } FaceLoopsOrientationValidationTask::FaceLoopsOrientationValidationTask() { m_extentName = "IfcFace"; } OdDAI::Logical FaceLoopsOrientationValidationTask::validate(OdDAI::InstanceValidationContext* pInstanceCtx, OdSharedPtr& invalidParams) { InvalidRxArrayValidationParams* invalidInstances = new InvalidRxArrayValidationParams(); invalidParams = invalidInstances; OdDAI::InstanceValidationContext* pIfcCtx = dynamic_cast(pInstanceCtx); if (pIfcCtx == nullptr) { return Logical::False; } OdIfc::OdIfcInstancePtr ifcFace = pIfcCtx->pInstance; OdDAIObjectIds bounds; if (!(ifcFace->getAttr(kBounds) >> bounds)) { return Logical::Unset; } unsigned int nBounds = bounds.size(); if (nBounds == 1) { return Logical::True; } OdDAI::Boolean res{ OdDAI::Boolean::True }; std::vector loopSignedAreaMap = collectSignedAreas(bounds); if (loopSignedAreaMap.size() > 1) { OdIfcFilePtr ifcFile = ifcFace->owningStepFile(); ifcFile->getModel(sdaiRW); double firstSignedArea = loopSignedAreaMap.at(0).signedArea; OdIfc::OdIfcInstancePtr firstBound = loopSignedAreaMap.at(0).idFaceBound.openObject(); OdDAI::Boolean firstBoundOrientation; firstBound->getAttr("orientation") >> firstBoundOrientation; /*if (firstBoundOrientation == OdDAI::Boolean::False) { if (firstSignedArea < 0.) { firstBound->putAttr("orientation", false); } if (firstSignedArea > 0.) { firstBound->putAttr("orientation", true); } }*/ for (size_t i = 1; i < loopSignedAreaMap.size(); ++i) { if ((loopSignedAreaMap.at(i).signedArea * firstSignedArea) > 0) // Same direction { OdIfc::OdIfcInstancePtr boundToReverse = loopSignedAreaMap.at(i).idFaceBound.openObject(); OdDAI::Boolean orientation; boundToReverse->getAttr("orientation") >> orientation; if (orientation.exists() == false) orientation = OdDAI::Boolean::False; if (firstBoundOrientation == orientation) { //if (orientation == OdDAI::Boolean::True) //boundToReverse->putAttr("orientation", false); //else //boundToReverse->putAttr("orientation", true); res = OdDAI::Boolean::False; } } } if (res == OdDAI::Boolean::False) invalidInstances->addData(InvalidRxObjectsValidationParams({ ifcFace }, "Two or more planar face bounds with same direction (signed area).", Logical::False)); } return res; } OdAnsiString FaceLoopsOrientationValidationTask::description() const { return OdAnsiString("IfcLoop bounds orientation validation."); } OdDAI::Logical FaceLoopsOrientationValidationHealer::heal(OdDAI::Model* model, OdDAI::ValidationTask::InvalidValidationParamsBase* invalidParams) { OdDAI::ValidationTask::InvalidRxObjectsValidationParams* rxObjectsParams = dynamic_cast(invalidParams); for (const auto& inst : rxObjectsParams->invalidObjects) { OdIfc::OdIfcInstancePtr ifcFace = inst; OdDAIObjectIds bounds; if (!(ifcFace->getAttr(kBounds) >> bounds)) { return Logical::Unset; } unsigned int nBounds = bounds.size(); if (nBounds == 1) { return Logical::True; } OdDAI::Boolean res{ OdDAI::Boolean::True }; std::vector loopSignedAreaMap = collectSignedAreas(bounds); if (loopSignedAreaMap.size() > 1) { OdIfcFilePtr ifcFile = ifcFace->owningStepFile(); ifcFile->getModel(sdaiRW); double firstSignedArea = loopSignedAreaMap.at(0).signedArea; OdIfc::OdIfcInstancePtr firstBound = loopSignedAreaMap.at(0).idFaceBound.openObject(); OdDAI::Boolean firstBoundOrientation; firstBound->getAttr("orientation") >> firstBoundOrientation; for (size_t i = 1; i < loopSignedAreaMap.size(); ++i) { if ((loopSignedAreaMap.at(i).signedArea * firstSignedArea) > 0) // Same direction { OdIfc::OdIfcInstancePtr boundToReverse = loopSignedAreaMap.at(i).idFaceBound.openObject(); OdDAI::Boolean orientation; boundToReverse->getAttr("orientation") >> orientation; if (orientation.exists() == false) orientation = OdDAI::Boolean::False; if (firstBoundOrientation == orientation) { if (orientation == OdDAI::Boolean::True) boundToReverse->putAttr("orientation", false); else boundToReverse->putAttr("orientation", true); } } } } } return OdDAI::Logical::True; }