# Scaling only the height of a parallelogram plane

Still learning Blender so sorry if the answer is obvious but I seem to have hit a mental block on this.

How do I scale a parallelogram (a plane) to give the result of halving its vertical (Z) height?

In the image:

• Left: The starting plane
• Centre: The result of scaling using: S Z 0.5
• Right: The result I want.

I can get the result I'm after in Edit mode by just moving the lower vertices upwards (the rightmost shape above), but I want to do be able to do this from a script in Object mode.

I've tried moving the center of rotation and origin to the center of the top edge before scaling, but it makes no difference.

## 3 Answers

I have a script that I think accomplishes your mission: http://web.purplefrog.com/~thoth/blender/python-cookbook/scale-parallelogram.html

import bpy
from mathutils import *

def share_vertex(e, horiz_edge):
return e.vertices[0] in horiz_edge.vertices or e.vertices[1] in horiz_edge.vertices

def horiz_score(vector, axis):
v2 = vector.copy()
v2[axis]=0
dz = abs(vector[axis])
if dz >0:
return v2.magnitude/ dz
else:
return float('inf')

def most_horizontal_edge(obj, axis):

mesh = obj.data
global_verts = [ obj.matrix_world*v.co for v in mesh.vertices ]

score = None
rval = None
for e in mesh.edges:
v1 = global_verts[e.vertices[0]]
v2 = global_verts[e.vertices[1]]

s2 = horiz_score(v2-v1, axis)
if (score is None or s2 > score):
score = s2
rval = e

return rval

def scale_parallelogram(obj, factor, axis):

mesh = obj.data
horiz_edge = most_horizontal_edge(obj, axis)

global_verts = [ obj.matrix_world*v.co for v in mesh.vertices ]

if True:
other_verts = set(range(len(global_verts)))
other_verts.difference_update(horiz_edge.vertices)
else:
other_verts = set()
for e in mesh.edges:
if share_vertex(e, horiz_edge):
continue
other_verts.update(e.vertices)

#print("%r -vs- %r"%(horiz_edge.vertices[:], other_verts))

z1 = [ global_verts[i][axis] for i in horiz_edge.vertices]
z2 = [ global_verts[i][axis] for i in other_verts]

z_all = [ v[axis] for v in global_verts ]
delta_z =max(z_all)-min(z_all)
z_change = 0.5*(1-factor)*delta_z

delta_v = Vector([0,0,0])
delta_v[axis] = z_change

#print(global_verts[0])
mwi = obj.matrix_world.inverted()
if (min(z1) < min(z2)):
delta_v = -delta_v

for i in horiz_edge.vertices:
mesh.vertices[i].co = mwi * (global_verts[i] - delta_v)
for i in other_verts:
mesh.vertices[i].co = mwi * (global_verts[i] + delta_v)

#print(mesh.vertices[0].co)

mesh.update()

#
#

x_axis=0
y_axis=1
z_axis=2

for obj in bpy.context.selected_objects:
scale_parallelogram(obj, 0.5, z_axis)

• Thanks, I'll try it out. Do you know why scaling the parallelogram doesn't give the result I'm after? I'm guessing Blender uses a mathematically correct scale but it is not what I want in this situation? – Ash Oct 30 '15 at 5:57
• yeah, blender's scaling performs a transform like z' = az+b, but you want z'=z+delta where delta varies for the top and bottom edges. – Mutant Bob Oct 31 '15 at 4:23

As I've had no answers or comments (yet) or further information on if this is possible to do by scaling, I decided to write a python function to automate moving the vertices in global coordinates.

def SetVertexGlobal(objectName,vertexIndex, targetLocation):
obj  = bpy.data.objects[objectName]
mesh = obj.data
vert = mesh.vertices[vertexIndex]
mat_world = obj.matrix_world

pos_world = mat_world * vert.co

if targetLocation[0] is not None:
pos_world.x =targetLocation[0]

if targetLocation[1] is not None:
pos_world.y =targetLocation[1]

if targetLocation[2] is not None:
pos_world.z =targetLocation[2]

vert.co = mat_world.inverted() * pos_world


'vertexIndex' is the index of the required vertex to move in the mesh of object.

You can find out the index by typing: bpy.app.debug = True in the Python console. Then, in edit mode on your object, enable 'Indices' in the Mesh Display of the Information panel. Slect a vertex to see the index.

'targetLocation' is a 3 item array containing a global X Y Z to move the vertex to. Pass None to ignore X Y Z, eg. [None, 4, None] to move the Y coordinate only.

You could also get what you want manually in this way:

1. in edit mode, select upper or lower edge
2. ctrle edge slide
3. press .5 enter

I don't know how to use this approach in a script, though, and you need to get into edit mode, anyway, so this is not a solution, but just to give you another idea.