Visualizing spur gear cutting in Blender

 To get a feeling on how spur gear teeth get their particular shape, I created a short visualizer:


The whole process of emulating the action of the cutter on the rotating blank was done in Blender, this time not using a add-on for once, but a simple script that runs from the text editor.

The script renders a frame, then applies the boolean difference modifier that the spur gear object (the 'blank') has, moves both the blank and the cutter and adds again a boolean modifier and repeats this as many times as desired.

I am not yet sure if I will write a small article explaining the code, it isn´t all that complicated, but in the meanwhile you can download the .blend file from my GitHub repository (click on the 'download raw file' icon in the upper right corner to download). 

Hexagonal patterns in Blender's geometry nodes

 I have been experimenting a bit with geometry nodes lately and I thought I´d share this one

The .blend file is available from my GitHub repository. (Click the 'download raw file' button in the upper right corner to download it; Inside is a sample scene, and the geometry node itself is called 'Hexagon pattern')

Tips

The input mesh is simply repeated across the pattern and nothing fancy is done to it. If the repeated meshes are butted up to each other you may want to remove any coinciding vertices, and I could have done that in the geometry nodes itself but that is unnecessary as you can easily apply a weld modifier to achieve the exact same effect. And other modifier too of course, like a solidify modifier perhaps, to give the grid some thickness.

If you just want a quick hexagonal pattern in a shader, you may want to have a look at this post.

Some details

The node setup is pretty straight forward:

We get the bounding box of the object we want to repeat, scale it a bit so we can add a gap if we like, and the repeat the mesh for a set number of iterations along the x-axis. Then we replicate the result along the y-direction and end with applying a material index.

If we look at the repeat x section, we see that there isn´t much to it:

We simply join a shifted version of the input for a set number of times. The offset in the x-direction is twice the maximum of x dimension of the bounding box, i.e. we assume that the input mesh is symmetrical around the origin.

The repeat y section isn´t all that much different, except for a little detail:

That detail is that we move the new mesh up in the y-direction by a configurable scaling factor, where the default is fit for a six sided cylinder, a.k.a. a hexagon, but you can change that to something else if needed.
We also move the row either to the right or the left, depending on whether we are in an odd or even row. We determine this odd/evenness by taking the iteration number module 2 and using a switch node to provide a multiplication factor of -1 or 1 respectively that we apply to our x offset. If we wouldn´t alternate this move in the x-direction we would get a skewed grid, which might be find, but I prefer to work with a square grid.



New blog: On the back of an envelope

 


I'm interested in more than just Blender ðŸ˜€ so I started a new blog. It might interest some readers of this blog as well as it has a bit of a mathematical focus, just like quite a few articles here.

It is all about assumptions and questions that may pop up in everyday life and that may be solved with a bit of thinking and pen and paper, hence the title "On the back of an envelope".

Focus is on doing our own research up, backed up by proper references to articles by real people, and no easy AI slop. 

Blender add-on development: Multi-file add-ons and custom icons

There are new videos available in the video series on Blender add-on developments for beginners:

 

It is completely free and has a GitHub repository with code .

In this module we will refactor the render_done add-on encountered in the previous module into a multi-file add-on. The first video will take a look at why splitting up an add-on might be beneficial for maintenance, reuse and the options to include non-Python files, something we will make use of in the second video where we will add a custom icon to one of the operators.

Blender add-on development: Application handlers and presets

There are new videos available in the video series on Blender add-on developments for beginners:

 

It is completely free and has a GitHub repository with code .

In this module we will build an add-on that installs application handlers that will send an email once a tender job has finished. The first video will focus on application handlers, the second one the layout of user preferences to configure things like recipient and email server while the third is about the actual mail code. The fourth video will then tie this all together in a functional add-on. We end with a video that is decidedly not strictly beginner level where we cover creating presets in the user preferences, something that is a bit more involved than adding presets to operators.

Modal operators in Blender



To illustrate how to create a modal operator I created a small add-on, plumb_line, to illustrates a few key principles.

The add-on itself is a bit of a toy, allowing the user to move the 3d cursor around while showing the distance to an intersection point directly below it.

This is not very useful in itself, but it does

  • implement a modal operator
  • show how to create overlays in the 3d view, and
  • how to use Object.ray_cast() and Scene.ray_cast() to find intersections

And because the code covers all kinds of functionality, we structured the code into separate modules, so this add-on also implements some tricks to force module reloading on reinstalling the add-on to prevent having to restart Blender every time we change something. It also touches briefly on using numpy, and offers a lot of configuration options in the user preferences.

Quite a lot for a demo add-on, but in this article we focus on the modal operator.

Anatomy of a modal operator

Not all modal operators look the same, but a common pattern is shown below:

class OBJECT_OT_my_modal_operator(bpy.types.Operator): bl_idname = "object.modalop" bl_label = "Modal Operator" @classmethod def poll(cls, context): ... def modal(self, context, event): if some_condition: ... return {"RUNNING_MODAL"} else: ... return {"FINISHED"} # or CANCELED def invoke(self, context, event): ... context.window_manager.modal_handler_add(self) return {"RUNNING_MODAL"} def cancel(self, context: Context) -> None: ...

The poll() method has the same function as in a non-modal operator, but typically there is no execute() method.

If an operator is invoked, for example from a menu entry, its default invoke() method will typically call its execute() method.

In a modal operator we override the invoke() method, and instead of calling execute() we add a modal handler to the window manager and return "RUNNING_MODAL".

This will cause the window manager to call the modal() method on the handler, typically the operator itself, and keep on doing so as long as that method returns "RUNNING_MODAL".

The window manager will call the the modal() method each time an event occurs, and passes this event as an argument to the call. This event can be anything from a key press to a mouse move and we can even cause timer events to be passed if we enable a timer in the window manager.

This arrangement makes it possible to create interactive add-ons where the user uses keyboard or mouse actions to work with objects in a scene, until they end the interaction, typically by pressing escape or with a right mouse click.

The plumb_line invoke() method

Our operator will be invoked from the Object menu and it needs to do a few things:

  • position the the 3d cursor somewhere above the active object,
  • add a modal handler, and
  • add a pair of draw handlers that show the line and intersection point as well as some text with the distance.

It does a few other things as well, but the relevant lines of code look like this:

def invoke(self, context, event): ... z = max_world_z_of_bounding_box( context.active_object.bound_box, context.active_object.matrix_world ) context.scene.cursor.location = context.active_object.location.copy() context.scene.cursor.location.z = z + 3 # arbitrary offset ... context.window_manager.modal_handler_add(self) ... self.post_view_handler = bpy.types.SpaceView3D.draw_handler_add( draw_handler_post_view, (), "WINDOW", "POST_VIEW" ) self.post_pixel_handler = bpy.types.SpaceView3D.draw_handler_add( draw_handler_post_pixel, (), "WINDOW", "POST_PIXEL" ) return {"RUNNING_MODAL"}

It uses a helper function defined elsewhere to calculate the highest z-coordinate of the active object's bounding box and then copies the location to keep the cursor centered above the object in the x and y directions, but sets its z-coordinate to whatever we calculated plus an arbitrary offset, 3 in this case. Note that we needed to copy the object location because assigning it to the cursor location would also cause the object location to change every time the cursor location is updated as they would refer to the same Vector object.

Next, we add a modal handler, in this case the operator itself. This will cause the window manager to calle the modal() method and we will look at that in the next section.

Finally, we install two draw handlers, also defined in a separate module. The post view draw handler works in 3d space, and will show the actual 'plumb line' going from the 3d cursor to the intersection with the active object. The post pixel draw handler works in 2d on top of everything, and is used to show the text with the measured distance and the intersection highlight. We might cover these handlers in a future blog post.

The return value indicates that we are not done yet, and want to keep on running.

The plumb_line modal() method

def modal(self, context, event): context.area.tag_redraw() if event.type in {"RIGHTMOUSE", "ESC"}: self.cancel(context) return {"CANCELLED"} ... if event.type in {"UP_ARROW", "DOWN_ARROW", ...}: if ( event.value == "PRESS" ): ... context.scene.cursor.location.y += increment ... # calculate the intersection with the object below the cursor context.window_manager.target = worldspace_location context.window_manager.distance_label = ... if ( event.type.find("MOUSE") >= 0 or event.type.find("NUMPAD") >= 0 ): return {"PASS_THROUGH"} return {"RUNNING_MODAL"}

The first thing we do is to make sure that we mark the area for redraw, so that no matter what we do, our draw handlers will be executed.

Next we check if the event was a press of the escape key or a right mouse click, as these are the common way in Blender to interrupt an operation. If it was, we call the cancel() method to remove our draw handlers and return "CANCELLED" to signal that we are done.

We look for some other key presses as well, but we skip that here, but we also look for arrow keys. If such a key was pressed, so not released because we do not want to process a key click twice, we calculate how the position of the 3d cursor should change and the update the cursor location. By looking for just key presses, as opposed to key releases, the user can also keep the key pressed and cause rapid repetition.

With the new cursor location we then calculate the point of intersection with object straight below the cursor. We don´t show that code here, we might cover that in a future blog article, but the result ends up in the worldspace_location variable. We store that in the target property of the window_manager where it will be picked up by our draw handlers to draw a line from the 3d cursor to that point. We also calculate the distance and store that in a window manager property too, to be picked up by our other draw handler that will display this in a text label next to the line.

Because we also want to give the user the option to navigate the 3d view to get a different perspective, so we also check if the event was a mouse event or numpad key, in which case we return "PASS_THROUGH". This will not end our operator but cause the event to pass to Blender's regular event processing, which will move the view around accordingly.

Any other events are simply ignored and we return "RUNNING_MODAL" to keep the operator running.

You may have noticed that none of the paths through the code ever return "FINISHED" and that is intentional: This add-on doesn´t change anything in the scene, just displays information in an overlay, so there is no distinction between canceling an operation or finishing it. That is also why we didn't add "UNDO" to the bl_options variable, as there will never anything to be undone. (In fact, we didn´t define bl_options at all, but its default is just {"REGISTER"})

Code availability

The full code, with full type annotation and lots of additional information in the comments and a readme is available on GitHub.

More ...

If you like these kind of detailed explanations, keep an eye out for future blog posts, or have a look at the Blender add-on development playlist on my YouTube channel.

And if you really, really, like it (and can afford it), you might consider leaving me a tip on Ko-Fi.

Blender add-on development: overlays and user preferences

There are new videos available in the video series on Blender add-on developments for beginners:

 

It is completely free and has a GitHub repository with code .

In this module we will build an add-on that will show the distances between objects as lines and labels in an overlay. The first video will discuss draw handlers, while the other videos will make use of them to create a complete add-on, one that also includes user preferences to choose line color and font size.