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
-
New Slimbook EVO with Raw AMD Ryzen Power
If you're looking for serious power in a 14" ultrabook that is powered by Linux, Slimbook has just the thing for you.
-
The Gnome Foundation Struggling to Stay Afloat
The foundation behind the Gnome desktop environment is having to go through some serious belt-tightening due to continued financial problems.
-
Thousands of Linux Servers Infected with Stealth Malware Since 2021
Perfctl is capable of remaining undetected, which makes it dangerous and hard to mitigate.
-
Halcyon Creates Anti-Ransomware Protection for Linux
As more Linux systems are targeted by ransomware, Halcyon is stepping up its protection.
-
Valve and Arch Linux Announce Collaboration
Valve and Arch have come together for two projects that will have a serious impact on the Linux distribution.
-
Hacker Successfully Runs Linux on a CPU from the Early ‘70s
From the office of "Look what I can do," Dmitry Grinberg was able to get Linux running on a processor that was created in 1971.
-
OSI and LPI Form Strategic Alliance
With a goal of strengthening Linux and open source communities, this new alliance aims to nurture the growth of more highly skilled professionals.
-
Fedora 41 Beta Available with Some Interesting Additions
If you're a Fedora fan, you'll be excited to hear the beta version of the latest release is now available for testing and includes plenty of updates.
-
AlmaLinux Unveils New Hardware Certification Process
The AlmaLinux Hardware Certification Program run by the Certification Special Interest Group (SIG) aims to ensure seamless compatibility between AlmaLinux and a wide range of hardware configurations.
-
Wind River Introduces eLxr Pro Linux Solution
eLxr Pro offers an end-to-end Linux solution backed by expert commercial support.