Initializing 3D Canvas...

The Polyline

2 min read1 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."""
4
5 # It must have at least 2 points to form a segment
6 if len(points) < 2:
7 raise ValueError("Polyline requires at least 2 points")
8
9 self.Points = points
10
11 @property
12 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.00
2 min read1 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."""
3
4 segments = []
5
6 # A polyline with N points has N-1 segments
7 for i in range(len(polyline.Points) - 1):
8 ptA = polyline.Points[i]
9 ptB = polyline.Points[i+1]
10
11 # Create a Line from point A to point B
12 segment = Line(ptA, ptB)
13 segments.append(segment)
14
15 return segments
Highlight Segment
0.00
1 min read1 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."""
3
4 total_length = 0.0
5
6 # Just sum up the distance between each consecutive point
7 for i in range(len(polyline.Points) - 1):
8 ptA = polyline.Points[i]
9 ptB = polyline.Points[i+1]
10
11 total_length += ptA.DistanceTo(ptB)
12
13 return total_length
2 min read1 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."""
3
4 closest_pt = None
5 min_dist = float('inf')
6
7 # Check every single line segment
8 for i in range(len(polyline.Points) - 1):
9 segment = Line(polyline.Points[i], polyline.Points[i+1])
10
11 # Closest point on this specific line segment
12 pt_on_segment = segment.ClosestPoint(test_pt, limitToFiniteSegment=True)
13
14 dist = test_pt.DistanceTo(pt_on_segment)
15 if dist < min_dist:
16 min_dist = dist
17 closest_pt = pt_on_segment
18
19 return closest_pt
Test Point X
3.00
Test Point Y
3.00
1 min read1 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."""
3
4 segments = []
5
6 # Creates independent LineCurve objects for each segment
7 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))
11
12 return segments
Explode Distance
0.00
2 min read1 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."""
3
4 total_len = polyline.Length
5 target_dist = t * total_len
6
7 accumulated = 0.0
8 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)
12
13 if accumulated + seg_len >= target_dist or i == len(polyline.Points) - 2:
14 # Linear interpolation on this segment
15 seg_t = 0.0
16 if seg_len > 0:
17 seg_t = (target_dist - accumulated) / seg_len
18 return ptA + (ptB - ptA) * seg_t
19
20 accumulated += seg_len
21 return polyline.Points[-1]
Parameter (t)
0.50
2 min read1 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."""
3
4 total_len = polyline.Length
5 target_dist = t * total_len
6
7 accumulated = 0.0
8 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)
12
13 if accumulated + seg_len >= target_dist or i == len(polyline.Points) - 2:
14 # Direction vector of this segment
15 direction = ptB - ptA
16 return direction.Unitize()
17
18 accumulated += seg_len
19 return (polyline.Points[-1] - polyline.Points[-2]).Unitize()
Parameter (t)
0.50
1 min read1 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."""
3
4 # In-place reversal of the coordinate array
5 polyline.Points.reverse()
6 return polyline
Reverse Direction
2 min read1 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."""
3
4 # Check all pairs of non-adjacent segments
5 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 vertex
10 segB = Line(polyline.Points[j], polyline.Points[j+1])
11
12 if segA.IntersectsWith(segB, limitToSegments=True):
13 return True
14 return False
Control Vertex Y
5.00
2 min read1 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."""
3
4 # 1. Find the total length of the path
5 total_length = pline.Length
6
7 # 2. Calculate the distance between each point
8 segment_length = total_length / count
9
10 # 3. Evaluate the polyline at each specific length along the path
11 points = []
12 for i in range(count + 1):
13 target_len = i * segment_length
14 pt = pline.PointAtLength(target_len)
15 points.append(pt)
16
17 return points
Divide Count
8.00
4 min read1 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 polyline
6
7 offset_points = []
8 n = len(polyline.Points)
9 up = Vector3d(0, 0, 1)
10
11 # 1. Compute segment directions and unit normals
12 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)
19
20 # 2. Compute offset vertices
21 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]
29
30 miter_dir = (n1 + n2).Unitize()
31 dot = n1.DotProduct(n2)
32 cos_half = ((1.0 + dot) / 2.0) ** 0.5
33
34 miter_len = distance
35 if cos_half > 0.1:
36 miter_len = distance / cos_half
37
38 offset_points.append(polyline.Points[i] + miter_dir * miter_len)
39
40 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.50
The 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 read1 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."""
3
4 # Built-in check (often strict equality)
5 is_closed = pline.IsClosed
6
7 # 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]
10
11 is_closed_in_tolerance = start_pt.DistanceTo(end_pt) <= tolerance
12
13 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.00
Why 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 read1 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 False
5
6 inside = False
7 n = len(polyline.Points)
8 x, y = pt.X, pt.Y
9
10 for i in range(n - 1):
11 p1 = polyline.Points[i]
12 p2 = polyline.Points[i+1]
13
14 # Test if ray crosses boundary
15 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 inside
17
18 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.00
Test Point Y
0.50
3 min read1 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.Origin
5
6 area = 0.0
7 cx = 0.0
8 cy = 0.0
9 n = len(polyline.Points)
10
11 for i in range(n - 1):
12 p1 = polyline.Points[i]
13 p2 = polyline.Points[i+1]
14
15 factor = (p1.X * p2.Y) - (p2.X * p1.Y)
16 area += factor
17 cx += (p1.X + p2.X) * factor
18 cy += (p1.Y + p2.Y) * factor
19
20 area = area / 2.0
21 if abs(area) < 1e-9:
22 return 0.0, Point3d.Origin
23
24 cx = cx / (6.0 * area)
25 cy = cy / (6.0 * area)
26
27 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.50
3 min read1 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 polyline
5
6 new_points = []
7 n = len(polyline.Points)
8
9 for i in range(n):
10 # Keep endpoints for open polylines
11 if (i == 0 or i == n - 1) and not polyline.IsClosed:
12 new_points.append(polyline.Points[i])
13 continue
14
15 p = polyline.Points[i]
16 prev_p = polyline.Points[(i - 1) % n]
17 next_p = polyline.Points[(i + 1) % n]
18
19 # Slices corner along segments
20 to_prev = (prev_p - p).Unitize() * distance
21 to_next = (next_p - p).Unitize() * distance
22
23 new_points.append(p + to_prev)
24 new_points.append(p + to_next)
25
26 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.40
2 min read1 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 polyline
7
8 for _ in range(iterations):
9 temp = [p for p in pts]
10 for i in range(1, n - 1):
11 # Average of adjacent neighbors
12 neighbor_avg = (pts[i-1] + pts[i+1]) / 2.0
13 # Interpolate between original and neighbor average
14 temp[i] = pts[i] + (neighbor_avg - pts[i]) * factor
15 pts = temp
16
17 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.00
3 min read1 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, None
6
7 total_len = polyline.Length
8 target_dist = t * total_len
9
10 accumulated = 0.0
11 split_idx = -1
12 split_pt = None
13
14 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)
18
19 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.0
21 split_pt = ptA + (ptB - ptA) * seg_t
22 split_idx = i
23 break
24 accumulated += seg_len
25
26 # Slice points and inject split intersection point
27 part1 = polyline.Points[:split_idx + 1] + [split_pt]
28 part2 = [split_pt] + polyline.Points[split_idx + 1:]
29
30 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.40
3 min read1 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."""
3
4 # Check end-to-start
5 if polyA.Points[-1].DistanceTo(polyB.Points[0]) <= tolerance:
6 return Polyline(polyA.Points[:-1] + polyB.Points)
7
8 # 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)
12
13 # 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)
17
18 # Check start-to-end
19 if polyA.Points[0].DistanceTo(polyB.Points[-1]) <= tolerance:
20 return Polyline(polyB.Points[:-1] + polyA.Points)
21
22 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.00
3 min read1 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 points
5
6 max_dist = 0.0
7 index = -1
8 start = points[0]
9 end = points[-1]
10 line_vec = end - start
11 line_len_sq = line_vec.SquareLength()
12
13 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_sq
19 t = max(0.0, min(1.0, t))
20 proj = start + line_vec * t
21 dist = p.DistanceTo(proj)
22
23 if dist > max_dist:
24 max_dist = dist
25 index = i
26
27 if max_dist > tolerance:
28 left = SimplifyDouglasPeucker(points[:index+1], tolerance)
29 right = SimplifyDouglasPeucker(points[index:], tolerance)
30 return left[:-1] + right
31 else:
32 return [start, end]
Tolerance
0.50