[addon] mesh_3points_flat_trim_yi

use 3 selected points to flat trim the other elements.

location : [edit mode] 3dview - toolbar - mesh tool - [flat trim by 3points]





# mesh_3points_flat_trim_yi.py
bl_info = {
    "name": "3points flat trim",
    "author": "NirenYang[BlenderCN]",
    "version": (0, 1),
    "blender": (2, 75, 5),
    "location": "3d view - toolbar",
    "description": "3points flat trim",
    "warning": "",
    "wiki_url": "",
    "tracker_url": "",
    "category": "mesh",
}

import bpy
import bmesh
from mathutils.geometry import intersect_line_plane, distance_point_to_plane, normal

    
enum_ref = [( 'previous', 'previous', '前三个' ),
            ( 'last', 'last', '后三个' )]
class MESH_OT_3points_flat_trim(bpy.types.Operator):
    """
    用指定的三个点作为flat参考平面
    """
    bl_idname = 'mesh.3points_flat_trim'
    bl_label = 'flat trim by 3points'
    bl_options = {'REGISTER', 'UNDO'}
    
    ref_order = bpy.props.EnumProperty(name='参考平面', description='选择序列的 前三个/后三个 作为参考面', items=enum_ref, default="last")
    filter_distance = bpy.props.FloatProperty(name='距离过滤', description='仅作用大于此距离的', default=0.0, precision=3, min=0.0)
    
    
    @classmethod
    def poll(cls, context):
        obj = context.active_object
        return (obj and obj.type == 'MESH')
        
    def execute(self, context):
        C = context
        D = bpy.data
        ob = C.active_object
        
        #if bpy.app.debug != True:
        #    bpy.app.debug = True
        #    if C.active_object.show_extra_indices != True:
        #        C.active_object.show_extra_indices = True

        if ob.mode == 'OBJECT':
            me = C.object.data
            bm = bmesh.new()
            bm.from_mesh(me)
        else:
            obj = C.edit_object
            me = obj.data
            bm = bmesh.from_edit_mesh(me)

        bm.select_history.validate()
        if len(bm.select_history) < 3:
            self.report({'INFO'}, '先选择3个点')
            return {'CANCELLED'}
        
        points3Index = []
        points3 = []
        _ordering = bm.select_history if self.ref_order=="previous" else list(bm.select_history)[::-1]
        for i in _ordering:
            if len(points3) >= 3:
                break
            elif isinstance(i, bmesh.types.BMVert):
                points3.append(i.co)
                points3Index.append(i.index)
        print(points3Index)
        if len(points3) < 3:
            self.report({'INFO'}, '至少需要选择3个点')
            return {'CANCELLED'}
        
            
        points3Normal = normal(*points3)
        for v in bm.verts:
            if v.select and v.index not in points3Index:
                _move = True
                if self.filter_distance > 0.0:
                    _move = abs(distance_point_to_plane(v.co, points3[0], points3Normal)) < self.filter_distance
                if _move == True:
                    v.co = intersect_line_plane(v.co, v.co+points3Normal, points3[0], points3Normal)

        if ob.mode == 'OBJECT':
            bm.to_mesh(me)
            bm.free()
        else:
            bmesh.update_edit_mesh(me, True)
            
        return {'FINISHED'}

        
def menu_func_MESH_OT_3points_flat_trim(self, context):
    self.layout.operator(MESH_OT_3points_flat_trim.bl_idname,
                        text=MESH_OT_3points_flat_trim.bl_label)

def register():   
    bpy.utils.register_class(MESH_OT_3points_flat_trim) 
    bpy.types.VIEW3D_PT_tools_meshedit.append(menu_func_MESH_OT_3points_flat_trim)
    
def unregister():
    bpy.types.VIEW3D_PT_tools_meshedit.remove(menu_func_MESH_OT_3points_flat_trim)
    bpy.utils.unregister_class(MESH_OT_3points_flat_trim)
    




if __name__ == "__main__":
    register()

Great job! I believe people making mechanical things will love it.

Actually it has been discussed at StackExchange here.

Here is my translated version, hopefuly more readable for English speakers (and allow me to name it “Coplanar by 3 Verts” here :stuck_out_tongue: )


bl_info = {
    "name": "Coplanar by 3 Verts",
    "author": "NirenYang[BlenderCN]",
    "version": (0, 1),
    "blender": (2, 75, 5),
    "location": "3d view - toolbar",
    "description": "Make vertices coplanar using a plane defined by the first/last three selected verts.",
    "warning": "",
    "wiki_url": "",
    "tracker_url": "",
    "category": "mesh",
}
import bpy
import bmesh
from mathutils.geometry import intersect_line_plane, distance_point_to_plane, normal
    
enum_ref = [( 'first', 'First', 'Defined by the first three selected verts' ),
            ( 'last', 'Last', 'Defined by the last three selected verts' )]
class MESH_OT_3points_flat_trim(bpy.types.Operator):
    """
    Manually pick three vertices to define the reference plane
    """
    bl_idname = 'mesh.3points_flat_trim'
    bl_label = 'Coplanar by 3 Verts'
    bl_options = {'REGISTER', 'UNDO'}
    
    ref_order = bpy.props.EnumProperty(name='Refferece Plane', description='Use the first/last three selected vertices to define the reference plane', items=enum_ref, default="last")
    filter_distance = bpy.props.FloatProperty(name='Filter Distance', description='Only affects vertices further than this distance', default=0.0, precision=3, min=0.0)
    
    
    @classmethod
    def poll(cls, context):
        obj = context.active_object
        return (obj and obj.type == 'MESH')
        
    def execute(self, context):
        C = context
        D = bpy.data
        ob = C.active_object
        
        #if bpy.app.debug != True:
        #    bpy.app.debug = True
        #    if C.active_object.show_extra_indices != True:
        #        C.active_object.show_extra_indices = True
        if ob.mode == 'OBJECT':
            me = C.object.data
            bm = bmesh.new()
            bm.from_mesh(me)
        else:
            obj = C.edit_object
            me = obj.data
            bm = bmesh.from_edit_mesh(me)
        bm.select_history.validate()
        if len(bm.select_history) < 3:
            self.report({'INFO'}, 'Pick three vertices first')
            return {'CANCELLED'}
        
        points3Index = []
        points3 = []
        _ordering = bm.select_history if self.ref_order=="first" else list(bm.select_history)[::-1]
        for i in _ordering:
            if len(points3) >= 3:
                break
            elif isinstance(i, bmesh.types.BMVert):
                points3.append(i.co)
                points3Index.append(i.index)
        print(points3Index)
        if len(points3) < 3:
            self.report({'INFO'}, 'At least three vertices are needed being selected')
            return {'CANCELLED'}
        
            
        points3Normal = normal(*points3)
        for v in bm.verts:
            if v.select and v.index not in points3Index:
                _move = True
                if self.filter_distance > 0.0:
                    _move = abs(distance_point_to_plane(v.co, points3[0], points3Normal)) < self.filter_distance
                if _move == True:
                    v.co = intersect_line_plane(v.co, v.co+points3Normal, points3[0], points3Normal)
        if ob.mode == 'OBJECT':
            bm.to_mesh(me)
            bm.free()
        else:
            bmesh.update_edit_mesh(me, True)
            
        return {'FINISHED'}
        
def menu_func_MESH_OT_3points_flat_trim(self, context):
    self.layout.operator(MESH_OT_3points_flat_trim.bl_idname,
                        text=MESH_OT_3points_flat_trim.bl_label)
def register():   
    bpy.utils.register_class(MESH_OT_3points_flat_trim) 
    bpy.types.VIEW3D_PT_tools_meshedit.append(menu_func_MESH_OT_3points_flat_trim)
    
def unregister():
    bpy.types.VIEW3D_PT_tools_meshedit.remove(menu_func_MESH_OT_3points_flat_trim)
    bpy.utils.unregister_class(MESH_OT_3points_flat_trim)
    
 

if __name__ == "__main__":
    register()

Hi @nirenyang,

Thanks a lot for sharing this useful job.
Have a nice day.
Byebye
Spirou4D

I dont get it… How does this supposed to work? I selected 3 vertices and press coplanar, nothing happens.

Hi turgee, it is mainly used for “flattening” polygons by using the plane defined by 3 verts.

I tried to make a short demo video, fyi:

NOTE: Currently it doesn’t work with verts selected by using Border Select, Circle Select, etc, since the coordinates cannot be used to determine the plane.

Leon, thank you very much for the explenation.
Indeed, very useful!

Hi @nirenyang,

But Blender coder Campbell Barton has summit a commit today “Make faces coplanare”:

May be your add-on is threatened, not? …No, not really, I mean but you could contact C Barton may be?
Thanks for the vid to @Leon_Cheung

@Spirou4D I’ve experienced that cool feature a few days ago. Actually this addon was born because it is what that feature lacks of(at least currently). nirenyang and I have also considered to discuss with Campbell about that. Yeah, I think we will try that. :slight_smile:

Thanks for your reply @Spirou4D, I’m not a prefessional coder and just want a better Blender.

so… It’s good to know some professional guy get it.

Great. This is really useful for manipulating and retopologizing imported CAD models.

I copypaste it into a txt, change the extension to py and it shows an error, I tried copying various times and it always happens, and I can’t even do the same with the chinese version cause it says I need another character codification to save it :confused:

Hi. bloox64. plz check the version by Leon_Cheung:

But still some strange html code there.
" &lt;" must replace by “<”,
" &gt;" must replace by “>”

Thank you! Adding </> solved the problem!