1
$\begingroup$

I am writing a python script to make a chair. I need to use the loopcut_slide() after that the extrude operators. here is my code

    import bpy
import time
import mathutils
from mathutils import Vector
import numpy as np
start_time=time.time
from mathutils import *
from math import *
import random
D = bpy.data
C = bpy.context
#Clean the scene
bpy.ops.object.select_by_type(type='MESH')
#bpy.ops.object.delete()
bpy.ops.object.select_by_type(type='CAMERA')
bpy.ops.object.delete()
bpy.ops.object.select_by_type(type='LAMP')
bpy.ops.object.delete()
def view3d_find( return_area = False ):
    # returns first 3d view, normally we get from context
    for area in bpy.context.window.screen.areas:
        if area.type == 'VIEW_3D':
            v3d = area.spaces[0]
            rv3d = v3d.region_3d
            for region in area.regions:
                if region.type == 'WINDOW':
                    if return_area: return region, rv3d, v3d, area
                    return region, rv3d, v3d
    return None, None

#make a chair
D.objects["Cube"].scale[0] = .2
D.objects["Cube"].scale[1] = .2
D.objects["Cube"].scale[2] = 0.005

D.objects["Cube"].location[0] = 0
D.objects["Cube"].location[1] = 0
D.objects["Cube"].location[2] = 0

bpy.ops.object.mode_set(mode='EDIT')

region, rv3d, v3d, area = view3d_find(True)
override = {
    'scene'  : bpy.context.scene,
    'region' : region,
    'area'   : area,
    'space'  : v3d
}

bpy.ops.mesh.loopcut_slide(override,MESH_OT_loopcut={"number_cuts":1, "smoothness":0, "falloff":'INVERSE_SQUARE', "edge_index":11, "mesh_select_mode_init":(True, False, False)}, TRANSFORM_OT_edge_slide={"value":-0.9, "single_side":False, "mirror":False, "snap":False, "snap_target":'CLOSEST', "snap_point":(0, 0, 0), "snap_align":False, "snap_normal":(0, 0, 0), "correct_uv":False, "release_confirm":False})
bpy.ops.mesh.loopcut_slide(override,MESH_OT_loopcut={"number_cuts":1, "smoothness":0, "falloff":'INVERSE_SQUARE', "edge_index":11, "mesh_select_mode_init":(True, False, False)}, TRANSFORM_OT_edge_slide={"value":-0.9, "single_side":False, "mirror":False, "snap":False, "snap_target":'CLOSEST', "snap_point":(0, 0, 0), "snap_align":False, "snap_normal":(0, 0, 0), "correct_uv":False, "release_confirm":False})
bpy.ops.mesh.loopcut_slide(override,MESH_OT_loopcut={"number_cuts":1, "smoothness":0, "falloff":'INVERSE_SQUARE', "edge_index":18, "mesh_select_mode_init":(True, False, False)}, TRANSFORM_OT_edge_slide={"value":-0.9, "single_side":False, "mirror":False, "snap":False, "snap_target":'CLOSEST', "snap_point":(0, 0, 0), "snap_align":False, "snap_normal":(0, 0, 0), "correct_uv":False, "release_confirm":False})
bpy.ops.mesh.loopcut_slide(override,MESH_OT_loopcut={"number_cuts":1, "smoothness":0, "falloff":'INVERSE_SQUARE', "edge_index":18, "mesh_select_mode_init":(True, False, False)}, TRANSFORM_OT_edge_slide={"value":0.9, "single_side":False, "mirror":False, "snap":False, "snap_target":'CLOSEST', "snap_point":(0, 0, 0), "snap_align":False, "snap_normal":(0, 0, 0), "correct_uv":False, "release_confirm":False})


bpy.ops.mesh.extrude_region_move(MESH_OT_extrude_region={"mirror":False}, TRANSFORM_OT_translate={"value":(0, 0, -0.191221), "constraint_axis":(False, False, True), "constraint_orientation":'NORMAL', "mirror":False, "proportional":'DISABLED', "proportional_edit_falloff":'SMOOTH', "proportional_size":1, "snap":False, "snap_target":'CLOSEST', "snap_point":(0, 0, 0), "snap_align":False, "snap_normal":(0, 0, 0), "gpencil_strokes":False, "texture_space":False, "remove_on_cancel":False, "release_confirm":False})


Now, I need to select faces and apply the extrude operator. 

1) How to set the face select mode. 2) and how to select the 4 faces as shown in the figure below

enter image description here enter image description here

$\endgroup$

1 Answer 1

2
$\begingroup$

This is not really a complete answer as it does not result in a complete chair but it covers the trickery you seem to need.

There are a few snafoos as I've copy/pasted from my own code base which treats a few features not present in your problem, it's useful code however so have at it I say.

The code is cut up so that I can describe what I'm doing but really it is a single script. So stitch it together and run it in blender then the chair button will appear at the base of the add mesh menu. Usefully I've split it up so that it may be made into a package aswell. So if you made each part into it's own file and resolved the imports as necessary you would have the start of a package. The double hash comments indicate what I'd call each file.

Imports

Imports gotta have imports. I use the bpy_extras methods as it provides you with a proper Scaling/Rotating/Translating matrix. It also does object/mesh (a.k.a data) registration within the scene. In prior question(s) of my own the more experienced guys indicated that they don't use this helper, I figure this gives the blender team some control over object creation in packages and therefore I use it. The rest of the imports are pretty standard stuff.

import bpy
import bpy_extras
import bmesh
from math      import *
from mathutils import *

Data

I do not fully utilize it here but these two methods allow for re-entrancy. That is you can edit the mesh after you've done something that breaks the history's breadcrumb trail. Usually I add an Edit Panel to facilitate this.

## utilities.py
def prop2data(prop, data) :
    """
    prop2data

    Given a PropertyGroup and ID-Data populate the latter from the former
    """
    for key in prop.rna_type.properties.keys() :
        if key in ["rna_type"] :
           continue
        if isinstance(getattr(prop, key), bpy.types.PropertyGroup) :
            if key not in data :
                data[key] = {}
            prop2data(getattr(prop, key), data[key])
        else:
            data[key] = getattr(prop, key)
            #print("{} : {} -> {}".format(key,getattr(prop, key),data[key]))

def data2prop(prop, data) :
    """
    data2prop

    Given a PropertyGroup and ID-Data populate the former from the latter
    """
    # The order of arguments matches prop2data this is deliberate and 
    # indicates that the structure is traversed according to Property-
    # Group rather then ID-Data (Which should be preferred).
    for key in prop.rna_type.properties.keys() :
        if key in ["rna_type"] :
           continue
        if isinstance(getattr(prop, key), bpy.types.PropertyGroup) :
            if key in data and isinstance(data[key], dict):
                data2prop(getattr(prop, key), data[key])
        else:
            #print("{} : {} <- {}".format(key, data[key], getattr(prop, key)))
            setattr(prop, key, data[key])

Properties

I wrap all the properties for a given geometry into a pointer class for various purposes. It's helpful to pass these parameters into the data2prop and prop2data methods and to toss them into the geometry method for example.

## Properties
class chairProperties(bpy.types.PropertyGroup) :
    """Chair properties"""
    height     = bpy.props.FloatProperty(
                 name        = "Height",
                 description = "The total height",
                 default     =      1.0,
                 min         =      0.0,
                 #update      = _height_,
                 unit        = 'LENGTH')
    length     = bpy.props.FloatProperty(
                 name        = "Length",
                 description = "The total length",
                 default     =       3.5,
                 min         =       0.0,
                 unit        =  'LENGTH')
    width      = bpy.props.FloatProperty(
                 name        = "Width",
                 description = "The total width",
                 default     =       2.5,
                 min         =       0.0,
                 unit        =  'LENGTH')
    inset      = bpy.props.FloatProperty(
                 name        = "Inset depth",
                 description = "The depth of the inset",
                 default     =       0.5,
                 min         =       0.0,
                 unit        =  'LENGTH')

Geometry

This next section has quite a bit going on. Firstly we provide stock unit vectors lying along the principle axes. This is largely done so that you're not dropping plus and minus signs everywhere.

The selectPlane method does most of the magic, I wrap it into a function so I can cherry pick verts, edges or faces. One can then make sub-selections of that selection to decide what is or isn't extruded.

clearGeometry simply strips out any mesh data while preserving the object and object.data (mesh) data blocks.

chairGeometry populates the object.data with the geometry you require. Here you should note that when selectPlane is called you must use a location and normal vector that are rotated with the object's co-ordinates, the way you do this is different. For the location you're flinging a point about in 3 space. For the normal you're taking one of those principle vectors and flinging it about in 3 space, you then need to re-center it about a zero by subtracting a zero that you also flung about in 3 space. Finally the last trick is in the selections by using sets, fx and fy you can make sub-selections using set theory. fx&fy in this case selects the corner faces, 'fx|fy' would select the skirt, fx|fy-fx&fy the edges and 'go-fx|fy' would get the center. This provides quite a lot of flexibility. For the rest there is a little algebraic trickery to make the center of the object coincide with the base of the chair once everything is extruded. This is also used to select the appropriate faces.

## geometry.py
AXES = {'+X':Vector(( 1,0,0)), '+Y':Vector((0, 1,0)), '+Z':Vector((0,0, 1)),
        '-X':Vector((-1,0,0)), '-Y':Vector((0,-1,0)), '-Z':Vector((0,0,-1))}

def selectPlane(bm, normal = 'z', location = Vector((0,0,0)), feature = 'verts', tolerance = 0.1) : # bpy.context.active_object.location
    '''
    Select all the geometry within a plane described by a position and a normal
    '''
    normal = normal if isinstance(normal, Vector) else AXES[normal.upper()]
    if isinstance(feature, list) :
       return {key : planeSelect(bm, normal, feature, location, tolerance) for key in feature}
    else : 
       return {'verts' : [vert for vert in bm.verts if abs(vert.co.__sub__(location).dot(normal)) < tolerance],
               'edges' : [], 
               'faces' : [face for face in bm.faces if all([abs(vert.co.__sub__(location).dot(normal)) < tolerance for vert in face.verts])]}[feature]

def clearGeometry(sc, ob, me, ps):
    '''
    I understand to be the best method of clearing the mesh data
    '''
    bm = bmesh.new()
    bm.to_mesh(me)
    me.update()
    sc.update()

def chairGeometry(sc, ob, me, ps, matrix = Matrix.Identity(4)) :
    '''
    Create a Chair Shape
    '''
    bm = bmesh.new()
    # Provide Geomerty
    bmesh.ops.create_cube(bm, size=1.0, matrix=Matrix([[ps.length,       0,                   0,0],
                                                       [        0,ps.width,                   0,0],
                                                       [        0,       0,ps.height - ps.inset,0],
                                                       [        0,       0,                   0,1]]))
    # Lower Geometry
    cx = [-ps.length/2+ps.inset,ps.length/2-ps.inset]
    for x in cx :
        go = selectPlane(bm, 
                         normal   = matrix*AXES['-Z'] - matrix*Vector((0,0,0)), 
                         location = matrix*Vector((0, 0, -(ps.height - ps.inset)/2)),
                         feature  = 'faces')
        go = list({item for face in go for item in [face] + face.verts[:] + face.edges[:]})
        go = bmesh.ops.bisect_plane(bm, geom=go, plane_co=(x,0,0), plane_no=(1,0,0))
    cy = [-ps.width/2+ps.inset,ps.width/2-ps.inset]
    for y in cy :
        go = selectPlane(bm, 
                         normal   = matrix*AXES['-Z'] - matrix*Vector((0,0,0)), 
                         location = matrix*Vector((0, 0, -(ps.height - ps.inset)/2)),
                         feature  = 'faces')
        go = list({item for face in go for item in [face] + face.verts[:] + face.edges[:]})
        go = bmesh.ops.bisect_plane(bm, geom=go, plane_co=(0,y,0), plane_no=(0,1,0))
    go = selectPlane(bm, 
                     normal   = matrix*AXES['+Z'] - matrix*Vector((0,0,0)), 
                     location = matrix*Vector((0, 0, -(ps.height - ps.inset)/2)),
                     feature  = 'faces')
    fx = {face for face in go if any([v.co.x < min(cx) or v.co.x > max(cx) for v in face.verts])}
    fy = {face for face in go if any([v.co.y < min(cy) or v.co.y > max(cy) for v in face.verts])}
    # the next line is useful if the bmesh operator needs faces, edges and verts
    #go = [i for s in {[face] + face.edges[:] + face.verts[:] for face in fx|fy} for i in s] 
    go = bmesh.ops.extrude_face_region(bm, geom=list(fx&fy))['geom']
    for v in list({item for item in go if isinstance(item,bmesh.types.BMVert)}) : v.co.z-= ps.inset
    # Update the Object
    bmesh.ops.transform(bm, matrix=Matrix.Translation((0,0,ps.height/2 + ps.inset/2))*matrix, verts=bm.verts)
    bm.to_mesh(me)
    bm.free()
    me.update()

Operator

Next we provide the operator blender will call. This really just wraps the Create/Review/Update/Delete Geometry methods in blender specific boilerplate.

## operators.py
class MESH_OT_primitive_chair_add(bpy.types.Operator, bpy_extras.object_utils.AddObjectHelper):
    """Creates and Modifies a simple Chair"""
    bl_idname      = "mesh.primitive_chair_add"
    bl_label       = "Add Chair"
    bl_description = "This operator creates a Chair"
    bl_options     = {'REGISTER', 'UNDO'}
    part           = "Furniture:Chair"

    instantiate = bpy.props.BoolProperty(   name        = "New Object",
                                            description = "Used internally to control Object Creation versus Modification. See Object Creation in the Project Readme",
                                            default     = True,
                                            options     = {'HIDDEN'})
    dimensions  = bpy.props.PointerProperty(name        = "Dimensions",
                                            type        = chairProperties)

    def execute(self, context):
        if not self.instantiate and context.active_object and 'part' in context.active_object.data.keys() :
            sc = context.scene
            ob = context.active_object
            me = ob.data
            ps = self.dimensions
            data2prop(ps, me['size'])
            clearGeometry(sc, ob, me, me['size'])
        else :
            sc = context.scene
            me = bpy.data.meshes.new(name="Chair")
            od = bpy_extras.object_utils.object_data_add(context, me, operator=self)
            ob = od.object
            ps = self.dimensions # Using either od.object.GEOMETRY (Object Registered Properties) or self.GEOMETRY (Operator Registered Properties) os sc.GEOMETRY (Scene Registered Properties)
            me['ID']   = 'Not Used'   
            me['part'] = self.part,
            me['size'] = {} # Localized Data Store
        # Dimensions     
        # Your would adjust the properties here to ensure the various contraints are met
        # Geometry
        prop2data(ps, me['size'])
        chairGeometry(sc, ob, me, ps)
        return {"FINISHED"}

Interface

The interface consists of a button at the end of the Add Mesh menu. This is not really the proper place for this but it'll do.

## interface.py
def addChairMenu(self, context) :
     self.layout.operator(MESH_OT_primitive_chair_add.bl_idname, text="Add Chair")

Registration

The last bit simply un/registers everything and provides the name of some one to shout at when it all goes pear shaped.

## __init__.py
bl_info = {
    "name"         : "Furniture",
    "author"       : "Carel",
    "version"      : (0, 1),
    "category"     : "User",
    "location"     : "View3D > Add > Furniture",
    "description"  : "This allows one to create various funriture, specifically a chair",
    "warning"      : "Alpha Release, may wobble",
 }

def register():
    # Properties
    bpy.utils.register_class(chairProperties)
    # Operator(s)
    bpy.utils.register_class(MESH_OT_primitive_chair_add)
    # Menu(s)
    bpy.types.INFO_MT_add.append(addChairMenu)

def unregister():
    # Menu(s)
    bpy.types.INFO_MT_add.remove(addChairMenu)
    # Operator(s)
    bpy.utils.register_class(MESH_OT_primitive_chair_add)
    # Properties
    bpy.utils.register_class(chairProperties)


if __name__ == "__main__" :
    register()

Notes

This code sort of stems from this question if you want to understand it better. There is also now a related question.

$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .