Duplicate clipped curves in Rhino 5

Hi everyone, I’m a C# developer trying to wrap my head around Rhino’s API v5.

I’m having an issue regarding clipping planes.
I need to copy a line that is currently being clipped. The problem is that when I call

GetObject gc = new GetObject();
<...>
gc.Object(0).Curve().DuplicateCurve();

The duplicate will, of course, get me the whole curve and not just the visible (non clipped) part.
The purpose of this is that I later want to display the (trimmed) duplicate with a DisplayConduit.

In the image you can see that the curves of interest, highlighted with a display conduit in purple, are visible beyond the clipping plane.
I’ve tried to generate a planeSurface object from the clipping plane, find the intersection between the plane and the line and then Trim() the duplicate up to the intersection point, but this didn’t get me anywhere useful. Any suggestion?
What I want to do is pretty much replicate Rhino’s behavior when I click on a clipped line, which is highlight only the visible portion.

Why didn’t that work? I think that’s the first thing I would have tried. If you are able to feed Curve.Trim() an interval you want to keep, the rest (what is clipped) should be trimmed away. It may be a bit tricky to figure out which side of the clipping plane the curve is on, though.

Thanks for the answer, it’s probably my fault, but I’m not able to get the plane itself to do the
Rhino.Geometry.Intersect.Intersection.CurvePlane(curve,clippingPlane)
the best I can do is duplicating the geometry of the RhinoObject in question:

var geo = clippingPlaneReference.Geometry.Duplicate();
Brep.TryConvertBrep(geo);

Which gives me just the small square that represents the actual plane, needless to say the overextended lines do not intersect that brep (image above).

Finding the right “side” could also be another challenge.

import rhinoscriptsyntax as rs
import scriptcontext as sc
import Rhino

def TrimCurveByClipppingPlane():
    crvID=rs.GetObject("Select curve",4,preselect=True)
    if not crvID: return
    
    clipID=rs.GetObject("Select clipping plane",rs.filter.clippingplane)
    if not clipID: return
    
    crv=sc.doc.Objects.Find(crvID).Geometry
    clip=sc.doc.Objects.Find(clipID).Geometry
    cp_plane=clip.Plane
    
    tol=sc.doc.ModelAbsoluteTolerance
    insec=Rhino.Geometry.Intersect.Intersection.CurvePlane(crv,cp_plane,tol)
    if insec:
        params=[]
        for event in insec:
            params.append(event.ParameterA)
        splits=crv.Split(params)
        if splits:
            splitIDs=[sc.doc.Objects.AddCurve(split) for split in splits]
            rs.DeleteObject(crvID)
    #now it's up to you to figure out which parts to delete...
    sc.doc.Views.Redraw()
TrimCurveByClipppingPlane()

Edit: Hmmm, something is not working here, if I move the clipping plane after the first placement, the clipping plane surface’s plane does not update. So this won’t work. wonder if this is a known bug… :confused:

Yes, this appears to be a bug in V5, it is working in V6… Maybe I can find a workaround.

Hi @Helvetosaur, @Riccardo_Di_Lorenzo,

i can confirm this is an old bug which was fixed in V6. Below is an example how to make it work in V5 and V6 and how to get the pieces which are on the positive side of the clipping plane after splitting.

CurveClipWithClippingPlane.py (1.3 KB)

_
c.

@Helvetosaur, @clement
Thank you guys for your interest, I don’t think I can upgrade my Rhino version, so I guess I’ll have to find a workaround or stick with it.
@clement I’ve checked this XformWorldToCPlane function in python, are you aware of the equivalent in C# or at least point me in the right direction?

I think @clement’s version should work in V5 as well.

For C# I think you need to call methods via RhinoCommon.

The api for Rhino.Geometry.Transform is here

http://developer.rhino3d.com/5/api/RhinoCommonWin/html/T_Rhino_Geometry_Transform.htm

The plane-to-plane transform is here:

http://developer.rhino3d.com/5/api/RhinoCommonWin/html/M_Rhino_Geometry_Transform_PlaneToPlane.htm
(you would feed it the world and current cplanes)

World CPlane is here:

http://developer.rhino3d.com/5/api/RhinoCommonWin/html/T_Rhino_Geometry_Plane.htm

The active view CPlane needs to be gotten via the active Rhino document, I think you need to go through:

Rhino.RhinoDoc.Views.ActiveView.ActiveViewport.ConstructionPlane()
http://developer.rhino3d.com/api/RhinoCommon/html/P_Rhino_DocObjects_Tables_ViewTable_ActiveView.htm

There are a lot of C# code samples in the api docs.
HTH, --Mitch

@Riccardo_Di_Lorenzo, @Helvetosaur,

the view CPlane is not required to get the transform. You may change this line in my example:

cpt = rs.XformWorldToCPlane(mid, plane)

to this to get the same transform:

vec = mid - plane.Origin
cpt = Rhino.Geometry.Point3d(vec*plane.XAxis, vec*plane.YAxis, vec*plane.ZAxis)

Note that plane is the clipping plane. I’ve transformed the midpoint of each splitted curve segment from there to world coordinates so i can measure it’s Z coordinate.

_
c.

I tried what you suggested and it works fine, also, using the CPlane or translating the World Plane seems to create no difference (as expected).
Unfortunately the problem seems to be more complicated than I originally thought. Since I’m dealing with multiple clipping planes the approach “translate the middle an see if middle.Z is positive” doesn’t exactly work (though is definitely the best solution I have for now).

Imagine a line clipping multiple planes more than once (think a circle with r>distance between opposite facing clipPlanes), By taking the positive portion against each of the clipping planes I effectively end up having two “half circles”. For some reason I’m not able to calculate the boolean intersection between the two breps, and when displaying them I end up “recreating” the whole shape, at least visually, which extends beyond the clipping planes.
I attach some code that uses your good idea of the translation (using the CPlane as domain basis, as suggested by @Helvetosaur)

private void GetBrepFromVisibleCurve(Curve crv, out Brep curveBrep)
        {           
            var tmp1 = new Brep();//all the segments
            var tmp2 = new Brep();//only the visible segments

            for (int i = 0; i < clipIDList.Count; i++)
            {
                var clipping = new ObjRef(clipIDList[i]); //each of the clipping planes
                var intersections = Intersection.CurvePlane(crv, clipping.ClippingPlaneSurface().Plane, 1e-5);
                if (intersections != null)
                {                    
                    var splitList = new List<Double>();
                    foreach (var inters in intersections)
                    {
                        if (inters.IsPoint)
                        {
                            splitList.Add(inters.ParameterA);//Because I can have more than 1 inters. with one plane
                        }
                    }
                    var pieces = crv.Split(splitList);

                    foreach (var segment in pieces)
                    {
                        tmp1.AddEdgeCurve(segment);
                        var mid = segment.PointAt(segment.Domain.Mid);
                        //determine the right side to add to the list by translating an internal point
                        //of the segment (mid) on the clipping plane ref frame
                        var trs = Transform.ChangeBasis(doc.Views.ActiveView.ActiveViewport.ConstructionPlane(), clipping.ClippingPlaneSurface().Plane);
                        mid.Transform(trs);
                        if (mid.Z > 0)
                        {
                            tmp2.AddEdgeCurve(segment);                          
                        }
                    }
                }
            }            
            var intersection = Brep.CreateBooleanIntersection(tmp1, tmp2, 1);//<---EMPTY SET
//....standardize,compact the brep[], display....

@Riccardo_Di_Lorenzo,

i am not sure why you use Transform.ChangeBasis together with the view CPlane ? What does this have to do with breps ? Our examples work for curves and clipping planes.

The evaluation if a mid point of a splitted curve segment is on one side of the clipping plane is world based, not view based or based on view construction planes. Note what i’ve shown in my reply above which explains how to replace rs.XFormWorldToCPlane, there is no view construction plane involved.

You might first get this working with a single clipping plane, not only with line curves but closed curves. These require extra checks as you might get multiple segments visible on one side of a clipping plane after splitting, because the curve falls apart at the start / end point.

Once you got this working you can loop all split results (segments) through the remaining clipping planes. If the mid point Z-value check passes all clipping planes, then the splitted segment is visible. Does that help ?

_
c.

@clement

I believe that both approaches do the same thing, the change basis would apply a transformation matrix to move the segment into the plane reference frame and then calculate the Z coordinate of the mid point relative to its reference frame. But I agree that your approach that uses scalar products is simpler, faster and more elegant :slight_smile:.
That said I’m not sure I explained to you the problem that having multiple clipping plane introduces, hoping that a picture says more than 1000 words here’s what happens when you Z-check your segments against multiple, opposite facing, clipping planes:

as you can see I can correctly “hide” the clipped part, but when you display the result of all the clipping planes you graphically have the effect have the union of the visible segments (imagine overlapping the two purple polylines).
The breps got involved the moment I tried to get the boolean intersection of the two segment sets, unfortunately to no avail. Maybe I’m misunderstanding how the boolean intersection function works, but what I want to do is, as you can imagine, get only the 2 central parts of the purple polyline. Curve class seems to have the same methods to get the boolean intersection, but those too led nowhere.

This is because the cpt.Z>0 constraint is not enough, or at least is not sufficient, I’m not sure I get what you’re proposing in the last phrase.

Hi Riccardo,

please see my example file and script. First you get all intersections of each curve with all clipping planes, then split, then check the mid points of all split results against all planes. If the segment midpoint gives a positive value, the segment is visible.

It also handles the case of curves which are not intersecting with a clipping plane and of closed curves which would result into 2 segments because of the seam. It works with oposite clipping planes as far as i can see. Note that you see the clipping only in perspective view, all other views ignore the clipping planes.

CurvesClipWithClippingPlanes.3dm (48.2 KB)
CurvesClipWithClippingPlanes.py (2.7 KB)

Does that help ?
_
c.

Thank you very much, I came up with a similar solution in C# and it seems to work fine :slight_smile: