This tutorial shows how to composite paths in generativepy. You should read through the fill and stroke tutorial first, if you haven't already.
generativepy allows you to draw various preset shapes, such as rectangles, circles, and triangles, but of course you can only use these to draw a limited set of shapes.
It also allows you to draw some different line types - straight lines, bezier curves, polylines, and circular/elliptical arcs.
Composite paths provide a way to create new shapes by combining several line types to create new shapes.
Here are some examples of composite paths:
The magenta polygon on the left of the image shows the basic technique of creating composite paths. Since this is just a normal polygon, we don't really need to do it this way, we could just use a
Polygon object. In fact we draw this same shape in the polygons tutorial.
But in this case we will draw the polygon by composing lines, purely to illustrate composite paths. Here is the code:
Line(ctx).of_start_end((50, 50), (150, 100)).add() Line(ctx).of_end((150, 200)).extend_path().add() Line(ctx).of_end((100, 300))\ .extend_path(close=True)\ .fill(Color('magenta'))
Before we look at this in detail, we need to quickly look at how the context,
ctx works with paths. The context stores a path internally, and we can add to that path with more code. The context also maintains a current point. The current point starts at (0, 0), but whenever we draw anything, the current point is set to the end of the last thing we drew. This allows us to join lines and curves to make shapes.
The code creates a single shape from three Line objects like this:
Linecreates a line from
(150, 100), in the normal way. But instead of stroking the line, we call
add, which simply adds the line to the path that is stored by the context.
Lineis created using
of_end- this only defines the end point, not that start point. It then calls
extend_path. This means that instead of creating a new path for this line, we will add it to the existing path. This line will start at the current point ie
(150, 100)where the previous line ended, and end at
(150, 200). Once again we call
addto add the line to the path without drawing it.
Lineis also created using
of_end. It then calls
extend_path, but this time with
True, to close the shape. This line will start at the current point ie
(150, 200)where the previous line ended, and end at
(100, 300). A final line will be added to close the polygon. This time we call
fillto draw the shape.
The top waving flag shape, in the centre of the image, is composed of a mix of bezier curves and lines:
Bezier(ctx).of_abcd((250, 50), (400, 0), (300, 100), (450, 50))\ .add() Line(ctx).of_end((450, 150)).extend_path().add() Bezier(ctx).of_bcd((300, 200), (400, 100), (250, 150))\ .extend_path(close=True)\ .fill(Color('yellow'))\ .stroke(Color('darkblue'), 5)
In this case:
Beziercreates a curve using
of_abcd. The curve goes from
(450, 50), with handles at
(300, 100). As before, we call
addto add the curve to the path.
Lineextends the path using a line to
Bezieradds another curve. This time we use
of_bcdbecause the start of the curve
ais the current point, ie the end of the previous line. It then calls
extend_path, but this time with
True, to close the shape. We then fill and stroke the shape.
The lower flag shape looks identical to the previous one, but it is drawn using a single
Polygon object. You can use either style, whichever you prefer.
Polygon(ctx).of_points([(250, 250), (400, 200, 300, 300, 450, 250), (450, 350), (300, 400, 400, 300, 250, 350)])\ .fill(Color('yellow'))\ .stroke(Color('darkblue'), 5)
This defines a closed polygon of four points. However, the
Polygon object allows you to create bezier curves rather than lines, by passing in 6 values rather than 2. Basically this list of points specifies a line from
(250, 250) to
[(250, 250), (450, 250)]
But this list specifies a curve from
(250, 250) to
(450, 250), with handles at
(400, 200) and
[(250, 250), (400, 200, 300, 300, 450, 250)]
The pill shape on at the right of the image is created using two semi-circular arcs, joined by straight lines:
Circle(ctx).of_center_radius((550, 200), 50)\ .as_arc(math.pi/2, 3*math.pi/2)\ .add() Circle(ctx).of_center_radius((700, 200), 50)\ .as_arc(3*math.pi/2, math.pi/2)\ .extend_path(close=True)\ .stroke(Color('darkgreen'), 10)
Circle draws the semicircle on the left of the pill, the second
Circle draws the semicircle of the right of the pill.
Note that we don't need to explicitly draw the lines. When we add an arc to a path, it automatically adds a straight line from tehcurrent point to the start of the arc.
Copyright (c) Axlesoft Ltd 2020