/// <summary> /// Set SolidOrShellTessellationControls to use Coarse options. /// </summary> /// <param name="tessellationControls">The SolidOrShellTessellationControls to modify.</param> static public void SetDefaultCoarseTessellationControls(SolidOrShellTessellationControls tessellationControls) { // Note that this is consistent to how setting Coarse currently works; there could // potentially be other options we'd want to tweak upon switching to coarse, but this // routine will let us make those changes in one code location. tessellationControls.LevelOfDetail = 0.25; tessellationControls.MinAngleInTriangle = 0; }
private void ExportSolid(Solid solid) { SolidOrShellTessellationControls solidOrShellTessellationControls = new SolidOrShellTessellationControls { LevelOfDetail = userSetting.LevelOfDetail / 30.0, Accuracy = 0.1, MinAngleInTriangle = 0.0001, MinExternalAngleBetweenTriangles = 1.0 }; try { TriangulatedSolidOrShell triangulatedSolidOrShell = SolidUtils.TessellateSolidOrShell(solid, solidOrShellTessellationControls); int shellComponentCount = triangulatedSolidOrShell.ShellComponentCount; for (int i = 0; i < shellComponentCount; i++) { TriangulatedShellComponent shellComponent = triangulatedSolidOrShell.GetShellComponent(i); ModelGeometry exportedGeometry = new ModelGeometry { Transform = transformationStack.Peek(), Points = new List <XYZ>(shellComponent.VertexCount) }; for (int num = 0; num != shellComponent.VertexCount; num++) { exportedGeometry.Points.Add(shellComponent.GetVertex(num)); } exportedGeometry.Indices = new List <int>(shellComponent.TriangleCount * 3); for (int j = 0; j < shellComponent.TriangleCount; j++) { TriangleInShellComponent triangle = shellComponent.GetTriangle(j); exportedGeometry.Indices.Add(triangle.VertexIndex0); exportedGeometry.Indices.Add(triangle.VertexIndex1); exportedGeometry.Indices.Add(triangle.VertexIndex2); } exportedGeometry.CalculateNormals(false); exportedGeometry.CalculateUVs(true, false); ElementId materialElementId = solid.Faces.get_Item(0).MaterialElementId; Tuple <Document, ElementId> tuple = new Tuple <Document, ElementId>(documentStack.Peek(), materialElementId); ChangeCurrentMaterial(tuple); documentAndMaterialIdToGeometries[tuple].Add(exportedGeometry); } } catch (Exception) { } }
/// <summary> /// Returns the tessellation controls with the right setings for insulations for a duct of type elbow,tee or cross /// </summary> /// <param name="controls">The controls to be used in the tessellation</param> /// <param name="lod">the level og detail (high/medium/low/extra low) high is autodesk default and will not change anything</param> /// <param name="type">the type of the duct. </param> /// <returns></returns> public static SolidOrShellTessellationControls GetTessellationControlsForInsulation(SolidOrShellTessellationControls controls, int lod, int type) { if (type == 5) //Elbow { switch (lod) { case 1: controls.Accuracy = 0.6; controls.LevelOfDetail = 0.1; controls.MinAngleInTriangle = 0.13; controls.MinExternalAngleBetweenTriangles = 1.2; break; case 2: controls.Accuracy = 0.6; controls.LevelOfDetail = 0.3; controls.MinAngleInTriangle = 0.13; controls.MinExternalAngleBetweenTriangles = 0.7; break; case 3: controls.Accuracy = 0.5; controls.LevelOfDetail = 0.4; controls.MinAngleInTriangle = 0.13; controls.MinExternalAngleBetweenTriangles = 0.35; break; case 4: break; } } else if (type == 6) //Tee { switch (lod) { case 1: controls.Accuracy = 0.6; controls.LevelOfDetail = 0.1; controls.MinAngleInTriangle = 0.13; controls.MinExternalAngleBetweenTriangles = 1.2; break; case 2: controls.Accuracy = 0.6; controls.LevelOfDetail = 0.2; controls.MinAngleInTriangle = 0.13; controls.MinExternalAngleBetweenTriangles = 0.9; break; case 3: controls.Accuracy = 0.5; controls.LevelOfDetail = 0.4; controls.MinAngleInTriangle = 0.13; controls.MinExternalAngleBetweenTriangles = 0.55; break; case 4: break; } } else if (type == 8) //Cross { switch (lod) { case 1: controls.Accuracy = 0.6; controls.LevelOfDetail = 0.1; controls.MinAngleInTriangle = 0.13; controls.MinExternalAngleBetweenTriangles = 1.2; break; case 2: controls.Accuracy = 0.6; controls.LevelOfDetail = 0.2; controls.MinAngleInTriangle = 0.13; controls.MinExternalAngleBetweenTriangles = 0.9; break; case 3: controls.Accuracy = 0.5; controls.LevelOfDetail = 0.4; controls.MinAngleInTriangle = 0.13; controls.MinExternalAngleBetweenTriangles = 0.55; break; case 4: break; } } return controls; }
/// <summary> /// Get tessellation control information for the given element. /// </summary> /// <param name="element">The element</param> /// <param name="tessellationControls">The origin tessellation control</param> /// <remarks>This method doesn't alter tessellationControls</remarks> public static SolidOrShellTessellationControls GetTessellationControl(Element element, SolidOrShellTessellationControls tessellationControls) { SolidOrShellTessellationControls copyTessellationControls = CopyTessellationControls(tessellationControls); Document document = element.Document; // For duct and insulation, we use a different set of levels of detail. int LOD = ExporterCacheManager.ExportOptionsCache.LevelOfDetail; Element elementType = null; //Use the insulations host as the host will have the same shape as the insulation, and then triangulate the insulation. if (element as DuctInsulation != null) { ElementId hostId = (element as DuctInsulation).HostElementId; Element hostElement = document.GetElement(hostId); elementType = document.GetElement(hostElement.GetTypeId()); } else { elementType = document.GetElement(element.GetTypeId()); } if (elementType as FamilySymbol != null) { FamilySymbol symbol = elementType as FamilySymbol; Family family = symbol.Family; if (family != null) { Parameter para = family.LookupParameter("Part Type"); if (para != null) { if (element as DuctInsulation != null) { copyTessellationControls = GetTessellationControlsForInsulation(copyTessellationControls, LOD, para.AsInteger()); } else { copyTessellationControls = GetTessellationControlsForDuct(copyTessellationControls, LOD, para.AsInteger()); } } } } return copyTessellationControls; }
/// <summary> /// Creates a copy of the given SolidOrShellTessellationControls object /// </summary> /// <param name="tessellationControls">The given SolidOrShellTessellationControls object</param> /// <returns>The copy of the input object</returns> public static SolidOrShellTessellationControls CopyTessellationControls(SolidOrShellTessellationControls tessellationControls) { SolidOrShellTessellationControls newTessellationControls = new SolidOrShellTessellationControls(); if (tessellationControls.Accuracy > 0 && tessellationControls.Accuracy <= 30000) newTessellationControls.Accuracy = tessellationControls.Accuracy; if (tessellationControls.LevelOfDetail >= 0 && tessellationControls.LevelOfDetail <= 1) newTessellationControls.LevelOfDetail = tessellationControls.LevelOfDetail; if (tessellationControls.MinAngleInTriangle >= 0 && tessellationControls.MinAngleInTriangle < Math.PI / 3) newTessellationControls.MinAngleInTriangle = tessellationControls.MinAngleInTriangle; if (tessellationControls.MinExternalAngleBetweenTriangles > 0 && tessellationControls.MinExternalAngleBetweenTriangles <= 30000) newTessellationControls.MinExternalAngleBetweenTriangles = tessellationControls.MinExternalAngleBetweenTriangles; return newTessellationControls; }
/// <summary> /// Create a new list of geometry objects from the /// given input. As input, we supply the result of /// Room.GetClosedShell. The output is the exact /// same solid lacking whatever flaws are present /// in the input solid. /// </summary> static IList <GeometryObject> CopyGeometry( GeometryElement geo, ElementId materialId, List <IntPoint3d> coords, List <TriangleIndices> indices) { TessellatedShapeBuilderResult result = null; TessellatedShapeBuilder builder = new TessellatedShapeBuilder(); // Need to include the key in the value, otherwise // no way to access it later, cf. // https://stackoverflow.com/questions/1619090/getting-a-keyvaluepair-directly-from-a-dictionary Dictionary <XYZ, KeyValuePair <XYZ, int> > pts = new Dictionary <XYZ, KeyValuePair <XYZ, int> >( new XyzEqualityComparer()); int nSolids = 0; //int nFaces = 0; int nTriangles = 0; //int nVertices = 0; List <XYZ> vertices = new List <XYZ>(3); foreach (GeometryObject obj in geo) { Solid solid = obj as Solid; if (null != solid) { if (0 < solid.Volume) { ++nSolids; builder.OpenConnectedFaceSet(false); #region Create a new solid based on tessellation of the invalid room closed shell solid #if CREATE_NEW_SOLID_USING_TESSELATION Debug.Assert( SolidUtils.IsValidForTessellation(solid), "expected a valid solid for room closed shell"); SolidOrShellTessellationControls controls = new SolidOrShellTessellationControls() { // // Summary: // A positive real number specifying how accurately a triangulation should approximate // a solid or shell. // // Exceptions: // T:Autodesk.Revit.Exceptions.ArgumentOutOfRangeException: // When setting this property: The given value for accuracy must be greater than // 0 and no more than 30000 feet. // This statement is not true. I set Accuracy = 0.003 and an exception was thrown. // Setting it to 0.006 was acceptable. 0.03 is a bit over 9 mm. // // Remarks: // The maximum distance from a point on the triangulation to the nearest point on // the solid or shell should be no greater than the specified accuracy. This constraint // may be approximately enforced. Accuracy = 0.03, // // Summary: // An number between 0 and 1 (inclusive) specifying the level of detail for the // triangulation of a solid or shell. // // Exceptions: // T:Autodesk.Revit.Exceptions.ArgumentOutOfRangeException: // When setting this property: The given value for levelOfDetail must lie between // 0 and 1 (inclusive). // // Remarks: // Smaller values yield coarser triangulations (fewer triangles), while larger values // yield finer triangulations (more triangles). LevelOfDetail = 0.1, // // Summary: // A non-negative real number specifying the minimum allowed angle for any triangle // in the triangulation, in radians. // // Exceptions: // T:Autodesk.Revit.Exceptions.ArgumentOutOfRangeException: // When setting this property: The given value for minAngleInTriangle must be at // least 0 and less than 60 degrees, expressed in radians. The value 0 means to // ignore the minimum angle constraint. // // Remarks: // A small value can be useful when triangulating long, thin objects, in order to // keep the number of triangles small, but it can result in long, thin triangles, // which are not acceptable for all applications. If the value is too large, this // constraint may not be satisfiable, causing the triangulation to fail. This constraint // may be approximately enforced. A value of 0 means to ignore the minimum angle // constraint. MinAngleInTriangle = 3 * Math.PI / 180.0, // // Summary: // A positive real number specifying the minimum allowed value for the external // angle between two adjacent triangles, in radians. // // Exceptions: // T:Autodesk.Revit.Exceptions.ArgumentOutOfRangeException: // When setting this property: The given value for minExternalAngleBetweenTriangles // must be greater than 0 and no more than 30000 feet. // // Remarks: // A small value yields more smoothly curved triangulated surfaces, usually at the // expense of an increase in the number of triangles. Note that this setting has // no effect for planar surfaces. This constraint may be approximately enforced. MinExternalAngleBetweenTriangles = 0.2 * Math.PI }; TriangulatedSolidOrShell shell = SolidUtils.TessellateSolidOrShell(solid, controls); int n = shell.ShellComponentCount; Debug.Assert(1 == n, "expected just one shell component in room closed shell"); TriangulatedShellComponent component = shell.GetShellComponent(0); int coordsBase = coords.Count; int indicesBase = indices.Count; n = component.VertexCount; for (int i = 0; i < n; ++i) { XYZ v = component.GetVertex(i); coords.Add(new IntPoint3d(v)); } n = component.TriangleCount; for (int i = 0; i < n; ++i) { TriangleInShellComponent t = component.GetTriangle(i); vertices.Clear(); vertices.Add(component.GetVertex(t.VertexIndex0)); vertices.Add(component.GetVertex(t.VertexIndex1)); vertices.Add(component.GetVertex(t.VertexIndex2)); indices.Add(new TriangleIndices( coordsBase + t.VertexIndex0, coordsBase + t.VertexIndex1, coordsBase + t.VertexIndex2)); TessellatedFace tf = new TessellatedFace( vertices, materialId); if (builder.DoesFaceHaveEnoughLoopsAndVertices(tf)) { builder.AddFace(tf); ++nTriangles; } } #else // Iterate over the individual solid faces foreach (Face f in solid.Faces) { vertices.Clear(); #region Use face triangulation #if USE_FACE_TRIANGULATION Mesh mesh = f.Triangulate(); int n = mesh.NumTriangles; for (int i = 0; i < n; ++i) { MeshTriangle triangle = mesh.get_Triangle(i); XYZ p1 = triangle.get_Vertex(0); XYZ p2 = triangle.get_Vertex(1); XYZ p3 = triangle.get_Vertex(2); vertices.Clear(); vertices.Add(p1); vertices.Add(p2); vertices.Add(p3); TessellatedFace tf = new TessellatedFace( vertices, materialId); if (builder.DoesFaceHaveEnoughLoopsAndVertices(tf)) { builder.AddFace(tf); ++nTriangles; } } #endif // USE_FACE_TRIANGULATION #endregion // Use face triangulation #region Use original solid and its EdgeLoops #if USE_EDGELOOPS // This returns arbitrarily ordered and // oriented edges, so no solid can be // generated. foreach (EdgeArray loop in f.EdgeLoops) { foreach (Edge e in loop) { XYZ p = e.AsCurve().GetEndPoint(0); XYZ q = p; if (pts.ContainsKey(p)) { KeyValuePair <XYZ, int> kv = pts[p]; q = kv.Key; int n = kv.Value; pts[p] = new KeyValuePair <XYZ, int>( q, ++n); Debug.Print("Ignoring vertex at {0} " + "with distance {1} to existing " + "vertex {2}", p, p.DistanceTo(q), q); } else { pts[p] = new KeyValuePair <XYZ, int>( p, 1); } vertices.Add(q); ++nVertices; } } #endif // USE_EDGELOOPS #endregion // Use original solid and its EdgeLoops #region Use original solid and GetEdgesAsCurveLoops #if USE_AS_CURVE_LOOPS // The solids generated by this have some weird // normals, so they do not render correctly in // the Forge viewer. Revert to triangles again. IList <CurveLoop> loops = f.GetEdgesAsCurveLoops(); foreach (CurveLoop loop in loops) { foreach (Curve c in loop) { XYZ p = c.GetEndPoint(0); XYZ q = p; if (pts.ContainsKey(p)) { KeyValuePair <XYZ, int> kv = pts[p]; q = kv.Key; int n = kv.Value; pts[p] = new KeyValuePair <XYZ, int>( q, ++n); Debug.Print("Ignoring vertex at {0} " + "with distance {1} to existing " + "vertex {2}", p, p.DistanceTo(q), q); } else { pts[p] = new KeyValuePair <XYZ, int>( p, 1); } vertices.Add(q); ++nVertices; } } #endif // USE_AS_CURVE_LOOPS #endregion // Use original solid and GetEdgesAsCurveLoops builder.AddFace(new TessellatedFace( vertices, materialId)); ++nFaces; } #endif // CREATE_NEW_SOLID_USING_TESSELATION #endregion // Create a new solid based on tessellation of the invalid room closed shell solid builder.CloseConnectedFaceSet(); builder.Target = TessellatedShapeBuilderTarget.AnyGeometry; // Solid failed builder.Fallback = TessellatedShapeBuilderFallback.Mesh; // use Abort if target is Solid builder.Build(); result = builder.GetBuildResult(); // Debug printout log of current solid's glTF facet data n = coords.Count - coordsBase; Debug.Print("{0} glTF vertex coordinates " + "in millimetres:", n); Debug.Print(string.Join(" ", coords .TakeWhile <IntPoint3d>((p, i) => coordsBase <= i) .Select <IntPoint3d, string>(p => p.ToString()))); n = indices.Count - indicesBase; Debug.Print("{0} glTF triangles:", n); Debug.Print(string.Join(" ", indices .TakeWhile <TriangleIndices>((ti, i) => indicesBase <= i) .Select <TriangleIndices, string>(ti => ti.ToString()))); } } } return(result.GetGeometricalObjects()); }
/* void f() { var cx, cy, cz, volume, v, i, x1, y1, z1, x2, y2, z2, x3, y3, z3; volume = 0; cx = 0; cy = 0; cz = 0; // Assuming vertices are in vertX[i], vertY[i], and vertZ[i] // and faces are faces[i, j] where the first index indicates the // face and the second index indicates the vertex of that face // The value in the faces array is an index into the vertex array i = 0; repeat (numFaces) { x1 = vertX[faces[i, 0]]; y1 = vertY[faces[i, 0]]; z1 = vertZ[faces[i, 0]]; x2 = vertX[faces[i, 1]]; y2 = vertY[faces[i, 1]]; z2 = vertZ[faces[i, 1]]; x3 = vertX[faces[i, 2]]; y3 = vertY[faces[i, 2]]; z3 = vertZ[faces[i, 2]]; v = x1*(y2*z3 - y3*z2) + y1*(z2*x3 - z3*x2) + z1*(x2*y3 - x3*y2); volume += v; cx += (x1 + x2 + x3)*v; cy += (y1 + y2 + y3)*v; cz += (z1 + z2 + z3)*v; i += 1; } // Set centroid coordinates to their final value cx /= 4 * volume; cy /= 4 * volume; cz /= 4 * volume; // And, just in case you want to know the total volume of the model: volume /= 6; } */ CentroidVolume GetCentroid( Solid solid ) { CentroidVolume cv = new CentroidVolume(); double v; XYZ v0, v1, v2; SolidOrShellTessellationControls controls = new SolidOrShellTessellationControls(); controls.LevelOfDetail = 0; TriangulatedSolidOrShell triangulation = null; try { triangulation = SolidUtils.TessellateSolidOrShell( solid, controls ); } catch( Autodesk.Revit.Exceptions .InvalidOperationException ) { return null; } int n = triangulation.ShellComponentCount; for( int i = 0; i < n; ++i ) { TriangulatedShellComponent component = triangulation.GetShellComponent( i ); int m = component.TriangleCount; for( int j = 0; j < m; ++j ) { TriangleInShellComponent t = component.GetTriangle( j ); v0 = component.GetVertex( t.VertexIndex0 ); v1 = component.GetVertex( t.VertexIndex1 ); v2 = component.GetVertex( t.VertexIndex2 ); v = v0.X*(v1.Y*v2.Z - v2.Y*v1.Z) + v0.Y*(v1.Z*v2.X - v2.Z*v1.X) + v0.Z*(v1.X*v2.Y - v2.X*v1.Y); cv.Centroid += v * (v0 + v1 + v2); cv.Volume += v; } } // Set centroid coordinates to their final value cv.Centroid /= 4 * cv.Volume; XYZ diffCentroid = cv.Centroid - solid.ComputeCentroid(); Debug.Assert( 0.6 > diffCentroid.GetLength(), "expected centroid approximation to be " + "similar to solid ComputeCentroid result" ); // And, just in case you want to know // the total volume of the model: cv.Volume /= 6; double diffVolume = cv.Volume - solid.Volume; Debug.Assert( 0.3 > Math.Abs( diffVolume / cv.Volume ), "expected volume approximation to be " + "similar to solid Volume property value" ); return cv; }
/// <summary> /// Returns the tessellation controls with the right setings for an elbow,tee or cross. /// </summary> /// <param name="controls">The controls to be used in the tessellation</param> /// <param name="lod">the level og detail (high/medium/low/extra low) high is autodesk default and will not change anything</param> /// <param name="type">the type of the duct. </param> /// <returns></returns> private static SolidOrShellTessellationControls GetTessellationControlsForDuct(SolidOrShellTessellationControls controls, int lod, int type) { if (type == 5) //Elbow { switch (lod) { case 1: controls.Accuracy = 0.81; controls.LevelOfDetail = 0.05; controls.MinAngleInTriangle = 0.13; controls.MinExternalAngleBetweenTriangles = 1.7; break; case 2: controls.Accuracy = 0.84; controls.LevelOfDetail = 0.4; controls.MinAngleInTriangle = 0.13; controls.MinExternalAngleBetweenTriangles = 1.25; break; case 3: controls.Accuracy = 0.74; controls.LevelOfDetail = 0.4; controls.MinAngleInTriangle = 0.13; controls.MinExternalAngleBetweenTriangles = 0.74; break; case 4: break; } } else if (type == 6) //Tee { switch (lod) { case 1: controls.Accuracy = 1.21; controls.LevelOfDetail = 0.05; controls.MinAngleInTriangle = 0.13; controls.MinExternalAngleBetweenTriangles = 1.7; break; case 2: controls.Accuracy = 0.84; controls.LevelOfDetail = 0.3; controls.MinAngleInTriangle = 0.13; controls.MinExternalAngleBetweenTriangles = 1.0; break; case 3: controls.Accuracy = 0.84; controls.LevelOfDetail = 0.4; controls.MinAngleInTriangle = 0.13; controls.MinExternalAngleBetweenTriangles = 0.54; break; case 4: break; } } else if (type == 8) //Cross { switch (lod) { case 1: controls.Accuracy = 0.81; controls.LevelOfDetail = 0.05; controls.MinAngleInTriangle = 0.13; controls.MinExternalAngleBetweenTriangles = 1.7; break; case 2: controls.Accuracy = 0.84; controls.LevelOfDetail = 0.2; controls.MinAngleInTriangle = 0.13; controls.MinExternalAngleBetweenTriangles = 0.8; break; case 3: controls.Accuracy = 0.81; controls.LevelOfDetail = 0.4; controls.MinAngleInTriangle = 0.13; controls.MinExternalAngleBetweenTriangles = 0.84; break; case 4: break; } } return controls; }
/* * void f() * { * var cx, cy, cz, volume, v, i, x1, y1, z1, x2, y2, z2, x3, y3, z3; * volume = 0; * cx = 0; cy = 0; cz = 0; * // Assuming vertices are in vertX[i], vertY[i], and vertZ[i] * // and faces are faces[i, j] where the first index indicates the * // face and the second index indicates the vertex of that face * // The value in the faces array is an index into the vertex array * i = 0; * repeat (numFaces) { * x1 = vertX[faces[i, 0]]; y1 = vertY[faces[i, 0]]; z1 = vertZ[faces[i, 0]]; * x2 = vertX[faces[i, 1]]; y2 = vertY[faces[i, 1]]; z2 = vertZ[faces[i, 1]]; * x3 = vertX[faces[i, 2]]; y3 = vertY[faces[i, 2]]; z3 = vertZ[faces[i, 2]]; * v = x1*(y2*z3 - y3*z2) + y1*(z2*x3 - z3*x2) + z1*(x2*y3 - x3*y2); * volume += v; * cx += (x1 + x2 + x3)*v; * cy += (y1 + y2 + y3)*v; * cz += (z1 + z2 + z3)*v; * i += 1; * } * // Set centroid coordinates to their final value * cx /= 4 * volume; * cy /= 4 * volume; * cz /= 4 * volume; * // And, just in case you want to know the total volume of the model: * volume /= 6; * } */ CentroidVolume GetCentroid(Solid solid) { CentroidVolume cv = new CentroidVolume(); double v; XYZ v0, v1, v2; SolidOrShellTessellationControls controls = new SolidOrShellTessellationControls(); controls.LevelOfDetail = 0; TriangulatedSolidOrShell triangulation = null; try { triangulation = SolidUtils.TessellateSolidOrShell( solid, controls); } catch (Autodesk.Revit.Exceptions .InvalidOperationException) { return(null); } int n = triangulation.ShellComponentCount; for (int i = 0; i < n; ++i) { TriangulatedShellComponent component = triangulation.GetShellComponent(i); int m = component.TriangleCount; for (int j = 0; j < m; ++j) { TriangleInShellComponent t = component.GetTriangle(j); v0 = component.GetVertex(t.VertexIndex0); v1 = component.GetVertex(t.VertexIndex1); v2 = component.GetVertex(t.VertexIndex2); v = v0.X * (v1.Y * v2.Z - v2.Y * v1.Z) + v0.Y * (v1.Z * v2.X - v2.Z * v1.X) + v0.Z * (v1.X * v2.Y - v2.X * v1.Y); cv.Centroid += v * (v0 + v1 + v2); cv.Volume += v; } } // Set centroid coordinates to their final value cv.Centroid /= 4 * cv.Volume; XYZ diffCentroid = cv.Centroid - solid.ComputeCentroid(); Debug.Assert(0.6 > diffCentroid.GetLength(), "expected centroid approximation to be " + "similar to solid ComputeCentroid result"); // And, just in case you want to know // the total volume of the model: cv.Volume /= 6; double diffVolume = cv.Volume - solid.Volume; Debug.Assert(0.3 > Math.Abs( diffVolume / cv.Volume), "expected volume approximation to be " + "similar to solid Volume property value"); return(cv); }