├── LICENSE.txt ├── README.md ├── ch01_code ├── 1-1.py ├── 1-2.batch ├── 1-3.sh ├── 1-4.py ├── 1-5.py └── 1-6.py ├── ch02_code ├── 2-1.py ├── 2-10.py ├── 2-11.py ├── 2-12.py ├── 2-13.py ├── 2-2.py ├── 2-3.py ├── 2-4.py ├── 2-5.py ├── 2-6.py ├── 2-7.py ├── 2-8.py └── 2-9.py ├── ch03_code ├── 3-1.py ├── 3-10.py ├── 3-11.py ├── 3-12.py ├── 3-13.py ├── 3-14.py ├── 3-2.py ├── 3-3.py ├── 3-4.py ├── 3-6.py ├── 3-7.py └── 3-9.py ├── ch04_code ├── 4-1.obj ├── 4-10.py ├── 4-2.stl ├── 4-3.ply ├── 4-4.obj ├── 4-5.obj ├── 4-6.obj ├── 4-7.obj ├── 4-8.obj └── 4-9.obj ├── ch05_code ├── 5-1.py ├── 5-2.py ├── 5-3.py ├── 5-4.py └── 5-5.py ├── ch06_code ├── 6-1.py ├── 6-2.py ├── 6-3.py ├── 6-4.py ├── 6-5.py └── 6-6.py ├── ch07_code ├── 7-1.txt ├── 7-2.py ├── 7-3.py ├── 7-4.py ├── 7-5.py ├── 7-6.py ├── 7-7.py └── 7-8.py ├── ch08_code ├── 8-1.py ├── 8-2.py ├── 8-3.py └── 8-4.py ├── contributing.md └── utility_code └── ut.py /LICENSE.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/blender-python-api/b4b929ebb9f4719b84233447880f6738adf9b6d4/LICENSE.txt -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Apress Source Code 2 | 3 | This repository accompanies [*The Blender Python API*](http://www.apress.com/9781484228012) by Chris Conlan (Apress, 2017). 4 | 5 | [comment]: #cover 6 | 7 | 8 | Download the files as a zip using the green button, or clone the repository to your machine using Git. 9 | 10 | ## Releases 11 | 12 | Release v1.0 corresponds to the code in the published book, without corrections or updates. 13 | 14 | ## Contributions 15 | 16 | See the file Contributing.md for more information on how you can contribute to this repository. 17 | -------------------------------------------------------------------------------- /ch01_code/1-1.py: -------------------------------------------------------------------------------- 1 | bpy.ops.transform.translate(value=(3.05332, 0, 0), constraint_axis=(True, False, False), 2 | constraint_orientation='GLOBAL', mirror=False, proportional='DISABLED', 3 | proportional_edit_falloff='SMOOTH', proportional_size=1, release_confirm=True) -------------------------------------------------------------------------------- /ch01_code/1-2.batch: -------------------------------------------------------------------------------- 1 | # Assuming you are starting from C:\Users\%USERNAME% 2 | cd Desktop\blender-2.78c-windows64 3 | blender 4 | 5 | # Navigating from anywhere on the Windows 6 | # filesystem to Blender on the Desktop 7 | cd C:\Users\%USERNAME%\Desktop\blender-2.78c-windows64 8 | blender 9 | 10 | # If an existing Blender install causes 11 | # the wrong version to open, use blender.exe 12 | cd C:\Users\%USERNAME%\Desktop\blender-2.78c-windows64 13 | blender.exe -------------------------------------------------------------------------------- /ch01_code/1-3.sh: -------------------------------------------------------------------------------- 1 | # Navigating to Blender on the Desktop from 2 | # anywhere in the filesystem for Linux 3 | cd ~/Desktop/blender-2.78c-linux-glibc211-x86_64 4 | ./blender 5 | 6 | 7 | # Navigating to Blender in the home directory for OSX 8 | cd ~/Desktop/blender-2.78c-OSX-10.6-x86_64 9 | ./blender -------------------------------------------------------------------------------- /ch01_code/1-4.py: -------------------------------------------------------------------------------- 1 | bpy.ops.mesh.primitive_cube_add(radius=1, view_align=False, enter_editmode=False, 2 | location=(0, 0, 0), layers=(True, False, False, False, False, False, False, False, 3 | False, False, False, False, False, False, False, False, False, False, False, False)) -------------------------------------------------------------------------------- /ch01_code/1-5.py: -------------------------------------------------------------------------------- 1 | # Make a bigger cube sitting in the first quadrant 2 | bpy.ops.mesh.primitive_cube_add(radius=3, location=(5, 5, 5)) -------------------------------------------------------------------------------- /ch01_code/1-6.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | for k in range(5): 4 | for j in range(5): 5 | for i in range(5): 6 | bpy.ops.mesh.primitive_cube_add(radius=0.25, location=(i, j, k)) -------------------------------------------------------------------------------- /ch02_code/2-1.py: -------------------------------------------------------------------------------- 1 | # Outputs bpy.data.objects datablocks 2 | bpy.context.selected_objects -------------------------------------------------------------------------------- /ch02_code/2-10.py: -------------------------------------------------------------------------------- 1 | import ut 2 | import csv 3 | import urllib.request 4 | 5 | ################### 6 | # Reading in Data # 7 | ################### 8 | 9 | # Read iris.csv from file repository 10 | url_str = 'http://blender.chrisconlan.com/iris.csv' 11 | iris_csv = urllib.request.urlopen(url_str) 12 | iris_ob = csv.reader(iris_csv.read().decode('utf-8').splitlines()) 13 | 14 | # Store header as list, and data as list of lists 15 | iris_header = [] 16 | iris_data = [] 17 | 18 | for v in iris_ob: 19 | if not iris_header: 20 | iris_header = v 21 | else: 22 | v = [float(v[0]), 23 | float(v[1]), 24 | float(v[2]), 25 | float(v[3]), 26 | str(v[4])] 27 | iris_data.append(v) -------------------------------------------------------------------------------- /ch02_code/2-11.py: -------------------------------------------------------------------------------- 1 | # Columns: 2 | # 'Sepal.Length', 'Sepal.Width', 3 | # 'Petal.Length', 'Petal.Width', 'Species' 4 | 5 | # Visualize 3 dimensions 6 | # Sepal.Length, Sepal.Width, and 'Petal.Length' 7 | 8 | # Clear scene 9 | ut.delete_all() 10 | 11 | # Place data 12 | for i in range(0, len(iris_data)): 13 | ut.create.sphere('row-' + str(i)) 14 | v = iris_data[i] 15 | ut.act.scale((0.25, 0.25, 0.25)) 16 | ut.act.location((v[0], v[1], v[2])) -------------------------------------------------------------------------------- /ch02_code/2-12.py: -------------------------------------------------------------------------------- 1 | # Columns: 2 | # 'Sepal.Length', 'Sepal.Width', 3 | # 'Petal.Length', 'Petal.Width', 'Species' 4 | 5 | # Visualize 4 dimensions 6 | # Sepal.Length, Sepal.Width, 'Petal.Length', 7 | # and scale the object by a factor of 'Petal.Width' 8 | 9 | # Clear scene 10 | ut.delete_all() 11 | 12 | # Place data 13 | for i in range(0, len(iris_data)): 14 | ut.create.sphere('row-' + str(i)) 15 | v = iris_data[i] 16 | scale_factor = 0.2 17 | ut.act.scale((v[3] * scale_factor,) * 3) 18 | ut.act.location((v[0], v[1], v[2])) -------------------------------------------------------------------------------- /ch02_code/2-13.py: -------------------------------------------------------------------------------- 1 | # Columns: 2 | # 'Sepal.Length', 'Sepal.Width', 3 | # 'Petal.Length', 'Petal.Width', 'Species' 4 | 5 | # Visualize 5 dimensions 6 | # Sepal.Length, Sepal.Width, 'Petal.Length', 7 | # and scale the object by a factor of 'Petal.Width' 8 | # setosa = sphere, versicolor = cube, virginica = cone 9 | 10 | # Clear scene 11 | ut.delete_all() 12 | 13 | # Place data 14 | for i in range(0, len(iris_data)): 15 | 16 | v = iris_data[i] 17 | 18 | if v[4] == 'setosa': 19 | ut.create.sphere('setosa-' + str(i)) 20 | if v[4] == 'versicolor': 21 | ut.create.cube('versicolor-' + str(i)) 22 | if v[4] == 'virginica': 23 | ut.create.cone('virginica-' + str(i)) 24 | 25 | scale_factor = 0.2 26 | ut.act.scale((v[3] * scale_factor,) * 3) 27 | ut.act.location((v[0], v[1], v[2])) -------------------------------------------------------------------------------- /ch02_code/2-2.py: -------------------------------------------------------------------------------- 1 | # Return the names of selected objects 2 | [k.name for k in bpy.context.selected_objects] 3 | 4 | # Return the locations of selected objects 5 | # (location of origin assuming no pending transformations) 6 | [k.location for k in bpy.context.selected_objects] 7 | -------------------------------------------------------------------------------- /ch02_code/2-3.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | def mySelector(objName, additive=False): 4 | 5 | # By default, clear other selections 6 | if not additive: 7 | bpy.ops.object.select_all(action='DESELECT') 8 | 9 | # Set the 'select' property of the datablock to True 10 | bpy.data.objects[objName].select = True 11 | 12 | 13 | # Select only 'Cube' 14 | mySelector('Cube') 15 | 16 | # Select 'Sphere', keeping other selections 17 | mySelector('Sphere', additive=True) 18 | 19 | # Translate selected objects 1 unit along the x-axis 20 | bpy.ops.transform.translate(value=(1, 0, 0)) -------------------------------------------------------------------------------- /ch02_code/2-4.py: -------------------------------------------------------------------------------- 1 | # Returns bpy.data.objects datablock 2 | bpy.context.object 3 | 4 | # Longer synonym for the above line 5 | bpy.context.active_object 6 | 7 | # Accessing the 'name' and 'location' values of the datablock 8 | bpy.context.object.name 9 | bpy.context.object.location -------------------------------------------------------------------------------- /ch02_code/2-5.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | def myActivator(objName): 4 | 5 | # Pass bpy.data.objects datablock to scene class 6 | bpy.context.scene.objects.active = bpy.data.objects[objName] 7 | 8 | 9 | # Acivate the object named 'Sphere' 10 | myActivator('Sphere') 11 | 12 | # Verify the 'Sphere' was activated 13 | print("Active object:", bpy.context.object.name) 14 | 15 | # Selected objects were unaffected 16 | print("Selected objects:", bpy.context.selected_objects) -------------------------------------------------------------------------------- /ch02_code/2-6.py: -------------------------------------------------------------------------------- 1 | # bpy.data.objects datablock for an object named 'Cube' 2 | bpy.data.objects['Cube'] 3 | 4 | # bpy.data.objects datablock for an object named 'eyeballSphere' 5 | bpy.data.objects['eyeballSphere'] -------------------------------------------------------------------------------- /ch02_code/2-7.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | def mySpecifier(objName): 4 | # Return the datablock 5 | return bpy.data.objects[objName] 6 | 7 | # Store a reference to the datablock 8 | myCube = mySpecifier('Cube') 9 | 10 | # Output the location of the origin 11 | print(myCube.location) 12 | 13 | # Works exactly the same as above 14 | myCube = bpy.data.objects['Cube'] 15 | print(myCube.location) -------------------------------------------------------------------------------- /ch02_code/2-8.py: -------------------------------------------------------------------------------- 1 | # Each line will return the same object type and memory address 2 | bpy.data 3 | bpy.data.objects.data 4 | bpy.data.objects.data.objects.data 5 | bpy.data.objects.data.objects.data.objects.data 6 | 7 | # References to the same object can be made across datablock types 8 | bpy.data.meshes.data 9 | bpy.data.meshes.data.objects.data 10 | bpy.data.meshes.data.objects.data.scenes.data.worlds.data.materials.data 11 | 12 | 13 | # Different types of datablocks also nest 14 | # Each of these lines returns the bpy.data.meshes datablock for 'Cube' 15 | bpy.data.meshes['Cube'] 16 | bpy.data.objects['Cube'].data 17 | bpy.data.objects['Cube'].data.vertices.data 18 | bpy.data.objects['Cube'].data.vertices.data.edges.data.materials.data -------------------------------------------------------------------------------- /ch02_code/2-9.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | 4 | # Selecting objects by name 5 | def select(objName): 6 | bpy.ops.object.select_all(action='DESELECT') 7 | bpy.data.objects[objName].select = True 8 | 9 | 10 | # Activating objects by name 11 | def activate(objName): 12 | bpy.context.scene.objects.active = bpy.data.objects[objName] 13 | 14 | 15 | class sel: 16 | """Function Class for operating on SELECTED objects""" 17 | 18 | # Differential 19 | def translate(v): 20 | bpy.ops.transform.translate( 21 | value=v, constraint_axis=(True, True, True)) 22 | 23 | # Differential 24 | def scale(v): 25 | bpy.ops.transform.resize(value=v, constraint_axis=(True, True, True)) 26 | 27 | # Differential 28 | def rotate_x(v): 29 | bpy.ops.transform.rotate(value=v, axis=(1, 0, 0)) 30 | 31 | # Differential 32 | def rotate_y(v): 33 | bpy.ops.transform.rotate(value=v, axis=(0, 1, 0)) 34 | 35 | # Differential 36 | def rotate_z(v): 37 | bpy.ops.transform.rotate(value=v, axis=(0, 0, 1)) 38 | 39 | 40 | class act: 41 | """Function Class for operating on ACTIVE objects""" 42 | 43 | # Declarative 44 | def location(v): 45 | bpy.context.object.location = v 46 | 47 | # Declarative 48 | def scale(v): 49 | bpy.context.object.scale = v 50 | 51 | # Declarative 52 | def rotation(v): 53 | bpy.context.object.rotation_euler = v 54 | 55 | # Rename the active object 56 | def rename(objName): 57 | bpy.context.object.name = objName 58 | 59 | 60 | class spec: 61 | """Function Class for operating on SPECIFIED objects""" 62 | 63 | # Declarative 64 | def scale(objName, v): 65 | bpy.data.objects[objName].scale = v 66 | 67 | # Declarative 68 | def location(objName, v): 69 | bpy.data.objects[objName].location = v 70 | 71 | # Declarative 72 | def rotation(objName, v): 73 | bpy.data.objects[objName].rotation_euler = v 74 | 75 | 76 | class create: 77 | """Function Class for CREATING Objects""" 78 | 79 | def cube(objName): 80 | bpy.ops.mesh.primitive_cube_add(radius=0.5, location=(0, 0, 0)) 81 | act.rename(objName) 82 | 83 | def sphere(objName): 84 | bpy.ops.mesh.primitive_uv_sphere_add(size=0.5, location=(0, 0, 0)) 85 | act.rename(objName) 86 | 87 | def cone(objName): 88 | bpy.ops.mesh.primitive_cone_add(radius1=0.5, location=(0, 0, 0)) 89 | act.rename(objName) 90 | 91 | 92 | # Delete an object by name 93 | def delete(objName): 94 | 95 | select(objName) 96 | bpy.ops.object.delete(use_global=False) 97 | 98 | 99 | # Delete all objects 100 | def delete_all(): 101 | 102 | if(len(bpy.data.objects) != 0): 103 | bpy.ops.object.select_all(action='SELECT') 104 | bpy.ops.object.delete(use_global=False) 105 | 106 | 107 | if __name__ == "__main__": 108 | 109 | # Create a cube 110 | create.cube('PerfectCube') 111 | 112 | # Differential transformations combine 113 | sel.translate((0, 1, 2)) 114 | 115 | sel.scale((1, 1, 2)) 116 | sel.scale((0.5, 1, 1)) 117 | 118 | sel.rotate_x(3.1415 / 8) 119 | sel.rotate_x(3.1415 / 7) 120 | 121 | sel.rotate_z(3.1415 / 3) 122 | 123 | # Create a cone 124 | create.cone('PointyCone') 125 | 126 | # Declaractive transformations overwrite 127 | act.location((-2, -2, 0)) 128 | spec.scale('PointyCone', (1.5, 2.5, 2)) 129 | 130 | # Create a Sphere 131 | create.sphere('SmoothSphere') 132 | 133 | # Declaractive transformations overwrite 134 | spec.location('SmoothSphere', (2, 0, 0)) 135 | act.rotation((0, 0, 3.1415 / 3)) 136 | act.scale((1, 3, 1)) -------------------------------------------------------------------------------- /ch03_code/3-1.py: -------------------------------------------------------------------------------- 1 | # Set mode to Edit Mode 2 | bpy.ops.object.mode_set(mode="EDIT") 3 | 4 | # Set mode to Object Mode 5 | bpy.ops.object.mode_set(mode="OBJECT") -------------------------------------------------------------------------------- /ch03_code/3-10.py: -------------------------------------------------------------------------------- 1 | def coords(objName, space='GLOBAL'): 2 | 3 | # Store reference to the bpy.data.objects datablock 4 | obj = bpy.data.objects[objName] 5 | 6 | # Store reference to bpy.data.objects[].meshes datablock 7 | if obj.mode == 'EDIT': 8 | v = bmesh.from_edit_mesh(obj.data).verts 9 | elif obj.mode == 'OBJECT': 10 | v = obj.data.vertices 11 | 12 | if space == 'GLOBAL': 13 | # Return T * L as list of tuples 14 | return [(obj.matrix_world * v.co).to_tuple() for v in v] 15 | elif space == 'LOCAL': 16 | # Return L as list of tuples 17 | return [v.co.to_tuple() for v in v] 18 | 19 | 20 | class sel: 21 | 22 | # Add this to the ut.sel class, for use in object mode 23 | def transform_apply(): 24 | bpy.ops.object.transform_apply( 25 | location=True, rotation=True, scale=True) -------------------------------------------------------------------------------- /ch03_code/3-11.py: -------------------------------------------------------------------------------- 1 | import ut 2 | import importlib 3 | importlib.reload(ut) 4 | 5 | import bpy 6 | 7 | # Will fail if scene is empty 8 | bpy.ops.object.mode_set(mode='OBJECT') 9 | bpy.ops.object.select_all(action='SELECT') 10 | bpy.ops.object.delete() 11 | 12 | bpy.ops.mesh.primitive_cube_add(radius=0.5, location=(0, 0, 0)) 13 | bpy.context.object.name = 'Cube-1' 14 | 15 | # Check global and local coordinates 16 | print('\nBefore transform:') 17 | print('Global:', ut.coords('Cube-1', 'GLOBAL')[0:2]) 18 | print('Local: ', ut.coords('Cube-1', 'LOCAL')[0:2]) 19 | 20 | # Translate it along x = y = z 21 | # See the cube move in the 3D viewport 22 | bpy.ops.transform.translate(value = (3, 3, 3)) 23 | 24 | # Check global and local coordinates 25 | print('\nAfter transform, unapplied:') 26 | print('Global: ', ut.coords('Cube-1', 'GLOBAL')[0:2]) 27 | print('Local: ', ut.coords('Cube-1', 'LOCAL')[0:2]) 28 | 29 | # Apply transformation 30 | # Nothing changes in 3D viewport 31 | ut.sel.transform_apply() 32 | 33 | # Check global and local coordinates 34 | print('\nAfter transform, applied:') 35 | print('Global: ', ut.coords('Cube-1', 'GLOBAL')[0:2]) 36 | print('Local: ', ut.coords('Cube-1', 'LOCAL')[0:2]) 37 | 38 | 39 | ############################ Output ########################### 40 | # Before transform: 41 | # Global: [(-0.5, -0.5, -0.5), (-0.5, -0.5, 0.5)] 42 | # Local: [(-0.5, -0.5, -0.5), (-0.5, -0.5, 0.5)] 43 | # 44 | # After transform, unapplied: 45 | # Global: [(2.5, 2.5, 2.5), (2.5, 2.5, 3.5)] 46 | # Local: [(-0.5, -0.5, -0.5), (-0.5, -0.5, 0.5)] 47 | # 48 | # After transform, applied: 49 | # Global: [(2.5, 2.5, 2.5), (2.5, 2.5, 3.5)] 50 | # Local: [(2.5, 2.5, 2.5), (2.5, 2.5, 3.5)] 51 | ############################################################### -------------------------------------------------------------------------------- /ch03_code/3-12.py: -------------------------------------------------------------------------------- 1 | # Add in body of script, outside any class declarations 2 | def in_bbox(lbound, ubound, v, buffer=0.0001): 3 | return lbound[0] - buffer <= v[0] <= ubound[0] + buffer and \ 4 | lbound[1] - buffer <= v[1] <= ubound[1] + buffer and \ 5 | lbound[2] - buffer <= v[2] <= ubound[2] + buffer 6 | 7 | 8 | class act: 9 | 10 | # Add to ut.act class 11 | def select_by_loc(lbound=(0, 0, 0), ubound=(0, 0, 0), 12 | select_mode='VERT', coords='GLOBAL'): 13 | 14 | # Set selection mode, VERT, EDGE, or FACE 15 | selection_mode(select_mode) 16 | 17 | # Grab the transformation matrix 18 | world = bpy.context.object.matrix_world 19 | 20 | # Instantiate a bmesh object and ensure lookup table 21 | # Running bm.faces.ensure_lookup_table() works for all parts 22 | bm = bmesh.from_edit_mesh(bpy.context.object.data) 23 | bm.faces.ensure_lookup_table() 24 | 25 | # Initialize list of vertices and list of parts to be selected 26 | verts = [] 27 | to_select = [] 28 | 29 | # For VERT, EDGE, or FACE ... 30 | # 1. Grab list of global or local coordinates 31 | # 2. Test if the piece is entirely within the rectangular 32 | # prism defined by lbound and ubound 33 | # 3. Select each piece that returned True and deselect 34 | # each piece that returned False in Step 2 35 | 36 | if select_mode == 'VERT': 37 | if coords == 'GLOBAL': 38 | [verts.append((world * v.co).to_tuple()) for v in bm.verts] 39 | elif coords == 'LOCAL': 40 | [verts.append(v.co.to_tuple()) for v in bm.verts] 41 | 42 | [to_select.append(in_bbox(lbound, ubound, v)) for v in verts] 43 | for vertObj, select in zip(bm.verts, to_select): 44 | vertObj.select = select 45 | 46 | if select_mode == 'EDGE': 47 | if coords == 'GLOBAL': 48 | [verts.append([(world * v.co).to_tuple() 49 | for v in e.verts]) for e in bm.edges] 50 | elif coords == 'LOCAL': 51 | [verts.append([v.co.to_tuple() for v in e.verts]) 52 | for e in bm.edges] 53 | 54 | [to_select.append(all(in_bbox(lbound, ubound, v) 55 | for v in e)) for e in verts] 56 | for edgeObj, select in zip(bm.edges, to_select): 57 | edgeObj.select = select 58 | 59 | if select_mode == 'FACE': 60 | if coords == 'GLOBAL': 61 | [verts.append([(world * v.co).to_tuple() 62 | for v in f.verts]) for f in bm.faces] 63 | elif coords == 'LOCAL': 64 | [verts.append([v.co.to_tuple() for v in f.verts]) 65 | for f in bm.faces] 66 | 67 | [to_select.append(all(in_bbox(lbound, ubound, v) 68 | for v in f)) for f in verts] 69 | for faceObj, select in zip(bm.faces, to_select): 70 | faceObj.select = select -------------------------------------------------------------------------------- /ch03_code/3-13.py: -------------------------------------------------------------------------------- 1 | import ut 2 | import importlib 3 | importlib.reload(ut) 4 | 5 | import bpy 6 | 7 | # Will fail if scene is empty 8 | bpy.ops.object.mode_set(mode='OBJECT') 9 | bpy.ops.object.select_all(action='SELECT') 10 | bpy.ops.object.delete() 11 | 12 | bpy.ops.mesh.primitive_uv_sphere_add(size=0.5, location=(0, 0, 0)) 13 | bpy.ops.transform.resize(value = (5, 5, 5)) 14 | bpy.ops.object.mode_set(mode='EDIT') 15 | bpy.ops.mesh.select_all(action='DESELECT') 16 | 17 | # Selects upper right quadrant of sphere 18 | ut.act.select_by_loc((0, 0, 0), (1, 1, 1), 'VERT', 'LOCAL') 19 | 20 | # Selects nothing 21 | ut.act.select_by_loc((0, 0, 0), (1, 1, 1), 'VERT', 'GLOBAL') 22 | 23 | # Selects upper right quadrant of sphere 24 | ut.act.select_by_loc((0, 0, 0), (5, 5, 5), 'VERT', 'LOCAL') 25 | 26 | # Mess with it 27 | bpy.ops.transform.translate(value = (1, 1,1)) 28 | bpy.ops.transform.resize(value = (2, 2, 2)) 29 | 30 | 31 | # Selects lower half of sphere 32 | ut.act.select_by_loc((-5, -5, -5), (5, 5, -0.5), 'EDGE', 'GLOBAL') 33 | 34 | # Mess with it 35 | bpy.ops.transform.translate(value = (0, 0, 3)) 36 | bpy.ops.transform.resize(value = (0.1, 0.1, 0.1)) 37 | 38 | bpy.ops.object.mode_set(mode='OBJECT') -------------------------------------------------------------------------------- /ch03_code/3-14.py: -------------------------------------------------------------------------------- 1 | import ut 2 | import importlib 3 | importlib.reload(ut) 4 | 5 | import bpy 6 | 7 | from random import randint 8 | from math import floor 9 | 10 | # Must start in object mode 11 | bpy.ops.object.select_all(action='SELECT') 12 | bpy.ops.object.delete() 13 | 14 | # Create a cube 15 | bpy.ops.mesh.primitive_cube_add(radius=0.5, location=(0, 0, 0)) 16 | bpy.context.object.name = 'Cube-1' 17 | 18 | bpy.ops.object.mode_set(mode='EDIT') 19 | bpy.ops.mesh.select_all(action="DESELECT") 20 | 21 | for i in range(0, 100): 22 | 23 | # Grab the local coordinates 24 | coords = ut.coords('Cube-1', 'LOCAL') 25 | 26 | # Find the bounding box for the object 27 | lower_bbox = [floor(min([v[i] for v in coords])) for i in [0, 1, 2]] 28 | upper_bbox = [floor(max([v[i] for v in coords])) for i in [0, 1, 2]] 29 | 30 | # Select a random face 2x2x1 units wide, snapped to integer coordinates 31 | lower_sel = [randint(l, u) for l, u in zip(lower_bbox, upper_bbox)] 32 | upper_sel = [l + 2 for l in lower_sel] 33 | upper_sel[randint(0, 2)] -= 1 34 | 35 | ut.act.select_by_loc(lower_sel, upper_sel, 'FACE', 'LOCAL') 36 | 37 | # Extrude the surface along it aggregate vertical normal 38 | bpy.ops.mesh.extrude_region_move(TRANSFORM_OT_translate = 39 | {"value": (0, 0, 1), 40 | "constraint_axis": (True, True, True), 41 | "constraint_orientation":'NORMAL'}) 42 | -------------------------------------------------------------------------------- /ch03_code/3-2.py: -------------------------------------------------------------------------------- 1 | # Place in ut.py 2 | 3 | # Function for entering Edit Mode with no vertices selected, 4 | # or entering Object Mode with no additional processes 5 | 6 | def mode(mode_name): 7 | bpy.ops.object.mode_set(mode=mode_name) 8 | if mode_name == "EDIT": 9 | bpy.ops.mesh.select_all(action="DESELECT") -------------------------------------------------------------------------------- /ch03_code/3-3.py: -------------------------------------------------------------------------------- 1 | # Will use the cached version of ut.py from 2 | # your first import of the Blender session 3 | import ut 4 | ut.create.cube('myCube') 5 | 6 | 7 | # Will reload the module from the live script of ut.py 8 | # and create a new cached version for the session 9 | import importlib 10 | importlib.reload(ut) 11 | ut.create.cube('myCube') 12 | 13 | 14 | # This is what the header of your main script 15 | # should look like when editing custom modules 16 | import ut 17 | import importlib 18 | importlib.reload(ut) 19 | 20 | # Code using ut.py ... -------------------------------------------------------------------------------- /ch03_code/3-4.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import bmesh 3 | 4 | # Must start in object mode 5 | bpy.ops.object.mode_set(mode='OBJECT') 6 | bpy.ops.object.select_all(action='SELECT') 7 | bpy.ops.object.delete() 8 | 9 | # Create a cube and enter Edit Mode 10 | bpy.ops.mesh.primitive_cube_add(radius=1, location=(0, 0, 0)) 11 | bpy.ops.object.mode_set(mode='EDIT') 12 | 13 | # Set to "Face Mode" for easier visualization 14 | bpy.ops.mesh.select_mode(type = "FACE") 15 | 16 | # Register bmesh object and select various parts 17 | bm = bmesh.from_edit_mesh(bpy.context.object.data) 18 | 19 | # Deselect all verts, edges, faces 20 | bpy.ops.mesh.select_all(action="DESELECT") 21 | 22 | # Select a face 23 | bm.faces.ensure_lookup_table() 24 | bm.faces[0].select = True 25 | 26 | # Select an edge 27 | bm.edges.ensure_lookup_table() 28 | bm.edges[7].select = True 29 | 30 | # Select a vertex 31 | bm.verts.ensure_lookup_table() 32 | bm.verts[5].select = True -------------------------------------------------------------------------------- /ch03_code/3-6.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import bmesh 3 | 4 | # Must start in object mode 5 | bpy.ops.object.mode_set(mode='OBJECT') 6 | bpy.ops.object.select_all(action='SELECT') 7 | bpy.ops.object.delete() 8 | 9 | 10 | # Create a cube and rotate a face around the y-axis 11 | bpy.ops.mesh.primitive_cube_add(radius=0.5, location=(-3, 0, 0)) 12 | bpy.ops.object.mode_set(mode='EDIT') 13 | bpy.ops.mesh.select_all(action="DESELECT") 14 | 15 | # Set to face mode for transformations 16 | bpy.ops.mesh.select_mode(type = "FACE") 17 | 18 | bm = bmesh.from_edit_mesh(bpy.context.object.data) 19 | bm.faces.ensure_lookup_table() 20 | bm.faces[1].select = True 21 | bpy.ops.transform.rotate(value = 0.3, axis = (0, 1, 0)) 22 | 23 | 24 | bpy.ops.object.mode_set(mode='OBJECT') 25 | 26 | # Create a cube and pull an edge along the y-axis 27 | bpy.ops.mesh.primitive_cube_add(radius=0.5, location=(0, 0, 0)) 28 | bpy.ops.object.mode_set(mode='EDIT') 29 | bpy.ops.mesh.select_all(action="DESELECT") 30 | 31 | bm = bmesh.from_edit_mesh(bpy.context.object.data) 32 | bm.edges.ensure_lookup_table() 33 | bm.edges[4].select = True 34 | bpy.ops.transform.translate(value = (0, 0.5, 0)) 35 | 36 | 37 | bpy.ops.object.mode_set(mode='OBJECT') 38 | 39 | # Create a cube and pull a vertex 1 unit 40 | # along the y and z axes 41 | # Create a cube and pull an edge along the y-axis 42 | bpy.ops.mesh.primitive_cube_add(radius=0.5, location=(3, 0, 0)) 43 | bpy.ops.object.mode_set(mode='EDIT') 44 | bpy.ops.mesh.select_all(action="DESELECT") 45 | 46 | bm = bmesh.from_edit_mesh(bpy.context.object.data) 47 | bm.verts.ensure_lookup_table() 48 | bm.verts[3].select = True 49 | bpy.ops.transform.translate(value = (0, 1, 1)) 50 | 51 | bpy.ops.object.mode_set(mode='OBJECT') -------------------------------------------------------------------------------- /ch03_code/3-7.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import bmesh 3 | 4 | # Must start in object mode 5 | bpy.ops.object.mode_set(mode='OBJECT') 6 | bpy.ops.object.select_all(action='SELECT') 7 | bpy.ops.object.delete() 8 | 9 | 10 | # Create a cube and rotate a face around the y-axis 11 | bpy.ops.mesh.primitive_cube_add(radius=0.5, location=(-3, 0, 0)) 12 | bpy.ops.object.mode_set(mode='EDIT') 13 | bpy.ops.mesh.select_all(action="DESELECT") 14 | 15 | # Set to face mode for transformations 16 | bpy.ops.mesh.select_mode(type = "FACE") 17 | 18 | bm = bmesh.from_edit_mesh(bpy.context.object.data) 19 | bm.faces.ensure_lookup_table() 20 | bm.faces[1].select = True 21 | bpy.ops.transform.rotate(value = 0.3, axis = (0, 1, 0)) 22 | 23 | 24 | bpy.ops.object.mode_set(mode='OBJECT') 25 | 26 | # Create a cube and pull an edge along the y-axis 27 | bpy.ops.mesh.primitive_cube_add(radius=0.5, location=(0, 0, 0)) 28 | bpy.ops.object.mode_set(mode='EDIT') 29 | bpy.ops.mesh.select_all(action="DESELECT") 30 | 31 | bm = bmesh.from_edit_mesh(bpy.context.object.data) 32 | bm.edges.ensure_lookup_table() 33 | bm.edges[4].select = True 34 | bpy.ops.transform.translate(value = (0, 0.5, 0)) 35 | 36 | 37 | bpy.ops.object.mode_set(mode='OBJECT') 38 | 39 | # Create a cube and pull a vertex 1 unit 40 | # along the y and z axes 41 | # Create a cube and pull an edge along the y-axis 42 | bpy.ops.mesh.primitive_cube_add(radius=0.5, location=(3, 0, 0)) 43 | bpy.ops.object.mode_set(mode='EDIT') 44 | bpy.ops.mesh.select_all(action="DESELECT") 45 | 46 | bm = bmesh.from_edit_mesh(bpy.context.object.data) 47 | bm.verts.ensure_lookup_table() 48 | bm.verts[3].select = True 49 | bpy.ops.transform.translate(value = (0, 1, 1)) 50 | 51 | bpy.ops.object.mode_set(mode='OBJECT') -------------------------------------------------------------------------------- /ch03_code/3-9.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import bmesh 3 | 4 | # Will fail if scene is empty 5 | bpy.ops.object.mode_set(mode='OBJECT') 6 | bpy.ops.object.select_all(action='SELECT') 7 | bpy.ops.object.delete() 8 | 9 | 10 | # Create a cube and extrude the top face away from it 11 | bpy.ops.mesh.primitive_cube_add(radius=0.5, location=(-3, 0, 0)) 12 | bpy.ops.object.mode_set(mode='EDIT') 13 | bpy.ops.mesh.select_all(action="DESELECT") 14 | 15 | # Set to face mode for transformations 16 | bpy.ops.mesh.select_mode(type = "FACE") 17 | 18 | 19 | bm = bmesh.from_edit_mesh(bpy.context.object.data) 20 | bm.faces.ensure_lookup_table() 21 | bm.faces[5].select = True 22 | bpy.ops.mesh.extrude_region_move(TRANSFORM_OT_translate = 23 | {"value": (0.3, 0.3, 0.3), 24 | "constraint_axis": (True, True, True), 25 | "constraint_orientation":'NORMAL'}) 26 | 27 | bpy.ops.object.mode_set(mode='OBJECT') 28 | 29 | 30 | # Create a cube and subdivide the top face 31 | bpy.ops.mesh.primitive_cube_add(radius=0.5, location=(0, 0, 0)) 32 | bpy.ops.object.mode_set(mode='EDIT') 33 | bpy.ops.mesh.select_all(action="DESELECT") 34 | 35 | bm = bmesh.from_edit_mesh(bpy.context.object.data) 36 | bm.faces.ensure_lookup_table() 37 | bm.faces[5].select = True 38 | bpy.ops.mesh.subdivide(number_cuts = 1) 39 | 40 | bpy.ops.mesh.select_all(action="DESELECT") 41 | bm.faces.ensure_lookup_table() 42 | bm.faces[5].select = True 43 | bm.faces[7].select = True 44 | bpy.ops.transform.translate(value = (0, 0, 0.5)) 45 | 46 | bpy.ops.object.mode_set(mode='OBJECT') 47 | 48 | 49 | # Create a cube and add a random offset to each vertex 50 | bpy.ops.mesh.primitive_cube_add(radius=0.5, location=(3, 0, 0)) 51 | bpy.ops.object.mode_set(mode='EDIT') 52 | bpy.ops.mesh.select_all(action="SELECT") 53 | bpy.ops.transform.vertex_random(offset = 0.5) 54 | 55 | bpy.ops.object.mode_set(mode='OBJECT') -------------------------------------------------------------------------------- /ch04_code/4-1.obj: -------------------------------------------------------------------------------- 1 | # Use hashes to leave comments in .obj files 2 | # The 'o' tag is used to name objects 3 | # all data following an 'o' tag is considered 4 | # to have this name until another name is entered 5 | o MySimpleFace 6 | 7 | # Vertices are entered with the 'v' tag as 8 | # space-delimited (x, y, z) tuples 9 | v -1.00 0.00 1.00 10 | v 1.00 0.00 1.00 11 | v -1.00 0.00 -1.00 12 | v 1.00 0.00 -1.00 13 | 14 | # Texture coordinate are entered with the 'vt' tag as 15 | # space-delimited (u, v) tuples, between 0 and 1 16 | vt 0.00 1.00 17 | vt 1.00 1.00 18 | vt 0.00 0.00 19 | vt 1.00 0.00 20 | 21 | # Normal vectors are entered with the 'vn' tag as 22 | # space-delimited (x, y, z) tuples, can be normal vectors if desired 23 | vn 0.0000 1.0000 0.0000 24 | 25 | # Indices are entered with the 'f' (for face) tag as 26 | # space-delimited triplets of v, vt, and vn indices as 27 | # f v_i/vt_i/vn_i v_j/vt_j/vn_j v_k/vt_k/vn_k 28 | # Faces can have any number (three or more) coplanar points 29 | f 2/2/1 3/3/1 1/1/1 30 | f 2/2/1 4/4/1 3/3/1 31 | 32 | # Alternatively, the faces section for this face can be 33 | # written as a single coplanar quadrilateral: 34 | f 1/1/1 2/2/1 4/4/1 3/3/1 35 | 36 | # Alternatively, the texture coordinates can be 37 | # excluded with double slashes 38 | f 1//1 2//1 4//1 3//1 -------------------------------------------------------------------------------- /ch04_code/4-10.py: -------------------------------------------------------------------------------- 1 | # Add modifier to selected objects 2 | bpy.ops.object.modifier_add(type='EDGE_SPLIT') 3 | 4 | # Set split threshold in radians 5 | bpy.context.object.modifiers["EdgeSplit"].split_angle = (3.1415 / 180) * 5 6 | 7 | # Apply modifier 8 | bpy.ops.object.modifier_apply(apply_as='DATA', modifier='EdgeSplit') -------------------------------------------------------------------------------- /ch04_code/4-2.stl: -------------------------------------------------------------------------------- 1 | solid MyFace 2 | facet normal 0.0 0.0 1.0 3 | outer loop 4 | vertex -1.0 -1.0 0.0 5 | vertex 1.0 -1.0 0.0 6 | vertex -1.0 1.0 0.0 7 | endloop 8 | endfacet 9 | facet normal 0.0 0.0 1.0 10 | outer loop 11 | vertex 1.0 -1.0 0.0 12 | vertex 1.0 1.0 0.0 13 | vertex -1.0 1.0 0.0 14 | endloop 15 | endfacet 16 | endsolid MyFace -------------------------------------------------------------------------------- /ch04_code/4-3.ply: -------------------------------------------------------------------------------- 1 | ply 2 | format ascii 1.0 3 | comment specifies a simple face 4 | element vertex 4 5 | property float32 x 6 | property float32 y 7 | property float32 z 8 | element face 1 9 | property list uint8 int32 vertex_indices 10 | end_header 11 | -1 -1 0 12 | 1 -1 0 13 | 1 1 0 14 | -1 1 0 15 | 4 1 0 3 2 16 | -------------------------------------------------------------------------------- /ch04_code/4-4.obj: -------------------------------------------------------------------------------- 1 | o NaiveCube 2 | 3 | # (36 * 3) + (36 * 3) = 216 floats 4 | # (12 * 3) + (12 * 3) = 72 integers 5 | 6 | v 1.000000 -1.000000 -1.000000 7 | v 1.000000 -1.000000 1.000000 8 | v -1.000000 -1.000000 1.000000 9 | v -1.000000 -1.000000 -1.000000 10 | v 1.000000 1.000000 -0.999999 11 | v 0.999999 1.000000 1.000001 12 | v -1.000000 1.000000 1.000000 13 | v -1.000000 1.000000 -1.000000 14 | v 1.000000 -1.000000 -1.000000 15 | v 1.000000 -1.000000 -1.000000 16 | v 1.000000 -1.000000 1.000000 17 | v 1.000000 -1.000000 1.000000 18 | v -1.000000 -1.000000 -1.000000 19 | v -1.000000 -1.000000 -1.000000 20 | v 1.000000 1.000000 -0.999999 21 | v 1.000000 1.000000 -0.999999 22 | v -1.000000 -1.000000 1.000000 23 | v -1.000000 -1.000000 1.000000 24 | v 0.999999 1.000000 1.000001 25 | v 0.999999 1.000000 1.000001 26 | v -1.000000 1.000000 1.000000 27 | v -1.000000 1.000000 1.000000 28 | v -1.000000 1.000000 -1.000000 29 | v -1.000000 1.000000 -1.000000 30 | v 1.000000 -1.000000 -1.000000 31 | v -1.000000 -1.000000 1.000000 32 | v 0.999999 1.000000 1.000001 33 | v -1.000000 1.000000 -1.000000 34 | v 1.000000 -1.000000 -1.000000 35 | v 1.000000 -1.000000 1.000000 36 | v 1.000000 1.000000 -0.999999 37 | v -1.000000 -1.000000 1.000000 38 | v -1.000000 -1.000000 1.000000 39 | v 0.999999 1.000000 1.000001 40 | v -1.000000 1.000000 -1.000000 41 | v -1.000000 1.000000 -1.000000 42 | 43 | vn 0.0000 -1.0000 0.0000 44 | vn 0.0000 1.0000 0.0000 45 | vn 1.0000 -0.0000 0.0000 46 | vn 0.0000 -0.0000 1.0000 47 | vn -1.0000 -0.0000 -0.0000 48 | vn 0.0000 0.0000 -1.0000 49 | vn 0.0000 -1.0000 0.0000 50 | vn 0.0000 1.0000 0.0000 51 | vn 1.0000 -0.0000 0.0000 52 | vn 0.0000 -0.0000 1.0000 53 | vn -1.0000 -0.0000 -0.0000 54 | vn 0.0000 0.0000 -1.0000 55 | vn 0.0000 -1.0000 0.0000 56 | vn 0.0000 1.0000 0.0000 57 | vn 1.0000 -0.0000 0.0000 58 | vn 0.0000 -0.0000 1.0000 59 | vn -1.0000 -0.0000 -0.0000 60 | vn 0.0000 0.0000 -1.0000 61 | vn 0.0000 -1.0000 0.0000 62 | vn 0.0000 1.0000 0.0000 63 | vn 1.0000 -0.0000 0.0000 64 | vn 0.0000 -0.0000 1.0000 65 | vn -1.0000 -0.0000 -0.0000 66 | vn 0.0000 0.0000 -1.0000 67 | vn 0.0000 -1.0000 0.0000 68 | vn 0.0000 1.0000 0.0000 69 | vn 1.0000 -0.0000 0.0000 70 | vn 0.0000 -0.0000 1.0000 71 | vn -1.0000 -0.0000 -0.0000 72 | vn 0.0000 0.0000 -1.0000 73 | vn 0.0000 -1.0000 0.0000 74 | vn 0.0000 1.0000 0.0000 75 | vn 1.0000 -0.0000 0.0000 76 | vn 0.0000 -0.0000 1.0000 77 | vn -1.0000 -0.0000 -0.0000 78 | vn 0.0000 0.0000 -1.0000 79 | 80 | f 9//1 17//13 13//25 81 | f 24//2 20//14 16//26 82 | f 15//3 12//15 10//27 83 | f 6//4 18//16 2//28 84 | f 3//5 23//17 14//29 85 | f 1//6 8//18 5//30 86 | f 29//7 11//19 32//31 87 | f 36//8 22//20 34//32 88 | f 31//9 19//21 30//33 89 | f 27//10 21//22 33//34 90 | f 26//11 7//23 35//35 91 | f 25//12 4//24 28//36 -------------------------------------------------------------------------------- /ch04_code/4-5.obj: -------------------------------------------------------------------------------- 1 | o SharingCube 2 | 3 | # (8 * 3) + (6 * 3) = 42 floats 4 | # (12 * 3) + (12 * 3) = 72 integers 5 | 6 | v 1.000000 -1.000000 -1.000000 7 | v 1.000000 -1.000000 1.000000 8 | v -1.000000 -1.000000 1.000000 9 | v -1.000000 -1.000000 -1.000000 10 | v 1.000000 1.000000 -0.999999 11 | v 0.999999 1.000000 1.000001 12 | v -1.000000 1.000000 1.000000 13 | v -1.000000 1.000000 -1.000000 14 | 15 | vn 0.0000 -1.0000 0.0000 16 | vn 0.0000 1.0000 0.0000 17 | vn 1.0000 -0.0000 0.0000 18 | vn 0.0000 -0.0000 1.0000 19 | vn -1.0000 -0.0000 -0.0000 20 | vn 0.0000 0.0000 -1.0000 21 | 22 | f 1//1 3//1 4//1 23 | f 8//2 6//2 5//2 24 | f 5//3 2//3 1//3 25 | f 6//4 3//4 2//4 26 | f 3//5 8//5 4//5 27 | f 1//6 8//6 5//6 28 | f 1//1 2//1 3//1 29 | f 8//2 7//2 6//2 30 | f 5//3 6//3 2//3 31 | f 6//4 7//4 3//4 32 | f 3//5 7//5 8//5 33 | f 1//6 4//6 8//6 34 | -------------------------------------------------------------------------------- /ch04_code/4-6.obj: -------------------------------------------------------------------------------- 1 | o CoplanarFaceCube 2 | 3 | # (8 * 3) + (6 * 3) = 42 floats 4 | # (6 * 4) + (6 * 4) = 48 integers 5 | 6 | v -1.000000 -1.000000 1.000000 7 | v -1.000000 1.000000 1.000000 8 | v -1.000000 -1.000000 -1.000000 9 | v -1.000000 1.000000 -1.000000 10 | v 1.000000 -1.000000 1.000000 11 | v 1.000000 1.000000 1.000000 12 | v 1.000000 -1.000000 -1.000000 13 | v 1.000000 1.000000 -1.000000 14 | 15 | vn -1.0000 0.0000 0.0000 16 | vn 0.0000 0.0000 -1.0000 17 | vn 1.0000 0.0000 0.0000 18 | vn 0.0000 0.0000 1.0000 19 | vn 0.0000 -1.0000 0.0000 20 | vn 0.0000 1.0000 0.0000 21 | 22 | f 1//1 2//1 4//1 3//1 23 | f 3//2 4//2 8//2 7//2 24 | f 7//3 8//3 6//3 5//3 25 | f 5//4 6//4 2//4 1//4 26 | f 3//5 7//5 5//5 1//5 27 | f 8//6 4//6 2//6 6//6 28 | -------------------------------------------------------------------------------- /ch04_code/4-7.obj: -------------------------------------------------------------------------------- 1 | o FaceNormalsCube 2 | 3 | # Theoretical .obj format, not valid 4 | # (8 * 3) + (6 * 3) = 42 floats 5 | # (6 * 4) + (6 * 1) = 30 integers 6 | 7 | v -1.000000 -1.000000 1.000000 8 | v -1.000000 1.000000 1.000000 9 | v -1.000000 -1.000000 -1.000000 10 | v -1.000000 1.000000 -1.000000 11 | v 1.000000 -1.000000 1.000000 12 | v 1.000000 1.000000 1.000000 13 | v 1.000000 -1.000000 -1.000000 14 | v 1.000000 1.000000 -1.000000 15 | 16 | vn -1.0000 0.0000 0.0000 17 | vn 0.0000 0.0000 -1.0000 18 | vn 1.0000 0.0000 0.0000 19 | vn 0.0000 0.0000 1.0000 20 | vn 0.0000 -1.0000 0.0000 21 | vn 0.0000 1.0000 0.0000 22 | 23 | # Face and normals defined as: 24 | # f (v_1, v_2, v_3, v_4)//n_1 25 | f (1 2 4 3)//1 26 | f (3 4 8 7)//2 27 | f (7 8 6 5)//3 28 | f (5 6 2 1)//4 29 | f (3 7 5 1)//5 30 | f (8 4 2 6)//6 -------------------------------------------------------------------------------- /ch04_code/4-8.obj: -------------------------------------------------------------------------------- 1 | o ConcentricCube 2 | v 1.000000 -1.000000 -1.000000 3 | v 1.000000 -1.000000 1.000000 4 | v -1.000000 -1.000000 1.000000 5 | v -1.000000 -1.000000 -1.000000 6 | v 1.000000 1.000000 -0.999999 7 | v 0.999999 1.000000 1.000001 8 | v -1.000000 1.000000 1.000000 9 | v -1.000000 1.000000 -1.000000 10 | 11 | vn 0.5773503 -0.5773503 -0.5773503 12 | vn 0.5773503 -0.5773503 0.5773503 13 | vn -0.5773503 -0.5773503 0.5773503 14 | vn -0.5773503 -0.5773503 -0.5773503 15 | vn 0.5773503 0.5773503 -0.5773497 16 | vn 0.5773497 0.5773503 0.5773508 17 | vn -0.5773503 0.5773503 0.5773503 18 | vn -0.5773503 0.5773503 -0.5773503 19 | 20 | f 1//1 3//3 4//4 21 | f 8//8 6//6 5//5 22 | f 5//5 2//2 1//1 23 | f 6//6 3//3 2//2 24 | f 3//3 8//8 4//4 25 | f 1//1 8//8 5//5 26 | f 1//1 2//2 3//3 27 | f 8//8 7//7 6//6 28 | f 5//5 6//6 2//2 29 | f 6//6 7//7 3//3 30 | f 3//3 7//7 8//8 31 | f 1//1 4//4 8//8 -------------------------------------------------------------------------------- /ch04_code/4-9.obj: -------------------------------------------------------------------------------- 1 | o PlanarCube 2 | v 1.000000 -1.000000 -1.000000 3 | v 1.000000 -1.000000 1.000000 4 | v -1.000000 -1.000000 1.000000 5 | v -1.000000 -1.000000 -1.000000 6 | v 1.000000 1.000000 -0.999999 7 | v 0.999999 1.000000 1.000001 8 | v -1.000000 1.000000 1.000000 9 | v -1.000000 1.000000 -1.000000 10 | 11 | vn 0.0000 -1.0000 0.0000 12 | vn 0.0000 1.0000 0.0000 13 | vn 1.0000 0.0000 0.0000 14 | vn -0.0000 -0.0000 1.0000 15 | vn -1.0000 -0.0000 -0.0000 16 | vn 0.0000 0.0000 -1.0000 17 | 18 | f 1//1 2//1 3//1 4//1 19 | f 5//2 8//2 7//2 6//2 20 | f 1//3 5//3 6//3 2//3 21 | f 2//4 6//4 7//4 3//4 22 | f 3//5 7//5 8//5 4//5 23 | f 5//6 1//6 4//6 8//6 -------------------------------------------------------------------------------- /ch05_code/5-1.py: -------------------------------------------------------------------------------- 1 | bl_info = { 2 | "name": "Simple Add-on Template", 3 | "author": "Chris Conlan", 4 | "location": "View3D > Tools > Simple Addon", 5 | "version": (1, 0, 0), 6 | "blender": (2, 7, 8), 7 | "description": "Starting point for new add-ons.", 8 | "wiki_url": "http://example.com", 9 | "category": "Development" 10 | } 11 | 12 | 13 | # Custom modules are imported here 14 | # See end of chapter example for suggested protocol 15 | 16 | import bpy 17 | 18 | # Panels, buttons, operators, menus, and 19 | # functions are all declared in this area 20 | 21 | # A simple Operator class 22 | 23 | 24 | class SimpleOperator(bpy.types.Operator): 25 | bl_idname = "object.simple_operator" 26 | bl_label = "Print an Encouraging Message" 27 | 28 | def execute(self, context): 29 | print("\n\n####################################################") 30 | print("# Add-on and Simple Operator executed succesfully!") 31 | print("# " + context.scene.encouraging_message) 32 | print("####################################################") 33 | return {'FINISHED'} 34 | 35 | @classmethod 36 | def register(cls): 37 | print("Registered class: %s " % cls.bl_label) 38 | 39 | # Register properties related to the class here 40 | bpy.types.Scene.encouraging_message = bpy.props.StringProperty( 41 | name="", 42 | description="Message to print to user", 43 | default="Have a nice day!") 44 | 45 | @classmethod 46 | def unregister(cls): 47 | print("Unregistered class: %s " % cls.bl_label) 48 | 49 | # Delete parameters related to the class here 50 | del bpy.types.Scene.encouraging_message 51 | 52 | 53 | # A simple button and input field in the Tools panel 54 | class SimplePanel(bpy.types.Panel): 55 | bl_space_type = "VIEW_3D" 56 | bl_region_type = "TOOLS" 57 | bl_category = "Simple Addon" 58 | bl_label = "Call Simple Operator" 59 | bl_context = "objectmode" 60 | 61 | def draw(self, context): 62 | self.layout.operator("object.simple_operator", 63 | text="Print Encouraging Message") 64 | self.layout.prop(context.scene, 'encouraging_message') 65 | 66 | @classmethod 67 | def register(cls): 68 | print("Registered class: %s " % cls.bl_label) 69 | # Register properties related to the class here. 70 | 71 | @classmethod 72 | def unregister(cls): 73 | print("Unregistered class: %s " % cls.bl_label) 74 | # Delete parameters related to the class here 75 | 76 | 77 | def register(): 78 | 79 | # Implicitly register objects inheriting bpy.types in current file and scope 80 | #bpy.utils.register_module(__name__) 81 | 82 | # Or explicitly register objects 83 | bpy.utils.register_class(SimpleOperator) 84 | bpy.utils.register_class(SimplePanel) 85 | 86 | print("%s registration complete\n" % bl_info.get('name')) 87 | 88 | 89 | def unregister(): 90 | 91 | # Always unregister in reverse order to prevent error due to 92 | # interdependencies 93 | 94 | # Explicity unregister objects 95 | # bpy.utils.unregister_class(SimpleOperator) 96 | # bpy.utils.unregister_class(SimplePanel) 97 | 98 | # Or unregister objects inheriting bpy.types in current file and scope 99 | bpy.utils.unregister_module(__name__) 100 | print("%s unregister complete\n" % bl_info.get('name')) 101 | 102 | 103 | # Only called during development with 'Text Editor -> Run Script' 104 | # When distributed as plugin, Blender will directly 105 | # and call register() and unregister() 106 | if __name__ == "__main__": 107 | 108 | try: 109 | unregister() 110 | except Exception as e: 111 | # Catch failure to unregister explicity 112 | print(e) 113 | pass 114 | 115 | register() 116 | -------------------------------------------------------------------------------- /ch05_code/5-2.py: -------------------------------------------------------------------------------- 1 | # Option 1: 2 | # Using implicit registration 3 | 4 | def register(): 5 | bpy.utils.register_module(__name__) 6 | 7 | 8 | def unregister(): 9 | bpy.utils.unregister_module(__name__) 10 | 11 | if __name__ == "__main__": 12 | register() 13 | 14 | # Option 2: 15 | # Using explicit registration 16 | 17 | def register(): 18 | bpy.utils.register_class(SimpleOperator) 19 | bpy.utils.register_class(SimplePanel) 20 | 21 | 22 | def unregister(): 23 | bpy.utils.unregister_class(SimpleOperator) 24 | bpy.utils.unregister_class(SimplePanel) 25 | 26 | if __name__ == "__main__": 27 | register() 28 | 29 | 30 | # Option 3 (Recommended) 31 | # Explicit registration and implicit unregistration 32 | # With safe + verbose single-script run 33 | 34 | def register(): 35 | bpy.utils.register_class(SimpleOperator) 36 | bpy.utils.register_class(SimplePanel) 37 | 38 | 39 | def unregister(): 40 | bpy.utils.unregister_class(SimpleOperator) 41 | bpy.utils.unregister_class(SimplePanel) 42 | 43 | if __name__ == "__main__": 44 | try: 45 | unregister() 46 | except Exception as e: 47 | print(e) 48 | pass 49 | 50 | register() -------------------------------------------------------------------------------- /ch05_code/5-3.py: -------------------------------------------------------------------------------- 1 | # Simple Operator with Extra Properties 2 | class SimpleOperator(bpy.types.Operator): 3 | bl_idname = "object.simple_operator" 4 | bl_label = "Print an Encouraging Message" 5 | 6 | def execute(self, context): 7 | print("\n\n####################################################") 8 | print("# Add-on and Simple Operator executed succesfully!") 9 | print("# Encouraging Message:", context.scene.encouraging_message) 10 | print("# My Int:", context.scene.my_int_prop) 11 | print("# My Float:", context.scene.my_float_prop) 12 | print("# My Bool:", context.scene.my_bool_prop) 13 | print("# My Int Vector:", *context.scene.my_int_vector_prop) 14 | print("# My Float Vector:", *context.scene.my_float_vector_prop) 15 | print("# My Bool Vector:", *context.scene.my_bool_vector_prop) 16 | print("####################################################") 17 | return {'FINISHED'} 18 | 19 | @classmethod 20 | def register(cls): 21 | print("Registered class: %s " % cls.bl_label) 22 | 23 | bpy.types.Scene.encouraging_message = bpy.props.StringProperty( 24 | name="", 25 | description="Message to print to user", 26 | default="Have a nice day!") 27 | 28 | bpy.types.Scene.my_int_prop = bpy.props.IntProperty( 29 | name="My Int", 30 | description="Sample integer property to print to user", 31 | default=123, 32 | min=100, 33 | max=200) 34 | 35 | bpy.types.Scene.my_float_prop = bpy.props.FloatProperty( 36 | name="My Float", 37 | description="Sample float property to print to user", 38 | default=3.1415, 39 | min=0.0, 40 | max=10.0, 41 | precision=4) 42 | 43 | bpy.types.Scene.my_bool_prop = bpy.props.BoolProperty( 44 | name="My Bool", 45 | description="Sample boolean property to print to user", 46 | default=True) 47 | 48 | bpy.types.Scene.my_int_vector_prop = bpy.props.IntVectorProperty( 49 | name="My Int Vector", 50 | description="Sample integer vector property to print to user", 51 | default=(1, 2, 3, 4), 52 | subtype='NONE', 53 | size=4) 54 | 55 | bpy.types.Scene.my_float_vector_prop = bpy.props.FloatVectorProperty( 56 | name="My Float Vector", 57 | description="Sample float vector property to print to user", 58 | default=(1.23, 2.34, 3.45), 59 | subtype='XYZ', 60 | size=3, 61 | precision=2) 62 | 63 | bpy.types.Scene.my_bool_vector_prop = bpy.props.BoolVectorProperty( 64 | name="My Bool Vector", 65 | description="Sample bool vector property to print to user", 66 | default=(True, False, True), 67 | subtype='XYZ', 68 | size=3) 69 | 70 | @classmethod 71 | def unregister(cls): 72 | print("Unregistered class: %s " % cls.bl_label) 73 | del bpy.types.Scene.encouraging_message 74 | 75 | 76 | # Simple button in Tools panel 77 | class SimplePanel(bpy.types.Panel): 78 | bl_space_type = "VIEW_3D" 79 | bl_region_type = "TOOLS" 80 | bl_category = "Simple Addon" 81 | bl_label = "Call Simple Operator" 82 | bl_context = "objectmode" 83 | 84 | def draw(self, context): 85 | self.layout.operator("object.simple_operator", 86 | text="Print Encouraging Message") 87 | self.layout.prop(context.scene, 'encouraging_message') 88 | self.layout.prop(context.scene, 'my_int_prop') 89 | self.layout.prop(context.scene, 'my_float_prop') 90 | self.layout.prop(context.scene, 'my_bool_prop') 91 | self.layout.prop(context.scene, 'my_int_vector_prop') 92 | self.layout.prop(context.scene, 'my_float_vector_prop') 93 | self.layout.prop(context.scene, 'my_bool_vector_prop') 94 | 95 | @classmethod 96 | def register(cls): 97 | print("Registered class: %s " % cls.bl_label) 98 | # Register properties related to the class here. 99 | 100 | @classmethod 101 | def unregister(cls): 102 | print("Unregistered class: %s " % cls.bl_label) -------------------------------------------------------------------------------- /ch05_code/5-4.py: -------------------------------------------------------------------------------- 1 | bpy.types.Scene.my_color_prop = bpy.props.FloatVectorProperty( 2 | name="My Color Property", 3 | description="Returns a vector of length 4", 4 | default=(0.322, 1.0, 0.182, 1.0), 5 | min=0.0, 6 | max=1.0, 7 | subtype='COLOR', 8 | size=4) -------------------------------------------------------------------------------- /ch05_code/5-5.py: -------------------------------------------------------------------------------- 1 | bl_info = { 2 | "name": "XYZ-Select", 3 | "author": "Chris Conlan", 4 | "location": "View3D > Tools > XYZ-Select", 5 | "version": (1, 0, 0), 6 | "blender": (2, 7, 8), 7 | "description": "Precision selection in Edit Mode", 8 | "category": "3D View" 9 | } 10 | 11 | 12 | ### Use these imports to during development ### 13 | 14 | ### Use these imports to during development ### 15 | import ut 16 | import importlib 17 | importlib.reload(ut) 18 | 19 | ### Use these imports to package and ship your add-on ### 20 | # if "bpy" in locals(): 21 | # import importlib 22 | # importlib.reload(ut) 23 | # print('Reloaded ut.py') 24 | # else: 25 | # from . import ut 26 | # print('Imported ut.py') 27 | 28 | 29 | import bpy 30 | import os 31 | import random 32 | 33 | # Simple Operator with Extra Properties 34 | 35 | class xyzSelect(bpy.types.Operator): 36 | bl_idname = "object.xyz_select" 37 | bl_label = "Select pieces of objects in Edit Mode with bounding boxes" 38 | 39 | def execute(self, context): 40 | 41 | scn = context.scene 42 | 43 | output = ut.act.select_by_loc(lbound=scn.xyz_lower_bound, 44 | ubound=scn.xyz_upper_bound, 45 | select_mode=scn.xyz_selection_mode, 46 | coords=scn.xyz_coordinate_system) 47 | 48 | print("Selected " + str(output) + " " + scn.xyz_selection_mode + "s") 49 | 50 | return {'FINISHED'} 51 | 52 | @classmethod 53 | def register(cls): 54 | print("Registered class: %s " % cls.bl_label) 55 | bpy.types.Scene.xyz_lower_bound = bpy.props.FloatVectorProperty( 56 | name="Lower", 57 | description="Lower bound of selection bounding box", 58 | default=(0.0, 0.0, 0.0), 59 | subtype='XYZ', 60 | size=3, 61 | precision=2 62 | ) 63 | bpy.types.Scene.xyz_upper_bound = bpy.props.FloatVectorProperty( 64 | name="Upper", 65 | description="Upper bound of selection bounding box", 66 | default=(1.0, 1.0, 1.0), 67 | subtype='XYZ', 68 | size=3, 69 | precision=2 70 | ) 71 | 72 | # Menus for EnumProperty's 73 | selection_modes = [ 74 | ("VERT", "Vert", "", 1), 75 | ("EDGE", "Edge", "", 2), 76 | ("FACE", "Face", "", 3), 77 | ] 78 | bpy.types.Scene.xyz_selection_mode = \ 79 | bpy.props.EnumProperty(items=selection_modes, name="Mode") 80 | 81 | coordinate_system = [ 82 | ("GLOBAL", "Global", "", 1), 83 | ("LOCAL", "Local", "", 2), 84 | ] 85 | bpy.types.Scene.xyz_coordinate_system = \ 86 | bpy.props.EnumProperty(items=coordinate_system, name="Coords") 87 | 88 | @classmethod 89 | def unregister(cls): 90 | print("Unregistered class: %s " % cls.bl_label) 91 | del bpy.context.scene.xyz_coordinate_system 92 | del bpy.context.scene.xyz_selection_mode 93 | del bpy.context.scene.xyz_upper_bound 94 | del bpy.context.scene.xyz_lower_bound 95 | 96 | 97 | # Simple button in Tools panel 98 | class xyzPanel(bpy.types.Panel): 99 | bl_space_type = "VIEW_3D" 100 | bl_region_type = "TOOLS" 101 | bl_category = "XYZ-Select" 102 | bl_label = "Select by Bounding Box" 103 | 104 | @classmethod 105 | def poll(self, context): 106 | return context.object.mode == 'EDIT' 107 | 108 | def draw(self, context): 109 | scn = context.scene 110 | lay = self.layout 111 | lay.operator('object.xyz_select', text="Select Components") 112 | lay.prop(scn, 'xyz_lower_bound') 113 | lay.prop(scn, 'xyz_upper_bound') 114 | lay.prop(scn, 'xyz_selection_mode') 115 | lay.prop(scn, 'xyz_coordinate_system') 116 | 117 | @classmethod 118 | def register(cls): 119 | print("Registered class: %s " % cls.bl_label) 120 | 121 | @classmethod 122 | def unregister(cls): 123 | print("Unregistered class: %s " % cls.bl_label) 124 | 125 | 126 | def register(): 127 | # bpy.utils.register_module(__name__) 128 | 129 | bpy.utils.register_class(xyzSelect) 130 | bpy.utils.register_class(xyzPanel) 131 | 132 | print("%s registration complete\n" % bl_info.get('name')) 133 | 134 | 135 | def unregister(): 136 | # bpy.utils.unregister_class(xyzPanel) 137 | # bpy.utils.unregister_class(xyzSelect) 138 | 139 | bpy.utils.unregister_module(__name__) 140 | print("%s unregister complete\n" % bl_info.get('name')) 141 | 142 | 143 | if __name__ == "__main__": 144 | try: 145 | unregister() 146 | except Exception as e: 147 | print(e) 148 | pass 149 | 150 | register() -------------------------------------------------------------------------------- /ch06_code/6-1.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import datetime 3 | 4 | # Clear the scene 5 | bpy.ops.object.select_all(action='SELECT') 6 | bpy.ops.object.delete() 7 | 8 | # Create an object for our clock 9 | bpy.ops.object.text_add(location=(0, 0, 0)) 10 | bpy.context.object.name = 'MyTextObj' 11 | 12 | # Create a handler function 13 | def tell_time(dummy): 14 | current_time = datetime.datetime.now().strftime('%H:%M:%S.%f')[:-3] 15 | bpy.data.objects['MyTextObj'].data.body = current_time 16 | 17 | # Add to the list of handler functions "scene_update_pre" 18 | bpy.app.handlers.scene_update_pre.append(tell_time) 19 | -------------------------------------------------------------------------------- /ch06_code/6-2.py: -------------------------------------------------------------------------------- 1 | # Will only work if `tell_time` is in scope 2 | bpy.app.handlers.scene_update_pre.remove(tell_time) 3 | 4 | # Useful in development for a clean slate 5 | bpy.app.handlers.scene_update_pre.clear() 6 | 7 | # Remove handler at the end of the list and return it 8 | bpy.app.handlers.scene_update_pre.pop() -------------------------------------------------------------------------------- /ch06_code/6-3.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.app.handlers import persistent 3 | 4 | 5 | @persistent 6 | def load_diag(dummy): 7 | obs = bpy.context.scene.objects 8 | 9 | print('\n\n### File Diagnostics ###') 10 | print('Objects in Scene:', len(obs)) 11 | for ob in obs: 12 | print(ob.name, 'of type', ob.type) 13 | 14 | bpy.app.handlers.load_post.append(load_diag) 15 | 16 | 17 | # After reloading startup file: 18 | # 19 | # ### File Diagnostics ### 20 | # Objects in Scene: 3 21 | # Cube of type MESH 22 | # Lamp of type LAMP 23 | # Camera of type CAMERA -------------------------------------------------------------------------------- /ch06_code/6-4.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy_extras import view3d_utils 3 | import bgl 4 | import blf 5 | 6 | # Color and font size of text 7 | rgb_label = (1, 0.8, 0.1, 1.0) 8 | font_size = 16 9 | font_id = 0 10 | 11 | # Wrapper for mapping 3D viewport to OpenGL 2D region 12 | 13 | def gl_pts(context, v): 14 | return view3d_utils.location_3d_to_region_2d( 15 | context.region, 16 | context.space_data.region_3d, 17 | v) 18 | 19 | # Get the active object, find its 2D points, draw the name 20 | 21 | def draw_name(context): 22 | 23 | ob = context.object 24 | v = gl_pts(context, ob.location) 25 | 26 | bgl.glColor4f(*rgb_label) 27 | 28 | blf.size(font_id, font_size, 72) 29 | blf.position(font_id, v[0], v[1], 0) 30 | blf.draw(font_id, ob.name) 31 | 32 | 33 | # Add the handler 34 | # arguments: 35 | # function = draw_name, 36 | # tuple of parameters = (bpy.context,), 37 | # constant1 = 'WINDOW', 38 | # constant2 = 'POST_PIXEL' 39 | bpy.types.SpaceView3D.draw_handler_add( 40 | draw_name, (bpy.context,), 'WINDOW', 'POST_PIXEL') -------------------------------------------------------------------------------- /ch06_code/6-5.py: -------------------------------------------------------------------------------- 1 | bl_info = { 2 | "name": "Simple Line and Text Drawing", 3 | "author": "Chris Conlan", 4 | "location": "View3D > Tools > Drawing", 5 | "version": (1, 0, 0), 6 | "blender": (2, 7, 8), 7 | "description": "Minimal add-on for line and text drawing with bgl and blf. " 8 | "Adapted from Antonio Vazquez's (antonioya) Archmesh.", 9 | "wiki_url": "http://example.com", 10 | "category": "Development" 11 | } 12 | 13 | import bpy 14 | import bmesh 15 | import os 16 | 17 | import bpy_extras 18 | import bgl 19 | import blf 20 | 21 | # view3d_utils must be imported explicitly 22 | from bpy_extras import view3d_utils 23 | 24 | def draw_main(self, context): 25 | """Main function, toggled by handler""" 26 | 27 | scene = context.scene 28 | indices = context.scene.gl_measure_indices 29 | 30 | # Set color and fontsize parameters 31 | rgb_line = (0.173, 0.545, 1.0, 1.0) 32 | rgb_label = (1, 0.8, 0.1, 1.0) 33 | fsize = 16 34 | 35 | # Enable OpenGL drawing 36 | bgl.glEnable(bgl.GL_BLEND) 37 | bgl.glLineWidth(1) 38 | 39 | # Store reference to active object 40 | ob = context.object 41 | 42 | # Draw vertex indices 43 | if scene.gl_display_verts: 44 | label_verts(context, ob, rgb_label, fsize) 45 | 46 | # Draw measurement 47 | if scene.gl_display_measure: 48 | if(indices[1] < len(ob.data.vertices)): 49 | draw_measurement(context, ob, indices, rgb_line, rgb_label, fsize) 50 | 51 | # Draw name 52 | if scene.gl_display_names: 53 | draw_name(context, ob, rgb_label, fsize) 54 | 55 | # Disable OpenGL drawings and restore defaults 56 | bgl.glLineWidth(1) 57 | bgl.glDisable(bgl.GL_BLEND) 58 | bgl.glColor4f(0.0, 0.0, 0.0, 1.0) 59 | 60 | 61 | class glrun(bpy.types.Operator): 62 | """Main operator, flicks handler on/off""" 63 | 64 | bl_idname = "glinfo.glrun" 65 | bl_label = "Display object data" 66 | bl_description = "Display aditional information in the 3D Viewport" 67 | 68 | # For storing function handler 69 | _handle = None 70 | 71 | # Enable GL drawing and add handler 72 | @staticmethod 73 | def handle_add(self, context): 74 | if glrun._handle is None: 75 | glrun._handle = bpy.types.SpaceView3D.draw_handler_add( 76 | draw_main, (self, context), 'WINDOW', 'POST_PIXEL') 77 | context.window_manager.run_opengl = True 78 | 79 | # Disable GL drawing and remove handler 80 | @staticmethod 81 | def handle_remove(self, context): 82 | if glrun._handle is not None: 83 | bpy.types.SpaceView3D.draw_handler_remove(glrun._handle, 'WINDOW') 84 | glrun._handle = None 85 | context.window_manager.run_opengl = False 86 | 87 | # Flicks OpenGL handler on and off 88 | # Make sure to flick "off" before reloading script when live editing 89 | def execute(self, context): 90 | if context.area.type == 'VIEW_3D': 91 | 92 | if context.window_manager.run_opengl is False: 93 | self.handle_add(self, context) 94 | context.area.tag_redraw() 95 | else: 96 | self.handle_remove(self, context) 97 | context.area.tag_redraw() 98 | 99 | return {'FINISHED'} 100 | else: 101 | print("3D Viewport not found, cannot run operator.") 102 | return {'CANCELLED'} 103 | 104 | 105 | class glpanel(bpy.types.Panel): 106 | """Standard panel with scene variables""" 107 | 108 | bl_idname = "glinfo.glpanel" 109 | bl_label = "Display Object Data" 110 | bl_space_type = 'VIEW_3D' 111 | bl_region_type = "TOOLS" 112 | bl_category = 'Drawing' 113 | 114 | def draw(self, context): 115 | lay = self.layout 116 | scn = context.scene 117 | 118 | box = lay.box() 119 | 120 | if context.window_manager.run_opengl is False: 121 | icon = 'PLAY' 122 | txt = 'Display' 123 | else: 124 | icon = 'PAUSE' 125 | txt = 'Hide' 126 | 127 | box.operator("glinfo.glrun", text=txt, icon=icon) 128 | 129 | box.prop(scn, "gl_display_names", toggle=True, icon="OUTLINER_OB_FONT") 130 | box.prop(scn, "gl_display_verts", toggle=True, icon='DOT') 131 | box.prop(scn, "gl_display_measure", toggle=True, icon="ALIGN") 132 | box.prop(scn, "gl_measure_indices") 133 | 134 | @classmethod 135 | def register(cls): 136 | 137 | bpy.types.Scene.gl_display_measure = bpy.props.BoolProperty( 138 | name="Measures", 139 | description="Display measurements for specified indices in active mesh.", 140 | default=True, 141 | ) 142 | 143 | bpy.types.Scene.gl_display_names = bpy.props.BoolProperty( 144 | name="Names", 145 | description="Display names for selected meshes.", 146 | default=True, 147 | ) 148 | 149 | bpy.types.Scene.gl_display_verts = bpy.props.BoolProperty( 150 | name="Verts", 151 | description="Display vertex indices for selected meshes.", 152 | default=True, 153 | ) 154 | 155 | bpy.types.Scene.gl_measure_indices = bpy.props.IntVectorProperty( 156 | name="Indices", 157 | description="Display measurement between supplied vertices.", 158 | default=(0, 1), 159 | min=0, 160 | subtype='NONE', 161 | size=2) 162 | 163 | print("registered class %s " % cls.bl_label) 164 | 165 | @classmethod 166 | def unregister(cls): 167 | del bpy.types.Scene.gl_display_verts 168 | del bpy.types.Scene.gl_display_names 169 | del bpy.types.Scene.gl_display_measure 170 | del bpy.types.Scene.gl_measure_indices 171 | 172 | print("unregistered class %s " % cls.bl_label) 173 | 174 | 175 | ##### Button-activated drawing functions ##### 176 | 177 | # Draw the name of the object on its origin 178 | def draw_name(context, ob, rgb_label, fsize): 179 | a = gl_pts(context, ob.location) 180 | bgl.glColor4f(rgb_label[0], rgb_label[1], rgb_label[2], rgb_label[3]) 181 | draw_text(a, ob.name, fsize) 182 | 183 | 184 | # Draw line between two points, draw the distance 185 | def draw_measurement(context, ob, pts, rgb_line, rgb_label, fsize): 186 | # pts = (index of vertex #1, index of vertex #2) 187 | 188 | a = coords(ob, pts[0]) 189 | b = coords(ob, pts[1]) 190 | 191 | d = dist(a, b) 192 | 193 | mp = midpoint(a, b) 194 | 195 | a = gl_pts(context, a) 196 | b = gl_pts(context, b) 197 | mp = gl_pts(context, mp) 198 | 199 | bgl.glColor4f(rgb_line[0], rgb_line[1], rgb_line[2], rgb_line[3]) 200 | draw_line(a, b) 201 | 202 | bgl.glColor4f(rgb_label[0], rgb_label[1], rgb_label[2], rgb_label[3]) 203 | draw_text(mp, '%.3f' % d, fsize) 204 | 205 | 206 | # Label all possible vertices of object 207 | def label_verts(context, ob, rgb, fsize): 208 | try: 209 | # attempt get coordinates, will except if object does not have vertices 210 | v = coords(ob) 211 | bgl.glColor4f(rgb[0], rgb[1], rgb[2], rgb[3]) 212 | for i in range(0, len(v)): 213 | loc = gl_pts(context, v[i]) 214 | draw_text(loc, str(i), fsize) 215 | except AttributeError: 216 | # Except attribute error to not fail on lights, cameras, etc 217 | pass 218 | 219 | # Convert 3D points to OpenGL-compatible 2D points 220 | def gl_pts(context, v): 221 | return bpy_extras.view3d_utils.location_3d_to_region_2d( 222 | context.region, 223 | context.space_data.region_3d, 224 | v) 225 | 226 | ##### Core drawing functions ##### 227 | # Generic function for drawing text on screen 228 | def draw_text(v, display_text, fsize, font_id=0): 229 | if v: 230 | blf.size(font_id, fsize, 72) 231 | blf.position(font_id, v[0], v[1], 0) 232 | blf.draw(font_id, display_text) 233 | return 234 | 235 | # Generic function for drawing line on screen 236 | def draw_line(v1, v2): 237 | if v1 and v2: 238 | bgl.glBegin(bgl.GL_LINES) 239 | bgl.glVertex2f(*v1) 240 | bgl.glVertex2f(*v2) 241 | bgl.glEnd() 242 | return 243 | 244 | 245 | ##### Utilities ##### 246 | 247 | # Returns all coordinates or single coordinate of object 248 | # Can toggle between GLOBAL and LOCAL coordinates 249 | def coords(obj, ind=None, space='GLOBAL'): 250 | if obj.mode == 'EDIT': 251 | v = bmesh.from_edit_mesh(obj.data).verts 252 | elif obj.mode == 'OBJECT': 253 | v = obj.data.vertices 254 | 255 | if space == 'GLOBAL': 256 | if isinstance(ind, int): 257 | return (obj.matrix_world * v[ind].co).to_tuple() 258 | else: 259 | return [(obj.matrix_world * v.co).to_tuple() for v in v] 260 | 261 | elif space == 'LOCAL': 262 | if isinstance(ind, int): 263 | return (v[ind].co).to_tuple() 264 | else: 265 | return [v.co.to_tuple() for v in v] 266 | 267 | # Returns euclidean distance between two 3D points 268 | def dist(x, y): 269 | return ((x[0] - y[0])**2 + (x[1] - y[1])**2 + (x[2] - y[2])**2)**0.5 270 | 271 | # Returns midpoint between two 3D points 272 | def midpoint(x, y): 273 | return ((x[0] + y[0]) / 2, (x[1] + y[1]) / 2, (x[2] + y[2]) / 2) 274 | 275 | 276 | ##### Registration ##### 277 | def register(): 278 | """Register objects inheriting bpy.types in current file and scope""" 279 | 280 | # bpy.utils.register_module(__name__) 281 | 282 | # Explicitly register objects 283 | bpy.utils.register_class(glrun) 284 | bpy.utils.register_class(glpanel) 285 | 286 | wm = bpy.types.WindowManager 287 | wm.run_opengl = bpy.props.BoolProperty(default=False) 288 | 289 | print("%s registration complete\n" % bl_info.get('name')) 290 | 291 | 292 | def unregister(): 293 | 294 | wm = bpy.context.window_manager 295 | p = 'run_opengl' 296 | if p in wm: 297 | del wm[p] 298 | 299 | # remove OpenGL data 300 | glrun.handle_remove(glrun, bpy.context) 301 | 302 | # Always unregister in reverse order to prevent error due to 303 | # interdependencies 304 | 305 | # Explicity unregister objects 306 | # bpy.utils.unregister_class(glpanel) 307 | # bpy.utils.unregister_class(glrun) 308 | 309 | # Unregister objects inheriting bpy.types in current file and scope 310 | bpy.utils.unregister_module(__name__) 311 | print("%s unregister complete\n" % bl_info.get('name')) 312 | 313 | 314 | # Only called during development with 'Text Editor -> Run Script' 315 | # When distributed as plugin, Blender will directly call register() 316 | if __name__ == "__main__": 317 | try: 318 | os.system('clear') 319 | unregister() 320 | except Exception as e: 321 | print(e) 322 | pass 323 | finally: 324 | register() 325 | -------------------------------------------------------------------------------- /ch06_code/6-6.py: -------------------------------------------------------------------------------- 1 | # Draws the distance between the origins of each object supplied 2 | def draw_distance_matrix(context, obs, rgb_line, rgb_label, fsize): 3 | 4 | N = len(obs) 5 | for j in range(0, N): 6 | for i in range(j + 1, N): 7 | a = obs[i].location 8 | b = obs[j].location 9 | d = dist(a, b) 10 | mp = midpoint(a, b) 11 | 12 | a_2d = gl_pts(context, a) 13 | b_2d = gl_pts(context, b) 14 | mp_2d = gl_pts(context, mp) 15 | 16 | bgl.glColor4f(*rgb_line) 17 | draw_line(a_2d, b_2d) 18 | 19 | bgl.glColor4f(*rgb_label) 20 | draw_text(mp_2d, '%.3f' % d, fsize) 21 | 22 | 23 | # Add this to draw_main() to draw between all selected objects: 24 | # obs = context.selected_objects 25 | # draw_distance_matrix(context, obs, rgb_line, rgb_label, fsize) 26 | 27 | # Add this to draw_main() to draw between all objects in scene: 28 | # obs = context.scene.objects 29 | # draw_distance_matrix(context, obs, rgb_line, rgb_label, fsize) -------------------------------------------------------------------------------- /ch07_code/7-1.txt: -------------------------------------------------------------------------------- 1 | ### Single Scripts ### 2 | ### e.g. Node Wrangler ### 3 | node_wrangler.py 4 | 5 | 6 | ### Single-level or Flat Directories ### 7 | ### e.g. Mesh Insert ### 8 | mesh_inset/ 9 | |-- geom.py 10 | |-- __init__.py 11 | |-- model.py 12 | |-- offset.py 13 | `-- triquad.py 14 | 15 | 16 | ### Multi-level Directories ### 17 | ### e.g. Rigify ### 18 | rigify 19 | |-- CREDITS 20 | |-- generate.py 21 | |-- __init__.py 22 | |-- metarig_menu.py 23 | |-- metarigs 24 | | |-- human.py 25 | | |-- __init__.py 26 | | `-- pitchipoy_human.py 27 | |-- README 28 | |-- rig_lists.py 29 | |-- rigs 30 | | |-- basic 31 | | | |-- copy_chain.py 32 | | | |-- copy.py 33 | | | `-- __init__.py 34 | | |-- biped 35 | | | |-- arm 36 | | | | |-- deform.py 37 | | | | |-- fk.py 38 | | | | |-- ik.py 39 | | | | `-- __init__.py 40 | | | |-- __init__.py 41 | | | |-- leg 42 | | | | |-- deform.py 43 | | | | |-- fk.py 44 | | | | |-- ik.py 45 | | | | `-- __init__.py 46 | | | `-- limb_common.py 47 | | |-- finger.py 48 | | |-- __init__.py 49 | | |-- misc 50 | | | |-- delta.py 51 | | | `-- __init__.py 52 | | |-- neck_short.py 53 | | |-- palm.py 54 | | |-- pitchipoy 55 | | | |-- __init__.py 56 | | | |-- limbs 57 | | | | |-- arm.py 58 | | | | |-- __init__.py 59 | | | | |-- leg.py 60 | | | | |-- limb_utils.py 61 | | | | |-- paw.py 62 | | | | |-- super_arm.py 63 | | | | |-- super_front_paw.py 64 | | | | |-- super_leg.py 65 | | | | |-- super_limb.py 66 | | | | |-- super_rear_paw.py 67 | | | | `-- ui.py 68 | | | |-- simple_tentacle.py 69 | | | |-- super_copy.py 70 | | | |-- super_face.py 71 | | | |-- super_finger.py 72 | | | |-- super_palm.py 73 | | | |-- super_torso_turbo.py 74 | | | |-- super_widgets.py 75 | | | `-- tentacle.py 76 | | `-- spine.py 77 | |-- rig_ui_pitchipoy_template.py 78 | |-- rig_ui_template.py 79 | |-- ui.py 80 | `-- utils.py 81 | -------------------------------------------------------------------------------- /ch07_code/7-2.py: -------------------------------------------------------------------------------- 1 | bl_info = { 2 | "name": "Add-on Sandbox", 3 | "author": "Chris Conlan", 4 | "version": (1, 0, 0), 5 | "blender": (2, 78, 0), 6 | "location": "View3D", 7 | "description": "Within-filesystem Add-on Development Sandbox", 8 | "category": "Development", 9 | } 10 | 11 | 12 | def register(): 13 | pass 14 | 15 | 16 | def unregister(): 17 | pass 18 | 19 | 20 | # Not required and will not be called, 21 | # but good for consistency 22 | if __name__ == '__main__': 23 | register() 24 | -------------------------------------------------------------------------------- /ch07_code/7-3.py: -------------------------------------------------------------------------------- 1 | if "bpy" in locals(): 2 | # Runs if add-ons are being reloaded with F8 3 | import importlib 4 | importlib.reload(ut) 5 | print('Reloaded ut.py') 6 | else: 7 | # Runs first time add-on is loaded 8 | from . import ut 9 | print('Imported ut.py') 10 | 11 | # bpy should be imported after this block of code 12 | import bpy -------------------------------------------------------------------------------- /ch07_code/7-4.py: -------------------------------------------------------------------------------- 1 | bpy.ops.import_scene.obj(filepath=myAbsoluteFilepath) -------------------------------------------------------------------------------- /ch07_code/7-5.py: -------------------------------------------------------------------------------- 1 | # Adapted from Antonio Vazquez's Archimesh 2 | import bpy 3 | 4 | # Clear scene 5 | bpy.ops.object.mode_set(mode='OBJECT') 6 | bpy.ops.object.select_all(action='SELECT') 7 | bpy.ops.object.delete() 8 | 9 | # Manipulate Python lists of vertex and face data... 10 | # Sample here creates a triangular pyramid 11 | myvertex = [(0.0, 0.0, 0.0), (1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 1.0)] 12 | myfaces = [(1, 2, 3), (1, 2, 4), (1, 3, 4), (2, 3, 4)] 13 | 14 | ############################################################## 15 | 16 | # Option #1 - bpy.ops.object.add() 17 | bpy.ops.object.add(type='MESH') 18 | mainobject = bpy.context.object 19 | mainmesh = mainobject.data 20 | mainmesh.name = 'WindowMesh' 21 | mainobject.name = 'WindowObject' 22 | 23 | # Write the Python data to the mesh and update it 24 | mainmesh.from_pydata(myvertex, [], myfaces) 25 | mainmesh.update(calc_edges = True) 26 | 27 | ############################################################## 28 | 29 | # WARNING: Known to cause crashses and segmentation faults in 30 | # certain operating systems. Linux builds are safe. 31 | # Option #2 - bpy.data.meshes.new() 32 | mainmesh = bpy.data.meshes.new("WindowMesh") 33 | mainobject = bpy.data.objects.new("WindowObject", mainmesh) 34 | 35 | # Link the object to the scene, activate it, and select it 36 | bpy.context.scene.objects.link(mainobject) 37 | bpy.context.scene.objects.active = mainobject 38 | mainobject.select = True 39 | 40 | # Write the Python data to the mesh and update it 41 | mainmesh.from_pydata(myvertex, [], myfaces) 42 | mainmesh.update(calc_edges = True) 43 | 44 | ############################################################## -------------------------------------------------------------------------------- /ch07_code/7-6.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import ut 3 | import random 4 | 5 | # Clear scene, must be in object mode 6 | bpy.ops.object.select_all(action='SELECT') 7 | bpy.ops.object.delete() 8 | 9 | # size of maze 10 | maze_size = 20 11 | 12 | # height of maze 13 | maze_height = 1.0 14 | 15 | # Create NxN plane 16 | bpy.ops.mesh.primitive_plane_add(radius= maze_size/ 2, location=(0, 0, 0.1)) 17 | 18 | # Subdivide and deselect mesh 19 | bpy.ops.object.mode_set(mode='EDIT') 20 | bpy.ops.mesh.subdivide(number_cuts=maze_size - 1) 21 | bpy.ops.mesh.select_all(action='DESELECT') 22 | 23 | # Set starting point 24 | v = [-maze_size / 2, -maze_size / 2] 25 | 26 | # Stop iterating if point strays buf away from plane 27 | buf = 5 28 | b = [-maze_size / 2 - buf, maze_size / 2 + buf] 29 | 30 | # Probability of point moving forward 31 | fp = 0.6 32 | 33 | 34 | while b[0] <= v[0] <= b[1] and b[0] <= v[1] <= b[1]: 35 | 36 | # Select square in front of v 37 | ut.act.select_by_loc(lbound=(v[0] - 0.5, v[1] - 0.5, 0), 38 | ubound=(v[0] + 1.5, v[1] + 1.5, 1), 39 | select_mode='FACE', coords='GLOBAL', 40 | additive=True) 41 | 42 | # Returns 0 or 1 43 | ind = random.randint(0, 1) 44 | 45 | # Returns -1 or 1 with probability 1 - fp or fp 46 | dir = (int(random.random() > 1 - fp) * 2) - 1 47 | 48 | # Adjusts point 49 | v[ind] += dir 50 | 51 | 52 | bpy.ops.mesh.select_all(action='INVERT') 53 | bpy.ops.mesh.extrude_region_move(TRANSFORM_OT_translate={"value": (0, 0, maze_height), 54 | "constraint_axis": (False, False, True)} 55 | ) 56 | 57 | 58 | bpy.ops.object.mode_set(mode='OBJECT') 59 | -------------------------------------------------------------------------------- /ch07_code/7-7.py: -------------------------------------------------------------------------------- 1 | # Simple button in Tools panel 2 | class SimplePanel(bpy.types.Panel): 3 | bl_space_type = "VIEW_3D" 4 | bl_region_type = "TOOLS" 5 | bl_category = "Simple Addon" 6 | bl_label = "Call Simple Operator" 7 | 8 | def draw(self, context): 9 | # Store reference to context.scene 10 | scn = context.scene 11 | 12 | # Store reference to self.layout 13 | lay = self.layout 14 | 15 | # Create box 16 | box = lay.box() 17 | box.operator("object.simple_operator", text="Print #1") 18 | box.prop(scn, 'encouraging_message') 19 | 20 | # Create another box 21 | box = lay.box() 22 | # Create a row within it 23 | row = box.row() 24 | # We can jam a few things on the same row 25 | row.operator("object.simple_operator", text="Print #2") 26 | row.prop(scn, 'encouraging_message') 27 | 28 | # Create yet another box 29 | box = lay.box() 30 | # Create a row just for a label 31 | row = box.row() 32 | row.label('There is a split row below me!') 33 | # Create a split row within it 34 | row = box.row() 35 | splitrow = row.split(percentage=0.2) 36 | # Store references to each column of the split row 37 | left_col = splitrow.column() 38 | right_col = splitrow.column() 39 | left_col.operator("object.simple_operator", text="Print #3") 40 | right_col.prop(scn, 'encouraging_message') 41 | 42 | # Throw a separator in for white space... 43 | lay.separator() 44 | 45 | # We can create columns within rows... 46 | row = lay.row() 47 | col = row.column() 48 | col.prop(scn, 'my_int_prop') 49 | col.prop(scn, 'my_int_prop') 50 | col.prop(scn, 'my_int_prop') 51 | col = row.column() 52 | col.prop(scn, 'my_float_prop') 53 | col.label("I'm in the middle of a column") 54 | col.prop(scn, 'my_float_prop') 55 | 56 | # Throw a few separators in... 57 | lay.separator() 58 | lay.separator() 59 | lay.separator() 60 | 61 | # Same as above but with boxes... 62 | row = lay.row() 63 | box = row.box() 64 | box.prop(scn, 'my_int_prop') 65 | box.label("I'm in the box, bottom left.") 66 | box = row.box() 67 | box.prop(scn, 'my_bool_prop') 68 | box.operator("object.simple_operator", text="Print #4") -------------------------------------------------------------------------------- /ch07_code/7-8.py: -------------------------------------------------------------------------------- 1 | class SimplePanel(bpy.types.Panel): 2 | bl_space_type = "VIEW_3D" 3 | bl_region_type = "TOOLS" 4 | bl_category = "Simple Addon" 5 | bl_label = "Call Simple Operator" 6 | 7 | def draw(self, context): 8 | # Store reference to context.scene 9 | scn = context.scene 10 | 11 | # Store reference to self.layout 12 | lay = self.layout 13 | 14 | # Create a row within it 15 | row = lay.row() 16 | row.operator("object.simple_operator", text="#1", icon='OBJECT_DATA') 17 | row.operator("object.simple_operator", text="#2", icon='WORLD_DATA') 18 | row.operator("object.simple_operator", text="#3", icon='LAMP_DATA') 19 | 20 | row = lay.row() 21 | row.operator("object.simple_operator", text="#4", icon='SOUND') 22 | row.operator("object.simple_operator", text="#5", icon='MATERIAL') 23 | row.operator("object.simple_operator", text="#6", icon='ERROR') 24 | 25 | row = lay.row() 26 | row.operator("object.simple_operator", text="#7", icon='CANCEL') 27 | row.operator("object.simple_operator", text="#8", icon='PLUS') 28 | row.operator("object.simple_operator", text="#9", icon='LOCKED') 29 | 30 | row = lay.row() 31 | row.operator("object.simple_operator", text="#10", icon='HAND') 32 | row.operator("object.simple_operator", text="#11", icon='QUIT') 33 | row.operator("object.simple_operator", text="#12", icon='GAME') 34 | 35 | row = lay.row() 36 | row.operator("object.simple_operator", text="#13", icon='PARTICLEMODE') 37 | row.operator("object.simple_operator", text="#14", icon='MESH_MONKEY') 38 | row.operator("object.simple_operator", text="#15", icon='FONT_DATA') 39 | 40 | row = lay.row() 41 | row.operator("object.simple_operator", 42 | text="#16", icon='SURFACE_NSPHERE') 43 | row.operator("object.simple_operator", text="#17", icon='COLOR_RED') 44 | row.operator("object.simple_operator", text="#18", 45 | icon='FORCE_LENNARDJONES') 46 | 47 | row = lay.row() 48 | row.operator("object.simple_operator", text="#19", icon='MODIFIER') 49 | row.operator("object.simple_operator", text="#20", icon='MOD_SOFT') 50 | row.operator("object.simple_operator", text="#21", icon='MOD_DISPLACE') 51 | 52 | row = lay.row() 53 | row.operator("object.simple_operator", text="#22", icon='IPO_CONSTANT') 54 | row.operator("object.simple_operator", text="#23", icon='GRID') 55 | row.operator("object.simple_operator", text="#24", icon='FILTER') 56 | -------------------------------------------------------------------------------- /ch08_code/8-1.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import bmesh 3 | from mathutils import Color 4 | 5 | # Clear scene 6 | bpy.ops.object.mode_set(mode='OBJECT') 7 | bpy.ops.object.select_all(action='SELECT') 8 | bpy.ops.object.delete() 9 | 10 | # Create cube 11 | bpy.ops.mesh.primitive_cube_add(radius=1, location=(0, 0, 0)) 12 | 13 | bpy.ops.object.mode_set(mode='EDIT') 14 | 15 | # Create material to hold textures 16 | material_obj = bpy.data.materials.new('number_1_material') 17 | 18 | 19 | ### Begin configure the number one ### 20 | # Path to image 21 | imgpath = '/home/cconlan/Desktop/blender-book/ch08_pics/number_1.png' 22 | image_obj = bpy.data.images.load(imgpath) 23 | 24 | # Create image texture from image 25 | texture_obj = bpy.data.textures.new('number_1_tex', type='IMAGE') 26 | texture_obj.image = image_obj 27 | 28 | # Add texture slot for image texture 29 | texture_slot = material_obj.texture_slots.add() 30 | texture_slot.texture = texture_obj 31 | 32 | 33 | ### Begin configuring the number two ### 34 | # Path to image 35 | imgpath = '/home/cconlan/Desktop/blender-book/ch08_pics/number_2.png' 36 | image_obj = bpy.data.images.load(imgpath) 37 | 38 | # Create image texture from image 39 | texture_obj = bpy.data.textures.new('number_2_tex', type='IMAGE') 40 | texture_obj.image = image_obj 41 | 42 | # Add texture slot for image texture 43 | texture_slot = material_obj.texture_slots.add() 44 | texture_slot.texture = texture_obj 45 | 46 | # Tone down color map, turn on and tone up normal mapping 47 | texture_slot.diffuse_color_factor = 0.2 48 | texture_slot.use_map_normal = True 49 | texture_slot.normal_factor = 2.0 50 | 51 | 52 | ### Finish configuring textures ### 53 | # Add material to current object 54 | bpy.context.object.data.materials.append(material_obj) 55 | 56 | 57 | ### Begin configuring UV coordinates ### 58 | bm = bmesh.from_edit_mesh(bpy.context.edit_object.data) 59 | bm.faces.ensure_lookup_table() 60 | 61 | 62 | # Index of face to texture 63 | face_ind = 0 64 | bpy.ops.mesh.select_all(action='DESELECT') 65 | bm.faces[face_ind].select = True 66 | 67 | # Unwrap to instantiate uv layer 68 | bpy.ops.uv.unwrap() 69 | 70 | # Grab uv layer 71 | uv_layer = bm.loops.layers.uv.active 72 | 73 | # Begin mapping... 74 | loop_data = bm.faces[face_ind].loops 75 | 76 | # bottom right 77 | uv_data = loop_data[0][uv_layer].uv 78 | uv_data.x = 1.0 79 | uv_data.y = 0.0 80 | 81 | # top right 82 | uv_data = loop_data[1][uv_layer].uv 83 | uv_data.x = 1.0 84 | uv_data.y = 1.0 85 | 86 | # top left 87 | uv_data = loop_data[2][uv_layer].uv 88 | uv_data.x = 0.0 89 | uv_data.y = 1.0 90 | 91 | # bottom left 92 | uv_data = loop_data[3][uv_layer].uv 93 | uv_data.x = 0.0 94 | uv_data.y = 0.0 95 | 96 | 97 | # Change background color to white to match our example 98 | bpy.data.worlds['World'].horizon_color = Color((1.0, 1.0, 1.0)) 99 | 100 | # Switch to object mode to add lights 101 | bpy.ops.object.mode_set(mode='OBJECT') 102 | 103 | # Liberally add lights 104 | dist = 5 105 | for side in [-1, 1]: 106 | for coord in [0, 1, 2]: 107 | loc = [0, 0, 0] 108 | loc[coord] = side * dist 109 | bpy.ops.object.lamp_add(type='POINT', location=loc) 110 | 111 | # Switch to rendered mode to view results -------------------------------------------------------------------------------- /ch08_code/8-2.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | mats = bpy.data.materials 4 | for dblock in mats: 5 | if not dblock.users: 6 | mats.remove(dblock) 7 | 8 | 9 | texs = bpy.data.textures 10 | for dblock in mats: 11 | if not dblock.users: 12 | texs.remove(dblock) 13 | -------------------------------------------------------------------------------- /ch08_code/8-3.py: -------------------------------------------------------------------------------- 1 | 2 | # Point a light or camera at a location specified by "target" 3 | def point_at(ob, target): 4 | ob_loc = ob.location 5 | dir_vec = target - ob.location 6 | ob.rotation_euler = dir_vec.to_track_quat('-Z', 'Y').to_euler() 7 | 8 | 9 | # Return the aggregate bounding box of all meshes in a scene 10 | def scene_bounding_box(): 11 | 12 | # Get names of all meshes 13 | mesh_names = [v.name for v in bpy.context.scene.objects if v.type == 'MESH'] 14 | 15 | # Save an initial value 16 | # Save as list for single-entry modification 17 | co = coords(mesh_names[0])[0] 18 | bb_max = [co[0], co[1], co[2]] 19 | bb_min = [co[0], co[1], co[2]] 20 | 21 | # Test and store maxima and mimima 22 | for i in range(0, len(mesh_names)): 23 | co = coords(mesh_names[i]) 24 | for j in range(0, len(co)): 25 | for k in range(0, 3): 26 | if co[j][k] > bb_max[k]: 27 | bb_max[k] = co[j][k] 28 | if co[j][k] < bb_min[k]: 29 | bb_min[k] = co[j][k] 30 | 31 | # Convert to tuples 32 | bb_max = (bb_max[0], bb_max[1], bb_max[2]) 33 | bb_min = (bb_min[0], bb_min[1], bb_min[2]) 34 | 35 | return [bb_min, bb_max] -------------------------------------------------------------------------------- /ch08_code/8-4.py: -------------------------------------------------------------------------------- 1 | ### Assumes output of Listing 8.1 is in scene at runtime ### 2 | 3 | import bpy 4 | import bmesh 5 | import ut 6 | 7 | from math import pi, tan 8 | from mathutils import Vector 9 | 10 | # Get scene's bounding box (meshes only) 11 | bbox = ut.scene_bounding_box() 12 | 13 | # Calculate median of bounding box 14 | bbox_med = ( (bbox[0][0] + bbox[1][0])/2, 15 | (bbox[0][1] + bbox[1][1])/2, 16 | (bbox[0][2] + bbox[1][2])/2 ) 17 | 18 | # Calculate size of bounding box 19 | bbox_size = ( (bbox[1][0] - bbox[0][0]), 20 | (bbox[1][1] - bbox[0][1]), 21 | (bbox[1][2] - bbox[0][2]) ) 22 | 23 | 24 | # Add camera to scene 25 | bpy.ops.object.camera_add(location=(0, 0, 0), rotation=(0, 0, 0)) 26 | camera_obj = bpy.context.object 27 | camera_obj.name = 'Camera_1' 28 | 29 | # Required for us to manipulate FoV as angles 30 | camera_obj.data.lens_unit = 'FOV' 31 | 32 | # Set image resolution in pixels 33 | # Output will be half the pixelage set here 34 | scn = bpy.context.scene 35 | scn.render.resolution_x = 1800 36 | scn.render.resolution_y = 1200 37 | 38 | # Compute FoV angles 39 | aspect_ratio = scn.render.resolution_x / scn.render.resolution_y 40 | 41 | if aspect_ratio > 1: 42 | camera_angle_x = camera_obj.data.angle 43 | camera_angle_y = camera_angle_x / aspect_ratio 44 | else: 45 | camera_angle_y = camera_obj.data.angle 46 | camera_angle_x = camera_angle_y * aspect_ratio 47 | 48 | 49 | # Set the scene's camera to the our new camera 50 | scn.camera = camera_obj 51 | 52 | # Determine the distance to move the camera away from the scene 53 | camera_dist_x = (bbox_size[1]/2) * (tan(camera_angle_x / 2) ** -1) 54 | camera_dist_y = (bbox_size[2]/2) * (tan(camera_angle_y / 2) ** -1) 55 | camera_dist = max(camera_dist_x, camera_dist_y) 56 | 57 | # Multiply the distance by an arbitrary buffer 58 | camera_buffer = 1.10 59 | camera_dist *= camera_buffer 60 | 61 | # Position the camera to point up the x-axis 62 | camera_loc = (bbox[0][1] - camera_dist, bbox_med[1], bbox_med[2]) 63 | 64 | # Set new location and point camera at median of scene 65 | camera_obj.location = camera_loc 66 | ut.point_at(camera_obj, Vector(bbox_med)) 67 | 68 | 69 | # Set render path 70 | render_path = '/home/cconlan/Desktop/blender_render.png' 71 | bpy.data.scenes['Scene'].render.filepath = render_path 72 | 73 | # Render using Blender Render 74 | bpy.ops.render.render( write_still = True ) 75 | 76 | 77 | # Set render path 78 | render_path = '/home/cconlan/Desktop/opengl_render.png' 79 | bpy.data.scenes['Scene'].render.filepath = render_path 80 | 81 | # Render 3D viewport using OpenGL render 82 | bpy.ops.render.opengl( write_still = True , view_context = True ) 83 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing to Apress Source Code 2 | 3 | Copyright for Apress source code belongs to the author(s). However, under fair use you are encouraged to fork and contribute minor corrections and updates for the benefit of the author(s) and other readers. 4 | 5 | ## How to Contribute 6 | 7 | 1. Make sure you have a GitHub account. 8 | 2. Fork the repository for the relevant book. 9 | 3. Create a new branch on which to make your change, e.g. 10 | `git checkout -b my_code_contribution` 11 | 4. Commit your change. Include a commit message describing the correction. Please note that if your commit message is not clear, the correction will not be accepted. 12 | 5. Submit a pull request. 13 | 14 | Thank you for your contribution! -------------------------------------------------------------------------------- /utility_code/ut.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import bmesh 3 | 4 | # Function for selecting objects by name 5 | def select(objName): 6 | bpy.ops.object.select_all(action = 'DESELECT') 7 | bpy.data.objects[objName].select = True 8 | 9 | # Function for activating objects by name 10 | def activate(objName): 11 | bpy.context.scene.objects.active = bpy.data.objects[objName] 12 | 13 | # Function for entering Edit Mode with no vertices selected, 14 | # or entering Object Mode with no additional processes 15 | def mode(mode_name): 16 | bpy.ops.object.mode_set(mode = mode_name) 17 | if mode_name == "EDIT": 18 | bpy.ops.mesh.select_all(action="DESELECT") 19 | 20 | 21 | def selection_mode(type): 22 | bpy.ops.mesh.select_mode(type = type) 23 | 24 | 25 | def coords(objName, space = 'GLOBAL'): 26 | 27 | # Store reference to the bpy.data.objects datablock 28 | obj = bpy.data.objects[objName] 29 | 30 | # Store reference to bpy.data.objects[].meshes datablock 31 | if obj.mode == 'EDIT': 32 | v = bmesh.from_edit_mesh(obj.data).verts 33 | elif obj.mode == 'OBJECT': 34 | v = obj.data.vertices 35 | 36 | if space == 'GLOBAL': 37 | # Return T * L as list of tuples 38 | return [(obj.matrix_world * v.co).to_tuple() for v in v] 39 | elif space == 'LOCAL': 40 | # Return L as list of tuples 41 | return [v.co.to_tuple() for v in v] 42 | 43 | 44 | def scene_bounding_box(): 45 | 46 | # Get names of all meshes 47 | mesh_names = [v.name for v in bpy.context.scene.objects if v.type == 'MESH'] 48 | 49 | # Save an initial value 50 | # Save as list for single-entry modification 51 | co = coords(mesh_names[0])[0] 52 | bb_max = [co[0], co[1], co[2]] 53 | bb_min = [co[0], co[1], co[2]] 54 | 55 | # Test and store maxima and mimima 56 | for i in range(0, len(mesh_names)): 57 | co = coords(mesh_names[i]) 58 | for j in range(0, len(co)): 59 | for k in range(0, 3): 60 | if co[j][k] > bb_max[k]: 61 | bb_max[k] = co[j][k] 62 | if co[j][k] < bb_min[k]: 63 | bb_min[k] = co[j][k] 64 | 65 | # Convert to tuples 66 | bb_max = (bb_max[0], bb_max[1], bb_max[2]) 67 | bb_min = (bb_min[0], bb_min[1], bb_min[2]) 68 | 69 | return [bb_min, bb_max] 70 | 71 | 72 | 73 | 74 | def in_bbox(lbound, ubound, v, buffer = 0.0001): 75 | return lbound[0] - buffer <= v[0] <= ubound[0] + buffer and \ 76 | lbound[1] - buffer <= v[1] <= ubound[1] + buffer and \ 77 | lbound[2] - buffer <= v[2] <= ubound[2] + buffer 78 | 79 | 80 | def point_at(ob, target): 81 | ob_loc = ob.location 82 | dir_vec = target - ob.location 83 | ob.rotation_euler = dir_vec.to_track_quat('-Z', 'Y').to_euler() 84 | 85 | 86 | 87 | class sel: 88 | """Function Class for operating on SELECTED objects""" 89 | 90 | def transform_apply(): 91 | bpy.ops.object.transform_apply(location=True, rotation=True, scale=True) 92 | 93 | # Differential 94 | def translate(v): 95 | bpy.ops.transform.translate(value = v, constraint_axis = (True, True, True)) 96 | 97 | # Differential 98 | def scale(v): 99 | bpy.ops.transform.resize(value = v, constraint_axis = (True, True, True)) 100 | 101 | # Differential 102 | def rotate_x(v): 103 | bpy.ops.transform.rotate(value = v, axis = (1, 0, 0)) 104 | 105 | # Differential 106 | def rotate_y(v): 107 | bpy.ops.transform.rotate(value = v, axis = (0, 1, 0)) 108 | 109 | # Differential 110 | def rotate_z(v): 111 | bpy.ops.transform.rotate(value = v, axis = (0, 0, 1)) 112 | 113 | def transform_apply(): 114 | bpy.ops.object.transform_apply(location=True, rotation=True, scale=True) 115 | 116 | 117 | 118 | 119 | 120 | # Add in body of script, outside any class declarations 121 | def in_bbox(lbound, ubound, v, buffer = 0.0001): 122 | return lbound[0] - buffer <= v[0] <= ubound[0] + buffer and \ 123 | lbound[1] - buffer <= v[1] <= ubound[1] + buffer and \ 124 | lbound[2] - buffer <= v[2] <= ubound[2] + buffer 125 | 126 | ## Transformations with bpy 127 | class act: 128 | 129 | def select_by_loc(lbound = (0, 0, 0), ubound = (0, 0, 0), 130 | select_mode = 'VERT', coords = 'GLOBAL', 131 | additive = False): 132 | 133 | # Set selection mode, VERT, EDGE, or FACE 134 | selection_mode(select_mode) 135 | 136 | # Grab the transformation matrix 137 | world = bpy.context.object.matrix_world 138 | 139 | # Instantiate a bmesh object and ensure lookup table 140 | # Running bm.faces.ensure_lookup_table() works for all parts 141 | bm = bmesh.from_edit_mesh(bpy.context.object.data) 142 | bm.faces.ensure_lookup_table() 143 | 144 | # Initialize list of vertices and list of parts to be selected 145 | verts = [] 146 | to_select = [] 147 | 148 | # For VERT, EDGE, or FACE ... 149 | # 1. Grab list of global or local coordinates 150 | # 2. Test if the piece is entirely within the rectangular 151 | # prism defined by lbound and ubound 152 | # 3. Select each piece that returned True and deselect 153 | # each piece that returned False in Step 2 154 | 155 | if select_mode == 'VERT': 156 | if coords == 'GLOBAL': 157 | [verts.append((world * v.co).to_tuple()) for v in bm.verts] 158 | elif coords == 'LOCAL': 159 | [verts.append(v.co.to_tuple()) for v in bm.verts] 160 | 161 | [to_select.append(in_bbox(lbound, ubound, v)) for v in verts] 162 | for vertObj, select in zip(bm.verts, to_select): 163 | if additive: 164 | vertObj.select |= select 165 | else: 166 | vertObj.select = select 167 | 168 | if select_mode == 'EDGE': 169 | if coords == 'GLOBAL': 170 | [verts.append([(world * v.co).to_tuple() for v in e.verts]) for e in bm.edges] 171 | elif coords == 'LOCAL': 172 | [verts.append([v.co.to_tuple() for v in e.verts]) for e in bm.edges] 173 | 174 | [to_select.append(all(in_bbox(lbound, ubound, v) for v in e)) for e in verts] 175 | for edgeObj, select in zip(bm.edges, to_select): 176 | if additive: 177 | edgeObj.select |= select 178 | else: 179 | edgeObj.select = select 180 | 181 | if select_mode == 'FACE': 182 | if coords == 'GLOBAL': 183 | [verts.append([(world * v.co).to_tuple() for v in f.verts]) for f in bm.faces] 184 | elif coords == 'LOCAL': 185 | [verts.append([v.co.to_tuple() for v in f.verts]) for f in bm.faces] 186 | 187 | [to_select.append(all(in_bbox(lbound, ubound, v) for v in f)) for f in verts] 188 | for faceObj, select in zip(bm.faces, to_select): 189 | if additive: 190 | faceObj.select |= select 191 | else: 192 | faceObj.select = select 193 | 194 | return sum([1 for s in to_select if s]) 195 | 196 | # Declarative 197 | def location(v): 198 | bpy.context.object.location = v 199 | 200 | # Declarative 201 | def scale(v): 202 | bpy.context.object.scale = v 203 | 204 | # Declarative 205 | def rotation(v): 206 | bpy.context.object.rotation_euler = v 207 | 208 | # Rename the active object 209 | def rename(objName): 210 | bpy.context.object.name = objName 211 | 212 | def register_bmesh(): 213 | return bmesh.from_edit_mesh(bpy.context.object.data) 214 | 215 | def select_vert(bm, i): 216 | bm.verts.ensure_lookup_table() 217 | bm.verts[i].select = True 218 | 219 | def select_edge(bm, e): 220 | bm.edges.ensure_lookup_table() 221 | bm.edges[e].select = True 222 | 223 | def select_face(bm, f): 224 | bm.faces.ensure_lookup_table() 225 | bm.faces[f].select = True 226 | 227 | def deselect_all(): 228 | bpy.ops.mesh.select_all(action="DESELECT") 229 | 230 | def extrude(v): 231 | bpy.ops.mesh.extrude_region_move(TRANSFORM_OT_translate = 232 | {"value":v, 233 | "constraint_axis": (True, True, True), 234 | "constraint_orientation":'NORMAL'}) 235 | 236 | def subdivide(cuts = 1): 237 | bpy.ops.mesh.subdivide(number_cuts = cuts) 238 | 239 | def randomize(intensity = 0.1): 240 | bpy.ops.transform.vertex_random(offset = intensity) 241 | 242 | def select_all(): 243 | bpy.ops.mesh.select_all(action="SELECT") 244 | 245 | 246 | 247 | class spec: 248 | """Function Class for operating on SPECIFIED objects""" 249 | 250 | # Declarative 251 | def scale(objName, v): 252 | bpy.data.objects[objName].scale = v 253 | 254 | # Declarative 255 | def location(objName, v): 256 | bpy.data.objects[objName].location = v 257 | 258 | # Declarative 259 | def rotation(objName, v): 260 | bpy.data.objects[objName].rotation_euler = v 261 | 262 | 263 | class create: 264 | """Function Class for CREATING Objects""" 265 | 266 | def cube(objName): 267 | bpy.ops.mesh.primitive_cube_add(radius = 0.5, location = (0, 0, 0)) 268 | act.rename(objName) 269 | 270 | def sphere(objName): 271 | bpy.ops.mesh.primitive_uv_sphere_add(size = 0.5, location = (0, 0, 0)) 272 | act.rename(objName) 273 | 274 | def cone(objName): 275 | bpy.ops.mesh.primitive_cone_add(radius1 = 0.5, location = (0, 0, 0)) 276 | act.rename(objName) 277 | 278 | def plane(objName): 279 | bpy.ops.mesh.primitive_plane_add(radius = 0.5, location = (0, 0, 0)) 280 | act.rename(objName) 281 | 282 | 283 | def delete(objName): 284 | 285 | select(objName) 286 | bpy.ops.object.delete(use_global=False) 287 | 288 | 289 | def delete_all(): 290 | 291 | if( len(bpy.data.objects) != 0 ): 292 | bpy.ops.object.select_all(action = 'SELECT') 293 | bpy.ops.object.delete(use_global=False) 294 | 295 | 296 | if __name__ == "__main__": 297 | 298 | # Create a cube 299 | create.cube('PerfectCube') 300 | 301 | # Differential transformations combine 302 | sel.translate((0, 1, 2)) 303 | 304 | sel.scale((1, 1, 2)) 305 | sel.scale((0.5, 1, 1)) 306 | 307 | sel.rotate_x(3.1415/8) 308 | sel.rotate_x(3.1415/7) 309 | 310 | sel.rotate_z(3.1415/3) 311 | 312 | 313 | # Create a cone 314 | create.cone('PointyCone') 315 | 316 | # Declaractive transformations overwrite 317 | act.location((-2, -2, 0)) 318 | spec.scale('PointyCone', (1.5, 2.5, 2)) 319 | 320 | 321 | # Create a Sphere 322 | create.sphere('SmoothSphere') 323 | 324 | # Declaractive transformations overwrite 325 | spec.location('SmoothSphere', (2, 0, 0)) 326 | act.rotation((0, 0, 3.1415/3)) 327 | act.scale((1, 3, 1)) 328 | --------------------------------------------------------------------------------