diff options
author | meta-androcto <meta.androcto1@gmail.com> | 2017-03-20 02:33:38 +0300 |
---|---|---|
committer | meta-androcto <meta.androcto1@gmail.com> | 2017-03-20 02:33:38 +0300 |
commit | 8e3bfa5506ea110fe6793401b53da17c61061167 (patch) | |
tree | 8e53f830a72b434928b07b99befe8762431a4ed3 /mesh_extra_tools/mesh_select_tools | |
parent | 9007bcd10713e55168235e9e8420b17172674638 (diff) |
initial commit mesh edit tools: T50680
Diffstat (limited to 'mesh_extra_tools/mesh_select_tools')
9 files changed, 1875 insertions, 0 deletions
diff --git a/mesh_extra_tools/mesh_select_tools/__init__.py b/mesh_extra_tools/mesh_select_tools/__init__.py new file mode 100644 index 00000000..2d066c82 --- /dev/null +++ b/mesh_extra_tools/mesh_select_tools/__init__.py @@ -0,0 +1,68 @@ +# ##### 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 ##### +# menu & updates by meta-androcto # +# contributed to by Macouno, dustractor, liero, CoDEmanX, meta-androcto # + +bl_info = { + "name": "Select Tools", + "author": "Multiple Authors", + "version": (0, 3), + "blender": (2, 64, 0), + "location": "Editmode Select Menu/Toolshelf Tools Tab", + "description": "Adds More vert/face/edge select modes.", + "warning": "", + "wiki_url": "", + "tracker_url": "", + "category": "Mesh" + } + +if "bpy" in locals(): + import imp + imp.reload(mesh_select_by_direction) + imp.reload(mesh_select_by_edge_length) + imp.reload(mesh_select_by_pi) + imp.reload(mesh_select_by_type) + imp.reload(mesh_select_connected_faces) + imp.reload(mesh_index_select) + imp.reload(mesh_selection_topokit) + imp.reload(mesh_info_select) +else: + from . import mesh_select_by_direction + from . import mesh_select_by_edge_length + from . import mesh_select_by_pi + from . import mesh_select_by_type + from . import mesh_select_connected_faces + from . import mesh_index_select + from . import mesh_selection_topokit + from . import mesh_info_select + +import bpy + + +# Register + +def register(): + bpy.utils.register_module(__name__) + + +def unregister(): + bpy.utils.unregister_module(__name__) + + +if __name__ == "__main__": + register() diff --git a/mesh_extra_tools/mesh_select_tools/mesh_index_select.py b/mesh_extra_tools/mesh_select_tools/mesh_index_select.py new file mode 100644 index 00000000..cd558e85 --- /dev/null +++ b/mesh_extra_tools/mesh_select_tools/mesh_index_select.py @@ -0,0 +1,169 @@ +# gpl author: liero + +bl_info = { + "name": "Select by index", + "author": "liero", + "version": (0, 2), + "blender": (2, 55, 0), + "location": "View3D > Tool Shelf", + "description": "Select mesh data by index / area / length / cursor", + "category": "Mesh", + } + +import bpy +from bpy.types import Operator +from bpy.props import ( + BoolProperty, + FloatProperty, + EnumProperty, + ) + + +class SelVertEdgeFace(Operator): + bl_idname = "mesh.select_vert_edge_face_index" + bl_label = "Select mesh index" + bl_description = "Select Vertices, Edges, Faces by index" + bl_options = {"REGISTER", "UNDO"} + + select_type = EnumProperty( + items=[ + ('VERT', "Vertices", "Select Vertices by index"), + ('EDGE', "Edges", "Select Edges by index"), + ('FACE', "Faces", "Select Faces by index"), + ], + name="Selection Mode", + description="", + default='VERT', + ) + indice = FloatProperty( + name="Selected", + default=0, + min=0, max=100, + description="Percentage of selection", + precision=2, + subtype="PERCENTAGE" + ) + delta = BoolProperty( + name="Use Parameter", + default=False, + description="Select by Index / Parameter" + ) + flip = BoolProperty( + name="Reverse Order", + default=False, + description="Reverse selecting order" + ) + start_new = BoolProperty( + name="Fresh Start", + default=False, + description="Start from no previous selection\n" + "If unchecked the previous selection is kept" + ) + + delta_text = {'VERT': "Use Cursor", + 'EDGE': "Use Edges' Length", + 'FACE': "Use Faces' Area"} + + @classmethod + def poll(cls, context): + return (context.object is not None and context.object.type == 'MESH') + + def draw(self, context): + layout = self.layout + + layout.label("Selection Type:") + layout.prop(self, "select_type", text="") + layout.separator() + + layout.label("Selected:") + layout.prop(self, "indice", text="", slider=True) + + d_text = self.delta_text[self.select_type] + layout.prop(self, "delta", text=d_text) + + layout.prop(self, "flip") + layout.prop(self, "start_new") + + def execute(self, context): + obj = bpy.context.object + + if self.start_new: + bpy.ops.mesh.select_all(action='DESELECT') + + # Selection mode - Vertex, Edge, Face + if self.select_type == 'VERT': + bpy.context.tool_settings.mesh_select_mode = [True, False, False] + ver = obj.data.vertices + loc = context.scene.cursor_location + sel = [] + for v in ver: + d = v.co - loc + sel.append((d.length, v.index)) + sel.sort(reverse=self.flip) + bpy.ops.object.mode_set() + valor = round(len(sel) / 100 * self.indice) + if self.delta: + for i in range(len(sel[:valor])): + ver[sel[i][1]].select = True + else: + for i in range(len(sel[:valor])): + if self.flip: + ver[len(sel) - i - 1].select = True + else: + ver[i].select = True + + elif self.select_type == 'EDGE': + bpy.context.tool_settings.mesh_select_mode = [False, True, False] + ver = obj.data.vertices + edg = obj.data.edges + sel = [] + for e in edg: + d = ver[e.vertices[0]].co - ver[e.vertices[1]].co + sel.append((d.length, e.index)) + sel.sort(reverse=self.flip) + bpy.ops.object.mode_set() + valor = round(len(sel) / 100 * self.indice) + if self.delta: + for i in range(len(sel[:valor])): + edg[sel[i][1]].select = True + else: + for i in range(len(sel[:valor])): + if self.flip: + edg[len(sel) - i - 1].select = True + else: + edg[i].select = True + + elif self.select_type == 'FACE': + bpy.context.tool_settings.mesh_select_mode = [False, False, True] + fac = obj.data.polygons + sel = [] + for f in fac: + sel.append((f.area, f.index)) + sel.sort(reverse=self.flip) + bpy.ops.object.mode_set() + valor = round(len(sel) / 100 * self.indice) + if self.delta: + for i in range(len(sel[:valor])): + fac[sel[i][1]].select = True + else: + for i in range(len(sel[:valor])): + if self.flip: + fac[len(sel) - i - 1].select = True + else: + fac[i].select = True + + bpy.ops.object.mode_set(mode='EDIT') + + return {'FINISHED'} + + +def register(): + bpy.utils.register_class(SelVertEdgeFace) + + +def unregister(): + bpy.utils.register_class(SelVertEdgeFace) + + +if __name__ == '__main__': + register() diff --git a/mesh_extra_tools/mesh_select_tools/mesh_info_select.py b/mesh_extra_tools/mesh_select_tools/mesh_info_select.py new file mode 100644 index 00000000..448bdbdc --- /dev/null +++ b/mesh_extra_tools/mesh_select_tools/mesh_info_select.py @@ -0,0 +1,108 @@ +# ##### 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 ##### + +# By CoDEmanX + +import bpy +from bpy.types import ( + Panel, + Operator, + ) + + +class DATA_PT_info_panel(Panel): + """Creates a face info / select panel in the Object properties window""" + bl_label = "Face Info / Select" + bl_idname = "DATA_PT_face_info" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "data" + bl_options = {'DEFAULT_CLOSED'} + + @classmethod + def poll(self, context): + return (context.active_object is not None and + context.active_object.type == 'MESH') + + def draw(self, context): + layout = self.layout + + ob = context.active_object + + info_str = "" + tris = quads = ngons = 0 + + for p in ob.data.polygons: + count = p.loop_total + if count == 3: + tris += 1 + elif count == 4: + quads += 1 + else: + ngons += 1 + + info_str = " Ngons: %i Quads: %i Tris: %i" % (ngons, quads, tris) + + col = layout.column() + split = col.split(0.9) + + split.label(info_str, icon='MESH_DATA') + split.operator("mesh.refresh_info_panel", text="", icon="FILE_REFRESH") + + col = layout.column() + col.label("Select faces by type:") + + row = layout.row() + row.operator("data.facetype_select", text="Ngons").face_type = "5" + row.operator("data.facetype_select", text="Quads").face_type = "4" + row.operator("data.facetype_select", text="Tris").face_type = "3" + + +class MESH_OT_refresh_info_panel(Operator): + bl_idname = "mesh.refresh_info_panel" + bl_label = "Refresh" + bl_description = ("Refresh the info panel by switching to Object mode and back\n" + "Limitation: the information doesn't account modifiers\n" + "Be careful with usage if you need the Redo History in Edit Mode") + bl_options = {"REGISTER", "INTERNAL"} + + @classmethod + def poll(self, context): + return (context.active_object is not None and + context.active_object.type == 'MESH') + + def invoke(self, context, event): + return self.execute(context) + + def execute(self, context): + try: + mode = bpy.context.active_object.mode + + # switch to Object mode and restore selection + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.mode_set(mode=mode) + + return {'FINISHED'} + except: + import traceback + traceback.print_exc() + + self.report({'WARNING'}, + "The refresh could not be performed (Check the console for more info)") + + return {'CANCELLED'} diff --git a/mesh_extra_tools/mesh_select_tools/mesh_select_by_direction.py b/mesh_extra_tools/mesh_select_tools/mesh_select_by_direction.py new file mode 100644 index 00000000..7a82b949 --- /dev/null +++ b/mesh_extra_tools/mesh_select_tools/mesh_select_by_direction.py @@ -0,0 +1,220 @@ +# Copyright (C) 2011, Dolf Veenvliet +# Extrude a selection from a mesh multiple times + +# ***** 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 LICENCE BLOCK ***** +''' +bl_info = { + "name": "Select by direction", + "author": "Dolf Veenvliet", + "version": (1,), + "blender": (2, 56, 0), + "location": "View3D > Select", + "description": "Select all items whose normals face a certain direction", + "warning": "", + "wiki_url": "", + "tracker_url": "", + "category": "Mesh"} + +""" +Additional links: + Author Site: http://www.macouno.com + e-mail: dolf {at} macouno {dot} com +""" +''' + +import bpy +from bpy.types import Operator +from mathutils import Vector +from math import radians +from bpy.props import ( + FloatVectorProperty, + FloatProperty, + BoolProperty, + EnumProperty, + ) + + +class Select_by_direction(): + + # Initialise the class + def __init__(self, context, direction, divergence, extend, space): + + self.ob = context.active_object + bpy.ops.object.mode_set(mode='OBJECT') + + self.space = space + + # if we do stuff in global space we need to object matrix + if self.space == 'GLO': + # TODO: not sure if this is the correct way to solve the crash - lijenstina + mat = self.ob.matrix_world + mat_rot = mat.to_3x3().inverted() + direction = mat_rot * Vector(direction) + else: + direction = Vector(direction) + + direction = direction.normalized() + + vertSelect = bpy.context.tool_settings.mesh_select_mode[0] + edgeSelect = bpy.context.tool_settings.mesh_select_mode[1] + faceSelect = bpy.context.tool_settings.mesh_select_mode[2] + + if Vector(direction).length: + # Vert select + if vertSelect: + hasSelected = self.hasSelected(self.ob.data.vertices) + + for v in self.ob.data.vertices: + normal = v.normal + s = self.selectCheck(v.select, hasSelected, extend) + d = self.deselectCheck(v.select, hasSelected, extend) + + if s or d: + angle = direction.angle(normal) + + # Check if the verts match any of the directions + if s and angle <= divergence: + v.select = True + + if d and angle > divergence: + v.select = False + # Edge select + if edgeSelect: + hasSelected = self.hasSelected(self.ob.data.edges) + + for e in self.ob.data.edges: + s = self.selectCheck(e.select, hasSelected, extend) + d = self.deselectCheck(e.select, hasSelected, extend) + + # Check if the edges match any of the directions + if s or d: + normal = self.ob.data.vertices[e.vertices[0]].normal + normal += self.ob.data.vertices[e.vertices[1]].normal + + angle = direction.angle(normal) + + if s and angle <= divergence: + e.select = True + + if d and angle > divergence: + e.select = False + + # Face select + if faceSelect: + hasSelected = self.hasSelected(self.ob.data.polygons) + + # Loop through all the given faces + for f in self.ob.data.polygons: + s = self.selectCheck(f.select, hasSelected, extend) + d = self.deselectCheck(f.select, hasSelected, extend) + + if s or d: + angle = direction.angle(f.normal) + + # Check if the faces match any of the directions + if s and angle <= divergence: + f.select = True + + if d and angle > divergence: + f.select = False + + bpy.ops.object.mode_set(mode='EDIT') + + # See if the current item should be selected or not + def selectCheck(self, isSelected, hasSelected, extend): + # If the current item is not selected we may want to select + if not isSelected: + # If we are extending or nothing is selected we want to select + if extend or not hasSelected: + return True + + return False + + # See if the current item should be deselected or not + def deselectCheck(self, isSelected, hasSelected, extend): + # If the current item is selected we may want to deselect + if isSelected: + + # If something is selected and we're not extending we want to deselect + if hasSelected and not extend: + return True + + return False + + # See if there is at least one selected item + def hasSelected(self, items): + for item in items: + if item.select: + return True + + return False + + +class Select_init(Operator): + bl_idname = "mesh.select_by_direction" + bl_label = "Select by direction" + bl_description = ("Select all items with normals facing a certain direction,\n" + "defined by a vector with coordinates X, Y, Z") + bl_options = {'REGISTER', 'UNDO'} + + direction = FloatVectorProperty( + name="Direction", + description="Define a vector from the inputs axis X, Y, Z\n" + "Used to define the normals direction", + default=(0.0, 0.0, 1.0), + min=-100.0, max=100.0, + soft_min=-10.0, soft_max=10.0, + step=100, + precision=2 + ) + divergence = FloatProperty( + name="Divergence", + description="The number of degrees the selection may differ from the Vector\n" + "(Input is converted to radians)", + default=radians(30.0), + min=0.0, max=radians(360.0), + soft_min=0.0, soft_max=radians(360.0), + step=radians(5000), + precision=2, + subtype='ANGLE' + ) + extend = BoolProperty( + name="Extend", + description="Extend the current selection", + default=False + ) + # The spaces we use + spaces = (('LOC', 'Local', ''), ('GLO', 'Global', '')) + space = EnumProperty( + items=spaces, + name="Space", + description="The space to interpret the directions in", + default='LOC' + ) + + @classmethod + def poll(cls, context): + obj = context.active_object + return (obj and obj.type == 'MESH') + + def execute(self, context): + Select_by_direction(context, self.direction, self.divergence, self.extend, self.space) + + return {'FINISHED'} diff --git a/mesh_extra_tools/mesh_select_tools/mesh_select_by_edge_length.py b/mesh_extra_tools/mesh_select_tools/mesh_select_by_edge_length.py new file mode 100644 index 00000000..8430355c --- /dev/null +++ b/mesh_extra_tools/mesh_select_tools/mesh_select_by_edge_length.py @@ -0,0 +1,248 @@ +# mesh_select_by_edge_length.py Copyright (C) 2011, Dolf Veenvliet +# Extrude a selection from a mesh multiple times + +# ***** 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 LICENCE BLOCK ***** + +''' +bl_info = { + "name": "Select by edge length", + "author": "Dolf Veenvliet", + "version": (1,), + "blender": (2, 56, 0), + "location": "View3D > Select", + "description": "Select all items whose scale/length/surface matches a certain edge length", + "warning": "", + "wiki_url": "", + "tracker_url": "", + "category": "Mesh"} + +""" +Usage: + +Launch from from "Select -> By edge length" + +Additional links: + Author Site: http://www.macouno.com + e-mail: dolf {at} macouno {dot} com +""" +''' + +import bpy +from bpy.props import ( + FloatProperty, + BoolProperty, + EnumProperty, + ) + + +class Select_by_edge_length(): + + # Initialize the class + def __init__(self, context, edgeLength, edgeSize, extend, space, start_new): + + if start_new: + bpy.ops.mesh.select_all(action='DESELECT') + + self.ob = context.active_object + bpy.ops.object.mode_set(mode='OBJECT') + + self.space = space + self.obMat = self.ob.matrix_world + + bigger = (True if edgeSize == 'BIG' else False) + smaller = (True if edgeSize == 'SMALL' else False) + + # We ignore vert selections completely + edgeSelect = bpy.context.tool_settings.mesh_select_mode[1] + faceSelect = bpy.context.tool_settings.mesh_select_mode[2] + + # Edge select + if edgeSelect: + hasSelected = self.hasSelected(self.ob.data.edges) + + for e in self.ob.data.edges: + + if self.selectCheck(e.select, hasSelected, extend): + + lene = self.getEdgeLength(e.vertices) + + if (lene == edgeLength or (bigger and lene >= edgeLength) or + (smaller and lene <= edgeLength)): + e.select = True + + if self.deselectCheck(e.select, hasSelected, extend): + lene = self.getEdgeLength(e.vertices) + + if (lene != edgeLength and not (bigger and lene >= edgeLength) and + not (smaller and lene <= edgeLength)): + e.select = False + + # Face select + if faceSelect: + hasSelected = self.hasSelected(self.ob.data.polygons) + + # Loop through all the given faces + for f in self.ob.data.polygons: + + # Check if the faces match any of the directions + if self.selectCheck(f.select, hasSelected, extend): + + mine, maxe = 0.0, 0.0 + + for i, e in enumerate(f.edge_keys): + lene = self.getEdgeLength(e) + if not i: + mine = lene + maxe = lene + elif lene < mine: + mine = lene + elif lene > maxe: + maxe = lene + + if ((mine == edgeLength and maxe == edgeLength) or + (bigger and mine >= edgeLength) or + (smaller and maxe <= edgeLength)): + + f.select = True + + if self.deselectCheck(f.select, hasSelected, extend): + + mine, maxe = 0.0, 0.0 + + for i, e in enumerate(f.edge_keys): + lene = self.getEdgeLength(e) + if not i: + mine = lene + maxe = lene + elif lene < mine: + mine = lene + elif lene > maxe: + maxe = lene + + if ((mine != edgeLength and maxe != edgeLength) and + not (bigger and mine >= edgeLength) and + not (smaller and maxe <= edgeLength)): + + f.select = False + + bpy.ops.object.mode_set(mode='EDIT') + + # Get the lenght of an edge, by giving this function all verts (2) in the edge + def getEdgeLength(self, verts): + + vec1 = self.ob.data.vertices[verts[0]].co + vec2 = self.ob.data.vertices[verts[1]].co + + vec = vec1 - vec2 + + if self.space == 'GLO': + vec = self.obMat * vec + + return round(vec.length, 5) + + # See if the current item should be selected or not + def selectCheck(self, isSelected, hasSelected, extend): + + # If the current item is not selected we may want to select + if not isSelected: + + # If we are extending or nothing is selected we want to select + if extend or not hasSelected: + return True + + return False + + # See if the current item should be deselected or not + def deselectCheck(self, isSelected, hasSelected, extend): + + # If the current item is selected we may want to deselect + if isSelected: + + # If something is selected and we're not extending we want to deselect + if hasSelected and not extend: + return True + + return False + + # See if there is at least one selected item + def hasSelected(self, items): + + for item in items: + if item.select: + return True + + return False + + +class Select_init(bpy.types.Operator): + bl_idname = "mesh.select_by_edge_length" + bl_label = "Select by edge length" + bl_description = ("Select all items whose scale/length/surface matches a certain edge length \n" + "Does not work in Vertex Select mode") + bl_options = {'REGISTER', 'UNDO'} + + edgeLength = FloatProperty( + name="Edge length", + description="The comparison scale in Blender units", + default=1.0, + min=0.0, max=1000.0, + soft_min=0.0, soft_max=100.0, + step=100, + precision=2 + ) + # Changed to Enum as two separate Booleans didn't make much sense + sizes = (('SMALL', 'Smaller', "Select items smaller or equal the size setting"), + ('BIG', 'Bigger', "Select items bigger or equal to the size setting"), + ('EQUAL', 'Equal', "Select edges equal to the size setting")) + edgeSize = EnumProperty( + items=sizes, + name="Edge comparison", + description="Choose the relation to set edge lenght", + default='EQUAL' + ) + extend = BoolProperty( + name="Extend", + description="Extend the current selection", + default=False + ) + start_new = BoolProperty( + name="Fresh Start", + default=False, + description="Start from no previous selection" + ) + # The spaces we use + spaces = (('LOC', 'Local', "Use Local space"), + ('GLO', 'Global', "Use Global Space")) + space = EnumProperty( + items=spaces, + name="Space", + description="The space to interpret the directions in", + default='LOC' + ) + + @classmethod + def poll(cls, context): + obj = context.active_object + return (obj and obj.type == 'MESH' and not bpy.context.tool_settings.mesh_select_mode[0]) + + def execute(self, context): + Select_by_edge_length(context, self.edgeLength, self.edgeSize, + self.extend, self.space, self.start_new) + + return {'FINISHED'} diff --git a/mesh_extra_tools/mesh_select_tools/mesh_select_by_pi.py b/mesh_extra_tools/mesh_select_tools/mesh_select_by_pi.py new file mode 100644 index 00000000..6f2d4418 --- /dev/null +++ b/mesh_extra_tools/mesh_select_tools/mesh_select_by_pi.py @@ -0,0 +1,210 @@ +# mesh_select_by_pi.py Copyright (C) 2011, Dolf Veenvliet +# Extrude a selection from a mesh multiple times + +# ***** 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 LICENCE BLOCK ***** + +''' +bl_info = { + "name": "Select by pi", + "author": "Dolf Veenvliet", + "version": 1, + "blender": (2, 56, 0), + "location": "View3D > Select", + "description": "Select fake random based on pi", + "warning": "", + "wiki_url": "", + "tracker_url": "", + "category": "Mesh"} + +""" +Usage: + +Additional links: + Author Site: http://www.macouno.com + e-mail: dolf {at} macouno {dot} com +""" +''' + +import bpy +from bpy.types import Operator +from bpy.props import BoolProperty + + +class Select_by_pi(): + + # Initialise the class + def __init__(self, context, e, invert, extend, start_new): + + self.ob = context.active_object + # keep or not the original selection (helps with selected all) + if start_new: + bpy.ops.mesh.select_all(action='DESELECT') + + bpy.ops.object.mode_set(mode='OBJECT') + + self.invert = invert + + # Make pi as a list of integers + if e: + self.pi = list('27182818284590452353602874713526624977572470936999') + else: + self.pi = list('31415926535897932384626433832795028841971693993751') + + self.piLen = len(self.pi) + self.piPos = 0 + + vertSelect = bpy.context.tool_settings.mesh_select_mode[0] + edgeSelect = bpy.context.tool_settings.mesh_select_mode[1] + faceSelect = bpy.context.tool_settings.mesh_select_mode[2] + + # Vert select + if vertSelect: + hasSelected = self.hasSelected(self.ob.data.vertices) + + for v in self.ob.data.vertices: + s = self.selectCheck(v.select, hasSelected, extend) + d = self.deselectCheck(v.select, hasSelected, extend) + + # Check if the verts match any of the directions + if s and self.choose(): + v.select = True + + if d and not self.choose(): + v.select = False + + # Edge select + if edgeSelect: + hasSelected = self.hasSelected(self.ob.data.edges) + + for e in self.ob.data.edges: + s = self.selectCheck(e.select, hasSelected, extend) + d = self.deselectCheck(e.select, hasSelected, extend) + + if s and self.choose(): + e.select = True + + if d and not self.choose(): + e.select = False + + # Face select + if faceSelect: + hasSelected = self.hasSelected(self.ob.data.polygons) + + # Loop through all the given faces + for f in self.ob.data.polygons: + s = self.selectCheck(f.select, hasSelected, extend) + d = self.deselectCheck(f.select, hasSelected, extend) + + # Check if the faces match any of the directions + if s and self.choose(): + f.select = True + + if d and not self.choose(): + f.select = False + + bpy.ops.object.mode_set(mode='EDIT') + + # Choose by pi + def choose(self): + choice = True + + # We just choose the odd numbers + if int(self.pi[self.piPos]) % 2: + choice = False + + if self.invert: + choice = not choice + + self.incrementPiPos() + return choice + + # Increment the pi position + def incrementPiPos(self): + self.piPos += 1 + if self.piPos == self.piLen: + self.piPos = 0 + + # See if the current item should be selected or not + def selectCheck(self, isSelected, hasSelected, extend): + + # If the current item is not selected we may want to select + if not isSelected: + + # If we are extending or nothing is selected we want to select + if extend or not hasSelected: + return True + + return False + + # See if the current item should be deselected or not + def deselectCheck(self, isSelected, hasSelected, extend): + # If the current item is selected we may want to deselect + if isSelected: + # If something is selected and we're not extending we want to deselect + if hasSelected and not extend: + return True + + return False + + # See if there is at least one selected item + def hasSelected(self, items): + for item in items: + if item.select: + return True + + return False + + +class Select_init(Operator): + bl_idname = "mesh.select_by_pi" + bl_label = "Select by Pi or e" + bl_description = ("Select Vertices/Edges/Faces based on pi or e for a random-like selection\n" + "Number Pi (3.14 etc.) or e (2.71828 - Euler's number)") + bl_options = {'REGISTER', 'UNDO'} + + e = BoolProperty( + name="Use e", + description="Use e as the base of selection instead of pi", + default=False + ) + invert = BoolProperty( + name="Invert", + description="Invert the selection result", + default=False + ) + extend = BoolProperty( + name="Extend", + description="Extend the current selection", + default=False + ) + start_new = BoolProperty( + name="Fresh Start", + default=False, + description="Start from no previous selection" + ) + + @classmethod + def poll(cls, context): + obj = context.active_object + return (obj and obj.type == 'MESH') + + def execute(self, context): + Select_by_pi(context, self.e, self.invert, self.extend, self.start_new) + + return {'FINISHED'} diff --git a/mesh_extra_tools/mesh_select_tools/mesh_select_by_type.py b/mesh_extra_tools/mesh_select_tools/mesh_select_by_type.py new file mode 100644 index 00000000..b718d453 --- /dev/null +++ b/mesh_extra_tools/mesh_select_tools/mesh_select_by_type.py @@ -0,0 +1,78 @@ +# ##### 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 ##### + +# By CoDEmanX + +import bpy +from bpy.types import Operator +from bpy.props import ( + EnumProperty, + BoolProperty, + ) + + +class DATA_OP_facetype_select(Operator): + bl_idname = "data.facetype_select" + bl_label = "Select by face type" + bl_description = "Select all faces of a certain type" + bl_options = {'REGISTER', 'UNDO'} + + face_type = EnumProperty( + name="Select faces:", + items=(("3", "Triangles", "Faces made up of 3 vertices"), + ("4", "Quads", "Faces made up of 4 vertices"), + ("5", "Ngons", "Faces made up of 5 and more vertices")), + default="5" + ) + extend = BoolProperty( + name="Extend", + description="Extend Selection", + default=False + ) + + @classmethod + def poll(cls, context): + return context.active_object is not None and context.active_object.type == 'MESH' + + def execute(self, context): + try: + bpy.ops.object.mode_set(mode='EDIT') + + if not self.extend: + bpy.ops.mesh.select_all(action='DESELECT') + + context.tool_settings.mesh_select_mode = (False, False, True) + + if self.face_type == "3": + bpy.ops.mesh.select_face_by_sides(number=3, type='EQUAL') + elif self.face_type == "4": + bpy.ops.mesh.select_face_by_sides(number=4, type='EQUAL') + else: + bpy.ops.mesh.select_face_by_sides(number=4, type='GREATER') + + return {'FINISHED'} + + except Exception as e: + print("\n[Select by face type]\nERROR:\n") + + import traceback + traceback.printexc() + + self.report('WARNING', "Face selection could not be performed (Check the console for more info)") + + return {'CANCELLED'} diff --git a/mesh_extra_tools/mesh_select_tools/mesh_select_connected_faces.py b/mesh_extra_tools/mesh_select_tools/mesh_select_connected_faces.py new file mode 100644 index 00000000..ff131f9c --- /dev/null +++ b/mesh_extra_tools/mesh_select_tools/mesh_select_connected_faces.py @@ -0,0 +1,146 @@ +# Copyright (C) 2011, Dolf Veenvliet +# Extrude a selection from a mesh multiple times + +# ***** 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 LICENCE BLOCK ***** + +''' + bl_info = { + "name": "Select connected faces", + "author": "Dolf Veenvliet", + "version": (1,), + "blender": (2, 56, 0), + "location": "View3D > Select", + "description": "Select all faces connected to the current selection", + "warning": "", + "wiki_url": "", + "tracker_url": "", + "category": "Mesh"} + +""" +Usage: +Launch from from "Select -> Connected faces" + +Additional links: + Author Site: http://www.macouno.com + e-mail: dolf {at} macouno {dot} com +""" +''' + +import bpy +from bpy.types import Operator +from bpy.props import ( + IntProperty, + BoolProperty, + ) + + +class Select_connected_faces(): + # Initialize the class + def __init__(self, context, iterations, extend): + + self.ob = context.active_object + bpy.ops.object.mode_set(mode='OBJECT') + + # Make a list of all selected vertices + selVerts = [v.index for v in self.ob.data.vertices if v.select] + hasSelected = self.hasSelected(self.ob.data.polygons) + + for i in range(iterations): + nextVerts = [] + + for f in self.ob.data.polygons: + if self.selectCheck(f.select, hasSelected, extend): + + for v in f.vertices: + if v in selVerts: + f.select = True + + if f.select: + for v in f.vertices: + if v not in selVerts: + nextVerts.append(v) + + elif self.deselectCheck(f.select, hasSelected, extend): + for v in f.vertices: + if v in selVerts: + f.select = False + + selVerts = nextVerts + + bpy.ops.object.mode_set(mode='EDIT') + + # See if the current item should be selected or not + def selectCheck(self, isSelected, hasSelected, extend): + # If the current item is not selected we may want to select + if not isSelected: + return True + + return False + + # See if the current item should be deselected or not + def deselectCheck(self, isSelected, hasSelected, extend): + # If the current item is selected we may want to deselect + if isSelected: + # If something is selected and we're not extending we want to deselect + if hasSelected and not extend: + return True + + return False + + # See if there is at least one selected item + def hasSelected(self, items): + for item in items: + if item.select: + return True + + return False + + +class Select_init(Operator): + bl_idname = "mesh.select_connected_faces" + bl_label = "Select connected faces" + bl_description = ("Select all faces connected to the current selection \n" + "Works only in Face Selection mode") + bl_options = {'REGISTER', 'UNDO'} + + # Iterations + iterations = IntProperty( + name="Iterations", + default=1, + min=0, max=300, + soft_min=0, soft_max=100 + ) + extend = BoolProperty( + name="Extend", + description="Extend the current selection", + default=False + ) + + @classmethod + def poll(cls, context): + obj = context.active_object + return (obj and obj.type == 'MESH' and + bpy.context.tool_settings.mesh_select_mode[0] is False and + bpy.context.tool_settings.mesh_select_mode[1] is False and + bpy.context.tool_settings.mesh_select_mode[2] is True) + + def execute(self, context): + Select_connected_faces(context, self.iterations, self.extend) + + return {'FINISHED'} diff --git a/mesh_extra_tools/mesh_select_tools/mesh_selection_topokit.py b/mesh_extra_tools/mesh_select_tools/mesh_selection_topokit.py new file mode 100644 index 00000000..232714c4 --- /dev/null +++ b/mesh_extra_tools/mesh_select_tools/mesh_selection_topokit.py @@ -0,0 +1,628 @@ +# ##### 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 ##### + +bl_info = { + "name": "Topokit 2", + "author": "dustractor", + "version": (2, 0), + "blender": (2, 60, 0), + "location": "edit mesh vertices/edges/faces menus", + "description": "", + "warning": "", + "wiki_url": "", + "tracker_url": "", + "category": "Mesh"} + + +import bpy +from bpy.types import Operator +# In between calls, this stores any data that is expensive or static, +# matched to the size of the mesh and the id of the operator that created it +cachedata = dict() +# tkey is moved to mesh_extra_tools\__init__.py register function + + +# just a mix-in for the operators... +class meshpoller: + @classmethod + def poll(self, context): + try: + assert context.active_object.type == "MESH" + except: + return False + finally: + return True + + +# BEGIN VERTICES SECTION + +# This one works similarly to normal 'grow' (ctrl + NUMPAD_PLUS), +# except the original selection is not part of the result, +# +# 0--0--0 0--1--0 +# | | | | | | +# 0--1--0 --> 1--0--1 +# | | | | | | +# 0--0--0 0--1--0 + +class MESH_OT_vneighbors_edgewise(meshpoller, Operator): + bl_idname = "mesh.v2v_by_edge" + bl_label = "Neighbors by Edge" + bl_description = ("Select neighbour vertices of a starting selected vertex\n" + "Similar to Grow Selection - apart from the\n" + "original selection is not part of the result") + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + global cachedata + + bpy.ops.object.mode_set(mode="OBJECT") + obj = context.active_object + mesh = obj.data + meshkey = (len(mesh.vertices), len(mesh.edges), len(mesh.polygons), id(self)) + next_state = bytearray(meshkey[0]) + + if (meshkey == obj.tkkey) and (meshkey in cachedata): + vert_to_vert_map, prev_state = cachedata[meshkey] + else: + vert_to_vert_map = {i: {} for i in range(meshkey[0])} + for a, b in mesh.edge_keys: + vert_to_vert_map[a][b] = 1 + vert_to_vert_map[b][a] = 1 + obj.tkkey = meshkey + prev_state = None + + if not prev_state: + selected_vert_indices = filter(lambda _: mesh.vertices[_].select, + range(len(mesh.vertices))) + else: + selected_vert_indices = filter( + lambda _: mesh.vertices[_].select and not prev_state[_], + range(len(mesh.vertices)) + ) + + for v in selected_vert_indices: + for neighbor_index in vert_to_vert_map[v]: + next_state[neighbor_index] = True + mesh.vertices.foreach_set("select", next_state) + cachedata[meshkey] = (vert_to_vert_map, next_state) + bpy.ops.object.mode_set(mode="EDIT") + + return {"FINISHED"} + + +# This one is an alternate / counterpart to the previous. +# Think: diagonal opposite corners of a quad +# NOTE: does not apply to a triangle, since verts have no "opposite" +# +# 0--0--0 1--0--1 +# | | | | | | +# 0--1--0 --> 0--0--0 +# | | | | | | +# 0--0--0 1--0--1 + +class MESH_OT_vneighbors_facewise(meshpoller, Operator): + bl_idname = "mesh.v2v_facewise" + bl_label = "Neighbors by Face - Edge" + bl_description = ("Select diagonal opposite vertices of neighbour quads\n" + "Does not work with triangles\n" + "The original selection is not part of the result") + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + global cachedata + + bpy.ops.object.mode_set(mode="OBJECT") + obj = context.active_object + mesh = obj.data + meshkey = (len(mesh.vertices), len(mesh.edges), len(mesh.polygons), id(self)) + next_state = bytearray(meshkey[0]) + + if (meshkey == obj.tkkey) and (meshkey in cachedata): + vert_to_vert_map = cachedata[meshkey] + else: + vert_to_vert_map = {i: {} for i in range(meshkey[0])} + for a, b in mesh.edge_keys: + vert_to_vert_map[a][b] = 1 + vert_to_vert_map[b][a] = 1 + obj.tkkey = meshkey + faces = filter(lambda face: (len(face.vertices) == 4) and + (face.select is False), mesh.polygons) + for f in faces: + has = False + t = set() + for v in f.vertices: + if mesh.vertices[v].select: + has = True + t.update(vert_to_vert_map[v]) + if has: + for v in f.vertices: + if not mesh.vertices[v].select: + if v not in t: + next_state[v] = 1 + mesh.vertices.foreach_set("select", next_state) + cachedata[meshkey] = vert_to_vert_map + bpy.ops.object.mode_set(mode="EDIT") + + return {"FINISHED"} + +# END VERTICES SECTION + + +# BEGIN EDGES SECTION +# +--0--+--0--+--0--+ +--0--+--0--+--0--+ +# | | | | | | | | +# 0 0 0 0 0 1 1 0 +# | | | | | | | | +# +--0--+--1--+--0--+ ---> +--0--+--0--+--0--+ +# | | | | | | | | +# 0 0 0 0 0 1 1 0 +# | | | | | | | | +# +--0--+--0--+--0--+ +--0--+--0--+--0--+ + +class MESH_OT_eneighbors_shared_v_f(meshpoller, Operator): + bl_idname = "mesh.e2e_evfe" + bl_label = "Neighbors by Vert and Face" + bl_description = ("Select edges that share the neighbour vertices and faces\n" + "of the starting selected edge\n" + "The original selection is not part of the result") + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + global cachedata + + bpy.ops.object.mode_set(mode="OBJECT") + obj = context.active_object + mesh = obj.data + meshkey = (len(mesh.vertices), len(mesh.edges), len(mesh.polygons), id(self)) + state_mask = bytearray(meshkey[1]) + + if (meshkey == obj.tkkey) and (meshkey in cachedata): + edge_to_edges_dict = cachedata + else: + edge_key_to_index = {k: i for i, k in enumerate(mesh.edge_keys)} + edge_to_edges_dict = {i: set() for i in range(len(mesh.edges))} + for f in mesh.polygons: + fed = [edge_key_to_index[k] for k in f.edge_keys] + for k in f.edge_keys: + edge_to_edges_dict[edge_key_to_index[k]].update(fed) + obj.tkkey = meshkey + + for e in filter(lambda _: mesh.edges[_].select, edge_to_edges_dict): + k1 = set(mesh.edges[e].key) + for n in edge_to_edges_dict[e]: + k2 = set(mesh.edges[n].key) + if not k1.isdisjoint(k2): + state_mask[n] = True + + for e in mesh.edges: + e.select ^= state_mask[e.index] + cachedata[meshkey] = edge_key_to_index + bpy.ops.object.mode_set(mode="EDIT") + + return {"FINISHED"} + + +# +--0--+--0--+--0--+ +--0--+--0--+--0--+ +# | | | | | | | | +# 0 0 0 0 0 1 1 0 +# | | | | | | | | +# +--0--+--1--+--0--+ ---> +--1--+--0--+--1--+ +# | | | | | | | | +# 0 0 0 0 0 1 1 0 +# | | | | | | | | +# +--0--+--0--+--0--+ +--0--+--0--+--0--+ + +class MESH_OT_eneighbors_shared_v(meshpoller, Operator): + bl_idname = "mesh.e2e_eve" + bl_label = "Neighbors by Vert" + bl_description = ("Select edges that share the neighbour vertices\n" + "of the starting selected edge\n" + "The original selection is not part of the result") + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + bpy.ops.object.mode_set(mode="OBJECT") + mesh = context.active_object.data + state_mask = bytearray(len(mesh.edges)) + + for e in mesh.edges: + state_mask[e.index] = mesh.vertices[e.vertices[0]].select ^ mesh.vertices[e.vertices[1]].select + mesh.edges.foreach_set('select', state_mask) + bpy.ops.object.mode_set(mode="EDIT") + + return {"FINISHED"} + + +# +--0--+--0--+--0--+ +--0--+--1--+--0--+ +# | | | | | | | | +# 0 0 0 0 0 1 1 0 +# | | | | | | | | +# +--0--+--1--+--0--+ ---> +--0--+--0--+--0--+ +# | | | | | | | | +# 0 0 0 0 0 1 1 0 +# | | | | | | | | +# +--0--+--0--+--0--+ +--0--+--1--+--0--+ + +class MESH_OT_eneighbors_shared_f(meshpoller, Operator): + bl_idname = "mesh.e2e_efe" + bl_label = "Neighbors by Face" + bl_description = ("Select edges of neighbour faces to the starting selected edge\n" + "The original selection is not part of the result") + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + global cachedata + + bpy.ops.object.mode_set(mode="OBJECT") + obj = context.active_object + mesh = obj.data + meshkey = (len(mesh.vertices), len(mesh.edges), len(mesh.polygons), id(self)) + + if (meshkey == obj.tkkey) and (meshkey in cachedata): + edge_to_edges_dict = cachedata + else: + edge_key_to_index = {k: i for i, k in enumerate(mesh.edge_keys)} + edge_to_edges_dict = {i: set() for i in range(len(mesh.edges))} + for f in mesh.polygons: + fed = [edge_key_to_index[k] for k in f.edge_keys] + for k in f.edge_keys: + edge_to_edges_dict[edge_key_to_index[k]].update(fed) + obj.tkkey = meshkey + state_mask, esel = (bytearray(meshkey[1]), bytearray(meshkey[1])) + mesh.edges.foreach_get('select', esel) + + for e in filter(lambda _: mesh.edges[_].select, range(meshkey[1])): + for n in edge_to_edges_dict[e]: + state_mask[n] = 1 + + for e in range(meshkey[1]): + esel[e] ^= state_mask[e] + mesh.edges.foreach_set('select', esel) + cachedata[meshkey] = edge_to_edges_dict + bpy.ops.object.mode_set(mode="EDIT") + + return {"FINISHED"} + + +# Notice that on these next two, the original selection stays +# +--0--+--0--+--0--+ +--0--+--1--+--0--+ +# | | | | | | | | +# 0 0 0 0 0 0 0 0 +# | | | | | | | | +# +--0--+--1--+--0--+ ---> +--0--+--1--+--0--+ +# | | | | | | | | +# 0 0 0 0 0 0 0 0 +# | | | | | | | | +# +--0--+--0--+--0--+ +--0--+--1--+--0--+ + +class MESH_OT_eneighbors_shared_f_notv(meshpoller, Operator): + bl_idname = "mesh.e2e_efnve" + bl_label = "Lateral Neighbors" + bl_description = ("Select edges that are lateral neighbours\n" + "The original selection is included in the result") + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + global cachedata + + bpy.ops.object.mode_set(mode="OBJECT") + obj = context.active_object + mesh = obj.data + meshkey = (len(mesh.vertices), len(mesh.edges), len(mesh.polygons), id(self)) + state_mask = bytearray(meshkey[1]) + + if (meshkey == obj.tkkey) and (meshkey in cachedata): + edge_to_face_map, edge_key_to_index = cachedata[meshkey] + else: + edge_key_to_index = {} + edge_to_face_map = {i: set() for i in range(meshkey[1])} + for i, k in enumerate(mesh. edge_keys): + edge_key_to_index[k] = i + + for f in mesh.polygons: + for k in f.edge_keys: + edge_to_face_map[edge_key_to_index[k]].add(f.index) + obj.tkkey = meshkey + selected_edge_indices = filter(lambda _: mesh.edges[_].select, range(meshkey[1])) + + for e in selected_edge_indices: + for f in edge_to_face_map[e]: + for k in mesh.polygons[f].edge_keys: + hasv_in = False + for v in mesh.edges[e].key: + if v in k: + hasv_in = True + if hasv_in: + continue + else: + state_mask[edge_key_to_index[k]] = True + + for e in filter(lambda _: state_mask[_], range(meshkey[1])): + mesh.edges[e].select |= state_mask[e] + cachedata[meshkey] = (edge_to_face_map, edge_key_to_index) + bpy.ops.object.mode_set(mode="EDIT") + + return {"FINISHED"} + + +# +--0--+--0--+--0--+ +--0--+--0--+--0--+ +# | | | | | | | | +# 0 0 0 0 0 0 0 0 +# | | | | | | | | +# +--0--+--1--+--0--+ ---> +--1--+--1--+--1--+ +# | | | | | | | | +# 0 0 0 0 0 0 0 0 +# | | | | | | | | +# +--0--+--0--+--0--+ +--0--+--0--+--0--+ + +class MESH_OT_eneighbors_shared_v_notf(meshpoller, Operator): + bl_idname = "mesh.e2e_evnfe" + bl_label = "Longitudinal Edges" + bl_description = ("Select Edges along the same longitude of the starting edge\n" + "The original selection is included in the result") + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + global cachedata + + bpy.ops.object.mode_set(mode="OBJECT") + obj = context.active_object + mesh = obj.data + meshkey = (len(mesh.vertices), len(mesh.edges), len(mesh.polygons), id(self)) + state_mask = bytearray(meshkey[1]) + vstate = bytearray(meshkey[0]) + mesh.vertices.foreach_get('select', vstate) + + if (meshkey == obj.tkkey) and (meshkey in cachedata): + edge_to_face_map, vert_to_vert_map, edge_key_to_index = cachedata[meshkey] + else: + edge_key_to_index = {} + vert_to_vert_map = {i: set() for i in range(meshkey[0])} + edge_to_face_map = {i: set() for i in range(meshkey[1])} + + for i, k in enumerate(mesh.edge_keys): + edge_key_to_index[k] = i + vert_to_vert_map[k[0]].add(k[1]) + vert_to_vert_map[k[1]].add(k[0]) + + for f in mesh.polygons: + for k in f.edge_keys: + edge_to_face_map[edge_key_to_index[k]].add(f.index) + obj.tkkey = meshkey + selected_edge_indices = filter(lambda _: mesh.edges[_].select, range(meshkey[1])) + + for e in selected_edge_indices: + for v in mesh.edges[e].key: + state_mask[v] ^= 1 + + for f in edge_to_face_map[e]: + for v in mesh.polygons[f].vertices: + vstate[v] = 1 + + for v in filter(lambda _: state_mask[_], range(meshkey[1])): + for n in vert_to_vert_map[v]: + if not vstate[n] and (n != v): + mesh.edges[edge_key_to_index[(min(v, n), max(v, n))]].select = True + cachedata[meshkey] = (edge_to_face_map, vert_to_vert_map, edge_key_to_index) + bpy.ops.object.mode_set(mode="EDIT") + + return {"FINISHED"} + + +# Deselects edges which are at the edge of a face-selection, +# causing selection to 'shrink in' +class MESH_OT_inner_edges(meshpoller, Operator): + bl_idname = "mesh.ie" + bl_label = "Inner Edge Selection" + bl_description = ("Deselects edges which are at the border\n" + "of a starting face selection\n" + "causing the selection to shrink inwards") + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + global cachedata + + bpy.ops.object.mode_set(mode="OBJECT") + obj = context.active_object + mesh = obj.data + meshkey = (len(mesh.vertices), len(mesh.edges), len(mesh.polygons), id(self)) + state_mask = bytearray(meshkey[1]) + + if (meshkey == obj.tkkey) and (meshkey in cachedata): + edge_to_face_map = cachedata[meshkey] + else: + edge_key_to_index = {k: i for i, k in enumerate(mesh.edge_keys)} + edge_to_face_map = {i: set() for i in range(meshkey[1])} + for f in mesh.polygons: + for k in f.edge_keys: + edge_to_face_map[edge_key_to_index[k]].add(f.index) + obj.tkkey = meshkey + + for e in filter(lambda _: mesh.edges[_].select, range(meshkey[1])): + for f in edge_to_face_map[e]: + if mesh.polygons[f].select: + state_mask[e] ^= 1 + + for e in range(meshkey[1]): + mesh.edges[e].select ^= state_mask[e] + cachedata[meshkey] = edge_to_face_map + bpy.ops.object.mode_set(mode="EDIT") + + return {"FINISHED"} + +# END EDGES SECTION + + +# BEGIN FACES SECTION + +# here is another one which functions very similarly to the ctrl+NUMPAD_PLUS 'growth' +# but it deselects the original selection, of course. +# This would be your checkerboard-type growth. +# [0][0][0] [0][1][0] +# [0][1][0] ---> [1][0][1] +# [0][0][0] [0][1][0] + +class MESH_OT_fneighbors_shared_e(meshpoller, Operator): + bl_idname = "mesh.f2f_fef" + bl_label = "Neighbor Faces sharing an Edge" + bl_description = ("Selects faces that share an edge with the starting face selection\n" + "Similar to the Grow selection \n" + "The original selection is not part of the result") + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + global cachedata + + bpy.ops.object.mode_set(mode="OBJECT") + obj = context.active_object + mesh = obj.data + meshkey = (len(mesh.vertices), len(mesh.edges), len(mesh.polygons), id(self)) + + if (meshkey == obj.tkkey) and (meshkey in cachedata): + face_to_face_map = cachedata[meshkey] + else: + edge_key_to_index = {k: i for i, k in enumerate(mesh.edge_keys)} + edge_to_face_map = {i: set() for i in range(meshkey[1])} + for f in mesh.polygons: + for k in f.edge_keys: + edge_to_face_map[edge_key_to_index[k]].add(f.index) + face_to_face_map = {i: set() for i in range(meshkey[2])} + for f in mesh.polygons: + for k in f.edge_keys: + face_to_face_map[f.index].update(edge_to_face_map[edge_key_to_index[k]]) + obj.tkkey = meshkey + mask_state = bytearray(meshkey[2]) + + for f in filter(lambda _: mesh.polygons[_].select, range(meshkey[2])): + for n in face_to_face_map[f]: + mask_state[n] = True + + for f in range(meshkey[2]): + mesh.polygons[f].select ^= mask_state[f] + cachedata[meshkey] = face_to_face_map + bpy.ops.object.mode_set(mode="EDIT") + + return {"FINISHED"} + + +# [0][0][0] [1][0][1] +# [0][1][0] ---> [0][0][0] +# [0][0][0] [1][0][1] + +class MESH_OT_fneighbors_shared_v_note(meshpoller, Operator): + bl_idname = "mesh.f2f_fvnef" + bl_label = "Neighbors by Vertex not Edge" + bl_description = ("Select neighbour faces that share a vertex\n" + "with the starting selection\n" + "The original selection is not part of the result") + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + global cachedata + + bpy.ops.object.mode_set(mode="OBJECT") + obj = context.active_object + mesh = obj.data + meshkey = (len(mesh.vertices), len(mesh.edges), len(mesh.polygons), id(self)) + + if (meshkey == obj.tkkey) and (meshkey in cachedata): + edge_key_to_index = cachedata[meshkey] + else: + edge_key_to_index = {k: i for i, k in enumerate(mesh.edge_keys)} + obj.tkkey = meshkey + state_mask = bytearray(meshkey[2]) + face_verts = set() + + for f in filter(lambda _: mesh.polygons[_].select, range(meshkey[2])): + face_verts.update(mesh.polygons[f].vertices) + + for f in filter(lambda _: not mesh.polygons[_].select, range(meshkey[2])): + ct = 0 + for v in mesh.polygons[f].vertices: + ct += (v in face_verts) + if ct == 1: + state_mask[f] = 1 + mesh.polygons.foreach_set('select', state_mask) + cachedata[meshkey] = edge_key_to_index + bpy.ops.object.mode_set(mode="EDIT") + + return {"FINISHED"} + + +# https://en.wikipedia.org/wiki/Conway's_Game_of_Life +class MESH_OT_conway(meshpoller, Operator): + bl_idname = "mesh.conway" + bl_label = "Conway's Selection" + bl_description = ("Select Faces with the Conway's game of life algorithm\n" + "Requires an initial Face selection\n" + "The edges of the original selection are included in the result") + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + global cachedata + + bpy.ops.object.mode_set(mode="OBJECT") + obj = context.active_object + mesh = obj.data + meshkey = (len(mesh.vertices), len(mesh.edges), len(mesh.polygons), id(self)) + + if (meshkey == obj.tkkey) and (meshkey in cachedata): + vert_to_face_map = cachedata[meshkey] + else: + vert_to_face_map = {i: set() for i in range(meshkey[0])} + for f in mesh.polygons: + for v in f.vertices: + vert_to_face_map[v].add(f.index) + obj.tkkey = meshkey + sel = set() + uns = set() + F = {i: set() for i in range(meshkey[2])} + + for f in range(meshkey[2]): + for v in mesh.polygons[f].vertices: + for n in filter(lambda _: mesh.polygons[_].select and (_ != f), vert_to_face_map[v]): + F[f].add(n) + + for f in F: + if len(F[f]) == 3: + sel.add(f) + elif len(F[f]) != 2: + uns.add(f) + + for f in range(meshkey[2]): + if f in sel: + mesh.polygons[f].select = True + if f in uns: + mesh.polygons[f].select = False + cachedata[meshkey] = vert_to_face_map + bpy.ops.object.mode_set(mode="EDIT") + + return {"FINISHED"} + + +def register(): + bpy.utils.register_module(__name__) + + +def unregister(): + bpy.utils.unregister_module(__name__) + + +if __name__ == "__main__": + register() |