Build a clock with Blender and Python
Tick Tock
With a little help from Blender you can create your own 3D models – including animations. This article shows you how to assemble a partially automated virtual watch model with Blender and Python.
The free Blender program package for modeling, texturing, animation, and video and image editing can be found in the package repositories of most Linux distributions, and there is also a distribution-independent Snap package. Typing the snap install blender
command at the command line installs the graphics suite. If you need more in the way of installation options, you can download the application directly from the Blender Foundation website [1]. While you are there, you can also access the extensive documentation, tutorials, and examples and grab the versions for Windows, macOS, and others. On top of this, because the Blender Foundation provides the source code, the program can even be adapted to run on less common operating systems. Of course, in this case you will have to compile Blender yourself.
On Linux, either open Blender in a terminal or use the Open in Terminal shortcut in the window manager of your choice. If neither succeeds, first launch Blender and then try to discover the path of the Blender installation in the Python Interactive Console by typing bpy.app.binary_path
. Then enter this as the start parameter for the call in the terminal. This ensures that error messages and output from the Python command print("Hello world")
, for example, also reach their target (i.e., the terminal window). This is especially important if you don't just want to use the Blender Python Console in Blender's Scripting workspace and individual commands, but also want to call Python programs you saved previously.
Desktop
Blender's user interface is divided into workspaces. Each of them hosts a different collection of editors and windows that appear at specific positions on the screen. The program lists the available workspaces on the right below the menu bar. They include Layout, Modeling, Sculpting, UV Editing, and Animation. Almost all workspaces contain the 3D viewport window and other windows.
Figure 1 shows Blender after starting with the default basic scene. Basically, you can assemble 3D scenes to your heart's content directly in the interface. There are numerous tutorials on the Internet for getting started with Blender; a description of this would go beyond the scope of this article due to the range of functions to cover. Instead, the task at hand is to enable you to write tools for yourself in the form of Python scripts that let you build complex scenes one by one in a scripted way.
Clock Face
As an example, let's look at a three-dimensional clock face with raised numerals and hands. What's interesting in this context is the positions of the numerals on the dial. The only way to position them correctly is to use some trigonometric functions you may still recall from math.
But let's start by partially assembling the dial by hand, and at the same time you'll get to know some Blender Python. To do this, switch to the Scripting workspace. This will take you to a programming interface (Figure 2). On the left, you can see the familiar, somewhat reduced 3D viewport window. Below that are the Blender Python Console and the Info line. Clicking on Add | Mesh | Plane creates a square base for the dial. Parallel to this, Blender logs your manual actions in the Info window bottom left. The window content is very similar to the first line of Listing 1.
Listing 1
Blender Action
01 bpy.ops.mesh.primitive_plane_add(size=2, enter_editmode=False, align='WORLD', location=(0, 0, 0), scale=(1, 1, 1)) 02 bpy.ops.mesh.primitive_plane_add(size=5) 03 {'FINISHED'}
One thing up front: Blender forwards keyboard input directly to whichever window you mouse over. This means that you do not need to click on the window beforehand. But, especially in the scripting workspace, you need to make sure that the mouse pointer is in the right position when entering keystrokes. Start by moving the mouse over to the 3D viewport window, press X, and confirm the Delete prompt. The square base area should disappear again and another line will be logged in the Info window.
Then build the square base area again, but this time with a different size and using command input in the Blender Python Console (Listing 1, line 2). In the 3D viewport window, the program now shows you a larger version of the square base area. Some positive feedback appears in the Blender Python Console (line 3). The Info line below contains the complete log entry including all default values that are not specified.
If you want to familiarize yourself with how variables and loops work as a small exercise before actually building the dial, why not first create a small pyramid? To do this, enter the code from Listing 2 in the Blender Python Console. Note that loops in Python are always formed by indenting the loop block. In the Blender Python Console, end the indentation using a blank line at the end of the loop.
Listing 2
Pyramid Builder
i=1 while i < 9: bpy.ops.mesh.primitive_cube_add(size=1, enter_editmode=False, align='WORLD', location=(0, 0, i-0.5), scale=(9-i, 9-i, 1)) i+=1
Key Concepts
You can use the Python API to access the data in Blender in the same way as via the user interface. Basically, what you can access by pressing switches and buttons and selecting menu items can also be controlled using Python. All the data of the currently loaded Blender file can be accessed using the bpy.data
module.
The application organizes the data in Collections, which you access either by index or string. In the Outliner, there are precisely three objects after starting the program with the standard scene: bpy.data.objects['Camera']
, bpy.data.objects['Cube']
, and bpy.data.objects['Light']
. Objects can also be retrieved via their index numbers (e.g., the cube is bpy.data.objects[1]
).
As you are probably familiar with from the dialog-oriented interface, in many cases you do not select the object by typing its name, but use the mouse instead. You can even pick any number of objects at the same time, for example, to move them together. However, only one of them acts as the active object to which all actions refer. In addition to this, all the displayed values come from the active object. Operations that you want to act on multiple objects use this object as a reference, for example, if you assign a different material.
Within Python, you can access the active object using the bpy.context.object
command and access all the selected objects using bpy.context.selected_objects
. However, access to this Context is read-only. To change the values, you will need to call API functions or use bpy.data
.
Listing 3 shows the part of the Python script that positions the digits on the dial (Figure 3). Because the three-dimensional numerals have different widths and heights, you need to pay attention to the correct setting of the origin points of the numerals on the clock face. This ensures correct centering of the one- and two-digit numbers. Figure 4 illustrates the relationship of the trigonometric functions from Listing 4. Listing 5 shows the entire script (available online at [2]).
Listing 3
Preparations and Cleanup
import bpy import math import datetime bpy.ops.object.select_pattern(pattern='text*') bpy.ops.object.select_pattern(pattern='*hand') bpy.ops.object.select_pattern(pattern='plane') bpy.ops.object.delete() hour = datetime.datetime.now().hour minute = datetime.datetime.now().minute hourangle = 360 / 12 * (hour + minute/60) minuteangle = 360 / 60 * minute fromCenter = 3 angleInc = 30 * math.pi/180 bpy.ops.mesh.primitive_plane_add(size=9, enter_editmode=False, align='WORLD', location=(0, 0, 0), scale=(1, 1, 1))
Listing 4
Positioning the Digits
i = 1 while i <= 12: x=fromCenter * math.sin(angleInc * i) y=fromCenter * math.cos(angleInc * i) z=0.2 bpy.ops.object.text_add(location=(x,y,z)) ob=bpy.context.object ob.data.body = str(i) ob.modifiers.new("SOLIDIFIED TEXT","SOLIDIFY").thickness=0.2 bpy.ops.object.origin_set(type='GEOMETRY_ORIGIN', center='MEDIAN') i+=1
Listing 5
Finished Python Script
import bpy import math import datetime bpy.ops.object.select_pattern(pattern='Text*') bpy.ops.object.select_pattern(pattern='*Pointer') bpy.ops.object.select_pattern(pattern='plane') bpy.ops.object.delete() hour = datetime.datetime.now().hour minute = datetime.datetime.now().minute hourangle = 360 / 12 * (hour + minute/60) minuteangle = 360 / 60 * minute fromCenter = 3 angleInc = 30 * math.pi/180 bpy.ops.mesh.primitive_plane_add(size=9, enter_editmode=False, align='WORLD', location=(0, 0, 0), scale=(1, 1, 1)) i = 1 while i <= 12: x=fromCenter * math.sin(angleInc * i) y=fromCenter * math.cos(angleInc * i) z=0.2 bpy.ops.object.text_add(location=(x,y,z)) ob=bpy.context.object ob.data.body = str(i) ob.modifiers.new("SOLIDIFIED TEXT","SOLIDIFY").thickness=0.2 bpy.ops.object.origin_set(type='GEOMETRY_ORIGIN', center='MEDIAN') i+=1 bpy.ops.mesh.primitive_cube_add(size=2, enter_editmode=False, align='WORLD', location=(0, 0, 0.05), rotation=(0, 0, 0), scale=(1, 1, 1)) bpy.context.selected_objects[0].name="hour hand" bpy.context.selected_objects[0].dimensions=( 0.2, 2, 0.1) bpy.ops.transform.translate(value=(0, 1, 0)) bpy.context.scene.tool_settings.use_transform_data_origin = True bpy.ops.transform.translate(value=(0, -1, 0)) bpy.context.scene.tool_settings.use_transform_data_origin = False bpy.ops.transform.rotate(value= angleHours * math.pi/180) bpy.context.object.lock_location[0] = True bpy.context.object.lock_location[1] = True bpy.context.object.lock_location[2] = True bpy.context.object.lock_rotation[0] = True bpy.context.object.lock_rotation[1] = True bpy.ops.mesh.primitive_cube_add(size=2, enter_editmode=False, align='WORLD', location=(0, 0, 0.1), rotation=(0, 0, 0), scale=(1, 1, 1)) bpy.context.selected_objects[0].name="minute hand" bpy.context.selected_objects[0].dimensions=( 0.2, 2.6, 0.1) bpy.ops.transform.translate(value=(0, 1.3, 0)) bpy.context.scene.tool_settings.use_transform_data_origin = True bpy.ops.transform.translate(value=(0, -1.3, 0)) bpy.context.scene.tool_settings.use_transform_data_origin = False bpy.ops.transform.rotate(value= angleMinutes * math.pi/180) bpy.context.object.lock_location[0] = True bpy.context.object.lock_location[1] = True bpy.context.object.lock_location[2] = True bpy.context.object.lock_rotation[0] = True bpy.context.object.lock_rotation[1] = True
Buy this article as PDF
(incl. VAT)
Buy Linux Magazine
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Subscribe to our ADMIN Newsletters
Support Our Work
Linux Magazine content is made possible with support from readers like you. Please consider contributing when you’ve found an article to be beneficial.
News
-
Systemd Fixes Bug While Facing New Challenger in GNU Shepherd
The systemd developers have fixed a really nasty bug amid the release of the new GNU Shepherd init system.
-
AlmaLinux 10.0 Beta Released
The AlmaLinux OS Foundation has announced the availability of AlmaLinux 10.0 Beta ("Purple Lion") for all supported devices with significant changes.
-
Gnome 47.2 Now Available
Gnome 47.2 is now available for general use but don't expect much in the way of newness, as this is all about improvements and bug fixes.
-
Latest Cinnamon Desktop Releases with a Bold New Look
Just in time for the holidays, the developer of the Cinnamon desktop has shipped a new release to help spice up your eggnog with new features and a new look.
-
Armbian 24.11 Released with Expanded Hardware Support
If you've been waiting for Armbian to support OrangePi 5 Max and Radxa ROCK 5B+, the wait is over.
-
SUSE Renames Several Products for Better Name Recognition
SUSE has been a very powerful player in the European market, but it knows it must branch out to gain serious traction. Will a name change do the trick?
-
ESET Discovers New Linux Malware
WolfsBane is an all-in-one malware that has hit the Linux operating system and includes a dropper, a launcher, and a backdoor.
-
New Linux Kernel Patch Allows Forcing a CPU Mitigation
Even when CPU mitigations can consume precious CPU cycles, it might not be a bad idea to allow users to enable them, even if your machine isn't vulnerable.
-
Red Hat Enterprise Linux 9.5 Released
Notify your friends, loved ones, and colleagues that the latest version of RHEL is available with plenty of enhancements.
-
Linux Sees Massive Performance Increase from a Single Line of Code
With one line of code, Intel was able to increase the performance of the Linux kernel by 4,000 percent.