0
$\begingroup$

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?

enter image description here

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.

$\endgroup$
2
$\begingroup$

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)
$\endgroup$
  • $\begingroup$ 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? $\endgroup$ – Ash Oct 30 '15 at 5:57
  • $\begingroup$ 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. $\endgroup$ – Mutant Bob Oct 31 '15 at 4:23
0
$\begingroup$

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.

$\endgroup$
0
$\begingroup$

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.

$\endgroup$

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.