# Set object origin to minimum Z (set to floor) (using examples from @TLousky)

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!

Peter

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

The Data Set looks like this:

Name,Width,Depth,Height,Note
Clerk Table,49,31,40,
Old Folding Table,60,35,30,
Workstation,79,32,36,


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

import bpy,bmesh
import csv

fPath   = '/Users/peterf/Google Drive/2015 Projects-Strategy/401 N Dodge/Fraterdeus Design Studio Objects - Sheet1.csv'
''' Name    Width   Depth   Height  Note
'''
bpy.ops.object.select_all(action='TOGGLE')
bpy.ops.object.select_all(action='TOGGLE')
bpy.ops.object.delete(use_global=False)

# 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:
i=myLocationX=myLocationY=0

i+=1

myWidth=int(row['Width'])
myDepth=int(row['Depth'])
myHeight=int(row['Height'])

)

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']

select_lower_Z()

myLocationX+=myWidth+30
if (i%5==0):
myLocationY+=60
myLocationX=0

obj.location = ( myLocationX, myLocationY, 0 )

location = ( myLocationX, myLocationY, myHeight )
)

text = bpy.context.object
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()
text.lock_location=[True,True,True]

text_data=text.data

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.append(mat_red)
else:
text_data.materials[0] = mat_red


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

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
# 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.

• 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! Dec 4 '17 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.

e.g.

#        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