/// <summary> /// Execute a Boolean operation, and catch the exception. /// </summary> /// <param name="id">The id of the object demanding the Boolean operation.</param> /// <param name="secondId">The id of the object providing the second solid.</param> /// <param name="firstSolid">The first solid parameter to ExecuteBooleanOperation.</param> /// <param name="secondSolid">The second solid parameter to ExecuteBooleanOperation.</param> /// <param name="opType">The Boolean operation type.</param> /// <param name="suggestedShiftDirection">If the Boolean operation fails, a unit vector used to retry with a small shift. Can be null.</param> /// <returns>The result of the Boolean operation, or the first solid if the operation fails.</returns> public static Solid ExecuteSafeBooleanOperation(int id, int secondId, Solid firstSolid, Solid secondSolid, BooleanOperationsType opType, XYZ suggestedShiftDirection) { // Perform default operations if one of the arguments is null. if (firstSolid == null || secondSolid == null) { if (firstSolid == null && secondSolid == null) return null; switch (opType) { case BooleanOperationsType.Union: { if (firstSolid == null) return secondSolid; return firstSolid; } case BooleanOperationsType.Difference: { if (firstSolid == null) return null; return firstSolid; } default: // for .Intersect return null; } } Solid resultSolid = null; try { Solid secondOperand = secondSolid; resultSolid = BooleanOperationsUtils.ExecuteBooleanOperation(firstSolid, secondOperand, opType); } catch (Exception ex) { Importer.TheLog.LogError(id, ex.Message, false); resultSolid = firstSolid; } if (SolidValidator.IsValidGeometry(resultSolid)) return resultSolid; Importer.TheLog.LogError(id, opType.ToString() + " operation failed with void from #" + secondId.ToString(), false); return firstSolid; }
/// <summary> /// Execute a Boolean operation, and catch the exception. /// </summary> /// <param name="id">The id of the object demanding the Boolean operation.</param> /// <param name="secondId">The id of the object providing the second solid.</param> /// <param name="firstSolid">The first solid parameter to ExecuteBooleanOperation.</param> /// <param name="secondSolid">The second solid parameter to ExecuteBooleanOperation.</param> /// <param name="opType">The Boolean operation type.</param> /// <returns>The result of the Boolean operation, or the first solid if the operation fails.</returns> public static Solid ExecuteSafeBooleanOperation(int id, int secondId, Solid firstSolid, Solid secondSolid, BooleanOperationsType opType) { if (firstSolid == null || secondSolid == null) { if (firstSolid == null && secondSolid == null) return null; if (opType == BooleanOperationsType.Union) { if (firstSolid == null) return secondSolid; return firstSolid; } if (opType == BooleanOperationsType.Difference) { if (firstSolid == null) return null; return firstSolid; } // for .Intersect return null; } Solid resultSolid = null; try { resultSolid = BooleanOperationsUtils.ExecuteBooleanOperation(firstSolid, secondSolid, opType); } catch (Exception ex) { IFCImportFile.TheLog.LogError(id, ex.Message, false); resultSolid = firstSolid; } if (SolidValidator.IsValidGeometry(resultSolid)) return resultSolid; IFCImportFile.TheLog.LogError(id, opType.ToString() + " operation failed with void from #" + secondId.ToString(), false); return firstSolid; }
/// <summary> /// Execute a Boolean operation, and catch the exception. /// </summary> /// <param name="id">The id of the object demanding the Boolean operation.</param> /// <param name="secondId">The id of the object providing the second solid.</param> /// <param name="firstSolid">The first solid parameter to ExecuteBooleanOperation.</param> /// <param name="secondSolid">The second solid parameter to ExecuteBooleanOperation.</param> /// <param name="opType">The Boolean operation type.</param> /// <param name="suggestedShiftDirection">If the Boolean operation fails, a unit vector used to retry with a small shift. Can be null.</param> /// <returns>The result of the Boolean operation, or the first solid if the operation fails.</returns> public static Solid ExecuteSafeBooleanOperation(int id, int secondId, Solid firstSolid, Solid secondSolid, BooleanOperationsType opType, XYZ suggestedShiftDirection) { const double footToMillimeter = 1.0 / 304.8; // Perform default operations if one of the arguments is null. if (firstSolid == null || secondSolid == null) { if (firstSolid == null && secondSolid == null) return null; switch (opType) { case BooleanOperationsType.Union: { if (firstSolid == null) return secondSolid; return firstSolid; } case BooleanOperationsType.Difference: { if (firstSolid == null) return null; return firstSolid; } default: // for .Intersect return null; } } Solid resultSolid = null; bool failedAllAttempts = true; // We will attempt to do the Boolean operation 3 times: // 1st pass: With the passed-in arguments. // 2nd pass: With a 1mm shift in a direction in suggestedShiftDirection, or +Z if suggestedShiftDirection is null // 3rd pass: With a 1mm shift in a direction in -suggestedShiftDirection, or -Z if suggestedShiftDirection is null for (int ii = 0; ii < 3; ii++) { try { resultSolid = null; Solid secondOperand = secondSolid; if (ii > 0) { // 1 mm shift. XYZ shiftDirection = (suggestedShiftDirection == null) ? new XYZ(0, 0, 1) : suggestedShiftDirection; Transform secondSolidShift = Transform.CreateTranslation(shiftDirection * ((ii == 1) ? footToMillimeter : -footToMillimeter)); secondOperand = SolidUtils.CreateTransformed(secondOperand, secondSolidShift); } resultSolid = BooleanOperationsUtils.ExecuteBooleanOperation(firstSolid, secondOperand, opType); failedAllAttempts = false; } catch (Exception ex) { if (ii < 2) continue; Importer.TheLog.LogError(id, ex.Message, false); resultSolid = firstSolid; } if (SolidValidator.IsValidGeometry(resultSolid)) { // If we got here not on out first attempt, generate a warning, unless we got here because we gave up on our 3rd attempt. if (ii > 0 && !failedAllAttempts) Importer.TheLog.LogWarning(id, "The second argument in the Boolean " + opType.ToString() + " operation was shifted by 1mm to allow the operation to succeed. This may result in a very small difference in appearance.", false); return resultSolid; } } Importer.TheLog.LogError(id, opType.ToString() + " operation failed with void from #" + secondId.ToString(), false); return firstSolid; }
/// <summary> /// Return geometry for a particular representation item. /// </summary> /// <param name="shapeEditScope">The geometry creation scope.</param> /// <param name="lcs">Local coordinate system for the geometry, without scale.</param> /// <param name="scaledLcs">Local coordinate system for the geometry, including scale, potentially non-uniform.</param> /// <param name="guid">The guid of an element for which represntation is being created.</param> /// <returns>The created geometry.</returns> public IList <GeometryObject> CreateGeometry( IFCImportShapeEditScope shapeEditScope, Transform lcs, Transform scaledLcs, string guid) { IList <GeometryObject> firstSolids = FirstOperand.CreateGeometry(shapeEditScope, lcs, scaledLcs, guid); if (firstSolids != null) { foreach (GeometryObject potentialSolid in firstSolids) { if (!(potentialSolid is Solid)) { Importer.TheLog.LogError((FirstOperand as IFCRepresentationItem).Id, "Can't perform Boolean operation on a Mesh.", false); return(firstSolids); } } } IList <GeometryObject> secondSolids = null; if ((firstSolids != null || BooleanOperator == IFCBooleanOperator.Union) && (SecondOperand != null)) { try { using (IFCImportShapeEditScope.BuildPreferenceSetter setter = new IFCImportShapeEditScope.BuildPreferenceSetter(shapeEditScope, IFCImportShapeEditScope.BuildPreferenceType.ForceSolid)) { // Before we process the second operand, we are going to see if there is a uniform material set for the first operand // (corresponding to the solid in the Boolean operation). We will try to suggest the same material for the voids to avoid arbitrary // setting of material information for the cut faces. IFCStyledItem firstOperandStyledItem = GetStyledItemFromOperand(FirstOperand as IFCRepresentationItem); using (IFCImportShapeEditScope.IFCMaterialStack stack = new IFCImportShapeEditScope.IFCMaterialStack(shapeEditScope, firstOperandStyledItem, null)) { secondSolids = SecondOperand.CreateGeometry(shapeEditScope, lcs, scaledLcs, guid); } } } catch (Exception ex) { // We will allow something to be imported, in the case where the second operand is invalid. // If the first (base) operand is invalid, we will still fail the import of this solid. if (SecondOperand is IFCRepresentationItem) { Importer.TheLog.LogError((SecondOperand as IFCRepresentationItem).Id, ex.Message, false); } else { throw ex; } secondSolids = null; } } IList <GeometryObject> resultSolids = null; if (firstSolids == null) { if (BooleanOperator == IFCBooleanOperator.Union) { resultSolids = secondSolids; } } else if (secondSolids == null || BooleanOperator == null) { if (BooleanOperator == null) { Importer.TheLog.LogError(Id, "Invalid BooleanOperationsType.", false); } resultSolids = firstSolids; } else { BooleanOperationsType booleanOperationsType = BooleanOperationsType.Difference; switch (BooleanOperator) { case IFCBooleanOperator.Difference: booleanOperationsType = BooleanOperationsType.Difference; break; case IFCBooleanOperator.Intersection: booleanOperationsType = BooleanOperationsType.Intersect; break; case IFCBooleanOperator.Union: booleanOperationsType = BooleanOperationsType.Union; break; default: Importer.TheLog.LogError(Id, "Invalid BooleanOperationsType.", true); break; } resultSolids = new List <GeometryObject>(); foreach (GeometryObject firstSolid in firstSolids) { Solid resultSolid = (firstSolid as Solid); int secondId = (SecondOperand == null) ? -1 : (SecondOperand as IFCRepresentationItem).Id; XYZ suggestedShiftDirection = (SecondOperand == null) ? null : SecondOperand.GetSuggestedShiftDirection(lcs); foreach (GeometryObject secondSolid in secondSolids) { resultSolid = IFCGeometryUtil.ExecuteSafeBooleanOperation(Id, secondId, resultSolid, secondSolid as Solid, booleanOperationsType, suggestedShiftDirection); if (resultSolid == null) { break; } } if (resultSolid != null) { resultSolids.Add(resultSolid); } } } return(resultSolids); }
/// <summary> /// Execute a Boolean operation, and catch the exception. /// </summary> /// <param name="id">The id of the object demanding the Boolean operation.</param> /// <param name="secondId">The id of the object providing the second solid.</param> /// <param name="firstSolid">The first solid parameter to ExecuteBooleanOperation.</param> /// <param name="secondSolid">The second solid parameter to ExecuteBooleanOperation.</param> /// <param name="opType">The Boolean operation type.</param> /// <param name="suggestedShiftDirection">If the Boolean operation fails, a unit vector used to retry with a small shift. Can be null.</param> /// <returns>The result of the Boolean operation, or the first solid if the operation fails.</returns> public static Solid ExecuteSafeBooleanOperation(int id, int secondId, Solid firstSolid, Solid secondSolid, BooleanOperationsType opType, XYZ suggestedShiftDirection) { const double footToMillimeter = 1.0 / 304.8; // Perform default operations if one of the arguments is null. if (firstSolid == null || secondSolid == null) { if (firstSolid == null && secondSolid == null) { return(null); } switch (opType) { case BooleanOperationsType.Union: { if (firstSolid == null) { return(secondSolid); } return(firstSolid); } case BooleanOperationsType.Difference: { if (firstSolid == null) { return(null); } return(firstSolid); } default: // for .Intersect return(null); } } Solid resultSolid = null; bool failedAllAttempts = true; // We will attempt to do the Boolean operation here. // In the first pass, we will try to do the Boolean operation as-is. // For subsequent passes, we will shift the second operand by a small distance in // a given direction, using the following formula: // We start with a 1mm shift, and try each of (up to 4) shift directions given by // the shiftDirections list below, in alternating positive and negative directions. // In none of these succeed, we will increment the distance by 1mm and try again // until we reach numPasses. // Boolean operations are expensive, and as such we want to limit the number of // attempts we make here to balance fidelity and performance. Initial experimentation // suggests that a maximum 3mm shift is a good first start for this balance. IList <XYZ> shiftDirections = new List <XYZ>() { suggestedShiftDirection, XYZ.BasisZ, XYZ.BasisX, XYZ.BasisY }; const int numberOfNudges = 4; const int numPasses = numberOfNudges * 8 + 1; // 1 base, 8 possible nudges up to 0.75mm. double currentShiftFactor = 0.0; for (int ii = 0; ii < numPasses; ii++) { try { resultSolid = null; Solid secondOperand = secondSolid; if (ii > 0) { int shiftDirectionIndex = (ii - 1) % 4; XYZ shiftDirectionToUse = shiftDirections[shiftDirectionIndex]; if (shiftDirectionToUse == null) { continue; } // ((ii + 3) >> 3) * 0.25mm shift. Basically, a 0.25mm shift for every 8 attempts. currentShiftFactor = ((ii + 1) >> 3) * 0.25; int posOrNegDirection = (ii % 2 == 1) ? 1 : -1; double scale = currentShiftFactor * posOrNegDirection * footToMillimeter; Transform secondSolidShift = Transform.CreateTranslation(scale * shiftDirectionToUse); secondOperand = SolidUtils.CreateTransformed(secondOperand, secondSolidShift); } resultSolid = BooleanOperationsUtils.ExecuteBooleanOperation(firstSolid, secondOperand, opType); failedAllAttempts = false; } catch (Exception ex) { string msg = ex.Message; // This is the only error that we are trying to catch and fix. // For any other error, we will re-throw. if (!msg.Contains("Failed to perform the Boolean operation for the two solids")) { throw ex; } if (ii < numPasses - 1) { continue; } Importer.TheLog.LogError(id, msg, false); resultSolid = firstSolid; } if (SolidValidator.IsValidGeometry(resultSolid)) { // If we got here not on out first attempt, generate a warning, unless we got here because we gave up on our 3rd attempt. if (ii > 0 && !failedAllAttempts) { Importer.TheLog.LogWarning(id, "The second argument in the Boolean " + opType.ToString() + " operation was shifted by " + currentShiftFactor + "mm to allow the operation to succeed. This may result in a very small difference in appearance.", false); } return(resultSolid); } } Importer.TheLog.LogError(id, opType.ToString() + " operation failed with void from #" + secondId.ToString(), false); return(firstSolid); }
/// <summary> /// Execute a Boolean operation, and catch the exception. /// </summary> /// <param name="id">The id of the object demanding the Boolean operation.</param> /// <param name="secondId">The id of the object providing the second solid.</param> /// <param name="firstSolid">The first solid parameter to ExecuteBooleanOperation.</param> /// <param name="secondSolid">The second solid parameter to ExecuteBooleanOperation.</param> /// <param name="opType">The Boolean operation type.</param> /// <param name="suggestedShiftDirection">If the Boolean operation fails, a unit vector used to retry with a small shift. Can be null.</param> /// <returns>The result of the Boolean operation, or the first solid if the operation fails.</returns> public static Solid ExecuteSafeBooleanOperation(int id, int secondId, Solid firstSolid, Solid secondSolid, BooleanOperationsType opType, XYZ suggestedShiftDirection) { const double footToMillimeter = 1.0 / 304.8; // Perform default operations if one of the arguments is null. if (firstSolid == null || secondSolid == null) { if (firstSolid == null && secondSolid == null) { return(null); } switch (opType) { case BooleanOperationsType.Union: { if (firstSolid == null) { return(secondSolid); } return(firstSolid); } case BooleanOperationsType.Difference: { if (firstSolid == null) { return(null); } return(firstSolid); } default: // for .Intersect return(null); } } Solid resultSolid = null; bool failedAllAttempts = true; // We will attempt to do the Boolean operation 3 times: // 1st pass: With the passed-in arguments. // 2nd pass: With a 1mm shift in a direction in suggestedShiftDirection, or +Z if suggestedShiftDirection is null // 3rd pass: With a 1mm shift in a direction in -suggestedShiftDirection, or -Z if suggestedShiftDirection is null for (int ii = 0; ii < 3; ii++) { try { resultSolid = null; Solid secondOperand = secondSolid; if (ii > 0) { // 1 mm shift. XYZ shiftDirection = (suggestedShiftDirection == null) ? new XYZ(0, 0, 1) : suggestedShiftDirection; Transform secondSolidShift = Transform.CreateTranslation(shiftDirection * ((ii == 1) ? footToMillimeter : -footToMillimeter)); secondOperand = SolidUtils.CreateTransformed(secondOperand, secondSolidShift); } resultSolid = BooleanOperationsUtils.ExecuteBooleanOperation(firstSolid, secondOperand, opType); failedAllAttempts = false; } catch (Exception ex) { if (ii < 2) { continue; } Importer.TheLog.LogError(id, ex.Message, false); resultSolid = firstSolid; } if (SolidValidator.IsValidGeometry(resultSolid)) { // If we got here not on out first attempt, generate a warning, unless we got here because we gave up on our 3rd attempt. if (ii > 0 && !failedAllAttempts) { Importer.TheLog.LogWarning(id, "The second argument in the Boolean " + opType.ToString() + " operation was shifted by 1mm to allow the operation to succeed. This may result in a very small difference in appearance.", false); } return(resultSolid); } } Importer.TheLog.LogError(id, opType.ToString() + " operation failed with void from #" + secondId.ToString(), false); return(firstSolid); }
/// <summary> /// Internal constructor to make a solid by boolean operation. /// </summary> /// <param name="a"></param> /// <param name="b"></param> /// <param name="operationType"></param> internal Solid(Autodesk.Revit.DB.Solid a, Autodesk.Revit.DB.Solid b, BooleanOperationsType operationType) { Autodesk.Revit.DB.Solid result = null; switch (operationType) { case BooleanOperationsType.Difference: result = BooleanOperationsUtils.ExecuteBooleanOperation(a, b, BooleanOperationsType.Difference); break; case BooleanOperationsType.Intersect: result = BooleanOperationsUtils.ExecuteBooleanOperation(a, b, BooleanOperationsType.Intersect); break; case BooleanOperationsType.Union: result = BooleanOperationsUtils.ExecuteBooleanOperation(a, b, BooleanOperationsType.Union); break; } if (result == null) { throw new Exception("A boolean operation could not be completed with the provided solids."); } this.InternalSolid = result; }