Infinite Region on Nonmanifold Cube

I’ve come across an issue retrieving the infinite region on a non-manifold cube. The cube has internal planes dividing it into 4 internal regions as shown below (with a clipping plane). It also has some internal edges from earlier faces that have been moved/removed.

When I extract the infinite region I should just get a solid, manifold cube but instead get the geometry below. Note that the method to get the the brep representing the infinite region first calls the region.BoundaryBrep() method, which returns null, so I manually create a brep by extracting faces from the infinite region and merging them.

Here is the command that gets the infinite region, and I will also attach the cube model. If you can let me know why the infinite region is coming out as show that would be great. Thanks.

[CommandStyle(Style.ScriptRunner)]
public class InfiniteRegionTestCommand : Command
{
	/// <summary>
	/// Get the name of the command
	/// </summary>
	public override string EnglishName
	{
		get { return "TestGetInfiniteRegion"; }
	}

	/// <summary>
	/// Logic to execute the command
	/// </summary>
	/// <param name="doc"></param>
	/// <param name="mode"></param>
	/// <returns></returns>
	protected override Result RunCommand(RhinoDoc doc, RunMode mode)
	{
		try
		{
			if (RhinoGet.GetOneObject("Select surface/polysurface to extract infinite region.", false,
				ObjectType.Brep | ObjectType.Surface, out ObjRef objRef) == Result.Success)
			{
				Brep brep = null;
				if (objRef.Geometry().HasBrepForm && (brep = Brep.TryConvertBrep(objRef.Geometry())) != null)
				{
					Brep boundaryBrep = null;
					foreach (BrepRegion region in brep.GetRegions())
					{
						if (!region.IsFinite)
						{
							boundaryBrep = Utilities.GetBoundaryBrepFromRegion(region, doc.ModelAbsoluteTolerance);
							break;
						}
					}
					if (null != boundaryBrep && doc.Objects.AddBrep(boundaryBrep) != Guid.Empty) return Result.Success;
				}
			}
		}
		catch (Exception ex)
		{
			RhinoApp.WriteLine(string.Format("Failed command, {0}, because {1}", EnglishName, ex.Message));
		}
		return Result.Failure;
	}
}

public static class Utilities
{
	/// <summary>
	/// Workaround method to retrieve the boundary brep for the region. Handles the situation where the Rhino
	/// BoundaryBrep() method fails, which seems to happen on some occasions.
	/// </summary>
	/// <param name="rhinoRegion">The region to create a boundary brep from.</param>
	/// <param name="tol">The absolute tolerance to use for the operation</param>
	/// <param name="outwardNormals">True if the method should flip the normals if necessary to ensure the
	/// normals are pointin outward. Optional argument that defaults to true.</param>
	/// <returns>The boundary brep or null if no boundary brep could be created.</returns>
	public static Brep GetBoundaryBrepFromRegion(BrepRegion rhinoRegion, double tol, bool outwardNormals = true)
	{
		if (null == rhinoRegion)
		{
			return null;
		}

		// Try to retrieve the boundary brep from the Rhino region. If that fails construct your own from the brep region faces
		var boundaryBrep = rhinoRegion.BoundaryBrep();
		if (null == boundaryBrep || !boundaryBrep.IsValid || !boundaryBrep.IsSolid || !boundaryBrep.IsManifold)
		{
			if (rhinoRegion.Brep.IsManifold && rhinoRegion.Brep.GetRegions().Length == 2)
			{
				boundaryBrep = rhinoRegion.Brep;
			}
			else
			{
				// Create a list of breps corresponding to the regions faces
				List<Brep> faces = new List<Brep>();
				foreach (var fs in rhinoRegion.GetFaceSides())
				{
					faces.Add(fs.Face.DuplicateFace(true));
				}

				// Merge the face breps first joining them and then merging if the join produced 
				// multiple breps such as might happen for a region with internal voids.
				var boundaryBreps = Brep.JoinBreps(faces, tol);
				if (boundaryBreps.Count() == 1)
				{
					boundaryBrep = boundaryBreps[0];
				}
				else if (boundaryBreps.Count() > 1)
				{
					boundaryBrep = Brep.MergeBreps(boundaryBreps, tol);
				}
			}
		}
		if (null != boundaryBrep && boundaryBrep.IsSolid)
		{
			if ((outwardNormals && boundaryBrep.SolidOrientation == BrepSolidOrientation.Inward) ||
				(!outwardNormals && boundaryBrep.SolidOrientation == BrepSolidOrientation.Outward))
			{
				boundaryBrep.Flip(); // Flip the normals to get the desired normal direction
			}
		}
		return boundaryBrep;
	}
}

Nonmanifold Cube.3dm (131.0 KB)

Here is the model, thanks.

Hi @LarryL,

@chuck and I are looking at this.

– Dale

Many thanks @dale !

Hi Larry. The BREP Region Topology code is very robust, but it looks like there are some unrelated BREP operations that should delete the topology information, causing a recalculation next time it’s needed. One such operation is ON_Brep::Compact, which is called after you delete faces. I will fix that problem, but there may be more. The bad topology information is saved in the file, so you’re stuck for now with that file failing. If you can start with an empty file, create a brep and have this problem please post directions on how to do so. That may show us other places where we need to delete the topology information. @dale Maybe an SDK function to delete the region topology to clean out files like this?

Thanks for the report.

Thanks Chuck. This topology was created internally in my code by removing a face (which includes a call to Compact among many others), so I suspect it may be unique to my sequence of operations. Is there any way in the current RhinoCommon SDK to Invalidate or otherwise force topology to become cleared so that GetRegions will update? Maybe SetTolerancesBoxesAndFlags or Standardize?

Larry

Hi @LarryL,

I’ve added a new Brep.DestroyRegionTopology() method to RhinoCommon. If you call this before getting the Brep regions, your code seems to work, as Brep.GetRegions() will re-create the region topology if it does not exist.

https://mcneel.myjetbrains.com/youtrack/issue/RH-66199

– Dale

Thanks so much @dale and @chuck for addressing this!

Larry

RH-66199 is fixed in Rhino 7 Service Release 13 Release Candidate

Fantastic, thanks Brian, et al!