diff options
author | Campbell Barton <ideasman42@gmail.com> | 2013-03-24 18:30:17 +0400 |
---|---|---|
committer | Campbell Barton <ideasman42@gmail.com> | 2013-03-24 18:30:17 +0400 |
commit | 7e6e45d7a7c80798a21684c278334188cce0e501 (patch) | |
tree | e603b101870c061f79aabb8b2c6ea0ad61187a00 /object_print3d_utils/mesh_helpers.py | |
parent | 2b3a3f56e43bc3d6c7d1f7cda8a063319f0c6c36 (diff) |
move print toolbox into trunk
[[Split portion of a mixed commit.]]
Diffstat (limited to 'object_print3d_utils/mesh_helpers.py')
-rw-r--r-- | object_print3d_utils/mesh_helpers.py | 323 |
1 files changed, 323 insertions, 0 deletions
diff --git a/object_print3d_utils/mesh_helpers.py b/object_print3d_utils/mesh_helpers.py new file mode 100644 index 00000000..82db96bf --- /dev/null +++ b/object_print3d_utils/mesh_helpers.py @@ -0,0 +1,323 @@ +# ##### 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-80 compliant> + +# Generic helper functions, to be used by any modules. + +import bmesh +import array + + +def bmesh_copy_from_object(obj, transform=True, triangulate=True, apply_modifiers=False): + """ + Returns a transformed, triangulated copy of the mesh + """ + + assert(obj.type == 'MESH') + + if apply_modifiers and obj.modifiers: + import bpy + me = obj.to_mesh(bpy.context.scene, True, 'PREVIEW', calc_tessface=False) + bm = bmesh.new() + bm.from_mesh(me) + bpy.data.meshes.remove(me) + del bpy + else: + me = obj.data + if obj.mode == 'EDIT': + bm_orig = bmesh.from_edit_mesh(me) + bm = bm_orig.copy() + else: + bm = bmesh.new() + bm.from_mesh(me) + + # TODO. remove all customdata layers. + # would save ram + + if transform: + bm.transform(obj.matrix_world) + + if triangulate: + bmesh.ops.triangulate(bm, faces=bm.faces, use_beauty=True) + + return bm + + +def bmesh_from_object(obj): + """ + Object/Edit Mode get mesh, use bmesh_to_object() to write back. + """ + me = obj.data + is_editmode = (obj.mode == 'EDIT') + if is_editmode: + bm = bmesh.from_edit_mesh(me) + else: + bm = bmesh.new() + bm.from_mesh(me) + return bm + + +def bmesh_to_object(obj, bm): + """ + Object/Edit Mode update the object. + """ + me = obj.data + is_editmode = (obj.mode == 'EDIT') + if is_editmode: + bmesh.update_edit_mesh(me, True) + else: + bm.to_mesh(me) + # grr... cause an update + if me.vertices: + me.vertices[0].co[0] = me.vertices[0].co[0] + + +def bmesh_calc_volume(bm): + """ + Calculate the volume of a triangulated bmesh. + """ + def tri_signed_volume(p1, p2, p3): + return p1.dot(p2.cross(p3)) / 6.0 + return abs(sum((tri_signed_volume(*(v.co for v in f.verts)) + for f in bm.faces))) + + +def bmesh_calc_area(bm): + """ + Calculate the surface area. + """ + return sum(f.calc_area() for f in bm.faces) + + +def bmesh_check_self_intersect_object(obj): + """ + Check if any faces self intersect + + returns an array of edge index values. + """ + import bpy + + # Heres what we do! + # + # * Take original Mesh. + # * Copy it and triangulate it (keeping list of original edge index values) + # * Move the BMesh into a temp Mesh. + # * Make a temp Object in the scene and assign the temp Mesh. + # * For every original edge - ray-cast on the object to find which intersect. + # * Report all edge intersections. + + # Triangulate + bm = bmesh_copy_from_object(obj, transform=False, triangulate=False) + face_map_index_org = {f: i for i, f in enumerate(bm.faces)} + ret = bmesh.ops.triangulate(bm, faces=bm.faces, use_beauty=False) + face_map = ret["face_map"] + # map new index to original index + face_map_index = {i: face_map_index_org[face_map.get(f, f)] for i, f in enumerate(bm.faces)} + del face_map_index_org + del ret + + # Create a real mesh (lame!) + scene = bpy.context.scene + me_tmp = bpy.data.meshes.new(name="~temp~") + bm.to_mesh(me_tmp) + bm.free() + obj_tmp = bpy.data.objects.new(name=me_tmp.name, object_data=me_tmp) + scene.objects.link(obj_tmp) + scene.update() + ray_cast = obj_tmp.ray_cast + + faces_error = set() + + EPS_NORMAL = 0.0001 + EPS_CENTER = 0.00001 # should always be bigger + + for ed in me_tmp.edges: + v1i, v2i = ed.vertices + v1 = me_tmp.vertices[v1i] + v2 = me_tmp.vertices[v2i] + + # setup the edge with an offset + co_1 = v1.co.copy() + co_2 = v2.co.copy() + co_mid = (co_1 + co_2) * 0.5 + no_mid = (v1.normal + v2.normal).normalized() * EPS_NORMAL + co_1 = co_1.lerp(co_mid, EPS_CENTER) + no_mid + co_2 = co_2.lerp(co_mid, EPS_CENTER) + no_mid + + co, no, index = ray_cast(co_1, co_2) + if index != -1: + faces_error.add(face_map_index[index]) + + scene.objects.unlink(obj_tmp) + bpy.data.objects.remove(obj_tmp) + bpy.data.meshes.remove(me_tmp) + + return array.array('i', faces_error) + + +def bmesh_face_points_random(f, num_points=1, margin=0.05): + import random + from random import uniform + uniform_args = 0.0 + margin, 1.0 - margin + + # for pradictable results + random.seed(f.index) + + vecs = [v.co for v in f.verts] + + for i in range(num_points): + u1 = uniform(*uniform_args) + u2 = uniform(*uniform_args) + u_tot = u1 + u2 + + if u_tot > 1.0: + u1 = 1.0 - u1 + u2 = 1.0 - u2 + + side1 = vecs[1] - vecs[0] + side2 = vecs[2] - vecs[0] + + yield vecs[0] + u1 * side1 + u2 * side2 + + +def bmesh_check_thick_object(obj, thickness): + + import bpy + + # Triangulate + bm = bmesh_copy_from_object(obj, transform=True, triangulate=False) + # map original faces to their index. + face_index_map_org = {f: i for i, f in enumerate(bm.faces)} + ret = bmesh.ops.triangulate(bm, faces=bm.faces, use_beauty=False) + face_map = ret["face_map"] + del ret + # old edge -> new mapping + + # Convert new/old map to index dict. + + # Create a real mesh (lame!) + scene = bpy.context.scene + me_tmp = bpy.data.meshes.new(name="~temp~") + bm.to_mesh(me_tmp) + # bm.free() # delay free + obj_tmp = bpy.data.objects.new(name=me_tmp.name, object_data=me_tmp) + scene.objects.link(obj_tmp) + scene.update() + ray_cast = obj_tmp.ray_cast + + EPS_BIAS = 0.0001 + + faces_error = set() + + bm_faces_new = bm.faces[:] + + for f in bm_faces_new: + no = f.normal + no_sta = no * EPS_BIAS + no_end = no * thickness + for p in bmesh_face_points_random(f, num_points=6): + # Cast the ray backwards + p_a = p - no_sta + p_b = p - no_end + + co, no, index = ray_cast(p_a, p_b) + + if index != -1: + # Add the face we hit + for f_iter in (f, bm_faces_new[index]): + # if the face wasn't triangulated, just use existing + f_org = face_map.get(f_iter, f_iter) + f_org_index = face_index_map_org[f_org] + faces_error.add(f_org_index) + + # finished with bm + bm.free() + + scene.objects.unlink(obj_tmp) + bpy.data.objects.remove(obj_tmp) + bpy.data.meshes.remove(me_tmp) + + return array.array('i', faces_error) + + + +def object_merge(context, objects): + """ + Caller must remove. + """ + + import bpy + + def cd_remove_all_but_active(seq): + tot = len(seq) + if tot > 1: + act = seq.active_index + for i in range(tot - 1, -1, -1): + if i != act: + seq.remove(seq[i]) + + scene = context.scene + + # deselect all + for obj in scene.objects: + obj.select = False + + # add empty object + mesh_base = bpy.data.meshes.new(name="~tmp~") + obj_base = bpy.data.objects.new(name="~tmp~", object_data=mesh_base) + base_base = scene.objects.link(obj_base) + scene.objects.active = obj_base + obj_base.select = True + + # loop over all meshes + for obj in objects: + if obj.type != 'MESH': + continue + + # convert each to a mesh + mesh_new = obj.to_mesh(scene=scene, + apply_modifiers=True, + settings='PREVIEW', + calc_tessface=False) + + # remove non-active uvs/vcols + cd_remove_all_but_active(mesh_new.vertex_colors) + cd_remove_all_but_active(mesh_new.uv_textures) + + # join into base mesh + obj_new = bpy.data.objects.new(name="~tmp-new~", object_data=mesh_new) + base_new = scene.objects.link(obj_new) + obj_new.matrix_world = obj.matrix_world + + fake_context = context.copy() + fake_context["active_object"] = obj_base + fake_context["selected_editable_bases"] = [base_base, base_new] + + bpy.ops.object.join(fake_context) + del base_new, obj_new + + # remove object and its mesh, join does this + #~ scene.objects.unlink(obj_new) + #~ bpy.data.objects.remove(obj_new) + + bpy.data.meshes.remove(mesh_new) + + # return new object + return base_base + |