# Trying to create a script that makes roofs on selected boxes

I hope this question isn't too broad for Blender stackexchange.

Quite often in my work I import topographical data specifying the flat geometry of buildings. I'm trying to make a script that put roofs on these buildings. I have some Python experiences, but I'm having a really tough time using Python in Blender, and haven't been able to find any in depth tutorials to help me on my way.

The goal of this script is to iterate of all selected faces, check if they have four sides, extrude the faces, then scale the shortest edges to 0. I want to have the shortest edges so to roof aligns correctly. See the image for an example of what I want to achieve.

Here is a quick script that extrudes selected faces of selected mesh objects, if the face has 4 vertices and the angle between the face normal and the up axis is lower than 3°:

import bpy
import bmesh
from math import degrees
from mathutils import Vector

up = Vector((0, 0, 1))

for ob in bpy.context.selected_editable_objects:
if ob.type == 'MESH':
me = ob.data
if me.is_editmode:
bm = bmesh.from_edit_mesh(me)
else:
bm = bmesh.new()
bm.from_mesh(me)
bm.normal_update()

for face in bm.faces:
if len(face.verts) == 4 and face.select:
try:
angle = degrees(face.normal.angle(up))
except ValueError:
continue
if angle < 3: # some threshold
ret = bmesh.ops.extrude_face_region(bm, geom=[face])
verts = [e for e in ret['geom'] if isinstance(e, bmesh.types.BMVert)]
faces = [e for e in ret['geom'] if isinstance(e, bmesh.types.BMFace)]
bmesh.ops.translate(bm, vec=face.normal * -0.7, verts=verts)

e1, e2, e3, e4 = faces[0].edges
if (e1.calc_length() + e3.calc_length()) < (e2.calc_length() + e4.calc_length()):
edges = [e1, e3]
else:
edges = [e2, e4]
bmesh.ops.collapse(bm, edges=edges)

if me.is_editmode:
bmesh.update_edit_mesh(me)
else:
bm.to_mesh(me)
me.update()


The two shorter edges of the top-face is collapsed to create a sensible roof.

Note that it doesn't account of degenerated quads (non-planar, self-intersecting), but should work good enough for your use case.

Here is another solution:

import bpy
import bmesh

def extrude_and_collapse(obj, height):
if not obj or obj.mode != 'OBJECT':
raise RuntimeException("requires object mode")

bm = bmesh.new()
bm.from_mesh(obj.data)

selection = [face for face in bm.faces if face.select and len(face.verts) == 4]
for face in selection:
face.select = False

result = bmesh.ops.extrude_face_region(
bm,
geom=selection,
use_keep_orig=False
)

created_verts = tuple(elem for elem in result['geom'] if isinstance(elem, bmesh.types.BMVert))
created_faces = tuple(elem for elem in result['geom'] if isinstance(elem, bmesh.types.BMFace))
edit_faces = []

for face in created_faces:
face.tag = False
for vertex in face.verts:
if not vertex in created_verts:
break
else:
face.tag = True
edit_faces.append((face, face.normal.copy()))

collapse_edges = set()
for face, normal in edit_faces:
for vertex in face.verts:
n = 0
n += 1
vertex.co += height * normal / n

edge_min = min(face.edges, key=lambda x: x.calc_length())
if loop.face == face:
break
edge_opposite = loop.edge

bmesh.ops.collapse(
bm,
edges=list(collapse_edges)
)

bm.select_flush(True)
bm.normal_update()
bm.to_mesh(obj.data)
bm.free()
obj.data.update()

extrude_and_collapse(bpy.context.active_object, 1.0)

• For the given task it should work. (Test #1, #2) But it is not bulletproof. There still might occur plenty of situtations which the script does not handle. Jul 17 '14 at 17:48
• Another test Checking wether the edge ratio is correct, --- Selection, --- Result Jul 17 '14 at 18:02
• @MarcClintDion He assumes all houses are one object and the top faces are selected. Than it works nicely. Jul 17 '14 at 20:34