New script: apply variable scaling to vertices

Hi,
I wrote a small script which allows to apply a variable scale
to the x and y coordinates of an object.

This is handy if you have a cylinder which is 1 unit in diameter
on one side and should be 2 units on the other. (OK, in this case,
other methods work as well, but just think of othe objects without
rotational symmetry.)

The scaling is given for both ends and works on the x and y coordinates
only (hardcoded :frowning: ), and which factor applies, depends on the z coordinate.
A variable offset may be added as well.

Maybe this or similar functionality is available with built-in blender
tools, in this case I would welcome feedback, then I’ll have to
read the manual better.

Tested in blender 2.42a / Python 2.4.

Best regards,
Michael



#!BPY
"""
Name: 'Variable Scale'
Blender: 242
Group: 'Mesh'
Tooltip: 'Perform a x/y scaling depending on z coordinate.'
"""

__author__ = ["Michael Bischoff"]
__url__ = ("blender", "eldorado", "http://none.existing.de/blender/")
__version__ = "0.1"
__bpydoc__ = """\
Variable Scale

Perform a x/y scaling depending on z coordinate.
"""

#
# This script scales the x and y portion of the selected vertices
# depending on their z coordinate.
# scaling happens relatively to the 3D cursor (x and y components)
# the z component of the 3D cursor is irrelevant to this script
#

# ATTENTION!
# This script does work only as expected, if the object transformation
# is identity.
# This can be achieved by pressing ctrl-A (apply scale and rotation)
# for the object in "Object mode".
#

# ***** BEGIN GPL LICENSE BLOCK *****
#
# Script copyright (C) Michael Bischoff
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
# ***** END GPL LICENCE BLOCK *****
# --------------------------------------------------------------------------


import math
import Blender
from Blender import Mathutils, Window, NMesh, Draw, Scene, Noise
from Blender.Mathutils import *

import BPyMesh




def main():
        scn = Scene.GetCurrent()
        ob = scn.getActiveObject()

        is_editmode= Window.EditMode()

        if not is_editmode:
                Draw.PupMenu('Error! Must be in Edit mode%t|Please switch to edit mode, then call this script again.')
                return

        if ob == None or ob.getType() != 'Mesh':
                Draw.PupMenu('Error! No active mesh selected%t|You must have a mesh object selected.')
                return


        #=================================#
        # Popup menu to obtain parameters #
        #=================================#

        mode = Draw.Create(0)
        maxz_dx = Draw.Create(0.0)
        maxz_dy = Draw.Create(0.0)
        maxz_dz = Draw.Create(0.0)
        minz_fx = Draw.Create(1.0)
        minz_fy = Draw.Create(1.0)
        maxz_fx = Draw.Create(2.0)
        maxz_fy = Draw.Create(1.0)

        # Get parameters from user
        pup_block= [\
        'Transformation mode:',
        ('mode', mode, 0, 7, '0=exp, 1=linear, 2=quadratic, 3=cubic, 4=half sinus, 5=full sinus, 6=circle segment, 7 = alternate circle.'),\
        'max z translation:',
        ('dx', maxz_dx, -100.0, 100.0, 'dx at maximum z.'),\
        ('dy', maxz_dy, -100.0, 100.0, 'dy at maximum z.'),\
        ('dz', maxz_dz, -100.0, 100.0, 'dz at maximum z.'),\
        'scale at min z:',
        ('x scale', minz_fx, 0.0, 10.0, 'x scale at smallest z value.'),\
        ('y scale', minz_fy, 0.0, 10.0, 'y scale at smallest z value.'),\
        'scale at max z:',
        ('x scale', maxz_fx, 0.0, 10.0, 'x scale at biggest z value.'),\
        ('y scale', maxz_fy, 0.0, 10.0, 'y scale at biggest z value.'),\
        'Remember ctrl-A!!!',
        ]

        if not Draw.PupBlock('Define z-dependend scale / translation', pup_block):
                return

        mode = mode.val
        maxz_dx = maxz_dx.val
        maxz_dy = maxz_dy.val
        maxz_dz = maxz_dz.val
        minz_fx = minz_fx.val
        minz_fy = minz_fy.val
        maxz_fx = maxz_fx.val
        maxz_fy = maxz_fy.val

        # leave edit mode before getting mesh (suggested by Python reference)
        Window.EditMode(0)

        me = ob.getData(mesh=1)
        cursor_pos = Vector(Window.GetCursorPos())
        count = 0
        minz = 0.0
        maxz = 0.0

        for v in me.verts:
                if v.sel:
                        z = v.co[2]
                        if count == 1:
                                minz = z
                                maxz = z
                        else:
                                if minz > z:
                                        minz = z
                                if maxz < z:
                                        maxz = z
                        count += 1

        if minz == maxz:
                Draw.PupMenu('Error! All vertices (if any) have same z coordinate.')
                return

        dz = maxz - minz
        print 'minz = %f|' % minz
        print 'maxz = %f|' % maxz

        for v in me.verts:
                if v.sel:
                        # default mode: linear
                        alpha = (v.co[2] - minz) / dz

                        # other modes might change the mapping, however, it's
                        # always in the range [0,1]
                        if mode == 0:
                                # apply exponential transformation
                                alpha = (math.exp(alpha) - 1.0) / (math.e - 1.0)
                        if mode == 2:
                                # apply quadratic transformation
                                alpha *= alpha
                        if mode == 3:
                                # apply cubic transformation
                                alpha *= alpha * alpha

                        if mode == 4:
                                # apply sinus transformation (half periode)
                                alpha = 0.5 * (1.0 - math.cos(math.pi * alpha))
                        if mode == 5:
                                # apply sinus transformation (full periode)
                                alpha = 0.5 * (1.0 - math.cos(2.0 * math.pi * alpha))
                        if mode == 6:
                                # apply circle segment transformation
                                if alpha > 1.0:
                                        alpha = 1.0 # paranoia to avoid negative root exceptions
                                alpha = 1.0 - alpha
                                alpha = math.sqrt(1.0 - alpha * alpha)
                        if mode == 7:
                                # apply circle segment transformation
                                if alpha > 1.0:
                                        alpha = 1.0 # paranoia to avoid negative root exceptions
                                alpha = 1.0 - math.sqrt(1.0 - alpha * alpha)

                        scalex = alpha * maxz_fx + (1.0 - alpha) * minz_fx - 1.0
                        scaley = alpha * maxz_fy + (1.0 - alpha) * minz_fy - 1.0
                        v.co[0] += alpha * maxz_dx + scalex * (v.co[0] - cursor_pos[0])
                        v.co[1] += alpha * maxz_dy + scaley * (v.co[1] - cursor_pos[1])
                        v.co[2] += alpha * maxz_dz

        # update mesh data
        me.update()

        # TODO: recalc normals. according to doc, a suitable parameter to
        # me.update() should be able to do this, but this did not work in 2.42a

        # return to edit mode
        Window.EditMode(1)

        statmsg = 'Processed %i vertices|' % count
        Draw.PupMenu(statmsg)


if __name__ == '__main__':
        main()

From the description, I had a hard time figuring out what this was susposed to do. After trying it out, I believe this would be better described as a “Taper” script.

Maybe this or similar functionality is available with built-in blender
tools, in this case I would welcome feedback, then I’ll have to
read the manual better.

You can get the same effect by using a lattice modifier and the applying it to the mesh.

Hi,
sorry, next time I’ll invest some more effort into documentation :wink:

Thanks a lot for the hint with the lattices. I’ll try that!

Best regards,
Michael