I am writing a script to classify nested curves. The goal is to identify the outer bounding curves (the red contours in the attached picture) and the inside curves (the blue ones).
My current script works, but it becomes very slow (or ‘laggy’) when processing a large number of objects. How can I optimize this script for better performance?
-- coding: utf-8 --
import rhinoscriptsyntax as rs
import scriptcontext as sc
import Rhino
import math
def cpcs_filt(rhino_object, geometry, component_index):
try:
if rs.IsCurve(rhino_object.Id) and rs.IsCurveClosed(rhino_object.Id) and rs.IsCurvePlanar(rhino_object.Id):
return True
except Exception:
pass
return False
def to_plane_xy(pt, plane):
v = pt - plane.Origin
x = v.X * plane.XAxis.X + v.Y * plane.XAxis.Y + v.Z * plane.XAxis.Z
y = v.X * plane.YAxis.X + v.Y * plane.YAxis.Y + v.Z * plane.YAxis.Z
return (x, y)
def point_in_polygon_strict(x, y, poly):
“”“Strict point-in-polygon: True only if strictly inside (not on edge/vertex).”“”
n = len(poly)
if n < 4:
return False
eps = 1e-9
j = n - 1
inside = False
for i in range(n):
xi, yi = poly[i]
xj, yj = poly[j]
# on vertex
if abs(x - xi) < eps and abs(y - yi) < eps:
return False
# on edge (colinear + between)
dx1 = x - xi; dy1 = y - yi
dx2 = xj - xi; dy2 = yj - yi
cross = dx1 * dy2 - dy1 * dx2
if abs(cross) < 1e-9:
if (min(xi,xj) - eps <= x <= max(xi,xj) + eps) and (min(yi,yj) - eps <= y <= max(yi,yj) + eps):
return False
# ray cast
if ((yi > y) != (yj > y)) and (x < (xj - xi) * (y - yi) / (yj - yi + 0.0) + xi):
inside = not inside
j = i
return inside
def sample_curve_points(crv, sample_count):
pts =
try:
params = crv.DivideByCount(sample_count, True)
if params:
pts = [crv.PointAt(t) for t in params]
else:
dom = crv.Domain
pts = [crv.PointAt(dom.T0 + (dom.Length * i / float(sample_count))) for i in range(sample_count + 1)]
except Exception:
dom = crv.Domain
pts = [crv.PointAt(dom.T0 + (dom.Length * i / float(sample_count))) for i in range(sample_count + 1)]
return pts
def classify_by_nesting_parity(guids,sample_count=160):
items = []
for g in guids:
crv = rs.coercecurve(g)
if not crv or not crv.IsClosed:
continue
ok, plane = crv.TryGetPlane()
if not ok:
plane = Rhino.Geometry.Plane.WorldXY
items.append((g, crv, plane))
if not items:
print("No valid closed planar curves.")
return [], []
# Build sampled polygon (in its own plane coords) and also keep sampled 3D points
polygons = {} # guid -> (poly2_coords_list, plane, sample_pts3)
for g, crv, plane in items:
pts3 = sample_curve_points(crv, sample_count)
poly2 = [to_plane_xy(p, plane) for p in pts3]
if poly2 and poly2[0] != poly2[-1]:
poly2.append(poly2[0])
polygons[g] = (poly2, plane, pts3)
inside = []
bounding = []
# For each curve A count how many other curves strictly contain it (all samples of A inside B)
total = len(items)
for idx, (gA, crvA, plA) in enumerate(items):
# use the sample points already computed for A (if present)
if gA in polygons:
ptsA = polygons[gA][2]
else:
ptsA = sample_curve_points(crvA, sample_count)
container_count = 0
for gB, (polyB, plB, _) in polygons.items():
if gB == gA:
continue
if not polyB:
continue
# test all sample points of A in B's polygon (project each point to B plane)
all_inside = True
for p in ptsA:
x, y = to_plane_xy(p, plB)
if not point_in_polygon_strict(x, y, polyB):
all_inside = False
break
if all_inside:
container_count += 1
# classification by parity: odd -> inside; even -> bounding
if (container_count % 2) == 1:
inside.append(gA)
try:
rs.ObjectColor(gA, (0, 255, 0))
except: pass
else:
bounding.append(gA)
try:
rs.ObjectColor(gA, (255, 0, 0))
except: pass
print("Classified: inside = {}, bounding = {} (sample_count={})".format(len(inside), len(bounding), sample_count))
return inside, bounding
def main():
guids = rs.GetObjects(“Select closed planar curves (filtered)”, rs.filter.curve,
custom_filter=cpcs_filt, preselect=True)
if not guids:
print(“No curves selected.”)
return
,
rs.EnableRedraw(False)
classify_by_nesting_parity(guids)
rs.EnableRedraw(True)
if name == “main”:
main()



