From ef5f9c3291ffec316e1d0a380ca2357351104a8d Mon Sep 17 00:00:00 2001 From: meta-androcto Date: Sun, 23 Jun 2019 15:44:10 +1000 Subject: mesh_f2: update to working version 1,8,4: T65867 Thanks ID_Inc --- mesh_f2.py | 322 +++++++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 281 insertions(+), 41 deletions(-) (limited to 'mesh_f2.py') diff --git a/mesh_f2.py b/mesh_f2.py index ae246f47..672ba886 100644 --- a/mesh_f2.py +++ b/mesh_f2.py @@ -1,3 +1,5 @@ +# Updated for 2.8 jan 5 2019 + # ##### BEGIN GPL LICENSE BLOCK ##### # # This program is free software; you can redistribute it and/or @@ -21,8 +23,8 @@ bl_info = { "name": "F2", "author": "Bart Crouch, Alexander Nedovizin, Paul Kotelevets " - "(concept design)", - "version": (1, 7, 3), + "(concept design), Adrian Rutkowski", + "version": (1, 8, 4), "blender": (2, 80, 0), "location": "Editmode > F", "warning": "", @@ -32,16 +34,46 @@ bl_info = { "category": "Mesh", } +# ref: https://github.com/Cfyzzz/Other-scripts/blob/master/f2.py import bmesh import bpy import itertools import mathutils +import math +from mathutils import Vector from bpy_extras import view3d_utils +# returns a custom data layer of the UV map, or None +def get_uv_layer(ob, bm, mat_index): + uv = None + uv_layer = None + if ob.material_slots: + me = ob.data + if me.uv_layers: + uv = me.uv_layers.active.name + # 'material_slots' is deprecated (Blender Internal) + # else: + # mat = ob.material_slots[mat_index].material + # if mat is not None: + # slot = mat.texture_slots[mat.active_texture_index] + # if slot and slot.uv_layer: + # uv = slot.uv_layer + # else: + # for tex_slot in mat.texture_slots: + # if tex_slot and tex_slot.uv_layer: + # uv = tex_slot.uv_layer + # break + if uv: + uv_layer = bm.loops.layers.uv.get(uv) + + return (uv_layer) + + # create a face from a single selected edge def quad_from_edge(bm, edge_sel, context, event): + addon_prefs = context.preferences.addons[__name__].preferences ob = context.active_object region = context.region region_3d = context.space_data.region_3d @@ -49,9 +81,9 @@ def quad_from_edge(bm, edge_sel, context, event): # find linked edges that are open (<2 faces connected) and not part of # the face the selected edge belongs to all_edges = [[edge for edge in edge_sel.verts[i].link_edges if \ - len(edge.link_faces) < 2 and edge != edge_sel and \ - sum([face in edge_sel.link_faces for face in edge.link_faces]) == 0] \ - for i in range(2)] + len(edge.link_faces) < 2 and edge != edge_sel and \ + sum([face in edge_sel.link_faces for face in edge.link_faces]) == 0] \ + for i in range(2)] if not all_edges[0] or not all_edges[1]: return @@ -64,7 +96,7 @@ def quad_from_edge(bm, edge_sel, context, event): vert = [vert for vert in edge.verts if not vert.select][0] world_pos = ob.matrix_world @ vert.co.copy() screen_pos = view3d_utils.location_3d_to_region_2d(region, - region_3d, world_pos) + region_3d, world_pos) dist = (mouse_pos - screen_pos).length if not min_dist or dist < min_dist[0]: min_dist = (dist, edge, vert) @@ -88,7 +120,7 @@ def quad_from_edge(bm, edge_sel, context, event): if not normal_edge.link_faces: # no connected faces, so no need to flip the face normal flip_align = False - if flip_align: # there is a face to which the normal can be aligned + if flip_align: # there is a face to which the normal can be aligned ref_verts = [v for v in normal_edge.link_faces[0].verts] if v3 in ref_verts: va_1 = v3 @@ -100,7 +132,7 @@ def quad_from_edge(bm, edge_sel, context, event): va_1 = v2 va_2 = v4 if (va_1 == ref_verts[0] and va_2 == ref_verts[-1]) or \ - (va_2 == ref_verts[0] and va_1 == ref_verts[-1]): + (va_2 == ref_verts[0] and va_1 == ref_verts[-1]): # reference verts are at start and end of the list -> shift list ref_verts = ref_verts[1:] + [ref_verts[0]] if ref_verts.index(va_1) > ref_verts.index(va_2): @@ -120,6 +152,9 @@ def quad_from_edge(bm, edge_sel, context, event): mat_index = ref_faces[0].material_index smooth = ref_faces[0].smooth + if addon_prefs.quad_from_e_mat: + mat_index = bpy.context.object.active_material_index + # create quad try: if v3 == v4: @@ -150,9 +185,9 @@ def quad_from_edge(bm, edge_sel, context, event): # adjust uv-map if __name__ != '__main__': - addon_prefs = context.preferences.addons[__name__].preferences if addon_prefs.adjustuv: - for (key, uv_layer) in bm.loops.layers.uv.items(): + uv_layer = get_uv_layer(ob, bm, mat_index) + if uv_layer: uv_ori = {} for vert in [v1, v2, v3, v4]: for loop in vert.link_loops: @@ -170,6 +205,7 @@ def quad_from_edge(bm, edge_sel, context, event): # create a face from a single selected vertex, if it is an open vertex def quad_from_vertex(bm, vert_sel, context, event): + addon_prefs = context.preferences.addons[__name__].preferences ob = context.active_object me = ob.data region = context.region @@ -185,13 +221,13 @@ def quad_from_vertex(bm, vert_sel, context, event): mouse_pos = mathutils.Vector([event.mouse_region_x, event.mouse_region_y]) for a, b in itertools.combinations(edges, 2): other_verts = [vert for edge in [a, b] for vert in edge.verts \ - if not vert.select] + if not vert.select] mid_other = (other_verts[0].co.copy() + other_verts[1].co.copy()) \ - / 2 + / 2 new_pos = 2 * (mid_other - vert_sel.co.copy()) + vert_sel.co.copy() world_pos = ob.matrix_world @ new_pos screen_pos = view3d_utils.location_3d_to_region_2d(region, region_3d, - world_pos) + world_pos) dist = (mouse_pos - screen_pos).length if not min_dist or dist < min_dist[0]: min_dist = (dist, (a, b), other_verts, new_pos) @@ -209,8 +245,8 @@ def quad_from_vertex(bm, vert_sel, context, event): normal_edge = edges[1] if not normal_edge.link_faces: # no connected faces, so no need to flip the face normal - flip_align = False - if flip_align: # there is a face to which the normal can be aligned + flip_align = False + if flip_align: # there is a face to which the normal can be aligned ref_verts = [v for v in normal_edge.link_faces[0].verts] if other_verts[0] in ref_verts: va_1 = other_verts[0] @@ -219,7 +255,7 @@ def quad_from_vertex(bm, vert_sel, context, event): va_1 = vert_sel va_2 = other_verts[1] if (va_1 == ref_verts[0] and va_2 == ref_verts[-1]) or \ - (va_2 == ref_verts[0] and va_1 == ref_verts[-1]): + (va_2 == ref_verts[0] and va_1 == ref_verts[-1]): # reference verts are at start and end of the list -> shift list ref_verts = ref_verts[1:] + [ref_verts[0]] if ref_verts.index(va_1) > ref_verts.index(va_2): @@ -235,6 +271,9 @@ def quad_from_vertex(bm, vert_sel, context, event): mat_index = ref_faces[0].material_index smooth = ref_faces[0].smooth + if addon_prefs.quad_from_v_mat: + mat_index = bpy.context.object.active_material_index + # create face between all 4 vertices involved verts = [other_verts[0], vert_sel, other_verts[1], vert_new] if flip_align: @@ -250,9 +289,9 @@ def quad_from_vertex(bm, vert_sel, context, event): # adjust uv-map if __name__ != '__main__': - addon_prefs = context.preferences.addons[__name__].preferences if addon_prefs.adjustuv: - for (key, uv_layer) in bm.loops.layers.uv.items(): + uv_layer = get_uv_layer(ob, bm, mat_index) + if uv_layer: uv_others = {} uv_sel = None uv_new = None @@ -264,7 +303,7 @@ def quad_from_vertex(bm, vert_sel, context, event): break if len(uv_others) == 2: mid_other = (list(uv_others.values())[0] + - list(uv_others.values())[1]) / 2 + list(uv_others.values())[1]) / 2 for loop in vert_sel.link_loops: if loop.face.index > -1: uv_sel = loop[uv_layer].uv @@ -288,22 +327,206 @@ def quad_from_vertex(bm, vert_sel, context, event): bpy.ops.object.mode_set(mode='EDIT') +def expand_vert(self, context, event): + addon_prefs = context.preferences.addons[__name__].preferences + ob = context.active_object + obj = bpy.context.object + me = obj.data + bm = bmesh.from_edit_mesh(me) + region = context.region + region_3d = context.space_data.region_3d + rv3d = context.space_data.region_3d + + for v in bm.verts: + if v.select: + v_active = v + + try: + depth_location = v_active.co + except: + return {'CANCELLED'} + # create vert in mouse cursor location + + mouse_pos = Vector((event.mouse_region_x, event.mouse_region_y)) + location_3d = view3d_utils.region_2d_to_location_3d(region, rv3d, mouse_pos, depth_location) + + c_verts = [] + # find and select linked edges that are open (<2 faces connected) add those edge verts to c_verts list + linked = v_active.link_edges + for edges in linked: + if len(edges.link_faces) < 2: + edges.select = True + for v in edges.verts: + if v is not v_active: + c_verts.append(v) + + # Compare distance in 2d between mouse and edges middle points + screen_pos_va = view3d_utils.location_3d_to_region_2d(region, region_3d, + ob.matrix_world @ v_active.co) + screen_pos_v1 = view3d_utils.location_3d_to_region_2d(region, region_3d, + ob.matrix_world @ c_verts[0].co) + screen_pos_v2 = view3d_utils.location_3d_to_region_2d(region, region_3d, + ob.matrix_world @ c_verts[1].co) + + mid_pos_v1 = Vector(((screen_pos_va[0] + screen_pos_v1[0]) / 2, (screen_pos_va[1] + screen_pos_v1[1]) / 2)) + mid_pos_V2 = Vector(((screen_pos_va[0] + screen_pos_v2[0]) / 2, (screen_pos_va[1] + screen_pos_v2[1]) / 2)) + + dist1 = math.log10(pow((mid_pos_v1[0] - mouse_pos[0]), 2) + pow((mid_pos_v1[1] - mouse_pos[1]), 2)) + dist2 = math.log10(pow((mid_pos_V2[0] - mouse_pos[0]), 2) + pow((mid_pos_V2[1] - mouse_pos[1]), 2)) + + bm.normal_update() + bm.verts.ensure_lookup_table() + + # Deselect not needed point and create new face + if dist1 < dist2: + c_verts[1].select = False + lleft = c_verts[0].link_faces + + else: + c_verts[0].select = False + lleft = c_verts[1].link_faces + + lactive = v_active.link_faces + # lverts = lactive[0].verts + + mat_index = lactive[0].material_index + smooth = lactive[0].smooth + + for faces in lactive: + if faces in lleft: + cface = faces + if len(faces.verts) == 3: + bm.normal_update() + bmesh.update_edit_mesh(obj.data) + bpy.ops.mesh.select_all(action='DESELECT') + v_active.select = True + bpy.ops.mesh.rip_edge_move('INVOKE_DEFAULT') + return {'FINISHED'} + + lverts = cface.verts + + # create triangle with correct normal orientation + # if You looking at that part - yeah... I know. I still dont get how blender calculates normals... + + # from L to R + if dist1 < dist2: + if (lverts[0] == v_active and lverts[3] == c_verts[0]) \ + or (lverts[2] == v_active and lverts[1] == c_verts[0]) \ + or (lverts[1] == v_active and lverts[0] == c_verts[0]) \ + or (lverts[3] == v_active and lverts[2] == c_verts[0]): + v_new = bm.verts.new(v_active.co) + face_new = bm.faces.new((c_verts[0], v_new, v_active)) + + elif (lverts[1] == v_active and lverts[2] == c_verts[0]) \ + or (lverts[0] == v_active and lverts[1] == c_verts[0]) \ + or (lverts[3] == v_active and lverts[0] == c_verts[0]) \ + or (lverts[2] == v_active and lverts[3] == c_verts[0]): + v_new = bm.verts.new(v_active.co) + face_new = bm.faces.new((v_active, v_new, c_verts[0])) + + else: + pass + # from R to L + else: + if (lverts[2] == v_active and lverts[3] == c_verts[1]) \ + or (lverts[0] == v_active and lverts[1] == c_verts[1]) \ + or (lverts[1] == v_active and lverts[2] == c_verts[1]) \ + or (lverts[3] == v_active and lverts[0] == c_verts[1]): + v_new = bm.verts.new(v_active.co) + face_new = bm.faces.new((v_active, v_new, c_verts[1])) + + elif (lverts[0] == v_active and lverts[3] == c_verts[1]) \ + or (lverts[2] == v_active and lverts[1] == c_verts[1]) \ + or (lverts[1] == v_active and lverts[0] == c_verts[1]) \ + or (lverts[3] == v_active and lverts[2] == c_verts[1]): + v_new = bm.verts.new(v_active.co) + face_new = bm.faces.new((c_verts[1], v_new, v_active)) + + else: + pass + + # set smooth and mat based on starting face + if addon_prefs.tris_from_v_mat: + face_new.material_index = bpy.context.object.active_material_index + else: + face_new.material_index = mat_index + face_new.smooth = smooth + + # update normals + bpy.ops.mesh.select_all(action='DESELECT') + v_new.select = True + bm.select_history.add(v_new) + + bm.normal_update() + bmesh.update_edit_mesh(obj.data) + bpy.ops.transform.translate('INVOKE_DEFAULT') + + +def checkforconnected(conection): + obj = bpy.context.object + me = obj.data + bm = bmesh.from_edit_mesh(me) + + # Checks for number of edes or faces connected to selected vertex + for v in bm.verts: + if v.select: + v_active = v + if conection == 'faces': + linked = v_active.link_faces + elif conection == 'edges': + linked = v_active.link_edges + + bmesh.update_edit_mesh(obj.data) + return len(linked) + + # autograb preference in addons panel class F2AddonPreferences(bpy.types.AddonPreferences): bl_idname = __name__ - adjustuv: bpy.props.BoolProperty( - name = "Adjust UV", - description = "Automatically update UV unwrapping", - default = True) - autograb: bpy.props.BoolProperty( - name = "Auto Grab", - description = "Automatically puts a newly created vertex in grab mode", - default = False) + adjustuv : bpy.props.BoolProperty( + name="Adjust UV", + description="Automatically update UV unwrapping", + default=False) + autograb : bpy.props.BoolProperty( + name="Auto Grab", + description="Automatically puts a newly created vertex in grab mode", + default=True) + extendvert : bpy.props.BoolProperty( + name="Enable Extend Vert", + description="Anables a way to build tris and quads by adding verts", + default=False) + quad_from_e_mat : bpy.props.BoolProperty( + name="Quad From Edge", + description="Use active material for created face instead of close one", + default=True) + quad_from_v_mat : bpy.props.BoolProperty( + name="Quad From Vert", + description="Use active material for created face instead of close one", + default=True) + tris_from_v_mat : bpy.props.BoolProperty( + name="Tris From Vert", + description="Use active material for created face instead of close one", + default=True) + ngons_v_mat : bpy.props.BoolProperty( + name="Ngons", + description="Use active material for created face instead of close one", + default=True) def draw(self, context): layout = self.layout - layout.prop(self, "autograb") - layout.prop(self, "adjustuv") + + col = layout.column() + col.label(text="behaviours:") + col.prop(self, "autograb") + col.prop(self, "adjustuv") + col.prop(self, "extendvert") + + col = layout.column() + col.label(text="use active material when creating:") + col.prop(self, "quad_from_e_mat") + col.prop(self, "quad_from_v_mat") + col.prop(self, "tris_from_v_mat") + col.prop(self, "ngons_v_mat") class MeshF2(bpy.types.Operator): @@ -317,7 +540,14 @@ class MeshF2(bpy.types.Operator): def poll(cls, context): # check we are in mesh editmode ob = context.active_object - return(ob and ob.type == 'MESH' and context.mode == 'EDIT_MESH') + return (ob and ob.type == 'MESH' and context.mode == 'EDIT_MESH') + + def usequad(self, bm, sel, context, event): + quad_from_vertex(bm, sel, context, event) + if __name__ != '__main__': + addon_prefs = context.preferences.addons[__name__].preferences + if addon_prefs.autograb: + bpy.ops.transform.translate('INVOKE_DEFAULT') def invoke(self, context, event): bm = bmesh.from_edit_mesh(context.active_object.data) @@ -326,16 +556,30 @@ class MeshF2(bpy.types.Operator): # original 'Make Edge/Face' behaviour try: bpy.ops.mesh.edge_face_add('INVOKE_DEFAULT') + addon_prefs = context.preferences.addons[__name__].preferences + if addon_prefs.ngons_v_mat: + bpy.ops.object.material_slot_assign() except: return {'CANCELLED'} elif len(sel) == 1: # single vertex selected -> mirror vertex and create new face - quad_from_vertex(bm, sel[0], context, event) - if __name__ != '__main__': - addon_prefs = context.preferences.addons[__name__].\ - preferences - if addon_prefs.autograb: - bpy.ops.transform.translate('INVOKE_DEFAULT') + addon_prefs = context.preferences.addons[__name__].preferences + if addon_prefs.extendvert: + if checkforconnected('faces') in [2]: + if checkforconnected('edges') in [3]: + expand_vert(self, context, event) + else: + self.usequad(bm, sel[0], context, event) + + elif checkforconnected('faces') in [1]: + if checkforconnected('edges') in [2]: + expand_vert(self, context, event) + else: + self.usequad(bm, sel[0], context, event) + else: + self.usequad(bm, sel[0], context, event) + else: + self.usequad(bm, sel[0], context, event) elif len(sel) == 2: edges_sel = [ed for ed in bm.edges if ed.select] if len(edges_sel) != 1: @@ -349,11 +593,7 @@ class MeshF2(bpy.types.Operator): # registration -classes = ( - MeshF2, - F2AddonPreferences, -) - +classes = [MeshF2, F2AddonPreferences] addon_keymaps = [] -- cgit v1.2.3