1# -*- coding:utf-8 -*- 2 3# ##### BEGIN GPL LICENSE BLOCK ##### 4# 5# This program is free software; you can redistribute it and/or 6# modify it under the terms of the GNU General Public License 7# as published by the Free Software Foundation; either version 2 8# of the License, or (at your option) any later version. 9# 10# This program is distributed in the hope that it will be useful, 11# but WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13# GNU General Public License for more details. 14# 15# You should have received a copy of the GNU General Public License 16# along with this program; if not, write to the Free Software Foundation, 17# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110- 1301, USA. 18# 19# ##### END GPL LICENSE BLOCK ##### 20 21# <pep8 compliant> 22 23# ---------------------------------------------------------- 24# Author: Stephen Leger (s-leger) 25# 26# ---------------------------------------------------------- 27import bpy 28import bmesh 29 30 31class BmeshEdit(): 32 @staticmethod 33 def _start(context, o): 34 """ 35 private, start bmesh editing of active object 36 """ 37 o.select_set(state=True) 38 context.view_layer.objects.active = o 39 bpy.ops.object.mode_set(mode='EDIT') 40 bm = bmesh.from_edit_mesh(o.data) 41 bm.verts.ensure_lookup_table() 42 bm.faces.ensure_lookup_table() 43 return bm 44 45 @staticmethod 46 def bmesh_join(context, o, list_of_bmeshes, normal_update=False): 47 """ 48 takes as input a list of bm references and outputs a single merged bmesh 49 allows an additional 'normal_update=True' to force _normal_ calculations. 50 """ 51 bm = BmeshEdit._start(context, o) 52 53 add_vert = bm.verts.new 54 add_face = bm.faces.new 55 add_edge = bm.edges.new 56 57 for bm_to_add in list_of_bmeshes: 58 offset = len(bm.verts) 59 60 for v in bm_to_add.verts: 61 add_vert(v.co) 62 63 bm.verts.index_update() 64 bm.verts.ensure_lookup_table() 65 66 if bm_to_add.faces: 67 layer = bm_to_add.loops.layers.uv.verify() 68 dest = bm.loops.layers.uv.verify() 69 for face in bm_to_add.faces: 70 f = add_face(tuple(bm.verts[i.index + offset] for i in face.verts)) 71 f.material_index = face.material_index 72 for j, loop in enumerate(face.loops): 73 f.loops[j][dest].uv = loop[layer].uv 74 bm.faces.index_update() 75 76 if bm_to_add.edges: 77 for edge in bm_to_add.edges: 78 edge_seq = tuple(bm.verts[i.index + offset] for i in edge.verts) 79 try: 80 add_edge(edge_seq) 81 except ValueError: 82 # edge exists! 83 pass 84 bm.edges.index_update() 85 86 # cleanup 87 for old_bm in list_of_bmeshes: 88 old_bm.free() 89 90 if normal_update: 91 bm.normal_update() 92 93 BmeshEdit._end(bm, o) 94 95 @staticmethod 96 def _end(bm, o): 97 """ 98 private, end bmesh editing of active object 99 """ 100 bm.normal_update() 101 bmesh.update_edit_mesh(o.data, True) 102 bpy.ops.object.mode_set(mode='OBJECT') 103 bm.free() 104 105 @staticmethod 106 def _matids(bm, matids): 107 for i, matid in enumerate(matids): 108 bm.faces[i].material_index = matid 109 110 @staticmethod 111 def _uvs(bm, uvs): 112 layer = bm.loops.layers.uv.verify() 113 l_i = len(uvs) 114 for i, face in enumerate(bm.faces): 115 if i > l_i: 116 raise RuntimeError("Missing uvs for face {}".format(i)) 117 l_j = len(uvs[i]) 118 for j, loop in enumerate(face.loops): 119 if j > l_j: 120 raise RuntimeError("Missing uv {} for face {}".format(j, i)) 121 loop[layer].uv = uvs[i][j] 122 123 @staticmethod 124 def _verts(bm, verts): 125 for i, v in enumerate(verts): 126 bm.verts[i].co = v 127 128 @staticmethod 129 def buildmesh(context, o, verts, faces, 130 matids=None, uvs=None, weld=False, 131 clean=False, auto_smooth=True, temporary=False): 132 133 if temporary: 134 bm = bmesh.new() 135 else: 136 bm = BmeshEdit._start(context, o) 137 bm.clear() 138 139 for v in verts: 140 bm.verts.new(v) 141 bm.verts.index_update() 142 bm.verts.ensure_lookup_table() 143 144 for f in faces: 145 bm.faces.new([bm.verts[i] for i in f]) 146 bm.faces.index_update() 147 bm.faces.ensure_lookup_table() 148 149 if matids is not None: 150 BmeshEdit._matids(bm, matids) 151 152 if uvs is not None: 153 BmeshEdit._uvs(bm, uvs) 154 155 if temporary: 156 return bm 157 158 if weld: 159 bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.001) 160 BmeshEdit._end(bm, o) 161 bpy.ops.object.mode_set(mode='EDIT') 162 bpy.ops.mesh.select_all(action='SELECT') 163 if auto_smooth: 164 bpy.ops.mesh.faces_shade_smooth() 165 o.data.use_auto_smooth = True 166 else: 167 bpy.ops.mesh.faces_shade_flat() 168 if clean: 169 bpy.ops.mesh.delete_loose() 170 bpy.ops.object.mode_set(mode='OBJECT') 171 172 @staticmethod 173 def addmesh(context, o, verts, faces, matids=None, uvs=None, weld=False, clean=False, auto_smooth=True): 174 bm = BmeshEdit._start(context, o) 175 nv = len(bm.verts) 176 nf = len(bm.faces) 177 178 for v in verts: 179 bm.verts.new(v) 180 181 bm.verts.ensure_lookup_table() 182 183 for f in faces: 184 bm.faces.new([bm.verts[nv + i] for i in f]) 185 186 bm.faces.ensure_lookup_table() 187 188 if matids is not None: 189 for i, matid in enumerate(matids): 190 bm.faces[nf + i].material_index = matid 191 192 if uvs is not None: 193 layer = bm.loops.layers.uv.verify() 194 for i, face in enumerate(bm.faces[nf:]): 195 for j, loop in enumerate(face.loops): 196 loop[layer].uv = uvs[i][j] 197 198 if weld: 199 bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.001) 200 BmeshEdit._end(bm, o) 201 bpy.ops.object.mode_set(mode='EDIT') 202 bpy.ops.mesh.select_all(action='SELECT') 203 if auto_smooth: 204 bpy.ops.mesh.faces_shade_smooth() 205 o.data.use_auto_smooth = True 206 else: 207 bpy.ops.mesh.faces_shade_flat() 208 if clean: 209 bpy.ops.mesh.delete_loose() 210 bpy.ops.object.mode_set(mode='OBJECT') 211 212 @staticmethod 213 def bevel(context, o, 214 offset, 215 offset_type='OFFSET', 216 segments=1, 217 profile=0.5, 218 # vertex_only=False, 219 clamp_overlap=True, 220 material=-1, 221 use_selection=True): 222 """ 223 /* Bevel offset_type slot values */ 224 enum { 225 BEVEL_AMT_OFFSET, 226 BEVEL_AMT_WIDTH, 227 BEVEL_AMT_DEPTH, 228 BEVEL_AMT_PERCENT 229 }; 230 """ 231 bm = bmesh.new() 232 bm.from_mesh(o.data) 233 bm.verts.ensure_lookup_table() 234 if use_selection: 235 geom = [v for v in bm.verts if v.select] 236 geom.extend([ed for ed in bm.edges if ed.select]) 237 else: 238 geom = bm.verts[:] 239 geom.extend(bm.edges[:]) 240 241 bmesh.ops.bevel(bm, 242 geom=geom, 243 offset=offset, 244 offset_type=offset_type, 245 segments=segments, 246 profile=profile, 247 # vertex_only=vertex_only, 248 clamp_overlap=clamp_overlap, 249 material=material) 250 251 bm.to_mesh(o.data) 252 bm.free() 253 254 @staticmethod 255 def bissect(context, o, 256 plane_co, 257 plane_no, 258 dist=0.001, 259 use_snap_center=False, 260 clear_outer=True, 261 clear_inner=False 262 ): 263 264 bm = bmesh.new() 265 bm.from_mesh(o.data) 266 bm.verts.ensure_lookup_table() 267 geom = bm.verts[:] 268 geom.extend(bm.edges[:]) 269 geom.extend(bm.faces[:]) 270 271 bmesh.ops.bisect_plane(bm, 272 geom=geom, 273 dist=dist, 274 plane_co=plane_co, 275 plane_no=plane_no, 276 use_snap_center=False, 277 clear_outer=clear_outer, 278 clear_inner=clear_inner 279 ) 280 281 bm.to_mesh(o.data) 282 bm.free() 283 284 @staticmethod 285 def solidify(context, o, amt, floor_bottom=False, altitude=0): 286 bm = bmesh.new() 287 bm.from_mesh(o.data) 288 bm.verts.ensure_lookup_table() 289 geom = bm.faces[:] 290 bmesh.ops.solidify(bm, geom=geom, thickness=amt) 291 if floor_bottom: 292 for v in bm.verts: 293 if not v.select: 294 v.co.z = altitude 295 bm.to_mesh(o.data) 296 bm.free() 297 298 @staticmethod 299 def verts(context, o, verts): 300 """ 301 update vertex position of active object 302 """ 303 bm = BmeshEdit._start(context, o) 304 BmeshEdit._verts(bm, verts) 305 BmeshEdit._end(bm, o) 306 307 @staticmethod 308 def aspect(context, o, matids, uvs): 309 """ 310 update material id and uvmap of active object 311 """ 312 bm = BmeshEdit._start(context, o) 313 BmeshEdit._matids(bm, matids) 314 BmeshEdit._uvs(bm, uvs) 315 BmeshEdit._end(bm, o) 316