Generative Art Python Tutorial for Penplotter
- acrylicode berlin
- May 19, 2021
- 4 min read
Updated: May 23, 2021
Hi there,
I am part of the Acrylicode team and today I want to share with you how I coded the algorithm for creating this artwork.

Colourful variation available as CleanNFT on https://www.hicetnunc.xyz/objkt/83667
The algorithm was written in Python, so you need a basic understanding of coding in Python. I try throughout the tutorial to explain every step thoroughly so it shouldn’t be too complicated. I also used Vsketch, an art toolkit, because it provides a simple API with svg export.
The main idea of the algorithm can be explained in 4 steps:
Create a grid of points and shift them in x and y coordinates by a small random amount.
Group the points by column and store all groups in a list.
Iterate over the grouped points and interpolate the current group with the next one (interpolate groups i and i+1)
Adjust the parameters to the desired output
All steps from installation until the output are listed and explained below
VSketch Installation
Type in a terminal following commands one after the other:
git clone https://github.com/abey79/vsketch
cd vsketch
python3 -m venv venv
source venv/bin/activate
pip install .
mkdir my_sketches
cd my_sketches
vsk init random_lines
vsk run random_lines(the pip install step may take a few minutes, be patient and there is a dot after the space) Then open your favourite code editor (I use VSCode) and under vsketch/my_sketches/random_lines you should see “sketch_random_lines.py”. This is where the code gets written, we code everything inside the draw() function.
In this tutorial we are going to be using the following functions vsk.random() , vsk.lerp() , vsk.polygon , vsk.point()
1. Step: Create a grid of points
We write a basic nested for-loop and add a vsk.random() to each coordinate point in the draw() function.
def draw(self, vsk: vsketch.Vsketch) -> None:
vsk.size("a3", landscape=False)
vsk.scale("cm")
for row in range(20):
for col in range(25):
x = row + vsk.random(1.5)
y = col + vsk.random(1)
vsk.point(x,y)When this gets executed you should see something like this:

2. Step: Grouping our Data
Now we slightly modify the code above by adding a variable that is going to contain all groups of lines “allColumnPoints” before the nested for-loops and a temporary variable that contains the current point group in the outer for-loop.
def draw(self, vsk: vsketch.Vsketch) -> None:
vsk.size("a3", landscape=False)
vsk.scale("cm")
allColumnsPoints = []
for row in range(20):
columnPoints = []
for col in range(25):
x = row + vsk.random(1.5)
y = col + vsk.random(1)
columnPoints.append((x,y))
allColumnsPoints.append(columnPoints)3. Step: Interpolation
For this next step we need to import numpy as a dependency first because we are going to use vks.lerp() and this accepts a np.array as a parameter. Write this in the first line of the file
import numpy as np Once we have done that, we iterate over the “allColumnsPoint”, we grab the current column(current iteration) and the next column(next iteration), we extract the x and y coordinates separately and create a numpy array with these values.
def draw(self, vsk: vsketch.Vsketch) -> None:
vsk.size("a3", landscape=False)
vsk.scale("cm")
allColumnsPoints = []
for row in range(20):
columnPoints = []
for col in range(25):
x = row + vsk.random(1.5)
y = col + vsk.random(1)
columnPoints.append((x,y))
allColumnsPoints.append(columnPoints)
for index in range(len(allColumnsPoints) -1):
currentColumnPoints = allColumnsPoints[index]
nextColumnPoints = allColumnsPoints[index+1]
currentColumnPointsUnzipped = zip(*currentColumnPoints)
currentColumnPointsUnzipped = list(currentColumnPointsUnzipped)
xTuples = currentColumnPointsUnzipped[0]
yTuples = currentColumnPointsUnzipped[1]
xCoordinatesCurrentColumn = np.array(xTuples)
yCoordinatesCurrentColumn = np.array(yTuples)
nextColumnPointsUnzipped = zip(*nextColumnPoints)
nextColumnPointsUnzipped = list(nextColumnPointsUnzipped)
xTuples = nextColumnPointsUnzipped[0]
yTuples = nextColumnPointsUnzipped[1]
xCoordinatesNextColumn = np.array(xTuples)
yCoordinatesNextColumn = np.array(yTuples)Now the “currentColumPoints” is an array of tuples [(x,y) , ….]. In the next line zip(*currentColumPoints) separates the x and y and we get an array of two tuples like this [(x1, x2, x,3,…), (y1,y2,y3,…)], the x-tuple is the first element containing the x- coordinates and the y-tuple contains the y-coordinates. Finally, we just convert the array to a numpy array and we repeat the same process for the “nextColumnPoints”.
We are now ready to interpolate the xCoordinatesCurrentColumn with the xCoordinatesNextColumn and the same for the yCoordinates. Once these points are interpolated individually, we zip them again so we have an array of tuples like this: [(x,y), …] and lastly we call the function vsk.polygon with the array we just created.
def draw(self, vsk: vsketch.Vsketch) -> None:
vsk.size("a3", landscape=False)
vsk.scale("cm")
allColumnsPoints = []
for row in range(20):
columnPoints = []
for col in range(25):
x = row + vsk.random(1.5)
y = col + vsk.random(1)
columnPoints.append((x,y))
allColumnsPoints.append(columnPoints)
for index in range(len(allColumnsPoints) -1):
currentColumnPoints = allColumnsPoints[index]
nextColumnPoints = allColumnsPoints[index+1]
currentColumnPointsUnzipped = zip(*currentColumnPoints)
currentColumnPointsUnzipped = list(currentColumnPointsUnzipped)
xTuples = currentColumnPointsUnzipped[0]
yTuples = currentColumnPointsUnzipped[1]
xCoordinatesCurrentColumn = np.array(xTuples)
yCoordinatesCurrentColumn = np.array(yTuples)
nextColumnPointsUnzipped = zip(*nextColumnPoints)
nextColumnPointsUnzipped = list(nextColumnPointsUnzipped)
xTuples = nextColumnPointsUnzipped[0]
yTuples = nextColumnPointsUnzipped[1]
xCoordinatesNextColumn = np.array(xTuples)
yCoordinatesNextColumn = np.array(yTuples)
interpolation_steps = 9
for interpolation_step in range(interpolation_steps):
interpolated_x = vsk.lerp(xCoordinatesCurrentColumn, xCoordinatesNextColumn, interpolation_step/interpolation_steps)
interpolated_y = vsk.lerp(yCoordinatesCurrentColumn, yCoordinatesNextColumn, interpolation_step/interpolation_steps)
interpolated_coordinates = zip(interpolated_x, interpolated_y)
vsk.polygon(interpolated_coordinates)You are done! You should now see something like this:

A little clarification on the line:
interpolated_x = vsk.lerp(xCoordinatesCurrentColumn, xCoordinatesNextColumn, interpolation_step/interpolation_steps)
Here we are interpolating xCoordinatesCurrentColumn, xCoordinatesNextColumn and the interpolation amount ( how far away from each of the two points should the line be interpolated). When the amount is 0 the interpolation is exactly the xCoordinatesCurrentColumn and when the amount is 1 the interpolation is exactly xCoordinatesNextColumn. For any value in between, it will be proportionally placed, which means 0.5 would be in the middle, 0.9 would be closer to the xCoordinatesNextColumn and 0.3 would be closer to xCoordinatesCurrentColumn. I hope it makes sense, you can further check the vsketch documentation for vsk.lerp().
4. Step: Fine Tuning
Now that you have the algorithm up and running, you can modify in the grid creation the range of the nested for-loops, for playing around with the width and the height. You can also change the range of vsk.random() to get different results, and finally, you can play with the interpolations_steps, by randomizing it, changing it gradually or only in the even iterations or be creative and come up with crazy new ideas yourself!
After the tuning, you can save it as svg with the like button in vsketch. An example of a plotted artwork:

I hope you enjoyed this short tutorial. You can always contact us via DM Instagram (@acrylicode.berlin), we appreciate any feedback, if it was too simple or too complex, or what would you like to see in a tutorial, we are open for suggestions. If you want to see a video walk-through, watch our youtube tutorial : https://www.youtube.com/watch?v=9NQVRFnkb1E
Support us:
- by collecting our nft from this tutorial at https://www.hicetnunc.xyz/objkt/83667
- sharing this content and following us in social media @acrylicode.berlin
- check out our shop https://www.acrylicode.com/shop/




Mình thường quan tâm đến cách một nền tảng tổ chức nội dung vì nếu bố cục không hợp lý thì càng sử dụng sẽ càng rối. Một hệ thống tốt cần đảm bảo mọi phần được sắp xếp trong cùng một cấu trúc rõ ràng để dễ theo dõi. Khi tìm hiểu về https://cm88-vn.bet/ mình nhận thấy các nội dung như thể thao, casino với Roulette, Poker cùng các game như bắn cá, đá gà đều được tích hợp trong cùng hệ thống. Điều này giúp việc sử dụng không bị gián đoạn và không cần làm quen lại nhiều lần. Nhờ vậy mình có thể duy trì trải nghiệm ổn định và sử dụng lâu hơn
Lần đầu tiếp cận một nền tảng giải trí thường không phản ánh hết chất lượng, nhưng những ấn tượng ban đầu như cách hiển thị, tốc độ phản hồi và bố cục tổng thể vẫn đủ để đánh giá mức độ hoàn thiện. Khi mọi thứ được sắp xếp hợp lý, người dùng có thể nhanh chóng hiểu cách sử dụng mà không cần tìm kiếm quá nhiều. Trong trường hợp này, https://123bsa.com/ cho thấy cách tổ chức trực quan, giúp việc tiếp cận các khu vực như thể thao hay casino với những trò như Baccarat, Roulette diễn ra thuận tiện, đồng thời các nội dung khác như game bài hoặc bắn cá vẫn được giữ trong cùng một…
Khả năng xử lý nhiều thao tác cùng lúc là yếu tố quan trọng giúp một nền tảng giải trí trực tuyến duy trì trải nghiệm ổn định khi người dùng vừa theo dõi dữ liệu vừa tham gia trò chơi và thực hiện giao dịch trong cùng một phiên sử dụng khi truy cập từ link vào sc88 quá trình xử lý vẫn diễn ra mượt mà và hạn chế độ trễ giữa các thao tác, đồng thời việc chuyển đổi giữa các danh mục được thực hiện nhanh, bên cạnh đó các sảnh thể thao casino live slot vẫn hoạt động đầy đủ giúp người dùng không bị gián đoạn khi sử dụng liên tục
Không chỉ nằm ở nội dung, tốc độ xử lý trên thiết bị di động cũng ảnh hưởng khá nhiều đến cảm nhận tổng thể khi sử dụng lâu dài. Với 78win, ứng dụng được tối ưu giúp việc chuyển giữa các khu vực như thể thao, casino hay slot diễn ra nhanh và không tạo cảm giác chậm. Người dùng có thể thao tác liên tục mà không cần tải lại nhiều lần. Điều này giúp trải nghiệm trở nên linh hoạt hơn trong nhiều tình huống khác nhau.
Trong một nền tảng có nhiều bước thao tác như đăng ký, nạp tiền hay rút tiền, cách tổ chức quy trình sẽ ảnh hưởng lớn đến khả năng sử dụng thực tế. Xét trên cấu trúc mà c168 run đang triển khai, các bước được trình bày theo thứ tự rõ ràng, giúp người dùng dễ dàng thực hiện mà không cần tìm hiểu quá nhiều. Thông tin hướng dẫn được tích hợp trực tiếp trong hệ thống, hạn chế việc phải tra cứu bên ngoài. Khi thao tác lần đầu, người dùng vẫn có thể hoàn thành các bước cơ bản mà không gặp trở ngại lớn. Điều này cho thấy nền tảng đã tối ưu quy trình…