blender-addons/archipack/bmesh_utils.py

316 lines
9.2 KiB
Python

# -*- coding:utf-8 -*-
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110- 1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
# ----------------------------------------------------------
# Author: Stephen Leger (s-leger)
#
# ----------------------------------------------------------
import bpy
import bmesh
class BmeshEdit():
@staticmethod
def _start(context, o):
"""
private, start bmesh editing of active object
"""
o.select_set(state=True)
context.view_layer.objects.active = o
bpy.ops.object.mode_set(mode='EDIT')
bm = bmesh.from_edit_mesh(o.data)
bm.verts.ensure_lookup_table()
bm.faces.ensure_lookup_table()
return bm
@staticmethod
def bmesh_join(context, o, list_of_bmeshes, normal_update=False):
"""
takes as input a list of bm references and outputs a single merged bmesh
allows an additional 'normal_update=True' to force _normal_ calculations.
"""
bm = BmeshEdit._start(context, o)
add_vert = bm.verts.new
add_face = bm.faces.new
add_edge = bm.edges.new
for bm_to_add in list_of_bmeshes:
offset = len(bm.verts)
for v in bm_to_add.verts:
add_vert(v.co)
bm.verts.index_update()
bm.verts.ensure_lookup_table()
if bm_to_add.faces:
layer = bm_to_add.loops.layers.uv.verify()
dest = bm.loops.layers.uv.verify()
for face in bm_to_add.faces:
f = add_face(tuple(bm.verts[i.index + offset] for i in face.verts))
f.material_index = face.material_index
for j, loop in enumerate(face.loops):
f.loops[j][dest].uv = loop[layer].uv
bm.faces.index_update()
if bm_to_add.edges:
for edge in bm_to_add.edges:
edge_seq = tuple(bm.verts[i.index + offset] for i in edge.verts)
try:
add_edge(edge_seq)
except ValueError:
# edge exists!
pass
bm.edges.index_update()
# cleanup
for old_bm in list_of_bmeshes:
old_bm.free()
if normal_update:
bm.normal_update()
BmeshEdit._end(bm, o)
@staticmethod
def _end(bm, o):
"""
private, end bmesh editing of active object
"""
bm.normal_update()
bmesh.update_edit_mesh(o.data, True)
bpy.ops.object.mode_set(mode='OBJECT')
bm.free()
@staticmethod
def _matids(bm, matids):
for i, matid in enumerate(matids):
bm.faces[i].material_index = matid
@staticmethod
def _uvs(bm, uvs):
layer = bm.loops.layers.uv.verify()
l_i = len(uvs)
for i, face in enumerate(bm.faces):
if i > l_i:
raise RuntimeError("Missing uvs for face {}".format(i))
l_j = len(uvs[i])
for j, loop in enumerate(face.loops):
if j > l_j:
raise RuntimeError("Missing uv {} for face {}".format(j, i))
loop[layer].uv = uvs[i][j]
@staticmethod
def _verts(bm, verts):
for i, v in enumerate(verts):
bm.verts[i].co = v
@staticmethod
def buildmesh(context, o, verts, faces,
matids=None, uvs=None, weld=False,
clean=False, auto_smooth=True, temporary=False):
if temporary:
bm = bmesh.new()
else:
bm = BmeshEdit._start(context, o)
bm.clear()
for v in verts:
bm.verts.new(v)
bm.verts.index_update()
bm.verts.ensure_lookup_table()
for f in faces:
bm.faces.new([bm.verts[i] for i in f])
bm.faces.index_update()
bm.faces.ensure_lookup_table()
if matids is not None:
BmeshEdit._matids(bm, matids)
if uvs is not None:
BmeshEdit._uvs(bm, uvs)
if temporary:
return bm
if weld:
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.001)
BmeshEdit._end(bm, o)
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='SELECT')
if auto_smooth:
bpy.ops.mesh.faces_shade_smooth()
o.data.use_auto_smooth = True
else:
bpy.ops.mesh.faces_shade_flat()
if clean:
bpy.ops.mesh.delete_loose()
bpy.ops.object.mode_set(mode='OBJECT')
@staticmethod
def addmesh(context, o, verts, faces, matids=None, uvs=None, weld=False, clean=False, auto_smooth=True):
bm = BmeshEdit._start(context, o)
nv = len(bm.verts)
nf = len(bm.faces)
for v in verts:
bm.verts.new(v)
bm.verts.ensure_lookup_table()
for f in faces:
bm.faces.new([bm.verts[nv + i] for i in f])
bm.faces.ensure_lookup_table()
if matids is not None:
for i, matid in enumerate(matids):
bm.faces[nf + i].material_index = matid
if uvs is not None:
layer = bm.loops.layers.uv.verify()
for i, face in enumerate(bm.faces[nf:]):
for j, loop in enumerate(face.loops):
loop[layer].uv = uvs[i][j]
if weld:
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.001)
BmeshEdit._end(bm, o)
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='SELECT')
if auto_smooth:
bpy.ops.mesh.faces_shade_smooth()
o.data.use_auto_smooth = True
else:
bpy.ops.mesh.faces_shade_flat()
if clean:
bpy.ops.mesh.delete_loose()
bpy.ops.object.mode_set(mode='OBJECT')
@staticmethod
def bevel(context, o,
offset,
offset_type='OFFSET',
segments=1,
profile=0.5,
# vertex_only=False,
clamp_overlap=True,
material=-1,
use_selection=True):
"""
/* Bevel offset_type slot values */
enum {
BEVEL_AMT_OFFSET,
BEVEL_AMT_WIDTH,
BEVEL_AMT_DEPTH,
BEVEL_AMT_PERCENT
};
"""
bm = bmesh.new()
bm.from_mesh(o.data)
bm.verts.ensure_lookup_table()
if use_selection:
geom = [v for v in bm.verts if v.select]
geom.extend([ed for ed in bm.edges if ed.select])
else:
geom = bm.verts[:]
geom.extend(bm.edges[:])
bmesh.ops.bevel(bm,
geom=geom,
offset=offset,
offset_type=offset_type,
segments=segments,
profile=profile,
# vertex_only=vertex_only,
clamp_overlap=clamp_overlap,
material=material)
bm.to_mesh(o.data)
bm.free()
@staticmethod
def bissect(context, o,
plane_co,
plane_no,
dist=0.001,
use_snap_center=False,
clear_outer=True,
clear_inner=False
):
bm = bmesh.new()
bm.from_mesh(o.data)
bm.verts.ensure_lookup_table()
geom = bm.verts[:]
geom.extend(bm.edges[:])
geom.extend(bm.faces[:])
bmesh.ops.bisect_plane(bm,
geom=geom,
dist=dist,
plane_co=plane_co,
plane_no=plane_no,
use_snap_center=False,
clear_outer=clear_outer,
clear_inner=clear_inner
)
bm.to_mesh(o.data)
bm.free()
@staticmethod
def solidify(context, o, amt, floor_bottom=False, altitude=0):
bm = bmesh.new()
bm.from_mesh(o.data)
bm.verts.ensure_lookup_table()
geom = bm.faces[:]
bmesh.ops.solidify(bm, geom=geom, thickness=amt)
if floor_bottom:
for v in bm.verts:
if not v.select:
v.co.z = altitude
bm.to_mesh(o.data)
bm.free()
@staticmethod
def verts(context, o, verts):
"""
update vertex position of active object
"""
bm = BmeshEdit._start(context, o)
BmeshEdit._verts(bm, verts)
BmeshEdit._end(bm, o)
@staticmethod
def aspect(context, o, matids, uvs):
"""
update material id and uvmap of active object
"""
bm = BmeshEdit._start(context, o)
BmeshEdit._matids(bm, matids)
BmeshEdit._uvs(bm, uvs)
BmeshEdit._end(bm, o)