Just started Blender (and Python) about a week ago, so please bare with me. I apologize ahead of time for confusing/misused terminology, etc.

I am working on a research project and I need to:

  • create a hemisphere with smaller objects laying flush on the circumference of it in precise locations.

I've been able to do this manually using shrinkwrap modifiers, but I need to be able to do it mathematically and through coding.

So far, I've been able to generate the location where the objects need to be made (I am using simple circles and will replace these with the objects that I need once I get the code working), but my issue is that I can't get the circles to have the correct orientation such that they follow the curvature of the hemisphere. I've spent a good deal of time looking online looking for solutions and reading about rotation matrices, but I can't seem to sort it out. To get the orientation correct, I ended up trying out a rotation_difference function based on something I read online but it didn't work (see below images).

If someone could review my code and advise me on how to possibly proceed, I would greatly appreciate it. Since my understanding of Blender and rotation matrices is not great, a written explanation with some snippets of example code would really go a long way to helping me out.

Here are some images to help clarify:

Here's the hemisphere with the circle translation locations in place: enter image description here

Here's what happens when I try to get the correct orientation of the circles (I made the larger radii circles on top of the smaller circles to make it easier to see for explanation purposes): enter image description here

And the code:

import bpy
import numpy as np
import math
from array import *
from mathutils import Vector
from math import degrees


# Calculates Rotation Matrix given euler angles.  Need angle in radians.
def eulerAnglesToRotationMatrix(theta) :

    R_x = np.array([[1,         0,                  0                   ],
                    [0,         math.cos(theta[0]), -math.sin(theta[0]) ],
                    [0,         math.sin(theta[0]), math.cos(theta[0])  ]

    R_y = np.array([[math.cos(theta[1]),    0,      math.sin(theta[1])  ],
                    [0,                     1,      0                   ],
                    [-math.sin(theta[1]),   0,      math.cos(theta[1])  ]

    R_z = np.array([[math.cos(theta[2]),    -math.sin(theta[2]),    0],
                    [math.sin(theta[2]),    math.cos(theta[2]),     0],
                    [0,                     0,                      1]

    R = np.dot(R_z, np.dot( R_y, R_x ))

    return R

#Variable Initiations.
maxYaw = (75) #degrees
maxPitch = (65) #degrees
radius = 0.3 #degrees

hemiRadius = 0.3
hemiSegments = 16
hemiRingCount = 16
hemiLocation = (0, 0, 0)
dropRadius = 0.03
dropVertices = 16

bpy.ops.mesh.primitive_uv_sphere_add(segments = hemiSegments, ring_count = hemiRingCount, size=hemiRadius, view_align=False, enter_editmode=True, location=hemiLocation)

#cut sphere in half vertically
bpy.ops.mesh.bisect(plane_co=(-0.3, 0, 0), plane_no=(0, 1, 0), clear_inner=True, clear_outer=False, xstart=494, xend=1343, ystart=384, yend=384)

#conversion into radians necessary for rotation matrix function
maxYaw = (maxYaw/180.0)*np.pi #now in radians
maxPitch = (maxPitch/180.0)*np.pi #now in radians

#locations of needed circles
rollPointsDeg = [[0, 0, 0],                #0 center
                [0, maxPitch, 0],          #1 top mid
                [0, maxPitch, maxYaw],     #2 top left
                [0, 0, maxYaw],            #3 mid left
                [0, -maxPitch, maxYaw],    #4 bot left
                [0, -maxPitch, 0],         #5 bot mid
                [0, -maxPitch, -maxYaw],   #6 bot rig
                [0, 0, -maxYaw],           #7 mid rig 
                [0, maxPitch, -maxYaw]]    #8 top rig

#results from rotation matrix: where the needed circles locations correspond to on the surface of the hemisphere
rollPoints =    [[-radius, 0, 0],   #0
                 [0, 0, 0],         #1        
                 [0, 0, 0],         #2  
                 [0, 0, 0],         #3
                 [0, 0, 0],         #4
                 [0, 0, 0],         #5
                 [0, 0, 0],         #6
                 [0, 0, 0],         #7
                 [0, 0, 0]]         #8

#the rotational matrix variable
rotMat = np.array([[0,0,0],[0,0,0],[0,0,0]])

#Rotation Matrix Loop.  

while i < 9:
    rotMat = eulerAnglesToRotationMatrix(rollPointsDeg[i]) #converts the rotMat into the rotational matrix
    rollPoints[i] = np.dot (rotMat, rollPoints[0])  #dot product of rotMatrix and rollPoints[0]
    i += 1

print("rollPoints:")            #####
j = 0                           #
while j < 9:                    # simple loop confirming the values of the rollpoints
    print(rollPoints[j])        #    
    j += 1                      #####

v0 = Vector(( 0, 0, 0))  #Set for rotation axis global
v1 = Vector(( 0, 0, 0))  #default initiation for RollPoint locations
rollAngles =    [[0, 0, 0],         #0
                 [0, 0, 0],         #1        
                 [0, 0, 0],         #2  
                 [0, 0, 0],         #3
                 [0, 0, 0],         #4
                 [0, 0, 0],         #5
                 [0, 0, 0],         #6
                 [0, 0, 0],         #7
                 [0, 0, 0]]         #8

#code to generate appropriate angles orientations that correspond with rollPoint locations
while i < 9:
    #start loop
    v1 = Vector((rollPoints[i][0], rollPoints[i][1], rollPoints[i][2]))
    rollAngles[i] = v1.rotation_difference( v0 ).to_euler()

    print( [ degrees(a) for a in rollAngles[i] ] )
    #this should provide output or angle rotation in each axis: [ #, #, #]

    #end loop

#comment out this line if getting errors from blender about <<operation cannot performe in edit mode

k = 0
while k < 9:
    bpy.context.scene.cursor_location = (0.0, 0.0, 0.0)

    bpy.ops.mesh.primitive_circle_add(vertices=16, radius=0.04, view_align=False, enter_editmode=False, location=(-rollPoints[k][1], -rollPoints[k][0],-rollPoints[k][2]), rotation=(-rollAngles[k][0], rollAngles[k][1], rollAngles[k][2]))

    bpy.context.scene.cursor_location = (0.0, 0.0, 0.0)
    k +=1

#currently, only the translation values seem to be correct, the orientation of the circle is NOT
#unsure how to fix this
  • $\begingroup$ No need for translations? Simply create all the circles at radius(hemisphere) and rotation 0,0,0.That is to say, at the North pole of the hemisphere. Then rotate them into place about the world (& hemisphere) origin, and they will naturally wind up tangential to the hemisphere? $\endgroup$ – Robin Betts Mar 18 '19 at 15:05
  • $\begingroup$ Hi. I have tried to improve the title of the question to make it clearer what you're actually asking. If I have mistinterpreted your question you can use the edit link below it to either edit or roll back my changes. $\endgroup$ – Ray Mairlot Mar 18 '19 at 15:57
  • $\begingroup$ Thank you very much for your assistance. I actually worked through the problem today and I have resolved it. My orientation values were the same as my original rotation and I was able to use them successfully as one of my rotational axis was constant zero so it didn't require building a secondary matrix. Finally, my hemisphere was orientated in an odd way which made the smaller circles seem misaligned. I changed the orientation of my hemisphere, then added pi/2 + rotation of one of my 3 axis and the model fit fine. Thank you again for your assistance! $\endgroup$ – user70711 Mar 18 '19 at 16:56
  • 2
    $\begingroup$ If you have a solution you should post it in the answer box below. $\endgroup$ – Ray Mairlot Mar 18 '19 at 17:02

The pose (location and orientation) of the circles was already contained within the math. The reason why the code wasn't adding the correct orientation (only location) was because of my poor coding. After I coded it, I re-coded some portions and didn't adjust the math properly. Specifically, I bisected the sphere at a different location to be able to better visualize the row/rings of the sphere mesh as they contacted my circles and, due to this, my x, y, z orientations were swapped up (in relation to the location of the bisected sphere).

I went back and bisected the sphere such that the orientation matched the orientation of the rotation matrix (and changed the index order and sign of the index back to its regular conditions (for example, -A[1], -A[0], -A[2] changed to A[0], A[1], A[2])). I then discarded the 'rotation difference' function and added the rotation values as my original rotations values. This worked because one of the three original rotation values was constant 0. If this value was not constant 0, then I would have had to create a second rotation matrix to determine the orientation values.

Thanks for the input everyone!

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.