Create IPE drawings in python.
Project description
IPEY
ipey is a python package that lets you programatically create Ipe drawings from python code. The project can be found on Github. The image below was created with python, ipey and some math.
Install
ipey is available via pypi and can be installed by
pip install ipey
Basic Usage
In this very basic example we create a ipe document, add a primitive and save the file. First, we create an empty document and add the first page to it. Then, we create a circle at position (x=400, y=400) with a radius of 20. Finally, we store the document as simple.xml.
from ipey.document import Document
from ipey.primitive import Circle
document = Document()
page = document.createPage()
circle = Circle((400,400), 20)
page.add(circle)
document.write('simple.xml')
Document Properties
Document objects have different properties that can be specified. The standard size uses the size of the Ipe basic style sheet. Here, we set the size to a square shape of sidelength 1000.
document = Document()
document.width = 1000
document.height = 1000
Furthermore, it is also possible to crop the drawing to the content. In this case the widht and height of the document is ignored and the bounding box of all elements in the scene is used to compute the appropriate document size. A margin can be used to keep elements away from the document border. If left unspecified the margin for all sides is 64.
from ipey.document import Margin
margin = Margin(top=100, bottom=100, left=100, right=200)
margin.bottom = 200
document = Document()
document.crop = True
document.margin = margin
Lastly, documents can be initialized with existing style sheets and a custom settings.ini. Style sheets are specified as a list of style sheets (including path) and appended to the document. For example, if custom colors are defined in such a style sheet they can then be used by assigning their name to the color property of elements.
document = Document(settings_path='my/path/to/settings.ini',
styles=['mystyle1.isy', 'path/to/my/style.isy'])
Pages, Layers, Views and Elements
It is possible to add multiple pages to a document. This can be achieved by either creating empty pages or copying an existing page (including all elements in a page at the time of copying).
from ipey.document import Document
from ipey.primitive import Circle
document = Document()
page1 = document.createPage()
page2 = document.createPage()
circle = Circle((400,400), 20)
page1.add(circle)
page3 = document.copyPage(page1)
Adding elements to a page can be achieved by either just appending the element or specifying a second element. Just adding an element will result in a drawing where the newest element is always the topmost. When a given element is also used, it can either be placed right before or right after the element in the drawing.
from ipey.document import Document
from ipey.primitive import Circle
document = Document()
page = document.createPage()
circle1 = Circle((400,400), 20)
page.add(circle)
circle2 = Circle((410,410), 20)
page.add(circle)
circle3 = Circle((420,420), 20)
page.add(circle)
circle4 = Circle((390,390), 20)
page.addBefore(circle4, circle1)
circle5 = Circle((430,430), 20)
page.addAfter(circle5, circle3)
Layers are simply created by adding the layer name to an element.
from ipey.document import Document
from ipey.primitive import Circle, Rectangle
document = Document()
page = document.createPage()
circle = Circle((400,400), 20)
circle.layer = 'circles'
page.add(circle)
rect = Rectangle((300,400),200,200)
rect.layer = 'rectangles'
page.add(rect)
Views can be added to page by specifying a name for the view and either a single layer or a list of layers. In the following example we create three views for the elements created above. One that only shows rectangles, one that only shows circles and one that shows circles and rectangles.
page.createView('circleView', 'circles')
page.createView('rectangleView', 'rectangles')
page.createView('bothView', ['circles', 'rectangles'])
It is also possible to add and remove layers from views, as well as removing a view from the page.
page.addToView('bothView', ['lines', 'labels'])
page.removeFromView('bothView', 'labels')
page.removeView('bothView')
Elements
Ipe drawings consist of elements. Currently, the following elements are supported.
Circle
Circles are created by specifying a center point and the radius.
from ipey.primitive import Circle
circle = Circle((100,100), 50)
Ellipse
Ellipsis are created by specifying three points - a center point, the vertex and the co-vertex.
from ipey.primitive import Ellipse
ellipse = Ellipse((100,100), (150,100), (100,200))
Rectangle
Two variants of creating rectangles are available. Rectangle creates a rectangle with anchor point at the left bottom corner. RectangleC with anchor point in the center.
from ipey.primitive import Rectangle, RectangleC
rect = Rectangle((200,200), 100, 50)
rectC = RectangleC((400,400), 100, 50)
Line
Lines are specified by giving a list of points. It is possible to add points to an existing line.
from ipey.primitive import Line
line = Line([(0,0), (100,100), (0,100)])
line.addPoint((300,300))
Arc
Arcs are created by specifying three points. The arc is starting at p1, going through p2 and ending at p3.
from ipey.primitive import Arc
arc = Arc((300,300), (400,400), (500, 300))
Polygon
Polygons are initialized by a list of points.
from ipey.primitive import Polygon
polygon = Polygon([(0,0), (100,100), (0,100)])
polygon.addPoint((300,300))
Spline
Splines are initialized by a list of points and the spline type.
from ipey.primitive import Spline, SplineType
spline1 = Spline([(0,0), (100,100), (0,100)], SplineType.BSPLINE)
spline2 = Spline([(0,0), (100,100), (0,100)], SplineType.CARDINAL)
spline3 = Spline([(0,0), (100,100), (0,100)], SplineType.SPIRO)
Glyph
Glyphs are initialized by an anchor point and a glyph type. If glyphs are specified in custom style sheets than the assigned name can be used.
from ipey.primitive import Glyph
glyph = Glyph((100,100), type='mark/fdisk(sfx)')
Label and Minipage
Labels and minipages are initialized with string representing the label text and an anchor point. Additionally, text alignment options can be specified. The isMath attribute specifies if a text is considered normal or in math mode.
from ipey.primitive import Label, Minipage
label = Label('test', (200, 300))
page.add(label)
label = Label('math_{yes}', (400, 300))
label.isMath = True
page.add(label)
label = Label('long red text in center', (400, 500))
label.vAlign = 'center'
label.hAlign = 'center'
label.stroke = 'red'
label.size = 'LARGE'
page.add(label)
minipage = Minipage('this is a very long text that should be displayed as a minipage with automatic line breaks depending on the width', (100, 700))
minipage.width = 200
page.add(minipage)
Element Properties
For all elements different drawing properties can be set. Not all properties work on every element. Colors can be specified by name, as defined in the style sheets, or with hex colors. Hex colors are automatically converted. Similarly, the pen size can be given named or as a float number.
from ipey.primitive import Line
line = Line([(100,100), (150,150), (200,200)])
line.fill = 'blue'
line.fill = '#4488FF'
line.stroke = 'red'
line.stroke = '#AF0000'
line.pen = 'fat'
line.pen = '5'
line.opacity = 0.5
line.stroke_opacity = 0.5
line.dash = 'dashed'
line.layer = 'lines'
line.arrow = True
line.rarrow = True
Cloning Element Styles
For all elements it is possible to pass an existing element of the same type. This copies the style of the passed element to the new element. In the example below a orange circle with a red fat border is created. A second larger circle at (200,200) is then created by using the first circle as prototype.
from ipey.primitive import Circle
circle1 = Circle((100,100), 50)
circle1.stroke = 'red'
circle1.pen = 'fat'
circle1.fill = 'orange'
circle2 = Circle(200,200), 100, prototype=circle1)
Complex examples
In the two examples showcased here we create more complex drawing. The first example is more of artistic nature. We create n rectangles of different colors and rotate them around a center point while also shrinking at each iteration.
For this we first create the document and add a page. Then, we create the first rectangle and add it to the page. Afterwards, in each iteration we clone the previous rectangle. The points of the cloned rectangle are transformed, such that a spiral rotation is achieved.
from ipey.document import Document, Margin
from ipey.primitive import Polygon
colors = ['#fafa6e','#edf76f','#e0f470','#d4f171','#c8ed73','#bcea75','#b0e678','#a5e27a','#99de7c','#8eda7f','#83d681','#79d283','#6ecd85','#64c987','#5ac489','#50bf8b','#46bb8c','#3cb68d','#32b18e','#28ac8f','#1ea78f','#12a28f','#039d8f','#00988e','#00938d','#008e8c','#00898a','#008488','#007e86','#007983','#057480','#0e6f7d','#156a79','#1a6575','#1e6071','#225b6c','#255667','#275163','#294d5d','#2a4858']
colors.reverse()
n = len(colors)
factor = 0.92
factory = 0.8
document = Document()
document.crop = True
document.margin = Margin(top=0, bottom=0, left=0, right=0)
page = document.createPage()
rect = Polygon([(0,0), (0, 600), (600,600), (600,0)])
rect.stroke = colors[0]
rect.fill = colors[0]
page.add(rect)
for i in range(1,n):
rect = rect.clone()
rect.stroke = colors[i]
rect.fill = colors[i]
points = rect.points
newP = []
for j in range(4):
p1 = points[j]
p2 = points[(j + 1) % 4]
dx = p2[0] - p1[0]
dy = p2[1] - p2[0]
pNew = (p1[0] + dx * factor, p1[1] + dy * factor)
newP.append(pNew)
rect.points = newP
page.add(rect)
document.write('examples/output/spiral.xml')
in the second example we compute the convex hull of a given point set but visualize every step of the algorithm in Ipe as its own page.
The points are initialized randomly and represented by glyphs. Every step of the algorithm is given its own page and the current convex hull of the step is represented as lines.
from ipey.document import Document
from ipey.primitive import Glyph, Line, Polygon
import random
def get_slope(p1, p2):
if p1[0] == p2[0]:
return float('inf')
else:
return 1.0*(p1[1]-p2[1])/(p1[0]-p2[0])
def get_cross_product(p1,p2,p3):
return ((p2[0] - p1[0])*(p3[1] - p1[1])) - ((p2[1] - p1[1])*(p3[0] - p1[0]))
points = [(random.randint(0,500),random.randint(0,500)) for i in range(40)]
document = Document()
document.crop = True
pagePoints = document.createPage()
for p in points:
glyph = Glyph(p, 'mark/fdisk(sfx)')
glyph.fill = '#000000'
glyph.stroke = '#ffffff'
glyph.pen = 'fat'
pagePoints.add(glyph)
points.sort(key=lambda x:[x[0],x[1]])
start = points.pop(0)
hull = [start]
points.sort(key=lambda p: (get_slope(p,start), -p[1],p[0]))
for p in points:
hull.append(p)
while len(hull) > 2 and get_cross_product(hull[-3],hull[-2],hull[-1]) < 0:
hull.pop(-2)
page = document.copyPage(pagePoints)
line = Line(hull)
page.add(line)
line.stroke = 'green'
page = document.copyPage(pagePoints)
poly = Polygon(hull)
page.add(poly)
document.write('examples/output/convexHull.xml')
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.