The Polyline
2 min read•1 page
The Polyline
A Polyline is simply a collection of points connected by straight line segments in sequential order. Unlike a NURBS Curve, it has no mathematical curvature—it is perfectly linear between each vertex. If the first and last points are identical, the polyline is considered Closed (a Polygon!)
python
1class Polyline:2 def __init__(self, points: list['Point3d']):3 """A polyline is an ordered sequence of points."""45 # It must have at least 2 points to form a segment6 if len(points) < 2:7 raise ValueError("Polyline requires at least 2 points")89 self.Points = points1011 @property12 def IsClosed(self) -> bool:13 """Returns True if the first and last points are coincident."""14 return self.Points[0] == self.Points[-1]
Point Count
4.002 min read•1 page
Segments:
Under the hood, a polyline is just an array of Point coordinates. Geometry engines create the "segments" dynamically by drawing a line from index `[i]` to `[i+1]`. A polyline with `N` points will always have `N-1` segments.
python
1def PolylineSegments(polyline: 'Polyline') -> list['Line']:2 """Extracts individual line segments from a polyline."""34 segments = []56 # A polyline with N points has N-1 segments7 for i in range(len(polyline.Points) - 1):8 ptA = polyline.Points[i]9 ptB = polyline.Points[i+1]1011 # Create a Line from point A to point B12 segment = Line(ptA, ptB)13 segments.append(segment)1415 return segments
Highlight Segment
0.001 min read•1 page
Length Calculation:
Calculating the length of a Polyline is incredibly fast and simple compared to NURBS Curves. You literally just loop through the points, calculate the distance between each pair (Pythagorean theorem), and add them up.
python
1def PolylineLength(polyline: 'Polyline') -> float:2 """Calculates the total length of a polyline."""34 total_length = 0.056 # Just sum up the distance between each consecutive point7 for i in range(len(polyline.Points) - 1):8 ptA = polyline.Points[i]9 ptB = polyline.Points[i+1]1011 total_length += ptA.DistanceTo(ptB)1213 return total_length
2 min read•1 page
Search: Closest Point:
To find the closest point on a Polyline, the algorithm simply loops through every individual line segment, finds the closest point on that specific segment, and keeps the overall winner with the shortest distance.
python
1def ClosestPointOnPolyline(polyline: 'Polyline', test_pt: 'Point3d') -> 'Point3d':2 """Finds the closest point on a polyline to a test point."""34 closest_pt = None5 min_dist = float('inf')67 # Check every single line segment8 for i in range(len(polyline.Points) - 1):9 segment = Line(polyline.Points[i], polyline.Points[i+1])1011 # Closest point on this specific line segment12 pt_on_segment = segment.ClosestPoint(test_pt, limitToFiniteSegment=True)1314 dist = test_pt.DistanceTo(pt_on_segment)15 if dist < min_dist:16 min_dist = dist17 closest_pt = pt_on_segment1819 return closest_pt
Test Point X
3.00Test Point Y
3.001 min read•1 page
Explode:
Exploding a Polyline breaks the continuous path into an array of completely independent, disconnected line segments. This is identical to the "Explode" command in CAD software like AutoCAD or Rhino.
python
1def ExplodePolyline(polyline: 'Polyline') -> list['LineCurve']:2 """Breaks a polyline into disconnected line segments."""34 segments = []56 # Creates independent LineCurve objects for each segment7 for i in range(len(polyline.Points) - 1):8 ptA = polyline.Points[i]9 ptB = polyline.Points[i+1]10 segments.append(LineCurve(ptA, ptB))1112 return segments
Explode Distance
0.002 min read•1 page
PointAt(t):
Evaluates the polyline at parameter
t (0.0 to 1.0). The algorithm calculates the cumulative lengths of the segments to determine which segment contains the target parameter, then performs a linear interpolation on that segment.python
1def PointAt(polyline: 'Polyline', t: float) -> 'Point3d':2 """Evaluates the polyline at a parameter t (0.0 to 1.0) along its total length."""34 total_len = polyline.Length5 target_dist = t * total_len67 accumulated = 0.08 for i in range(len(polyline.Points) - 1):9 ptA = polyline.Points[i]10 ptB = polyline.Points[i+1]11 seg_len = ptA.DistanceTo(ptB)1213 if accumulated + seg_len >= target_dist or i == len(polyline.Points) - 2:14 # Linear interpolation on this segment15 seg_t = 0.016 if seg_len > 0:17 seg_t = (target_dist - accumulated) / seg_len18 return ptA + (ptB - ptA) * seg_t1920 accumulated += seg_len21 return polyline.Points[-1]
Parameter (t)
0.502 min read•1 page
TangentAt(t):
Calculates the tangent vector (direction) of the polyline at parameter
t (0.0 to 1.0). Since a polyline has sudden direction changes at vertices, the tangent is constant across each segment and discontinuous at vertices.python
1def TangentAt(polyline: 'Polyline', t: float) -> 'Vector3d':2 """Calculates the unit tangent vector of the polyline at a parameter t."""34 total_len = polyline.Length5 target_dist = t * total_len67 accumulated = 0.08 for i in range(len(polyline.Points) - 1):9 ptA = polyline.Points[i]10 ptB = polyline.Points[i+1]11 seg_len = ptA.DistanceTo(ptB)1213 if accumulated + seg_len >= target_dist or i == len(polyline.Points) - 2:14 # Direction vector of this segment15 direction = ptB - ptA16 return direction.Unitize()1718 accumulated += seg_len19 return (polyline.Points[-1] - polyline.Points[-2]).Unitize()
Parameter (t)
0.501 min read•1 page
Reverse():
Inverts the order of vertices within the polyline. This reverses its travel direction, which is critical when matching orientations for lofting, sweeping, or extrusion operations in CAD.
python
1def Reverse(polyline: 'Polyline') -> 'Polyline':2 """Reverses the vertex order of the polyline, flipping its direction."""34 # In-place reversal of the coordinate array5 polyline.Points.reverse()6 return polyline
Reverse Direction
2 min read•1 page
IsSelfIntersecting():
Determines if a polyline crosses over itself. A simple polyline has no self-intersections, whereas a complex polyline intersects itself. Self-intersection tests are critical pre-validation steps for extrusion or polygon offset operations.
python
1def IsSelfIntersecting(polyline: 'Polyline') -> bool:2 """Returns True if any non-consecutive segments cross each other."""34 # Check all pairs of non-adjacent segments5 n = len(polyline.Points)6 for i in range(n - 2):7 segA = Line(polyline.Points[i], polyline.Points[i+1])8 for j in range(i + 2, n - 1):9 # Skip adjacent segment since they share a vertex10 segB = Line(polyline.Points[j], polyline.Points[j+1])1112 if segA.IntersectsWith(segB, limitToSegments=True):13 return True14 return False
Control Vertex Y
5.002 min read•1 page
Divide(n):
Distributes points along a polyline at even intervals.
python
1def DividePolylineByCount(pline: Polyline, count: int) -> list[Point3d]:2 """Places points at equal distances along the entire length of the polyline."""34 # 1. Find the total length of the path5 total_length = pline.Length67 # 2. Calculate the distance between each point8 segment_length = total_length / count910 # 3. Evaluate the polyline at each specific length along the path11 points = []12 for i in range(count + 1):13 target_len = i * segment_length14 pt = pline.PointAtLength(target_len)15 points.append(pt)1617 return points
Divide Count
8.004 min read•1 page
Offset(d)::
Generates a parallel path at a fixed distance, mitering or rounding the corners automatically.
python
1def OffsetPolyline(polyline: 'Polyline', distance: float) -> 'Polyline':2 """Naive implementation"""3 """Offsets a 2D/planar polyline by a given distance using miter corner offset."""4 if distance == 0.0:5 return polyline67 offset_points = []8 n = len(polyline.Points)9 up = Vector3d(0, 0, 1)1011 # 1. Compute segment directions and unit normals12 dirs = []13 norms = []14 for i in range(n - 1):15 d = (polyline.Points[i+1] - polyline.Points[i]).Unitize()16 dirs.append(d)17 n_vec = Vector3d.CrossProduct(d, up).Unitize()18 norms.append(n_vec)1920 # 2. Compute offset vertices21 for i in range(n):22 if i == 0:23 offset_points.append(polyline.Points[i] + norms[0] * distance)24 elif i == n - 1:25 offset_points.append(polyline.Points[i] + norms[-1] * distance)26 else:27 n1 = norms[i-1]28 n2 = norms[i]2930 miter_dir = (n1 + n2).Unitize()31 dot = n1.DotProduct(n2)32 cos_half = ((1.0 + dot) / 2.0) ** 0.53334 miter_len = distance35 if cos_half > 0.1:36 miter_len = distance / cos_half3738 offset_points.append(polyline.Points[i] + miter_dir * miter_len)3940 return Polyline(offset_points)
Offsetting a simple Line just moves it. But offsetting a Polyline is complex: if you move two connected segments outward, they detach at the corner. If you move them inward, they crash into each other.
Offset Distance
0.50The offset algorithm automatically solves this by either extending the segments until they intersect (a Sharp corner), or blending them with an arc (a Round/Fillet corner). This is the foundation for generating wall thicknesses, setback lines, and buffer zones in GIS.
2 min read•1 page
isClosed()::
Validates if the polyline forms a continuous loop by checking if the start and end coordinates are identical.
python
1def CheckPolylineClosure(pline: Polyline, tolerance: float = 0.001) -> bool:2 """Checks if a Polyline forms a closed loop."""34 # Built-in check (often strict equality)5 is_closed = pline.IsClosed67 # More robust check: is the start point extremely close to the end point?8 start_pt = pline[0]9 end_pt = pline[pline.Count - 1]1011 is_closed_in_tolerance = start_pt.DistanceTo(end_pt) <= tolerance1213 return is_closed_in_tolerance
A polyline is just an ordered list of points. If the very last point in the list shares the exact same `[x, y, z]` coordinates as the very first point, the polyline is considered "Closed" (a Polygon).
Close the Gap
0.00Why this matters: Every geometric operation that creates a surface, calculates an area, or performs a Boolean difference requires a closed loop. If the start and end points are off by even `0.0000001` units, the operation will fail silently. Always check closure!
2 min read•1 page
ContainsPoint(Point3d):
Determines if a planar point lies inside the boundary of a closed polyline (polygon) using ray casting.
python
1def ContainsPoint(polyline: 'Polyline', pt: 'Point3d') -> bool:2 """Ray-casting algorithm to determine if a point is inside a closed 2D/planar polyline."""3 if not polyline.IsClosed:4 return False56 inside = False7 n = len(polyline.Points)8 x, y = pt.X, pt.Y910 for i in range(n - 1):11 p1 = polyline.Points[i]12 p2 = polyline.Points[i+1]1314 # Test if ray crosses boundary15 if ((p1.Y > y) != (p2.Y > y)) and (x < (p2.X - p1.X) * (y - p1.Y) / (p2.Y - p1.Y) + p1.X):16 inside = not inside1718 return inside
The ray-casting algorithm casts an infinite horizontal ray from the test point to the right. Each time the ray crosses a polyline segment, we toggle an state. If the boundary is crossed an odd number of times, the point is inside.
Test Point X
0.00Test Point Y
0.503 min read•1 page
AreaAndCentroid:
Calculates the enclosed area and centroid (center of mass) of a closed planar polyline.
python
1def ComputeAreaAndCentroid(polyline: 'Polyline') -> tuple[float, 'Point3d']:2 """Calculates the signed area and centroid of a closed planar polyline (Shoelace formula)."""3 if not polyline.IsClosed:4 return 0.0, Point3d.Origin56 area = 0.07 cx = 0.08 cy = 0.09 n = len(polyline.Points)1011 for i in range(n - 1):12 p1 = polyline.Points[i]13 p2 = polyline.Points[i+1]1415 factor = (p1.X * p2.Y) - (p2.X * p1.Y)16 area += factor17 cx += (p1.X + p2.X) * factor18 cy += (p1.Y + p2.Y) * factor1920 area = area / 2.021 if abs(area) < 1e-9:22 return 0.0, Point3d.Origin2324 cx = cx / (6.0 * area)25 cy = cy / (6.0 * area)2627 return abs(area), Point3d(cx, cy, 0.0)
This utilizes the Shoelace Formula (signed area) and geometric integration to find the centroid. The order of vertices (clockwise vs. counter-clockwise) determines the sign of the area under the hood.
Stretch
1.503 min read•1 page
ChamferCorners:
Bevels sharp corners of a polyline by slicing each interior vertex at a specified distance.
python
1def ChamferCorners(polyline: 'Polyline', distance: float) -> 'Polyline':2 """Truncates corners of a polyline by slicing vertices at a specified distance."""3 if distance <= 0.0:4 return polyline56 new_points = []7 n = len(polyline.Points)89 for i in range(n):10 # Keep endpoints for open polylines11 if (i == 0 or i == n - 1) and not polyline.IsClosed:12 new_points.append(polyline.Points[i])13 continue1415 p = polyline.Points[i]16 prev_p = polyline.Points[(i - 1) % n]17 next_p = polyline.Points[(i + 1) % n]1819 # Slices corner along segments20 to_prev = (prev_p - p).Unitize() * distance21 to_next = (next_p - p).Unitize() * distance2223 new_points.append(p + to_prev)24 new_points.append(p + to_next)2526 return Polyline(new_points)
A chamfer cuts off sharp angles, turning single vertex points into two bevel vertices. A fillet operation is similar but rounds the corner with an arc instead of a straight line.
Bevel Distance
0.402 min read•1 page
SmoothLaplacian:
Reduces high-frequency geometric noise or jagged edges by moving vertices towards the midpoint of their neighbors.
python
1def SmoothLaplacian(polyline: 'Polyline', iterations: int, factor: float = 0.5) -> 'Polyline':2 """Applies Laplacian vertex smoothing to reduce geometric noise."""3 pts = [p for p in polyline.Points]4 n = len(pts)5 if n < 3:6 return polyline78 for _ in range(iterations):9 temp = [p for p in pts]10 for i in range(1, n - 1):11 # Average of adjacent neighbors12 neighbor_avg = (pts[i-1] + pts[i+1]) / 2.013 # Interpolate between original and neighbor average14 temp[i] = pts[i] + (neighbor_avg - pts[i]) * factor15 pts = temp1617 return Polyline(pts)
Laplacian smoothing iteratively shifts interior vertices. This reduces sharp variations (noise) but shrinks the overall geometry slightly. Keep endpoints fixed to maintain boundary anchors.
Smoothing Iterations
2.003 min read•1 page
Split(t):
Splits a continuous polyline into two independent polylines at parameter
t.python
1def SplitPolyline(polyline: 'Polyline', t: float) -> tuple['Polyline', 'Polyline']:2 """Splits a polyline into two sub-polylines at a normalized parameter t (0.0 to 1.0)."""3 t = max(0.0, min(1.0, t))4 if t <= 0.0 or t >= 1.0:5 return polyline, None67 total_len = polyline.Length8 target_dist = t * total_len910 accumulated = 0.011 split_idx = -112 split_pt = None1314 for i in range(len(polyline.Points) - 1):15 ptA = polyline.Points[i]16 ptB = polyline.Points[i+1]17 seg_len = ptA.DistanceTo(ptB)1819 if accumulated + seg_len >= target_dist or i == len(polyline.Points) - 2:20 seg_t = (target_dist - accumulated) / seg_len if seg_len > 0 else 0.021 split_pt = ptA + (ptB - ptA) * seg_t22 split_idx = i23 break24 accumulated += seg_len2526 # Slice points and inject split intersection point27 part1 = polyline.Points[:split_idx + 1] + [split_pt]28 part2 = [split_pt] + polyline.Points[split_idx + 1:]2930 return Polyline(part1), Polyline(part2)
The split operation locates the target segment using accumulated distance, computes the exact split coordinates, and rebuilds two new point sequences, copying the split point to both.
Split Parameter (t)
0.403 min read•1 page
Join()::
Assembles multiple polylines into a single continuous path if their endpoints lie within a tolerance.
python
1def JoinPolylines(polyA: 'Polyline', polyB: 'Polyline', tolerance: float = 0.001) -> 'Polyline':2 """Joins two polylines if their start/end points are coincident within a tolerance."""34 # Check end-to-start5 if polyA.Points[-1].DistanceTo(polyB.Points[0]) <= tolerance:6 return Polyline(polyA.Points[:-1] + polyB.Points)78 # Check end-to-end (requires reversing the second path)9 if polyA.Points[-1].DistanceTo(polyB.Points[-1]) <= tolerance:10 reversed_pts = [p for p in reversed(polyB.Points)]11 return Polyline(polyA.Points[:-1] + reversed_pts)1213 # Check start-to-start (requires reversing the first path)14 if polyA.Points[0].DistanceTo(polyB.Points[0]) <= tolerance:15 reversed_pts = [p for p in reversed(polyA.Points)]16 return Polyline(reversed_pts[:-1] + polyB.Points)1718 # Check start-to-end19 if polyA.Points[0].DistanceTo(polyB.Points[-1]) <= tolerance:20 return Polyline(polyB.Points[:-1] + polyA.Points)2122 return None # Cannot join
Joining checks the distance between all endpoint permutations (start/end). If endpoints match, it merges the coordinates, removing duplicate contact vertices and reversing path directions if necessary.
Gap Distance
1.003 min read•1 page
Simplify():
Raw polylines (like those from a mouse sketch or a GPS tracker) often have way too many points. The Douglas-Peucker algorithm removes redundant points that don't contribute much to the overall shape, based on a distance tolerance.
python
1def SimplifyDouglasPeucker(points: list['Point3d'], tolerance: float) -> list['Point3d']:2 """Recursive Douglas-Peucker algorithm for polyline simplification."""3 if len(points) < 3:4 return points56 max_dist = 0.07 index = -18 start = points[0]9 end = points[-1]10 line_vec = end - start11 line_len_sq = line_vec.SquareLength()1213 for i in range(1, len(points) - 1):14 p = points[i]15 if line_len_sq == 0.0:16 dist = p.DistanceTo(start)17 else:18 t = (p - start).DotProduct(line_vec) / line_len_sq19 t = max(0.0, min(1.0, t))20 proj = start + line_vec * t21 dist = p.DistanceTo(proj)2223 if dist > max_dist:24 max_dist = dist25 index = i2627 if max_dist > tolerance:28 left = SimplifyDouglasPeucker(points[:index+1], tolerance)29 right = SimplifyDouglasPeucker(points[index:], tolerance)30 return left[:-1] + right31 else:32 return [start, end]
Tolerance
0.50