Geometry.Curve.Offset returning only one curve when self-intersecting

Hi,

Reading the docs, the Curve.Offstet method should return an Array with more than one curve when the curve offset results in self intersections: " If the original curve had kinks or the offset curve had self intersections, you will get multiple segments in the output array.", but this is not happening. The output is just the overlaping portion of the resulting offset, I think its a bug:


If I try with Rhino offset command and “Trim = No” I get a correct offset, but there is not a “Trim” property in the RhinoCommon Method.

Python Code:

from scriptcontext import doc
import Rhino
import rhinoscriptsyntax as rs
from Rhino import Geometry as rg

plane = rg.Plane.WorldXY
tol = doc.ModelAbsoluteTolerance
off = x.Offset(plane, 6, tol, rg.CurveOffsetCornerStyle(2))

print off

a = off

I tried the 3 offset methods already (with vector and point), with the same result. How can I get the complete output array, or the offset without trim?

This is the curve:
intersection bug curve.3dm (25.5 KB)

Thanks!

Hi,

This is a known limitation of the offset Curve command.

It should be solved in Rhino WIP.

1 Like

I made a (very messy) workaround code in python that works in R7, if anyone interested I can clean the code and pass it to a C# component. It works with nurbs but needs aditional code for almost-intersecting curves.

The steps:

  1. Detect if the curve self intersects, and then trim it
  2. Find if the resultant curves can be closed
  3. Obtain the segments of the open lines and offset it
  4. Shatter all curves with intersection
  5. Clean the curves based on distance
  6. Join everything

The code (I plan to clean it up when I have time):

from scriptcontext import doc
import Rhino
import rhinoscriptsyntax as rs
from Rhino import Geometry as rg
from ghpythonlib.parallel import run
plane = rg.Plane.WorldXY
tol = doc.ModelAbsoluteTolerance


c = []
d = []

def shatterAll(crvs):
    if len(crvs) == 1:
        return crvs
    else:
        params = []
        for i in range(len(crvs)):
            params.append([])
        for i in range(len(crvs)):
            for j in range(i+1, len(crvs)):
                try:
                    events = rg.Intersect.Intersection.CurveCurve(crvs[i], crvs[j], tol, tol)
                    for event in events:
                        params[i].append(event.ParameterA)
                        params[j].append(event.ParameterB)
                except:
                    pass
        sub_curves = []
        for i in range(len(crvs)):
            if params[i]:
                split = crvs[i].Split(params[i])
                for split in split:
                    sub_curves.append(split)
            else:
                sub_curves.append(crvs[i])
        return sub_curves

def offsetCrvs(x):
    #the nasty mess is needed?
    defaultOffset = x.Offset(plane, distance, tol, rg.CurveOffsetCornerStyle(2))
    if defaultOffset[0].IsClosed:
        b = []
        #verify if curve interscets itself:
        int = rg.Intersect.Intersection.CurveSelf(x, tol)
        intCount = int.Count
        if intCount > 0:
            params = []
            for event in int:
                params.append(event.ParameterA)
                params.append(event.ParameterB)
            preSplit = rg.Curve.Split(x, params)
            for i, crvSplit in enumerate(preSplit):
                if crvSplit.IsClosed:
                    if str(crvSplit.ClosedCurveOrientation()) == 'Clockwise':
                        preSplit[i].Reverse()
        else:
            preSplit = [x]
        a =  preSplit
        valid = []
        
        #TODO: verify if bug will occur
        
        
        for crvS in preSplit:
            crvS = crvS.ToNurbsCurve()
            #get curve segments
            if isinstance(crvS, rg.NurbsCurve):
                SpanCount = crvS.SpanCount
                spanParams = []
                for i in range(SpanCount):
                    spanParams.append(crvS.SpanDomain(i)[0])
                    spanParams.append(crvS.SpanDomain(i)[1])
                splitAtSpans = crvS.Split(spanParams)
                toSegments = splitAtSpans
            else:
                toSegments = crvS
            try:
                segments = toSegments.DuplicateSegments()
            except:
                segments = toSegments
            #print segments
            for crv in segments:
                #verify if is closed
                offset = crv.Offset(plane, distance, tol, rg.CurveOffsetCornerStyle(2))
                if offset != None:
                    b.append(offset[0])
            #join reusults
            joined = rg.Curve.JoinCurves(b)
            #verify self intersections
            for crv in joined:
                selfInt = rg.Intersect.Intersection.CurveSelf(crv, tol)
                intersectionCount = selfInt.Count
                #split the damn crv:
                if intersectionCount > 0:
                    params = []
                    for event in selfInt:
                        params.append(event.ParameterA)
                        params.append(event.ParameterB)
                    split = crv.Split(params)
                    #filter the thingy things
                    for crvs in split:
                        #verify the distance from the input crv
                        minDist = crvs.ClosestPoints(x)
                        minDist = minDist[1].DistanceTo(minDist[2])
                        if minDist >= (distance-tol):
                            valid.append(crvs)
                else:
                    valid.append(crv)
        
        #need to cleanup the mess:
        dist_ = distance
        shatter = shatterAll(valid)
        if distance < 0:
            dist_ = abs(dist_)
        for crvs in shatter:
            cps = x.ClosestPoints(crvs)
            dist = cps[1].DistanceTo(cps[2])
            if dist > (dist_-tol):
                if crvs.IsValid:
                    if crvs.GetLength() > tol:
                        d.append(crvs.ToNurbsCurve())
    
        join = rg.Curve.JoinCurves(d, dist_)
        for crv in join:
            c.append(crv.ToNurbsCurve())
        
    else:
        c.append(defaultOffset[0])
    return c


run(offsetCrvs, x, True)
if bothSides:
    distance = -distance
    run(offsetCrvs, x, True)```

inputs: x[curve], distance[float], bothSides[bool]
output: c
1 Like