Increasing size of a grid primitive by one face unit

I’m trying to figure out a good way to go about adding faces along the perimeter of a grid. For instance, the default grid primitive creates a grid that is 9x9 faces in size. I want to be able to increase the size such that it becomes 11x11 faces in size. I need to be able to do this in a script, as I’ll be applying this operation to over 1000 grids with different origins and directions.

I am thinking I could select the non-manifold edges, then somehow select each individual side and extrude in that direction by the length of each individual grid edge. If I do that in a clockwise direction, I should have my grid extended appropriately. Then I just have to recalculate the normals.

I am unsure how to go about doing a few things though:

  • selecting each individual side instead of just ALL non-manifold edges
  • figuring out which direction to extrude in
  • getting the length of each individual edge so I know how far to extrude.

The grid faces I have to iterate over can be in the x/y, x/z, or y/z directions. fortunately, none of them would be in a slope.

Any suggestions? I am open to alternative methods too; this is just the first one I came up with.

I ended up solving this myself. I don’t think this is the most elegant solution, but it does accomplish what I needed to accomplish. Here is the code:


import bpy;
import bmesh;
import math;
import mathutils;

#################################
# functions

# returns a vector representing the coordinates the plane
# runs in, based on the face normal. for instance, a normal
# of z 1.0 means the plane is in the x/y orientation.
def normalToPlane(n):
    return mathutils.Vector((
        abs(abs(n.x) - 1),
        abs(abs(n.y) - 1),
        abs(abs(n.z) - 1)
    ));        




print("

START:
");

bpy.ops.object.mode_set(mode='OBJECT', toggle=False);
for obj in bpy.data.objects:
    if obj.type == 'MESH':        
        #select object
        bpy.context.scene.objects.active = obj;
        current_obj = bpy.context.active_object 
        bpy.ops.object.mode_set(mode='EDIT', toggle=False);
        bm = bmesh.from_edit_mesh(current_obj.data);
        bpy.ops.mesh.select_all(action = 'DESELECT');

        # get the face length of a side
        facesPerSide = math.sqrt(len(bm.faces));

        # sample a face to get normal         
        normal = bm.faces[0].normal;
        print(normal);

        # what is the grid's orientation?
        orientation = normalToPlane(normal);
        print(orientation);

        # sample an edge to get the length
        length = bm.edges[0].calc_length();


        # get a coordinate representing the center of the plane
        bpy.ops.mesh.select_non_manifold();
        center = mathutils.Vector((0, 0, 0));        
        for edge in bm.edges:
            center += (edge.verts[0].co + edge.verts[1].co) / 2;
        center /= len(bm.edges);
        center = bpy.context.object.matrix_world * center;
        print(str(center));

        # calculate bounds
        upperBoundX = round(((length * facesPerSide) / 2) + center.x, 4);
        lowerBoundX = round(center.x - ((length * facesPerSide) / 2), 4);
        upperBoundY = round(((length * facesPerSide) / 2) + center.y, 4);
        lowerBoundY = round(center.y - ((length * facesPerSide) / 2), 4);
        upperBoundZ = round(((length * facesPerSide) / 2) + center.z, 4);
        lowerBoundZ = round(center.z - ((length * facesPerSide) / 2), 4);
        print(upperBoundX);
        print(lowerBoundX);
        print(upperBoundY);
        print(lowerBoundY);
        print(upperBoundZ);
        print(lowerBoundZ);

        
        # let's select each non-manifold edge on all sides of the plane.
        # this should result in 4 selections. Go clockwise from "north"
        for i in range(4):
            # select non-mainifold edges
            bpy.ops.mesh.select_non_manifold();

            # deselect edges we do not want to process yet
            for edge in bm.edges:  
                if edge.select == True:  
                    # this is one of our selected edges. Where is it relative to center?
                    edgeLocation = bpy.context.object.matrix_world * ((edge.verts[0].co + edge.verts[1].co) / 2);
                    print(edgeLocation);

                    if(i == 0):
                        # "NORTH"
                        if(orientation.x > 0):
                            if(round(edgeLocation.x, 4) == lowerBoundX):                                
                                edge.select = True;
                            else:                                
                                edge.select = False;
                        else:
                            if(round(edgeLocation.z, 4) == upperBoundZ):                                
                                edge.select = True;
                            else:                                
                                edge.select = False;

                    if(i == 1):
                        # "EAST"
                        if(orientation.y > 0):
                            if(round(edgeLocation.y, 4) == upperBoundY):                                
                                edge.select = True;
                            else:                                
                                edge.select = False;
                        else:
                            if(round(edgeLocation.z, 4) == upperBoundZ):                                
                                edge.select = True;
                            else:                                
                                edge.select = False;    

                    if(i == 2):
                        # "SOUTH"
                        if(orientation.x > 0):
                            if(round(edgeLocation.x, 4) == upperBoundX):                                
                                edge.select = True;
                            else:
                                edge.select = False;
                        else:
                            if(round(edgeLocation.z, 4) == lowerBoundZ):
                                edge.select = True;
                            else:
                                edge.select = False;                    

                    if(i == 3):
                        # "WEST"
                        if(orientation.y > 0):
                            if(round(edgeLocation.y, 4) == lowerBoundY):
                                edge.select = True;
                            else:
                                edge.select = False;
                        else:
                            if(round(edgeLocation.z, 4) == lowerBoundZ):
                                edge.select = True;
                            else:
                                edge.select = False;
                                        
            
            # at this point, we have the edges we want selected. now extrude them
            # in the correct direction
            direction = mathutils.Vector((0,0,0));
            if(i == 0):
                if(orientation.x > 0):
                    direction.x = -length;
                else:
                    direction.z = length;
            elif(i == 1):
                if(orientation.y > 0):
                    direction.y = length;
                else:
                    direction.z = length;
            elif(i == 2):
                if(orientation.x > 0):
                    direction.x = length;
                else:
                    direction.z = -length;
            else:
                if(orientation.y > 0):
                    direction.y = -length;
                else:
                    direction.z = -length;

            bpy.ops.mesh.extrude_region_move(TRANSFORM_OT_translate={"value":direction});

    # reset for next iteration
    bpy.ops.object.mode_set(mode='OBJECT', toggle=False);

Of course, the biggest drawback to this is that it will only work for grids where the face normals are like (1, 0, 0), (0, 1, 0), etc.