Geometric markers in generativepy
Martin McBride, 2022-02-01
Tags marker angle line length parallel
Categories generativepy generativepy tutorial

This tutorial shows how to add geometric makers in generativepy.
There are several different markers:
AngleMarker
marks an angle. Double and triple markers can be used to indicate that two angles are identical. The marker also provides a right angle style.Tickmarker
marks a line with a single, double, or triple tick. This is used to indicate that two-line lengths are identical.Paramarker
marks a line with single, double, or triple arrows. This is used to indicate that two-line lengths are parallel.
Each marker is a shape object that is drawn separately from the lines themselves, although often using the same coordinates. They can be used Line
objects, or with Polygon
objects, or various other shapes.
AngleMarker example
Here is some simple code that draws various angle markers:
from generativepy.drawing import make_image, setup from generativepy.color import Color from generativepy.geometry import Line, Polygon, AngleMarker, Text, TickMarker, ParallelMarker def draw(ctx, pixel_width, pixel_height, frame_no, frame_count): setup(ctx, pixel_width, pixel_height, width=10, background=Color(0.8)) a = (1, 2) b = (7, 2) c = (3, 8) d = (9, 8) e = (5, 2) f = (5, 8) Polygon(ctx).of_points([a, b, d, c]).stroke(Color('blue'), line_width=.05) Line(ctx).of_start_end(e, f).stroke(Color('blue'), line_width=.05) AngleMarker(ctx).of_points(b, a, c).with_radius(.5).with_count(2).with_gap(0.15).stroke(Color('blue'), line_width=.05) AngleMarker(ctx).of_points(a, b, d).with_radius(.5).stroke(Color('blue'), line_width=.05) AngleMarker(ctx).of_points(c, d, b).with_radius(.5).with_count(2).with_gap(0.15).stroke(Color('blue'), line_width=.05) AngleMarker(ctx).of_points(a, c, d).with_radius(.5).stroke(Color('blue'), line_width=.05) AngleMarker(ctx).of_points(e, f, d).with_radius(.5).as_right_angle().stroke(Color('blue'), line_width=.05) Text(ctx).of('a', a).size(1).offset_towards(d, -0.5).fill(Color('red')) Text(ctx).of('b', b).size(1).offset_towards(c, -0.9).fill(Color('red')) Text(ctx).of('c', c).size(1).offset_towards(b, -0.9).fill(Color('red')) Text(ctx).of('d', d).size(1).offset_towards(a, -0.5).fill(Color('red')) Text(ctx).of('e', e).size(1).offset_towards(f, -0.3).fill(Color('red')) Text(ctx).of('f', f).size(1).offset_towards(e, -0.9).fill(Color('red')) make_image("angle-markers.png", draw, 600, 600)
The code for all the examples in this article is available on github in tutorial/shapes/markers.py.
Here is the resulting image:
We define the key points on the diagram as variables a
to f
, so we can use them to draw the shapes, markers and text labels. The image area is scaled to a user space of 10 by 10 units, using the setup
functions.
Next, we draw the main shapes - a polygon (a, b, d, c)
and a line (e, f)
.
Then we draw the angle markers, starting with the marker at corner a
:
AngleMarker(ctx).of_points(b, a, c).with_radius(.5).with_count(2).with_gap(0.15).stroke(Color('blue'), line_width=.05)
AngleMarker
is a Shape
object, that draws the small arc of the angle. of_points(b, a, c)
draws the angle at corner a
, between the lines ab
and ac
in a counter-clockwise direction. with_count(2)
creates a double arc. with_radius
sets the radius of the arc, and with_gap
sets the gap between the two arcs. The sizes depend on the scale of your drawing, so sometimes a bit of trial and error is required to get things right.
This we draw the angle marker at corner b
:
AngleMarker(ctx).of_points(a, b, d).with_radius(.5).stroke(Color('blue'), line_width=.05)
This time we draw the angle from the line bd
to the line db
, again in a counter-clockwise direction. This cause the angle marker to be drawn on the outer angle. If you wanted, instead, to draw the inner angle then simply reverse the order of the points - of_points(d, b, a)
.
To draw a single angle marker, we can leave out the call to with_count
because it defaults to 1. And we don't need to call with_gap
because there is no gap for a single marker.
The markers at c
and d
are similar.
We also have a right angle marker at corner e
, drawn like this:
AngleMarker(ctx).of_points(e, f, d).with_radius(.5).as_right_angle().stroke(Color('blue'), line_width=.05)
The key thing here is the as_right_angle
method, which alters the shape of the angle marker. One point to note is that generativepy doesn't check if the angle actually is a right angle. If you attempt to draw a right angle on an angle that isn't a right angle it will look quite odd. It is the responsibility of your code to check.
The remainder of the code draws the labels on the corners. This just uses Text
objects, with offsets to position the text slightly away from the corner. We won't explain it here, it is fairly standard behaviour.
TickMarker
Here is some simple code that draws tick markers:
def draw2(ctx, pixel_width, pixel_height, frame_no, frame_count): setup(ctx, pixel_width, pixel_height, width=10, background=Color(0.8)) a = (1, 2) b = (7, 2) c = (3, 8) d = (9, 8) e = (5, 2) f = (5, 8) Polygon(ctx).of_points([a, b, d, c]).stroke(Color('blue'), line_width=.05) TickMarker(ctx).of_start_end(a, b).with_length(.5).with_count(2).with_gap(0.15).stroke(Color('blue'), line_width=.05) TickMarker(ctx).of_start_end(c, d).with_length(.5).with_count(2).with_gap(0.15).stroke(Color('blue'), line_width=.05) TickMarker(ctx).of_start_end(a, c).with_length(.5).stroke(Color('blue'), line_width=.05) TickMarker(ctx).of_start_end(b, d).with_length(.5).stroke(Color('blue'), line_width=.05) Text(ctx).of('a', a).size(1).offset_towards(d, -0.5).fill(Color('red')) Text(ctx).of('b', b).size(1).offset_towards(c, -0.5).fill(Color('red')) Text(ctx).of('c', c).size(1).offset_towards(b, -0.9).fill(Color('red')) Text(ctx).of('d', d).size(1).offset_towards(a, -0.5).fill(Color('red')) make_image("tick-markers.png", draw2, 600, 600)
Here is the resulting image:
The structure of the code is similar to the angle marker example, except that points e
and f
are not used.
This code draws the single tick on the line ac
:
TickMarker(ctx).of_start_end(a, c).with_length(.5).stroke(Color('blue'), line_width=.05)
This draws a tick, halfway between points a
and c
, and perpendicular to the line ab
. The length of the tick line is set to 0.5 units, and the width of the tick line is set to 0.05.
This code draws the double tick on the line ab
:
TickMarker(ctx).of_start_end(a, b).with_length(.5).with_count(2).with_gap(0.15).stroke(Color('blue'), line_width=.05)
This is similar to the previous tick mark, but we have set with_count(2)
to draw 2 tick lines, and with_gap(0.15)
to set a small gap between the lines.
ParallelMarker
Here is some simple code that draws parallel markers:
def draw3(ctx, pixel_width, pixel_height, frame_no, frame_count): setup(ctx, pixel_width, pixel_height, width=10, background=Color(0.8)) a = (1, 2) b = (7, 2) c = (3, 8) d = (9, 8) e = (5, 2) f = (5, 8) Polygon(ctx).of_points([a, b, d, c]).stroke(Color('blue'), line_width=.05) ParallelMarker(ctx).of_start_end(a, b).with_length(.5).with_count(2).with_gap(0.15).stroke(Color('blue'), line_width=.05) ParallelMarker(ctx).of_start_end(c, d).with_length(.5).with_count(2).with_gap(0.15).stroke(Color('blue'), line_width=.05) ParallelMarker(ctx).of_start_end(a, c).with_length(.5).stroke(Color('blue'), line_width=.05) ParallelMarker(ctx).of_start_end(b, d).with_length(.5).stroke(Color('blue'), line_width=.05) Text(ctx).of('a', a).size(1).offset_towards(d, -0.5).fill(Color('red')) Text(ctx).of('b', b).size(1).offset_towards(c, -0.5).fill(Color('red')) Text(ctx).of('c', c).size(1).offset_towards(b, -0.9).fill(Color('red')) Text(ctx).of('d', d).size(1).offset_towards(a, -0.5).fill(Color('red')) make_image("parallel-markers.png", draw3, 600, 600)
Here is the resulting image:
This code is almost identical to the TickMarker
example. The only difference is that we use ParallelMarker
instead, so the code draws arrows rather than lines.