an.stage.path_geometry#
Stroked-path geometry — the executable spec of runtime.js::pathGeometry.
The runtime draws a path (an#160) from three things this module defines:
arc length — cumulative straight-segment lengths over the polyline;
trim — the visible span
[min(ts, te), max(ts, te)], clamped to[0, 1], as fractions of that length;dashes — an optional on/off pattern laid along the WHOLE path from arc length 0 (shifted by an offset) and only then clipped to the trimmed span, so a draw-on reveals dashes in place rather than making them crawl (an#161);
the arrowhead — a triangle whose tip is the trimmed end, oriented along the direction of the segment the tip lies on (the incoming segment when the tip sits exactly on a vertex).
tests/test_path.py lifts the real runtime.js functions, runs them under
node on a shared battery, and compares — behaviourally, like the channel
evaluator’s parity test. Both sides use the same operation order and no
trigonometry (a direction is a unit vector, never an angle), and every
operation is IEEE + - * / sqrt, which both languages round exactly — so
the parity is exact, not within a tolerance.
Cubic Béziers never reach the runtime: flatten_curve() turns them into a
polyline at compile time, so the wire carries one geometry kind.
>>> g = path_geometry([(0.0, 0.0), (100.0, 0.0)], 0.0, 0.5)
>>> g["stroke"]
[(0.0, 0.0), (50.0, 0.0)]
>>> g["head"] is None
True
>>> g = path_geometry([(0.0, 0.0), (100.0, 0.0), (100.0, 100.0)], 0.0, 0.75,
... head_length=20.0, head_width=10.0)
>>> g["head"] # tip at (100, 50), pointing DOWN (+y) along the second leg
[(100.0, 50.0), (95.0, 30.0), (105.0, 30.0)]
Module Attributes
Where the stroke stops under an arrowhead, as a fraction of the head's length back from the tip. |
|
The longest a variable-width stroke's corner may reach, as a multiple of its half-width there (an#161): past it the miter is cut to this length, so a hairpin turn does not throw a spike across the frame. |
Functions
|
The polyline the runtime draws for |
|
Arc length at each vertex. |
|
The point at arc length |
|
The sub-polyline between arc lengths |
|
The arc-length spans |
|
What the runtime draws: |
|
The stroke width at fraction |
|
The filled polygon of a variable-width stroke along |
- an.stage.path_geometry.HEAD_STROKE_INSET: float = 0.5#
Where the stroke stops under an arrowhead, as a fraction of the head’s length back from the tip. Half-way keeps a butt or round cap inside the head for the default proportions, so the stroke never pokes past the tip.
- an.stage.path_geometry.OUTLINE_MITER_LIMIT: float = 4.0#
The longest a variable-width stroke’s corner may reach, as a multiple of its half-width there (an#161): past it the miter is cut to this length, so a hairpin turn does not throw a spike across the frame.
- an.stage.path_geometry.cumulative_lengths(points)[source]#
Arc length at each vertex. Mirror of
runtime.js::pathLengths.>>> cumulative_lengths([(0, 0), (3, 4), (3, 10)]) [0.0, 5.0, 11.0]
- an.stage.path_geometry.dash_spans(a, b, dash, gap, offset)[source]#
The arc-length spans
[lo, hi]inside[a, b]that a dash covers.The pattern is anchored at arc length
0— dashkcovers[offset + k*period, offset + k*period + dash]— and is clipped to the window afterwards, so movingaorbnever moves a dash. Only IEEE+ - * /andfloor, in the orderruntime.js::pathDashSpansuses.>>> dash_spans(0.0, 100.0, 10.0, 15.0, 0.0) [(0.0, 10.0), (25.0, 35.0), (50.0, 60.0), (75.0, 85.0)] >>> dash_spans(30.0, 60.0, 10.0, 15.0, 0.0) # clipped, not re-anchored [(30.0, 35.0), (50.0, 60.0)] >>> dash_spans(0.0, 30.0, 10.0, 10.0, 5.0) # offset slides the pattern forward [(5.0, 15.0), (25.0, 30.0)]
- an.stage.path_geometry.flatten_curve(points, *, curve='polyline', samples=24, sampling='parameter')[source]#
The polyline the runtime draws for
points.curve="cubic"readspointsas chained cubic Béziers (p0 c1 c2 p1 c1 c2 p2 ...) and samples each atsamplessteps; shared endpoints appear once.sampling="parameter"(the default) steps uniformly in each curve’s parameter, so points crowd where the curve is slow;"arclength"(an#161) steps uniformly along its length, so the points are evenly spaced (a smoother bend for the same count).>>> flatten_curve([(0, 0), (10, 0)]) [(0.0, 0.0), (10.0, 0.0)] >>> pts = flatten_curve([(0, 0), (0, 10), (10, 10), (10, 0)], curve="cubic", samples=2) >>> pts [(0.0, 0.0), (5.0, 7.5), (10.0, 0.0)]
- an.stage.path_geometry.path_geometry(points, trim_start, trim_end, *, head_length=0.0, head_width=0.0, dash=0.0, gap=0.0, dash_offset=0.0, tail_head_length=0.0, tail_head_width=0.0, width=0.0, width_profile=None)[source]#
What the runtime draws:
{"stroke": [points], "head": [3 points] | None}.tail_head_length > 0adds a TAIL arrowhead at the trimmed start, pointing back along the path (an#161: a double-headed arrow with both); it is then under a"tail"key (absent otherwise), and the stroke startsHEAD_STROKE_INSETof its length in from that tip. While the visible length is shorter than the heads together, both are scaled by the same factor, so a draw-on grows them in.dash > 0makes the stroke a dash pattern:strokeis then[]and a"dashes"key (absent otherwise) holds one polyline per visible dash.width_profile(an#161) makes the stroke a variable-width SHAPE: an"outlines"key (absent otherwise) holds one polygon per stroke or dash (stroke_outline()), which the runtime fills instead of stroking.head_length > 0turns the arrowhead on. While the visible length is shorter than the head, the head is scaled byvisible / head_lengthso a draw-on grows it in rather than popping a full-size triangle at frame one. The stroke stopsHEAD_STROKE_INSETof the head’s length back from the tip, inside the head. Mirror ofruntime.js::pathGeometry.- Return type:
>>> path_geometry([(0.0, 0.0), (10.0, 0.0)], 0.3, 0.3) {'stroke': [], 'head': None}
- an.stage.path_geometry.point_at(points, cum, s)[source]#
The point at arc length
s. Mirror ofruntime.js::pathPointAt.
- an.stage.path_geometry.profile_width(profile, width, u)[source]#
The stroke width at fraction
uof the WHOLE path’s length (an#161):widthtimes the profile’s factor, linear between its stops. Mirror ofruntime.js::pathProfileWidth.- Return type:
>>> profile_width([(0.0, 1.0), (1.0, 0.0)], 8.0, 0.25) 6.0
- an.stage.path_geometry.stroke_outline(line, start, total, width, profile)[source]#
The filled polygon of a variable-width stroke along
line(an#161).lineis a trimmed piece of the path beginning at arc lengthstartof a pathtotallong, so a point’s width is read at its place on the WHOLE path: trimming never makes the width crawl. Each vertex is offset both ways along the bisector of its legs’ normals by half its width, lengthened to keep the stroke’s width through the corner (capped atOUTLINE_MITER_LIMIT). Butt ends. Left side forward, then the right side back. Mirror ofruntime.js::pathOutline.>>> stroke_outline([(0.0, 0.0), (10.0, 0.0)], 0.0, 10.0, 4.0, [(0.0, 1.0), (1.0, 0.5)]) [(0.0, 2.0), (10.0, 1.0), (10.0, -1.0), (0.0, -2.0)]