Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormeta-androcto <meta.androcto1@gmail.com>2017-03-20 02:33:38 +0300
committermeta-androcto <meta.androcto1@gmail.com>2017-03-20 02:33:38 +0300
commit8e3bfa5506ea110fe6793401b53da17c61061167 (patch)
tree8e53f830a72b434928b07b99befe8762431a4ed3 /mesh_extra_tools/mesh_select_tools
parent9007bcd10713e55168235e9e8420b17172674638 (diff)
initial commit mesh edit tools: T50680
Diffstat (limited to 'mesh_extra_tools/mesh_select_tools')
-rw-r--r--mesh_extra_tools/mesh_select_tools/__init__.py68
-rw-r--r--mesh_extra_tools/mesh_select_tools/mesh_index_select.py169
-rw-r--r--mesh_extra_tools/mesh_select_tools/mesh_info_select.py108
-rw-r--r--mesh_extra_tools/mesh_select_tools/mesh_select_by_direction.py220
-rw-r--r--mesh_extra_tools/mesh_select_tools/mesh_select_by_edge_length.py248
-rw-r--r--mesh_extra_tools/mesh_select_tools/mesh_select_by_pi.py210
-rw-r--r--mesh_extra_tools/mesh_select_tools/mesh_select_by_type.py78
-rw-r--r--mesh_extra_tools/mesh_select_tools/mesh_select_connected_faces.py146
-rw-r--r--mesh_extra_tools/mesh_select_tools/mesh_selection_topokit.py628
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()