I'm working off examples found here:

Getting global axis value of the vertex with the lowest value

(and other from @georges and @tlousky, many thanks!)

I'm working off a csv example and an 'set Z to floor' example.

However, I can't get the final piece of this to work. The script reads a CSV list of objects (basically the bounding boxes of my office furniture, to use for space planning), creates a basic cube, scales to the WxDxH values given, adds a text label and links the label to the cube. I've been able to get all that to work just fine (also arranging the new objects in nice rows of five) However the part that I can not figure out is how to get the Z minimum to 0 for each object.

Using the sample supplied by @georges, which is based on @tlousky's sample, I attempt to reset the origin of each object to the Z minimum. However, as the debug output shows, there is a disconnect between the vertex values of the cube inside the function versus outside...

A sample of the debug, and then the script itself:

{'Depth': '32', 'Height': '13', 'Name': 'Printer - Epson', 'Width': '33', 'Note': ''}

//This is inside the function, appears to be an un-transformed cube
[-1.0, 0.0, -1.0, 0.0, -1.0, 0.0, -1.0, 0.0]
-1.0  minZ

//This is outside the function, shows correctly scaled coords
[(163.5, 164.0, -7.0), (163.5, 164.0, 6.0), (163.5, 196.0, -7.0), (163.5, 196.0, 6.0), (196.5, 164.0, -7.0), (196.5, 164.0, 6.0), (196.5, 196.0, -7.0), (196.5, 196.0, 6.0)]  for v
-7.0  is minZ?
6.0  is minZ?
-7.0  is minZ?
6.0  is minZ?
-7.0  is minZ?
6.0  is minZ?
-7.0  is minZ?
6.0  is minZ?

Obviously, this will never be true,

if (mw * v.co).z == minZ: 
            print ('is minZ')
            v.select = True

and therefore no vertices are selected, and the Set Origin never happens.

I can not figure out why the data is different inside the function! Thanks in advance for tips, corrections or pointers!


PS, please forgive, I'm not completely naïve with python and blender, but pretty close ;-)

The Data Set looks like this:

Clerk Table,49,31,40,
Old Folding Table,60,35,30,

# import CSV data, build objects and labels, set to floor

import bpy,bmesh
import csv
from math import radians

fPath   = '/Users/peterf/Google Drive/2015 Projects-Strategy/401 N Dodge/Fraterdeus Design Studio Objects - Sheet1.csv'
''' Name    Width   Depth   Height  Note

# from https://blender.stackexchange.com/questions/55702/getting-global-axis-value-of-the-vertex-with-the-lowest-value?rq=1

def select_lower_Z():

    o = bpy.context.object  # active object
    mw = o.matrix_world      # Active object's world matrix
    glob_vertex_coordinates = [ mw * v.co for v in o.data.vertices ]    # Global coordinates of vertices

    bpy.ops.object.mode_set(mode = 'EDIT') #Change mode of selected object to Edit mode
    bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT') #Set the type in Edit mode to Vertices
    bpy.ops.mesh.select_all(action = 'DESELECT') #Deselect all 

    bpy.ops.object.mode_set(mode = 'OBJECT') #Change mode of selected object to Object mode

    # Find the lowest Z value amongst the object's verts
    minZ = min( [ co.z for co in glob_vertex_coordinates ] ) 
    print (( [ co.z for co in glob_vertex_coordinates ] ))
    print (minZ, " minZ") #debug
    verts = [o.matrix_world * vert.co for vert in o.data.vertices]

    plain_verts = [vert.to_tuple() for vert in verts]

    print(plain_verts,' for v') #debug

    # Select all the vertices that are on the lowest Z
    for v in o.data.vertices:

        print ((mw * v.co).z, ' is minZ?')
        if (mw * v.co).z == minZ: 
            print ('is minZ') # Debug
            v.select = True # NB <<<< THIS IS NEVER TRUE 

    bpy.ops.object.mode_set(mode = 'EDIT') #Change mode of selected object to Edit mode

    current_area_type = bpy.context.area.type#Save the current area type to a variable   
    area = bpy.context.area #Change the area to 3D view in order to get rid of wrong context error
    old_type = area.type #Change the area to 3D view in order to get rid of wrong context error
    area.type = 'VIEW_3D' #Change the area to 3D view in order to get rid of wrong context error
    bpy.ops.view3d.snap_cursor_to_selected() #Move the cursor to selected
    bpy.ops.object.mode_set(mode= 'OBJECT') #Set the mode back to Object mode
    bpy.ops.object.origin_set(type='ORIGIN_CURSOR') #Move the selected object origing's to the 3D cursor's location
    bpy.context.area.type = current_area_type #Set the area type back to what it was before changing it to 3D view

with open(fPath) as f:
    reader = csv.DictReader(f, delimiter=',')

    for row in reader:


            radius = 0.5

        obj = bpy.context.active_object
        print (row) #debug
        obj.scale.x = myWidth
        obj.scale.y = myDepth
        obj.scale.z = myHeight
        obj.name = 'object_'+row['Name']


        if (i%5==0): 

        obj.location = ( myLocationX, myLocationY, 0 )

            location = ( myLocationX, myLocationY, myHeight )

        text = bpy.context.object
        text.rotation_euler.z = radians(60)
        text.data.extrude     = 0.05
        text.data.bevel_depth = 0.01
        text.data.body        = row['Name']
        text.scale          = [4,4,2]
        text.name = 'label_'+row['Name']

        text.parent = obj
        text.matrix_parent_inverse = obj.matrix_world.inverted()


        mat_red = bpy.data.materials.new("Text")
        mat_red.diffuse_color = (1.0, 0.0, .0)

        if len(text_data.materials) == 0:
            text_data.materials[0] = mat_red

2 Answers 2


Shift the local coordinates up half the z dimension on cube creation.

Your script appears to me to be overly complicated. Making one cube with origin on bottom face of a cube is just a matter of adding half its z dimension to the local coordinates (vertex coords) after creation (while matrix world is unity which is when local and global coordinates are the same).

The dimensions of this cube can be set from the data. Its default location will remain at (0, 0, 0).

Here is a small sample script, using a list instead of csv file and not bothering to add text label or material.

import bpy

data = [
        ("Clerk Table", 49, 31, 40),
        ("Old Folding Table", 60, 35, 30),
        ("Workstation", 79, 32, 36)

context = bpy.context

def add_cube(name, w, d, h):
    cube = context.object
    # make the origin on bottom face.
    for v in cube.data.vertices:
        v.co.z += cube.dimensions.z / 2
    # set the dimensions
    cube.dimensions = (w, d, h)
    cube.name = name
    return cube

x = y = 0
for row, line in enumerate(data):
    name, w, d, h = line
    cube = add_cube(*line)  # same as add_cube(name, w, d, h)
    # set location        
    cube.location.x = x
    x += w + 30

Note: the script above uses the create primitive operator for each data element. If you are going to be creating a lot of objects it would be advisable to create only first with operator, and use cube = cube.copy() for the next.

PS not quite sure what you mean by inside and outside function. What it looks like is local coordinates versus global coordinates.

  • 1
    $\begingroup$ OK, I think I see what is happening here. Yes, the script is no doubt overly complicated, I'm like Dr Frankenstein, a snippet from here, a leg from there... I like the add_cube(*line) syntax! Very cool, I don't know that I've seen that before! Many thanks! $\endgroup$ Dec 4, 2017 at 3:51

Unfortunately, obj.scale is not automatically updated immediately after changing 'obj.scale'. You can see it by printing the matrix_world. Object is updated when you switch to the Edit Mode. So the data is different inside the function because.


#        obj.scale.x = myWidth
#        obj.scale.y = myDepth
#        obj.scale.z = myHeight

        mw = obj.matrix_world
        mw[0][0] = myWidth
        mw[1][1] = myDepth
        mw[2][2] = myHeight

You must log in to answer this question.

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