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.
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)
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()
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?
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.
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.
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.
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â.