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.