I'm trying to model and 3D print pinhole lenses / glasses but I'm having some trouble creating the hexagon shape with array modifiers.

The lens shape I'm trying to get is below. Lens

I was thinking of using the array modifiers because it will allow me to vary the hole size and the distance between the holes with mm precision. I would use small cylinders to cut the holes out of a larger cylinder by using a boolean difference on the larger lens/cylinder. See image below

Arrays modifier


The question is how can I create the hexagon shape of holes with modifiers? Is this the best way?


4 Answers 4


Here is Modifier version you asked for ...
... add a Circle 6 sided, extrude E, scale S - to create a segment with a hole

  • Array - use two of them to create like "rhombus" shape
  • Mirror X and Y - mirroring does some parts overlapping, but no problem ...
  • Weld is used to merge double vertices

enter image description here

Big thanks and credit goes to @Robin Betts. He added two drivers to a second Array modifier. So now just simply increasing/decreasign a count of the first Array automatically changes a count of second Array ... and more of that second driver maths correct distance for segments on X axis.

You don't have to use drivers to make this modifier stack to work. They just make a life easier :)

Notes: When you are happy with a number and size of holes you can easily continue with other modifiers ... Subsur, Solidify, Bevel all with a nice topology that wouldn't be happen (I guess) if you would use Boolean operation.

To get Circle shape around - apply modifiers (in case of version using drivers
search for Convert to Mesh to apply modifiers). In edit mode select outer loop, extrude, scale and
search for To Sphere set 1

enter image description here

Anyway, even Arrays / Mirror / Weld modifiers are applied you can any time select one hole loop and with Select Similar Shift+G > Length to select all holes and with Pivot Point > Individual Origins you can still adjust size of holes.

  • 4
    $\begingroup$ I think your answer is much better than mine. It's simpler, and answers the OP more directly, and wids up with better topo. I like your array set-up.. so simple to make the first X negative to keep the origin at the center for the mirror. I've taken inspiration from it and produced a version with drivers between the array mods, so all you need to do is change the count in the first array mod. You can then scale the hexes to dimension, and inset to dimension for the holes. $\endgroup$
    – Robin Betts
    Commented Feb 6, 2021 at 9:45
  • $\begingroup$ @RobinBetts Please post a full answer with a version provided in your linked blend file. It is awesome! I will delete mine, that is now intermediate process. Make a surface and hole at one segment is great point. Also I didn't notice -any new array added make it hard to find a right distance, so ... driver is more than welcomed :) I don't know how to use drivers, so go a head :) To readjust amount of holes just by one click is super useful. You are the best. Thanks $\endgroup$
    – vklidu
    Commented Feb 6, 2021 at 15:07
  • $\begingroup$ I wouldn't have thought of that way without your answer.. no way should you delete! Just plug that .blend into your answer, if you like, and/or point at the commentary. $\endgroup$
    – Robin Betts
    Commented Feb 6, 2021 at 15:32
  • $\begingroup$ Thanks this is Very Nice!!! $\endgroup$
    – Rick T
    Commented Feb 6, 2021 at 19:10

I don't know if this method will appeal to your precision requirement, but believe that, with care, the accuracy will be as good as that achieved by a boolean, especially if you take into account the topological anomalies that a boolean could introduce.

A (Edge Menu) subdivided fan-filled 6-sided circle automatically gives you a hexagonal grid. It is at this stage you can set the hole-intervals:

enter image description here

You can use the shipped add-on Loop Tools > Circle to make the perimeter circular, and set its radius. At this stage, you could select the whole face-region, and inset it slightly, to protect the perimeter.

All the other vertices can be CtrlShiftB bevelled to a small radius, so the resulting faces are easy to select (ShiftG) by area. They are circular for the most part, but just to be sure, we can use Loop Tools > Circle again on all the selected faces, and set their radius there. Now they can be given a border by using I (Inset) set to Outset.

enter image description here

Then the inner faces can be deleted to make the holes, and the whole object given Solidify, Bevel (by angle) and Subdivision Surface modifiers..

enter image description here

If you wanted more precision than that, you could measure the relative diameters of a hexagon in a similar grid with, and without, 2 levels of Catmull-Clark subdivision, in a larger-scale trial, and compensate accordingly when setting the diameter of the holes with Loop Tools.

enter image description here


If you want to have a live control as with the array modifier, you could try using an instancer:

  1. SHIFT + A, then M, then Y for a new cylinder.
  2. SHIFT + A, then M, then R for a new circle - set vertices to 6 and radius to 2 m.

  1. In Instancing choose Vertices.

  1. Select the cylinder, then the circle, press SHIFT + P then ENTER

  1. Press ALT + Z for X-ray mode.
  2. Select the "Circle" (more like hexagon actually) and press TAB to edit it.
  3. Press A to select all vertices.
  4. Press SHIFT + D to duplicate selected ring, then S, then 2 to scale it.
  5. Right-click, and choose Subdivide.
  6. Select the inner ring, then go to p. 8. but each time increase the scaling and number of subdivisions by 1.

  1. In the end you can press A to select all vertices, then S to scale and adjust spacing (in the GIF I've hidden the overlay)

  1. At the end, you can select the instancer ("Circle") press CTRL + A and then M to Make Instances Real
  • $\begingroup$ I'm beginning to think I should delete my answer, both yours and @vklidu's are more flexible. :) $\endgroup$
    – Robin Betts
    Commented Feb 6, 2021 at 9:52
  • $\begingroup$ @RobinBetts I like your answer and intended mine to be a supplementary alternative. In your case you could scale everything for different spacing and scale holes for different sizes. $\endgroup$ Commented Feb 6, 2021 at 10:22

Select your base cylinder, name it Base and add a custom property (make sure to add it for the object and not mesh):

Name it distance and make sure to click the blue button:

Now you can go to the Scripting tab, and paste the following script:

import bpy, math

rings = 5  

D = bpy.data
objs = D.objects

name_template = 'Generated.Cylinder.{}.{}'  # ring index . index on the ring
collection = D.collections['Collection']
base = objs['Base']
prop_name = 'distance'

def add_drivers(o, i, j):
    angle_deg = 360/6
    angle_rads = angle_deg * math.pi / 180
    offset = j % i / i  # percentage of the distance between one corner and another
    corner1 = j // i * angle_rads
    corner2 = (j // i + 1) * angle_rads
    if not offset:
        # corner cylinder
        expression = '{}' + f'({corner1}) * {prop_name} * {i}' 
        # cylinder between corners
        expression =  '{0}' + f'({corner1}) * {prop_name} * {i} * {offset} + '
        expression += '{0}' + f'({corner2}) * {prop_name} * {i} * {1-offset} '
    d = o.driver_add('location', 0 ).driver
    v = d.variables.new()
    v.name = prop_name
    v.targets[0].id = base
    v.targets[0].data_path = f'["{prop_name}"]'
    d.expression = expression.format('sin')

    d = o.driver_add('location', 1 ).driver
    v = d.variables.new()
    v.name = prop_name
    v.targets[0].id = base
    v.targets[0].data_path = f'["{prop_name}"]'
    d.expression = expression.format('cos')

for i in range(1, rings):
    for j in range(i*6):
        name = name_template.format(i, j)
        o = objs.get(name)
        if o is not None:
            objs.remove(o, do_unlink = True)
        o = base.copy()
        o.name = name
        o.data = base.data  #.copy() would allow to have different shapes for each object
        add_drivers(o, i, j)

Now, select the base cylinder, press N if the side panel is not visible, and you should see your custom property at the bottom of it. Changing it should adjust positions of all objects:

I used some (basic) Python shenanigans, so feel free to ask in the comments for clarification.


You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .