Parent inverse matrix question

Hello people,

I’ve been making a little script to automate the adding of a camera setup I frequently use.
However, when I get to the step of parenting the ring lights to my camera, its where it gets confusing.

Using the normal parenting functions works perfectly fine, but then the lights jump position. The internet has suggested using the inverse_matrix_parent function instead. But this gives me an error. Could anyone shed some light on what I am doing wrong? Below you will find the relevant code.

It is the last line that gives me the following error: ValueError: bpy_struct: item.attr = val: sequence expected at dimension 1, not ‘Object’
Is it expecting some sort of array of values? if I use the normal set_parent it works fine.

Is there some other way I should approach parenting the lamps to the camera?

#Create new light

#lamp location
for i in range(0,amount):
    lampData = bpy.data.lights.new('Area_light', "AREA")
    lampObject = bpy.data.objects.new('Area_light', lampData)
    #location
    lampObject.location.x = 5*math.cos(i) +  camObject.location.x 
    lampObject.location.z = 5*math.sin(i) +  camObject.location.z 
    lampObject.location.y = camObject.location.y

    #parent
    lampObject.matrix_parent_inverse = camObject

It depends on what you want to do. If i assume you want to position the lamps in a position relative to the camera object orientation, then you can just set the position offset and parent:

#location
lampObject.location.x = 5*math.cos(i)
lampObject.location.z = 5*math.sin(i)
#parent
lampObject.parent = camObject

Note that the position offset is relative to the camera transform, and you might want to change the axis you offset the ligths with, e.g. offseting the camera in relation to the .y axis rather then the .z axis (as camera objects are facing -z).


If you do not care about camera orientation, you might want to specify the lamps position in absolute coordinates and you should pass the inverse transform matrix to the parameter rather then the object itself (that’s why you get the error):

#location
lampObject.location.x = 5*math.cos(i) +  camObject.location.x 
lampObject.location.z = 5*math.sin(i) +  camObject.location.z 
lampObject.location.y = camObject.location.y

#parent
lampObject.parent = camObject
lampObject.matrix_parent_inverse = camObject.matrix_world.inverted()

I’m uncertain if this is actually what you want, as this code doesn’t take account for orientations applied to the camera object (the lamps will always be placed with identical world offsets and it does not consider the direction the camera is facing).

1 Like

Thank you for the explanation. Now I understand why I get the error! Great.
I do in fact want to parent the lights relative to the camera orientation. I hadn’t even thought about implementing an extra offset to counteract. Haha. I was so bogged up in my thinking process that such a simple solution was not something on my mind.
Thank you!

This sentence has solved my entire problem. I was not aware that this was actually why the lights were moving. I was assigning the positions in a world transformation.
All is set up now and my code is as follows:

#Create new light

#lamp location
for i in range(0,amount):
    lampData = bpy.data.lights.new('Area_light', "AREA")
    lampObject = bpy.data.objects.new('Area_light', lampData)
    #location
    lampObject.location.x = 5*math.cos(i) 
    lampObject.location.y = 5*math.sin(i)
    lampObject.location.z = camObject.location.z
    #parent
    lampObject.parent = camObject

I’m glad my solution was of help and it looks like you where able to solve it. However, i’m unsure if this line does what you intend it to do:

lampObject.location.z = camObject.location.z

It would place the lights either infront or behind the camera at a distance depending on the current position of ‘camObject’. This does not matter if the camera has any translation associated with it but i would assume you want to place the lights at some specific distance infront/behind the camera. E.g. the following would place the lights 5 units behind the camera:

lampObject.location.z = 5
1 Like

Thank you for your concern. It does however do what I want it to do. I am creating a ring light around my camera. The full script is added below, so you can inspect it. I need a camera, a camera focus object, and a ring of lights around it. If there is a fundamental flaw in the execution I am not aware of, please do let me know. I am only a beginner with python.

I can move the camera, which correctly moves the lights, and I can move the camera focus null, which moves the direction of the camera and lights.

import bpy
import math
from mathutils import Matrix

def create_cam_and_lights(amount):
#create new collection for camera
camColl = bpy.data.collections.new(“Camera”)
bpy.context.scene.collection.children.link(camColl)

#create collection for lights
lightColl = bpy.data.collections.new("Lights")
camColl.children.link(lightColl)

#create tracking empty
emptyObject = bpy.data.objects.new("Focus_object", None)
camColl.objects.link(emptyObject)


#Create camera
camData = bpy.data.cameras.new("camData")
camObject = bpy.data.objects.new("Camera", camData)
#link camera to camera collection
camColl.objects.link(camObject)



#modify camera parameters
camObject.location = [0,-10,0]
camData.dof_object = emptyObject
camData.cycles.aperture_size = 0.02


#track constraint
trackConstraint = camObject.constraints.new('TRACK_TO')
trackConstraint.target = emptyObject
trackConstraint.track_axis = "TRACK_NEGATIVE_Z"
trackConstraint.up_axis = "UP_Y"

    
    
#Create new light

#lamp location
for i in range(0,amount):
    lampData = bpy.data.lights.new('Area_light', "AREA")
    lampObject = bpy.data.objects.new('Area_light', lampData)
    #location
    lampObject.location.x = 5*math.cos(i) 
    lampObject.location.y = 5*math.sin(i)
    lampObject.location.z = camObject.location.z
    #parent
    lampObject.parent = camObject


    #light constraint
    lampTrack = lampObject.constraints.new('TRACK_TO')
    lampTrack.target = emptyObject
    lampTrack.track_axis = "TRACK_NEGATIVE_Z"
    lampTrack.up_axis = "UP_Y"
    #misc parameters
    lampData.use_nodes = True
    lampData.size = 1
    #Light material
    lnt = lampData.node_tree
    lnt.nodes["Emission"].inputs[1].default_value = 1000


#add to collection


    lightColl.objects.link(lampObject)

create_cam_and_lights(6)

Well the issue that i mentioned has to do with when you set the camera position, if you ever change the position coordinate you currently set to the camera:

camObject.location = [0,-10,0]

you will move the lights along the camera z axis with the same distance as the camera’s z coordinate value! This is not a concern after you create the setup etc. but it could be confusing if you ever create the camera setup at some custom coordinate. Anyhow just set the lamp offset to 0 (lampObject.location.z = 0) or just remove the line:

lampObject.location.z = camObject.location.z

You can always test what i mean by setting the z coordinate in the first line i mentioned to something else.

Now I see! You were right. I removed the line, just in case. I figure the more robust the code the better right.