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$
0

3 Answers 3

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$
2
  • $\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
    Commented Oct 30, 2015 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
    Commented Oct 31, 2015 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$

You must log in to answer this question.

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