APEC
September 22, 2020, 10:01am
1
Example:
If I use Select Similar - Amount of Adjacent Faces or Amount of Connected Edges
it select verts that makes shape
Is it possible after Select Similar deselect vertices that have angle not equal 180 degree?
Or maybe somehow select all vertices that have only 2 edges and 180 degree between?
RNavega
September 23, 2020, 12:52am
2
I don’t think there’s a built-in way to do that. You can do it from a script or the Python console by selecting all vertices that only link to two edges and whose edges have an (absolute) dot product close to 1.0.
On the console, something like this (untested):
import bmesh
bm = bmesh.from_edit_mesh(C.object.data)
selectColinear = lambda v: len(v.link_edges) == 2 and abs((v.co - v.link_edges[0].other_vert(v).co).dot(v.co - v.link_edges[1].other_vert(v).co)) > 0.9
for v in bm.verts:
v.select = selectColinear(v)
bmesh.update_edit_mesh(C.object.data)
Edit: updated to fix the errors discussed below.
1 Like
APEC
September 23, 2020, 7:28am
3
thanks.
Slightly modified but I think something is missing here
import bpy, bmesh
import numpy as np
me = bpy.context.object.data
bm = bmesh.from_edit_mesh(me)
selectColinear = lambda v: len(v.link_edges) == 2 and abs((v.co - v.link_edges[0].other_vert(v).co).dot(v.co - v.link_edges[1].other_vert(v))) > 0.9
bm.verts.foreach_set('select', [selectColinear(vertex) for vertex in bm.verts])
bmesh.update_edit_mesh(me)
AttributeError: ‘BMVertSeq’ object has no attribute ‘foreach_set’
RNavega
September 23, 2020, 9:23pm
4
Yeah, that’s the “untested” part heh.
In the absence of a foreach_set()
in a collection, just manually iterate over each vertex and set their select
attribute:
(...)
for vertex in bm.verts:
vertex.select = selectColinear(vertex)
bmesh.update_edit_mesh(me)
1 Like
RNavega
September 23, 2020, 10:30pm
5
If you only need to use it on that shape, I thought of a couple of methods:
Method 1: press A to select all vertices, then C to go Circle Select mode and just deselect the corners. Not that big of a deal.
Method 2: select all corners then press Ctrl + I to invert the selection (selecting every vertex that isn’t a corner).
1 Like
APEC
September 24, 2020, 7:02am
6
No, its for checking bad topology in complex mesh, you just one click found them and dissolve.
As I can found lambda needs its own context to hold its value
so to not get this warning it needs to add something more yet
AttributeError: Vector subtraction: (Vector - BMVert) invalid type for this operation
but anyway thanks for helping!
deadpin
September 24, 2020, 9:10am
7
In that case, would the existing bpy.ops.mesh.dissolve_limited()
operator in vertex mode do what you want? https://docs.blender.org/api/blender2.8/bpy.ops.mesh.html?highlight=dissolve_limited#bpy.ops.mesh.dissolve_limited
It has no issue cleaning up your example mesh above for example.
1 Like
RNavega
September 24, 2020, 9:11am
8
That’s a typo, I forgot to access .co
in this expression:
.dot(v.co - v.link_edges[1].other_vert(v))
It should be:
.dot(v.co - v.link_edges[1].other_vert(v).co)
That’s a built-in operation called Limited Dissolve . No need for a script x)
1 Like
APEC
September 24, 2020, 9:42am
9
Learn new about blender every day!
Thanks for helping!
1 Like
DotBow
September 25, 2020, 10:09am
10
Hi! Thanks for the code snippet! I turned it into an operator (there is more work can be done but that’s what it looks like for now):
import bmesh
import bpy
from bpy.props import BoolProperty, FloatProperty
class SelectCollinearVertices(bpy.types.Operator):
"""Select collinear vertices with 2 connected edges"""
bl_idname = "mesh.select_collinear_vertices"
bl_label = "Select Collinear Vertices"
bl_options = {'REGISTER', 'UNDO'}
factor: FloatProperty(name="Factor", default=0.9, min=0.0, max=1.0)
extend_selection: BoolProperty(name="Extend Selection", default=False)
@classmethod
def poll(cls, context):
ob = context.active_object
return all([bool(ob), ob.type == "MESH", ob.mode == "EDIT"])
def select_collinear(self, v):
le = v.link_edges
if len(le) == 2:
return (abs((v.co - le[0].other_vert(v).co).dot(v.co - le[1].other_vert(v).co)) > self.factor)
return False
def execute(self, context):
me = context.object.data
bm = bmesh.from_edit_mesh(me)
bm.select_mode = {'VERT'}
for v in bm.verts:
if not (self.extend_selection and v.select):
v.select = self.select_collinear(v)
bm.select_flush_mode()
bmesh.update_edit_mesh(me)
return {'FINISHED'}
def register():
bpy.utils.register_class(SelectCollinearVertices)
def unregister():
bpy.utils.unregister_class(SelectCollinearVertices)
if __name__ == "__main__":
register()
3 Likes
APEC
September 25, 2020, 10:17am
11
Wow, yes, thank you!
I did not want bothering people but Limit Dissolve does not do what I want exactly
Example
as you can see Limit Dissolve also dissolving some supported loops and edges.
DotBow
September 29, 2020, 12:59pm
12
After some testing I changed the behavior a bit so there is an ability to use actual angle values for selecting vertices.
Operator can be found under Shift+G (select similar) menu while in vertex edit mode.
from math import pi
import bmesh
import bpy
from bpy.props import BoolProperty, FloatProperty
from bpy.types import VIEW3D_MT_edit_mesh_select_similar
class SelectVerticesByAngle(bpy.types.Operator):
"""Select vertices with 2 connected edges by angle between them"""
bl_idname = "mesh.select_vertices_by_angle"
bl_label = "Select Vertices By Angle"
bl_options = {'REGISTER', 'UNDO'}
def update_min_angle(self, context):
if self.min_angle > self.max_angle:
self.max_angle = self.min_angle
def update_max_angle(self, context):
if self.max_angle < self.min_angle:
self.min_angle = self.max_angle
min_angle: FloatProperty(name="Min Angle", subtype='ANGLE',
default=pi * 0.75, min=0.0, max=pi,
update=update_min_angle)
max_angle: FloatProperty(name="Max Angle", subtype='ANGLE',
default=pi, min=0.0, max=pi,
update=update_max_angle)
extend: BoolProperty(name="Extend", default=False)
@classmethod
def poll(cls, context):
ob = context.active_object
return all([bool(ob), ob.type == "MESH", ob.mode == "EDIT"])
def select_by_angle(self, v):
le = v.link_edges
if len(le) == 2:
vec1 = v.co - le[0].other_vert(v).co
vec2 = v.co - le[1].other_vert(v).co
angle = abs((vec1).angle(vec2))
return (angle >= self.min_angle and angle <= self.max_angle)
return False
def execute(self, context):
me = context.object.data
bm = bmesh.from_edit_mesh(me)
bm.select_mode = {'VERT'}
for v in bm.verts:
if not (self.extend and v.select):
v.select = self.select_by_angle(v)
bm.select_flush_mode()
bmesh.update_edit_mesh(me)
return {'FINISHED'}
def check(self, context):
return True
def draw_select_vertices_by_angle(self, context):
mesh_select_mode = context.scene.tool_settings.mesh_select_mode
if tuple(mesh_select_mode) == (True, False, False):
layout = self.layout
layout.separator()
layout.operator(SelectVerticesByAngle.bl_idname,
text="Vertices By Angle")
def register():
bpy.utils.register_class(SelectVerticesByAngle)
VIEW3D_MT_edit_mesh_select_similar.append(draw_select_vertices_by_angle)
def unregister():
bpy.utils.unregister_class(SelectVerticesByAngle)
VIEW3D_MT_edit_mesh_select_similar.remove(draw_select_vertices_by_angle)
if __name__ == "__main__":
register()
2 Likes