Brep.Flip(), Brep.Face.Reverse(), Brep.Surface.Reverse Inconsistencies

This simple example using python in grasshopper works as expected.

Code in the python components:

import Rhino

if (flip == True): cutter.Flip();
tol = Rhino.RhinoDoc.ActiveDoc.ModelAbsoluteTolerance
a = brep.Trim(cutter, tol)

There are 2 variations of the RhinoCommon Brep.Trim() method. One takes a Brep as the cutter and the other takes a Plane as the cutter.

brep and cutter inputs to GhPython Script components in this file both have type hints set to brep

Brep_Trim_Test_py.gh (24.0 KB)
Geometry is internalized.

-Kevin

Dale - Please disregard the last post. After rebooting, it’s working just fine. I think the script engine gets hug up from time to time and rebooting fixes it. Thanks for your help.

Very nice Kevin. Thanks! After rebooting my script is working as expected. There was probably something hung up in the script engine.

I do have one more question after further investigation. Can you create a rectangular surface and run the following script?

    s = rs.GetSurfaceObject()
    so = rs.coercesurface(s[0])
    print str(so.NormalAt(0,0))
    rs.FlipSurface(s[0], True)
    so = rs.coercesurface(s[0])
    print str(so.NormalAt(0,0))

The script flips the surface as well as the normals. If you run it again it doesn’t flip it again back to it’s original orientation unless you run rs.FlipSurface(s[0], False), so how do I tell what the orientation of the surface is so that I can feed the FlipSurface the appropriate True/False value? Do I have to look at so.Brep.Faces[0].OrienationIsReversed? I figured there should be something at the surface level without looking at the brep face.

Furthermore, if the only way to tell if a surface is flipped is by looking at BrepFace.OrienationIsReversed, then I suppose the best way to handle flipping the orientation regardless of the current state is to run:

rs.FlipSurface(s[0], not so.Brep.Faces[0].OrientationIsReversed)

Hi @Mike24,

If you want to flip (reverse) the normal direction of a surface or polysurface, like the Dir command, then use Brep.Flip.

If you want to reverse the direction of a Brep face’s underlying surface, which has no effect on the owning face, then use Surface.Reverse.

Most users want this:

import Rhino
import scriptcontext as sc

def test_flip():
    filter = Rhino.DocObjects.ObjectType.Surface
    rc, objref = Rhino.Input.RhinoGet.GetOneObject("Select surface to flip", False, filter)
    if not objref or rc != Rhino.Commands.Result.Success: 
        return

    brep = objref.Brep()
    if not brep:
        return
        
    bcopy = brep.DuplicateBrep()
    bcopy.Flip() # what the Dir command does...
    
    sc.doc.Objects.Replace(objref, bcopy)
    sc.doc.Views.Redraw()
    
if __name__ == "__main__":
    test_flip()

– Dale

1 Like

Perfect…thanks Dale. This is great.

I have modified the underlying geometry of doc object before without duplicating it and then used sc.doc.Objects.Replace to push the geometry changes back to the guid. This has worked until I started trying to flip a surface using brep.Fip. I noticed that the object will not flip if a duplicate is not created first. Is that because when I call sc.doc.Objects.Replace, the underlying geometry is being used to push the changes into the guid and then at the same time Rhino is pushing the changes into the guid’s underlying geometry which could create inconsistent results I suppose. Please advise.
As a rule of thumb, one should always duplicate the object before manipulating it so that when you call sc.doc.Objects.Replace you have an object with all the changes will not be changed as Rhino updates the guid’s underlying geometry? I think this was causing my issue all along. I’m surprised that I have not ran into issues like this before. I have not seen anything in the documentation that talks about the necessity for duplicating objects and modifying the duplicate when wanting to push those changes back to the original document object. Maybe this topic needs to be added to an intro document so that other developers know they need to always duplicate the objects, if that’s the case. Please advise.

Please excuse me resurrecting this thread but I have a related question.

I have an example where I have drawn a simple 2d plane and very deliberately oriented the normal of that plane to face a certain direction so that I may use it to trim a tube that I have created. In code, I have written a comment to select the tube (as a Brep) and then select the cutting surface (as a Surface) and then I try to run the Brep.Trim(Brep, double) method. In order to run the trim method I must take my input cutting surface and call Surface.ToBrep(). Surface.ToBrep() returns me a Brep object where IsSurface is TRUE and it contains only a single face as I would expect, however, if I then add this Brep to my document and run Dir on it I see that the normal direction is the opposite of the original Surface. Also, if I inspect the Brep in memory and look at it’s OrientationIsReversed property, it is FALSE.

In short, taking a calling Surface.ToBrep() on a Surface is flipping the normal direction and I don’t understand why. What would be the way for me to check the Brep result of ToBrep() to make sure that the normal is the same as the original Surface?

How are you creating your plane (surface) ? Do you have a small sample to post?

I tested with this, but it seems to produce objects with the same normal direction.

import scriptcontext as sc
import Rhino

rot_ang=90 #change as desired
plane=Rhino.Geometry.Plane.WorldXY
plane.Rotate(Rhino.RhinoMath.ToRadians(rot_ang),plane.XAxis)
ivU=Rhino.Geometry.Interval(0,20)
ivV=Rhino.Geometry.Interval(0,10)
plane_srf=Rhino.Geometry.PlaneSurface(plane,ivU,ivV)
test_brep=plane_srf.ToBrep()

sc.doc.Objects.AddSurface(plane_srf)
sc.doc.Objects.AddBrep(test_brep)
sc.doc.Views.Redraw()

In my example I had drawn the surfaces by hand in Rhino and then ran a custom command to select and process them.

        protected override Result RunCommand(RhinoDoc doc, RunMode mode)
        {
            // Get object
            var getObject = new GetObject();
            getObject.EnablePostSelect(true);
            getObject.EnablePreSelect(false, true);
            getObject.GeometryFilter = ObjectType.Brep;
            getObject.SetCommandPrompt($"Select object");

            GetResult getObjectResult = getObject.Get();

            // Check command result
            Result getObjectCommandResult = getObject.CommandResult();

            if (getObjectCommandResult != Result.Success)
            {
                return Result.Failure;
            }

            // If no objects
            if (getObjectResult != GetResult.Object)
            {
                return Result.Failure;
            }

            Brep untrimmedTarget = getObject.Object(0).Brep();

            // Get cutters
            var getCutters = new GetObject();
            getCutters.EnablePostSelect(true);
            getCutters.EnablePreSelect(false, true);
            getCutters.GeometryFilter = ObjectType.Brep;
            getCutters.SetCommandPrompt($"Select cutting surfaces");

            GetResult getCuttersResult = getCutters.GetMultiple(1, 0);

            // Check command result
            Result getCuttersCommandResult = getCutters.CommandResult();

            if (getCuttersCommandResult != Result.Success)
            {
                return Result.Failure;
            }

            // If no objects
            if (getCuttersResult != GetResult.Object)
            {
                return Result.Failure;
            }

            ObjRef[] objRefCutters = getCutters.Objects();

            // Get cutter geometry
            IOrderedEnumerable<ObjRef> orderedObjRefCutters = objRefCutters.OrderBy(_ => _.Object().Name);

            var cutters = new List<Brep>();

            foreach (ObjRef orderedObjRefCutter in orderedObjRefCutters)
            {
                // THIS DOES NOT WORK
                Surface srf = orderedObjRefCutter.Surface();

                if (srf == null)
                {
                    continue;
                }

                Brep brep = srf.ToBrep();

                if (brep == null)
                {
                    continue;
                }

                if (brep.IsSurface)
                {
                    var face = brep.Faces[0];

                    if (face.OrientationIsReversed)
                    {
                        brep.Flip();
                    }
                }

                //// THIS WORKS
                //Brep brep = orderedObjRefCutter.Brep();

                //if (brep == null)
                //{
                //    continue;
                //}

                cutters.Add(brep);
            }

            var trimmedTargets = new List<Brep>
            {
                untrimmedTarget
            };

            for (int i = 0; i < cutters.Count; i++)
            {
                Brep cutter = cutters[i];

                var temp = new List<Brep>();

                foreach (Brep trimmedTarget in trimmedTargets)
                {
                    Brep[] results = trimmedTarget.Trim(cutter, CoreSettings.Tolerance);

                    if (results.Length <= 0)
                    {
                        temp.Add(trimmedTarget);
                    }
                    else
                    {
                        temp.AddRange(results);
                    }
                }

                trimmedTargets.Clear();
                trimmedTargets.AddRange(temp);
            }

            for (int i = 0; i < trimmedTargets.Count; i++)
            {
                doc.Objects.AddBrep(trimmedTargets[i], new ObjectAttributes() { Name = $"trimmedTargets[{i}]" });
            }

            doc.Views.Redraw();

            return Result.Success;
        }

The command prompts for an object (the tube) an then prompts for 1 or more cutting surfaces. The goal is to perform multiple trims, one per cutter. Just selecting the tube and the first cutter (the only one I left visible in the doc) the trim that is performed is returning the opposite part of the tube that I was expecting based on the normal of the surface.

Test_16_Full.3dm (62.6 KB)

Well, in the file you posted above, trimming seems to be working as predicted. The side of the base object that is in the direction of the surface normal (the “outside”) is trimmed away, the side opposite of that is kept.

Description:
Trims a brep with an oriented cutter. The parts of the brep that lie inside (opposite the normal) of the cutter are retained while the parts to the outside (in the direction of the normal) are discarded.

If I flip the trimming surface (with the Rhino “Flip” command) the trimmed side changes accordingly. My backfaces are set to display in red so you can see the direction of the surface.


Code used to test:

import rhinoscriptsyntax as rs
import scriptcontext as sc
import Rhino

def TestBrepTrimSide():
    base_obj_id=rs.GetObject("Select base brep",8+16,preselect=True)
    if not base_obj_id: return
    
    trim_obj_id=rs.GetObject("Select trimming surface",8)
    if not trim_obj_id: return
    
    base_obj=rs.coercerhinoobject(base_obj_id)
    base_obj_geo=base_obj.Geometry
    base_attrs=base_obj.Attributes.Duplicate()
    trim_obj=rs.coercerhinoobject(trim_obj_id)
    trim_obj_geo=trim_obj.Geometry
    tol=sc.doc.ModelAbsoluteTolerance
    
    trimmed=base_obj_geo.Trim(trim_obj_geo,tol)
    if trimmed:
        new_obj_ids=[sc.doc.Objects.AddBrep(brep,base_attrs) for brep in trimmed]
        sc.doc.Objects.Delete(base_obj)
        sc.doc.Views.Redraw()
TestBrepTrimSide()

Sorry it has taken me a bit to get back to this. It’s interesting because what you are seeing does not appear to be what I am seeing. I’ve tested this again with my exact 3dm file, unchanged, and this is what my command produces for results.

Animation

Hopefully this animation is clear but my command as it is currently written produces the opposite result from the trim when compared to the direction of the normal of my cutting surface.

Of note is that in my sample command code, if you comment out lines 120 to 133 and uncomment lines 135 to 141, this changes the behavior of my command and I get the result I expect. Something about the way that I am obtaining my Brep from the input (via a Surface instead of directly from the ObjRef) alters the outcome of the trim.

Latest command code:

using System.Collections.Generic;
using System.Linq;
using Rhino;
using Rhino.Commands;
using Rhino.DocObjects;
using Rhino.Geometry;
using Rhino.Input;
using Rhino.Input.Custom;

namespace TestPlugin.Core.Commands
{
    [CommandStyle(Style.Hidden)]
    public class TestCommand : Command
    {
        public TestCommand()
        {
            Instance = this;
        }

        public static TestCommand Instance
        {
            get; private set;
        }

        public override string EnglishName => $"testTrim";

        private ObjRef GetRequiredUntrimmedTarget(RhinoDoc doc)
        {
            // Get object
            var getObject = new GetObject();
            getObject.EnablePostSelect(true);
            getObject.EnablePreSelect(false, true);
            getObject.GeometryFilter = ObjectType.Brep;
            getObject.SetCommandPrompt($"Select object");

            // Check get result
            GetResult getObjectResult = getObject.Get();

            if (getObjectResult != GetResult.Object)
                return null;

            // Check command result
            Result getObjectCommandResult = getObject.CommandResult();

            if (getObjectCommandResult != Result.Success)
                return null;

            // Get untrimmed target
            ObjRef untrimmedTargetObjRef = getObject.Object(0);

            //if (!getObject.ObjectsWerePreselected)
            //{
            //    doc.Objects.Select(untrimmedTargetObjRef, false);

            //    doc.Views.Redraw();
            //}

            return untrimmedTargetObjRef;
        }

        private ObjRef[] GetRequiredCutters(RhinoDoc doc)
        {
            // Get cutters
            var getCutters = new GetObject();
            getCutters.EnablePostSelect(true);
            getCutters.EnablePreSelect(false, true);
            getCutters.GeometryFilter = ObjectType.Brep;
            getCutters.SetCommandPrompt($"Select cutting surfaces");

            // Check get result
            GetResult getCuttersResult = getCutters.GetMultiple(1, 0);

            if (getCuttersResult != GetResult.Object)
                return null;

            // Check command result
            Result getCuttersCommandResult = getCutters.CommandResult();

            if (getCuttersCommandResult != Result.Success)
                return null;

            // Get cutters
            ObjRef[] cutterObjRefs = getCutters.Objects();

            //if (!getCutters.ObjectsWerePreselected)
            //{
            //    doc.Objects.Select(cutterObjRefs, false);

            //    doc.Views.Redraw();
            //}

            return cutterObjRefs;
        }

        protected override Result RunCommand(RhinoDoc doc, RunMode mode)
        {
            // Get untrimmed target
            ObjRef untrimmedTargetObjRef = GetRequiredUntrimmedTarget(doc);

            if (untrimmedTargetObjRef == null)
                return Result.Failure;

            Brep untrimmedTarget = untrimmedTargetObjRef.Brep();

            // Get cutters
            ObjRef[] cutterObjRefs = GetRequiredCutters(doc);

            if (cutterObjRefs == null)
                return Result.Failure;

            if (cutterObjRefs.Length <= 0)
                return Result.Failure;

            IOrderedEnumerable<ObjRef> orderedObjRefCutters = cutterObjRefs.OrderBy(_ => _.Object().Name);

            var cutters = new List<Brep>();

            foreach (ObjRef orderedObjRefCutter in orderedObjRefCutters)
            {
                // THIS DOES NOT WORK
                Surface srf = orderedObjRefCutter.Surface();

                if (srf == null)
                {
                    continue;
                }

                Brep brep = srf.ToBrep();

                if (brep == null)
                {
                    continue;
                }

                //// THIS WORKS
                //Brep brep = orderedObjRefCutter.Brep();

                //if (brep == null)
                //{
                //    continue;
                //}

                cutters.Add(brep);
            }

            // Process trims
            var trimmedTargets = new List<Brep>
            {
                untrimmedTarget
            };

            for (int i = 0; i < cutters.Count; i++)
            {
                Brep cutter = cutters[i];

                var temp = new List<Brep>();

                foreach (Brep trimmedTarget in trimmedTargets)
                {
                    Brep[] results = trimmedTarget.Trim(cutter, 0.001);

                    if (results.Length <= 0)
                        temp.Add(trimmedTarget);
                    else
                        temp.AddRange(results);
                }

                trimmedTargets.Clear();
                trimmedTargets.AddRange(temp);
            }

            for (int i = 0; i < trimmedTargets.Count; i++)
            {                
                var attribs = new ObjectAttributes()
                {
                    Name = $"trimmedTargets[{i}]",
                    LayerIndex = untrimmedTargetObjRef.Object().Attributes.LayerIndex
                };

                doc.Objects.AddBrep(trimmedTargets[i], attribs);
            }

            // Hide original
            doc.Objects.Hide(untrimmedTargetObjRef, false);

            doc.Views.Redraw();

            return Result.Success;
        }
    }
}

Well, I don’t know exactly what orderedObjRefCutter.Surface() is actually doing, looks like it’s getting the underlying surface, which is actually flipped relative to the brep representation. I can repeat what you’re seeing with the following:

import rhinoscriptsyntax as rs
import scriptcontext as sc
import Rhino

def TestBrepTrimSide():
    base_obj_id=rs.GetObject("Select base brep",8+16,preselect=True)
    if not base_obj_id: return
    
    trim_obj_id=rs.GetObject("Select trimming surface",8)
    if not trim_obj_id: return
    
    base_obj=rs.coercerhinoobject(base_obj_id)
    base_obj_geo=base_obj.Geometry
    base_attrs=base_obj.Attributes.Duplicate()
    
    trim_obj=rs.coercerhinoobject(trim_obj_id)
    trim_obj_geo=trim_obj.Geometry
    
    tol=sc.doc.ModelAbsoluteTolerance
    
    #trims expected side
    #trimmed=base_obj_geo.Trim(trim_obj_geo,tol)
    
    #trims opposite side
    test_srf=trim_obj_geo.Surfaces[0]
    trimmed=base_obj_geo.Trim(test_srf.ToBrep(),tol)
    
    if trimmed:
        new_obj_ids=[sc.doc.Objects.AddBrep(brep,base_attrs) for brep in trimmed]
        sc.doc.Objects.Delete(base_obj)
        sc.doc.Views.Redraw()
TestBrepTrimSide()

Basically it’s getting the underlying surface and then converting it back to a Brep.

This then trims the same (natural?) side no matter which way the trimming surface is flipped in the Rhino interface. I need to check on whether OrientationIsReversed() detects this situation, but no time now…

I don’t think this step is really necessary, I don’t see any advantage to getting the underlying surface from the trim Brep and then converting it back to a Brep again when you already have what you need to trim in the first place.

I suppose fetching the trimming surface as a Surface first and then converting it to a Brep may be a misunderstanding on my part. My assumption was that the native object in Rhino is a Surface (because that’s the tool that I used and how it appears if I select it and run the “What” command) and so the correct way to handle it would be to get it first as a Surface.

I did try checking the OrientationIsReversed property on the Face of the Brep and it was returning false which left me further confused about the behavior I was seeing.

It appears that any type of existing surface or polysurface you pick from the Rhino document using GetObject() is stored as a Brep - except an for extrusion if you have UseExtrusions enabled.

Used this to test:

import Rhino

def GetObjectBasic(prompt):
    go = Rhino.Input.Custom.GetObject()
    go.SetCommandPrompt(prompt)
    get_rc = go.Get()
    if get_rc==Rhino.Input.GetResult.Object:
        return go.Object(0).Object()

rhobj=GetObjectBasic("Pick a surface/polysurface object")
if rhobj:
        geo=rhobj.Geometry
        print type(geo)

You can try it on any type of Rhino surface object that What describes - a surface, a plane surface, a sum surface, a polysurface, etc. - it prints <type 'Brep'>. If you have extrusions enabled and you pick an extrusion object, it does print `<type ‘Extrusion’>.

On the other hand, if you are creating geometry via RhinoCommon, before adding it to the document, it will still have the original geometry type that the creation method returns.

Edit - oh, by the way, if I run the following scriptlet and pick the green cutting surface in your original file -

import rhinoscriptsyntax as rs

obj_id=rs.GetObject()
if obj_id:
    rhobj=rs.coercerhinoobject(obj_id)
    geo=rhobj.Geometry
    print geo.Faces[0].OrientationIsReversed

– It prints True

If I run Flip in Rhino to flip the surface normal, the scriptlet then prints False. So the surface as it exists in the file is (according to Rhino’s rules) ‘flipped’.