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:
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?
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:
Detect if the curve self intersects, and then trim it
Find if the resultant curves can be closed
Obtain the segments of the open lines and offset it
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
This is a good first cut! I’m running into the same issue, but need it to work with polycurves as well. I was hoping to get to steal your workaround while we wait for Rhino 8, but it looks like I’ll have to do some additional work:
There would be an easier work around if we could have a trim option in RhinoCommon just like we do in the rhino command, then we could choose which loops to keep and which to throw out. I’m playing with the Rhino 8 version now, and it’s better behaved, but I’m still not happy about it. If the input curve isn’t self intersecting we get better behavior, but it if is, there’s some awkward results when offsetting on both sides.
So I guess I’d comeback your observation that there is not an option for controlling whether or not the offset is trimmed in the RhinoCommon API. @dale Is this something we could have?
I made a new version of this workaround that is compatible with polycurves too, but I need to decouple from a much larger script (it gets the distance from user strings, and offset both sides right now).