diff options
Diffstat (limited to 'mesh_tissue')
-rw-r--r-- | mesh_tissue/lattice.py | 421 |
1 files changed, 421 insertions, 0 deletions
diff --git a/mesh_tissue/lattice.py b/mesh_tissue/lattice.py new file mode 100644 index 00000000..2a62fe6c --- /dev/null +++ b/mesh_tissue/lattice.py @@ -0,0 +1,421 @@ +# ##### 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 ##### +#---------------------------- LATTICE ALONG SURFACE ---------------------------# +#--------------------------------- version 0.3 --------------------------------# +# # +# Automatically generate and assign a lattice that follows the active surface. # +# # +# (c) Alessandro Zomparelli # +# (2017) # +# # +# http://www.co-de-it.com/ # +# # +################################################################################ + +import bpy, bmesh +from mathutils import Vector, Matrix + +bl_info = { + "name": "Lattice", + "author": "Alessandro Zomparelli (Co-de-iT)", + "version": (0, 3), + "blender": (2, 7, 8), + "location": "", + "description": "Generate a Lattice based on a grid mesh", + "warning": "", + "wiki_url": "", + "tracker_url": "", + "category": "Mesh"} + + +def not_in(element, grid): + output = True + for loop in grid: + if element in loop: + output = False + break + return output + +def grid_from_mesh(mesh, swap_uv): + bm = bmesh.new() + bm.from_mesh(mesh) + verts_grid = [] + edges_grid = [] + faces_grid = [] + + running_grid = True + while running_grid: + verts_loop = [] + edges_loop = [] + faces_loop = [] + + # storing first point + verts_candidates = [] + if len(faces_grid) == 0: verts_candidates = bm.verts # for first loop check all vertices + else: verts_candidates = [v for v in bm.faces[faces_grid[-1][0]].verts if not_in(v.index, verts_grid)] # for other loops start form the vertices of the first face the last loop, skipping already used vertices + + # check for last loop + is_last = False + for vert in verts_candidates: + if len(vert.link_faces) == 1: # check if corner vertex + vert.select = True + verts_loop.append(vert.index) + is_last = True + break + if not is_last: + for vert in verts_candidates: + new_link_faces = [f for f in vert.link_faces if not_in(f.index, faces_grid)] + if len(new_link_faces) < 2: # check if corner vertex + vert.select = True + verts_loop.append(vert.index) + break + + running_loop = len(verts_loop) > 0 + + while running_loop: + bm.verts.ensure_lookup_table() + id = verts_loop[-1] + link_edges = bm.verts[id].link_edges + # storing second point + if len(verts_loop) == 1: # only one vertex stored in the loop + if len(faces_grid) == 0: ### first loop ### + edge = link_edges[swap_uv] # chose direction + for vert in edge.verts: + if vert.index != id: + vert.select = True + verts_loop.append(vert.index) # new vertex + edges_loop.append(edge.index) # chosen edge + faces_loop.append(edge.link_faces[0].index) # only one face + #edge.link_faces[0].select = True + else: ### other loops ### + for edge in bm.faces[faces_grid[-1][0]].edges: # start from the edges of the first face of the last loop + if bm.verts[verts_loop[0]] in edge.verts and bm.verts[verts_grid[-1][0]] not in edge.verts: # chose an edge starting from the first vertex that is not returning back + for vert in edge.verts: + if vert.index != id: + vert.select = True + verts_loop.append(vert.index) + edges_loop.append(edge.index) + for face in edge.link_faces: + if not_in(face.index,faces_grid): + faces_loop.append(face.index) + # continuing the loop + else: + for edge in link_edges: + for vert in edge.verts: + store_data = False + if not_in(vert.index, verts_grid) and vert.index not in verts_loop: + if len(faces_loop) > 0: + bm.faces.ensure_lookup_table() + if vert not in bm.faces[faces_loop[-1]].verts: store_data = True + else: + store_data = True + if store_data: + vert.select = True + verts_loop.append(vert.index) + edges_loop.append(edge.index) + for face in edge.link_faces: + if not_in(face.index, faces_grid): + faces_loop.append(face.index) + break + # ending condition + if verts_loop[-1] == id or verts_loop[-1] == verts_loop[0]: running_loop = False + + verts_grid.append(verts_loop) + edges_grid.append(edges_loop) + faces_grid.append(faces_loop) + if len(faces_loop) == 0: running_grid = False + return verts_grid, edges_grid, faces_grid + +class lattice_along_surface(bpy.types.Operator): + bl_idname = "object.lattice_along_surface" + bl_label = "Lattice along Surface" + bl_description = ("Automatically add a Lattice modifier to the selected " + "object, adapting it to the active one.\nThe active " + "object must be a rectangular grid compatible with the " + "Lattice's topology.") + bl_options = {'REGISTER', 'UNDO'} + + set_parent = bpy.props.BoolProperty( + name="Set Parent", default=True, + description="Automatically set the Lattice as parent") + + flipNormals = bpy.props.BoolProperty( + name="Flip Normals", default=False, + description="Flip normals direction") + + swapUV = bpy.props.BoolProperty( + name="Swap UV", default=False, + description="Flip grid's U and V") + + flipU = bpy.props.BoolProperty( + name="Flip U", default=False, + description="Flip grid's U") + + flipV = bpy.props.BoolProperty( + name="Flip V", default=False, + description="Flip grid's V") + + flipW = bpy.props.BoolProperty( + name="Flip W", default=False, + description="Flip grid's W") + + use_groups = bpy.props.BoolProperty( + name="Vertex Group", default=False, + description="Use active Vertex Group for lattice's thickness") + + high_quality_lattice = bpy.props.BoolProperty( + name="High quality", default=True, + description="Increase the the subdivisions in normal direction for a " + "more correct result") + + hide_lattice = bpy.props.BoolProperty( + name="Hide Lattice", default=True, + description="Automatically hide the Lattice object") + + scale_x = bpy.props.FloatProperty( + name="Scale X", default=1, min=0.001, + max=1, description="Object scale") + + scale_y = bpy.props.FloatProperty( + name="Scale Y", default=1, min=0.001, + max=1, description="Object scale") + + scale_z = bpy.props.FloatProperty( + name="Scale Z", default=1, min=0.001, + max=1, description="Object scale") + + thickness = bpy.props.FloatProperty( + name="Thickness", default=1, soft_min=0, + soft_max=5, description="Lattice thickness") + + displace = bpy.props.FloatProperty( + name="Displace", default=0, soft_min=-1, + soft_max=1, description="Lattice displace") + + def draw(self, context): + layout = self.layout + col = layout.column(align=True) + col.label(text="Thickness:") + col.prop( + self, "thickness", text="Thickness", icon='NONE', expand=False, + slider=True, toggle=False, icon_only=False, event=False, + full_event=False, emboss=True, index=-1) + col.prop( + self, "displace", text="Offset", icon='NONE', expand=False, + slider=True, toggle=False, icon_only=False, event=False, + full_event=False, emboss=True, index=-1) + row = col.row() + row.prop(self, "use_groups") + col.separator() + col.label(text="Scale:") + col.prop( + self, "scale_x", text="U", icon='NONE', expand=False, + slider=True, toggle=False, icon_only=False, event=False, + full_event=False, emboss=True, index=-1) + col.prop( + self, "scale_y", text="V", icon='NONE', expand=False, + slider=True, toggle=False, icon_only=False, event=False, + full_event=False, emboss=True, index=-1) + ''' + col.prop( + self, "scale_z", text="W", icon='NONE', expand=False, + slider=True, toggle=False, icon_only=False, event=False, + full_event=False, emboss=True, index=-1) + ''' + col.separator() + col.label(text="Flip:") + row = col.row() + row.prop(self, "flipU", text="U") + row.prop(self, "flipV", text="V") + row.prop(self, "flipW", text="W") + col.prop(self, "swapUV") + col.prop(self, "flipNormals") + col.separator() + col.label(text="Lattice Options:") + col.prop(self, "high_quality_lattice") + col.prop(self, "hide_lattice") + col.prop(self, "set_parent") + + def execute(self, context): + grid_obj = bpy.context.active_object + if grid_obj.type not in ('MESH', 'CURVE', 'SURFACE'): + self.report({'ERROR'}, "The surface object is not valid. Only Mesh," + "Curve and Surface objects are allowed.") + return {'CANCELLED'} + obj = None + for o in bpy.context.selected_objects: + if o.name != grid_obj.name and o.type in ('MESH', 'CURVE', \ + 'SURFACE', 'FONT'): + obj = o + o.select = False + break + try: + obj_dim = obj.dimensions + obj_me = obj.to_mesh(bpy.context.scene, apply_modifiers=True, + settings = 'PREVIEW') + except: + self.report({'ERROR'}, "The object to deform is not valid. Only " + "Mesh, Curve, Surface and Font objects are allowed.") + return {'CANCELLED'} + + bpy.ops.object.duplicate_move() + grid_obj = bpy.context.active_object + bpy.ops.object.transform_apply(location=True, rotation=True, scale=True) + grid_mesh = grid_obj.to_mesh(bpy.context.scene, apply_modifiers=True, + settings = 'PREVIEW') + # CREATING LATTICE + min = Vector((0,0,0)) + max = Vector((0,0,0)) + first = True + for v in obj_me.vertices: + vert = obj.matrix_world * v.co + if vert[0] < min[0] or first: + min[0] = vert[0] + if vert[1] < min[1] or first: + min[1] = vert[1] + if vert[2] < min[2] or first: + min[2] = vert[2] + if vert[0] > max[0] or first: + max[0] = vert[0] + if vert[1] > max[1] or first: + max[1] = vert[1] + if vert[2] > max[2] or first: + max[2] = vert[2] + first = False + bb = max-min + lattice_loc = (max+min)/2 + bpy.ops.object.add(type='LATTICE', view_align=False, + enter_editmode=False) + lattice = bpy.context.active_object + lattice.location = lattice_loc + lattice.scale = Vector((bb.x/self.scale_x, bb.y/self.scale_y, + bb.z/self.scale_z)) + if bb.x == 0: lattice.scale.x = 1 + if bb.y == 0: lattice.scale.y = 1 + if bb.z == 0: lattice.scale.z = 1 + bpy.context.scene.objects.active = obj + bpy.ops.object.modifier_add(type='LATTICE') + obj.modifiers[-1].object = lattice + + # set as parent + if self.set_parent: + obj.select = True + lattice.select = True + bpy.context.scene.objects.active = lattice + bpy.ops.object.parent_set(type='LATTICE') + + # reading grid structure + verts_grid, edges_grid, faces_grid = grid_from_mesh(grid_mesh, + swap_uv=self.swapUV) + nu = len(verts_grid) + nv = len(verts_grid[0]) + nw = 2 + scale_normal = self.thickness + + try: + lattice.data.points_u = nu + lattice.data.points_v = nv + lattice.data.points_w = nw + for i in range(nu): + for j in range(nv): + for w in range(nw): + if self.use_groups: + try: + displace = grid_obj.vertex_groups.active.weight(verts_grid[i][j])*scale_normal*bb.z + except: + displace = scale_normal*bb.z + else: displace = scale_normal*bb.z + target_point = (grid_mesh.vertices[verts_grid[i][j]].co + grid_mesh.vertices[verts_grid[i][j]].normal*(w + self.displace/2 - 0.5)*displace) - lattice.location + if self.flipW: w = 1-w + if self.flipU: i = nu-i-1 + if self.flipV: j = nv-j-1 + lattice.data.points[i + j*nu + w*nu*nv].co_deform.x = target_point.x / bpy.data.objects[lattice.name].scale.x + lattice.data.points[i + j*nu + w*nu*nv].co_deform.y = target_point.y / bpy.data.objects[lattice.name].scale.y + lattice.data.points[i + j*nu + w*nu*nv].co_deform.z = target_point.z / bpy.data.objects[lattice.name].scale.z + except: + bpy.ops.object.mode_set(mode='OBJECT') + grid_obj.select = True + lattice.select = True + obj.select = False + bpy.ops.object.delete(use_global=False) + bpy.context.scene.objects.active = obj + obj.select = True + bpy.ops.object.modifier_remove(modifier=obj.modifiers[-1].name) + if nu > 64 or nv > 64: + self.report({'ERROR'}, "Maximum resolution allowed for Lattice is 64") + else: + self.report({'ERROR'}, "The grid mesh is not correct") + return {'CANCELLED'} + + #grid_obj.data = old_grid_data + #print(old_grid_matrix) + #grid_obj.matrix_world = old_grid_matrix + + bpy.ops.object.mode_set(mode='OBJECT') + grid_obj.select = True + lattice.select = False + obj.select = False + bpy.ops.object.delete(use_global=False) + bpy.context.scene.objects.active = lattice + lattice.select = True + if self.high_quality_lattice: bpy.context.object.data.points_w = 8 + else: bpy.context.object.data.use_outside = True + if self.hide_lattice: + bpy.ops.object.hide_view_set(unselected=False) + bpy.context.scene.objects.active = obj + obj.select = True + lattice.select = False + if self.flipNormals: + try: + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.flip_normals() + bpy.ops.object.mode_set(mode='OBJECT') + except: + pass + return {'FINISHED'} + + +class lattice_along_surface_panel(bpy.types.Panel): + bl_label = "Modifiers Tools" + bl_category = "Tools" + bl_space_type = "VIEW_3D" + bl_region_type = "TOOLS" + bl_context = (("objectmode")) + + def draw(self, context): + layout = self.layout + col = layout.column(align=True) + try: + col.operator("object.lattice_along_surface", icon="MOD_LATTICE") + except: + pass + + +def register(): + bpy.utils.register_class(lattice_along_surface) + bpy.utils.register_class(lattice_along_surface_panel) + + +def unregister(): + bpy.utils.unregister_class(lattice_along_surface) + bpy.utils.unregister_class(lattice_along_surface_panel) + + +if __name__ == "__main__": + register() |