0
$\begingroup$

I currently have a script that scales total length and selects and scales vertex groups of a "whale" mesh. I noticed that the output when scaling vertex groups by a particular scaling factor does not result in the desired size (in terms of sum of edges within the vertex groups) and realized this was because I had to set the 3D cursor to the center of the groups (since I used a mirror modifier the center of the vgroup is really in between two vertices that I will note in the below code). However, since I have to run multiple iterations of this I need to use python rather than scale it manually, however I haven't had much luck figuring out the best way to incorporate this into my code. Does anyone have any thoughts?

To provide a visual:

I am setting the 3D cursor based on these vertices from one vertex group that will be scaled enter image description here enter image description here

I then select the whole vertex group and find the sum of the edges (the length of the vertex group) using the measureit addon (the sum is found on the right side of the image) enter image description here

Then if I scale that vertex group using the "s" keyboard command and scale by 2 (hit s and then 2) this is the output enter image description here enter image description here

The output of 209.5910 makes sense because 104..79550*2 is 209.59100.

However if I take the same steps as above and instead of scaling using the keyboard command I use

bpy.ops.transform.resize(value=(2,2,2))

My output is incorrect and it looks as though things were not properly scaled in the x direction enter image description here enter image description here

If you want to take a look at the problem as a whole the code is as follows (for the sake of ease this code is only representative of one iteration and the sfac and total length values are simplified numbers):

obj = bpy.context.active_object
totallength = [1]
whale = [0]
vgrp = ['Face','Reye','Rpostpec','Rantdslfin', 'Rpostpdslfin', 'Ranus', 
'Rmidkeel','Rend']
sfac = [2,2,2,2,2,2,2,2] #list of all scaling factors for vertex group and whale 
vgrpindex = [2,23,77,79,145,147,167,169,189,191,211,213,233,235,255,257] #list of indices of vertices to set 3D cursor to (in pairs of 2, first two correspond to Face, second two to Reye, etc.)
whalesDims = np.zeros((1,2))

def scale(vgrp,sfac): #scale vertex group of name vgrp based on scaling factor of name sfac
    bpy.ops.object.mode_set(mode='EDIT') #set program in edit mode
    bpy.ops.mesh.select_all(action='DESELECT') #deselect any previously selected vertex groups
    bpy.ops.object.vertex_group_set_active(group=vgrp) #set vertex group active based on name in vgrp
    bpy.ops.object.vertex_group_select() #select active vertex group
    bpy.ops.transform.resize(value=(sfac,sfac,sfac)) #resize vertex group by sfac
bpy.ops.object.mode_set(mode='OBJECT') #set program in object mode

def scale_whale(a,whale,vgrp,sfac):
    bpy.ops.object.mode_set(mode='OBJECT') #set program in object mode
    for w in whale:
        bpy.ops.transform.resize(value=(1,totallength[w],1)) #resizes the length of the whale (only the y axis) based on an index in total length corresponding to the index in whale
        for v in vgrp: #I think that this is where my new code setting the cursor to a particular location should go
            scale(v,sfac[vgrp.index(v)]) #scale each vertex group based on the index in sfac that matches the vgrp index
        bpy.ops.object.mode_set(mode='EDIT')
        bpy.ops.mesh.select_all(action='SELECT')
        bm=bmesh.new() #needed to calculate volume and area
        bm.from_object(bpy.context.object,bpy.context.scene) #needed to calculate volume and area
        volume = bm.calc_volume() #volume of whale
        area = ((sum(f.calc_area() for f in bm.faces)))
        print ('whale',w,'surface area = ',area, 'volume = ',volume) #print surface area and volume with each whale
   whalesDims[w, :] = (area,volume) #add the area and volume to an array
   print(whalesDims) #print out the list of whale areas and volumes

scale_whale(obj,whale,vgrp,sfac)

I assume the code that needs to be inserted would look something like this:

def vgrouporigin(vgroup):
    vertices=[e for e in bm.verts]
    for vert in vertices:
        if vgrp =='Face':
            if vert.index==2 or vert.index==23:
                vert.select=True
                #Code to set 3D cursor here
            else:
                vert.select=False
         if vgrp =='Reye':
            if vert.index==77 or vert.index==79:
                vert.select=True
                #Code to set 3D cursor here
            else:
                vert.select=False
        if vgrp =='Rpostpec':
            if vert.index==145 or vert.index==147:
                vert.select=True
                #Code to set 3D cursor here
            else:
                vert.select=False
        if etc. etc. etc.

vgrouporigin(vgrp)

However, I feel that there may be an easier way to loop through this/I'm still not sure of how to actually set the cursor to said location. I hope you guys can help me out! I apologize if the code is a bit messy, I'm very new at coding, particularly for Blender.

Here's a download of the blender file! https://blend-exchange.giantcowfilms.com/b/4457/

$\endgroup$
0
$\begingroup$

Using an armature.

Having looked into a few incarnations of your whale questions, I previously suggested using an armature.

Part 1, cleaning the model and making the armature.

The script below, sets the length of whale to an arbitrary 10 blender units, applies rotation and scale. Finds the median point of each listed vertex group Then adds an armature with a bone for each listed vertex group placed at those median points.

import bpy
from collections import defaultdict
from mathutils import Vector

context = bpy.context
vgrp = ['Face','Reye','Rpostpec','Rantdslfin', 'Rpostpdslfin', 'Ranus', 'Rmidkeel','Rend']

scene = context.scene
ob = context.object
me = ob.data
# move whale to origin
ob.location = (0, 0, 0)

# going to make it 10 blender units long
scale_factor = 10 / ob.dimensions.y
ob.scale  *= scale_factor
bpy.ops.object.transform_apply(rotation=True, scale=True)

vgs = defaultdict(list)
for v in me.vertices:
    for vg_name in vgrp:
        if ob.vertex_groups[vg_name].index in (g.group for g in v.groups):
            vgs[vg_name].append(v)

mw = ob.matrix_world
# add an armature
bpy.ops.object.armature_add()
arm_obj = scene.objects.active
arm_obj.matrix_world = mw
arm_obj.show_x_ray = True
arm = arm_obj.data
bpy.ops.object.mode_set(mode='EDIT')
bone_length = Vector((0, 0, 1))
# name the root bone
root_bone = arm.edit_bones[0]
arm.edit_bones.remove(root_bone)
# add an armature
for name, verts in vgs.items():
    bone = arm.edit_bones.new(name)
    pos = 1 / len(verts) * sum((v.co.yz for v in verts), Vector((0, 0)))
    bone.head  = pos.to_3d().zxy
    bone.tail = bone.head + bone_length
    bone.select = True

bpy.ops.object.mode_set(mode='POSE')
ob.parent = arm_obj
armmod = ob.modifiers.new(name="Armature", type='ARMATURE')
armmod.object = arm_obj

Here is the result, the base whale with a bone for each vertex group.

With all the bones selected, and with use individual origins selected as a pivot point centre, can "fatten up the whale" by a scale factor of 2 on each vertex group like so.

enter image description here

Test file to play around with to see if this approach suits your needs. If so comment and will add next step with (operator-less) bmesh code to take the measurements. Note: The whale length can be scaled via the armature object. To revert back to unity scale, select all bones in pose mode then Pose > Clear Transforms > Scale

$\endgroup$
  • $\begingroup$ thank you for your response. This definitely could be a potential fix, however it seems as though when scaling each group individually those sections of the mesh are no longer attached or connected, which I would need them to be to accurately calculate total surface area and volume (my final objective). Further, it looks as though when switching to edit mode none of the changes are uphold which wouldn't usually be a problem however there would be some edges that must be smoothed and the area of the tail stock would have to be examined in edit mode. $\endgroup$ – user33993 Feb 6 '18 at 16:57
  • $\begingroup$ On the mesh disconnect not sure I understand what you mean, could please post an image link displaying the problem. To see result in edit mode, click on the display modifier + adjust edit cage button icons on modifier panel,. Given I have a reasonable understanding of coding blender hard to fathom, from question code above, where the edge smoothing and tail stock examination comes in. The calc volume and surface area as shown above can be calculated simply without constantly switching to edit mode since bm.from_object takes into account the armature modifier deformations. $\endgroup$ – batFINGER Feb 6 '18 at 17:59
  • $\begingroup$ the smoothing and tail stock examinations are done manually after each iteration, I have to manually decide which edges need to be smoothed and just determine calculate the end product using mesh display and 3D print tools. I apologize this change wasn't reflected in the code. As per the issue with the edges not matching this is fixed when only scaling in the x,z directions. You can see what I was referring to in the second image under right ortho between the first two armatures (the second section overlaps with the first). $\endgroup$ – user33993 Feb 7 '18 at 14:06
  • $\begingroup$ Although I think this would be a great fix for the problem, the methods I am using must be accessible to those unfamiliar with Blender (just as I am) so I think addressing this issue of discrepancies in scaling between manual input in code is how I need to work out this problem. However this is definitely an option I'll keep in mind for future projects! $\endgroup$ – user33993 Feb 7 '18 at 14:07

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.