diff options
author | meta-androcto <meta.androcto1@gmail.com> | 2019-06-15 07:06:26 +0300 |
---|---|---|
committer | meta-androcto <meta.androcto1@gmail.com> | 2019-06-15 07:06:26 +0300 |
commit | 682d48cefcc35a6784d23c59b5d3333ffb1415c4 (patch) | |
tree | 0c791215b66b7a45484b995d7563a6af759a8bb0 /mesh_tools/mesh_filletplus.py | |
parent | c7b5ffcfd924a358b37aa16b2bb576b6244fe1d3 (diff) |
mesh_tools: restore to release: T63750 9e99e90f08c9
Diffstat (limited to 'mesh_tools/mesh_filletplus.py')
-rw-r--r-- | mesh_tools/mesh_filletplus.py | 430 |
1 files changed, 430 insertions, 0 deletions
diff --git a/mesh_tools/mesh_filletplus.py b/mesh_tools/mesh_filletplus.py new file mode 100644 index 00000000..01f2f67e --- /dev/null +++ b/mesh_tools/mesh_filletplus.py @@ -0,0 +1,430 @@ +# -*- coding: utf-8 -*- + +# ##### END 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": "FilletPlus", + "author": "Gert De Roost - original by zmj100", + "version": (0, 4, 3), + "blender": (2, 80, 0), + "location": "View3D > Tool Shelf", + "description": "", + "warning": "", + "wiki_url": "", + "category": "Mesh"} + + +import bpy +from bpy.props import ( + FloatProperty, + IntProperty, + BoolProperty, + ) +from bpy.types import Operator +import bmesh +from mathutils import Matrix +from math import ( + cos, pi, sin, + degrees, tan, + ) + + +def list_clear_(l): + if l: + del l[:] + return l + + +def get_adj_v_(list_): + tmp = {} + for i in list_: + try: + tmp[i[0]].append(i[1]) + except KeyError: + tmp[i[0]] = [i[1]] + try: + tmp[i[1]].append(i[0]) + except KeyError: + tmp[i[1]] = [i[0]] + return tmp + +class f_buf(): + # one of the angles was not 0 or 180 + check = False + + +def fillets(list_0, startv, vertlist, face, adj, n, out, flip, radius): + try: + dict_0 = get_adj_v_(list_0) + list_1 = [[dict_0[i][0], i, dict_0[i][1]] for i in dict_0 if (len(dict_0[i]) == 2)][0] + list_3 = [] + for elem in list_1: + list_3.append(bm.verts[elem]) + list_2 = [] + + p_ = list_3[1] + p = (list_3[1].co).copy() + p1 = (list_3[0].co).copy() + p2 = (list_3[2].co).copy() + + vec1 = p - p1 + vec2 = p - p2 + + ang = vec1.angle(vec2, any) + check_angle = round(degrees(ang)) + + if check_angle == 180 or check_angle == 0.0: + return False + else: + f_buf.check = True + + opp = adj + + if radius is False: + h = adj * (1 / cos(ang * 0.5)) + adj_ = adj + elif radius is True: + h = opp / sin(ang * 0.5) + adj_ = opp / tan(ang * 0.5) + + p3 = p - (vec1.normalized() * adj_) + p4 = p - (vec2.normalized() * adj_) + rp = p - ((p - ((p3 + p4) * 0.5)).normalized() * h) + + vec3 = rp - p3 + vec4 = rp - p4 + + axis = vec1.cross(vec2) + + if out is False: + if flip is False: + rot_ang = vec3.angle(vec4) + elif flip is True: + rot_ang = vec1.angle(vec2) + elif out is True: + rot_ang = (2 * pi) - vec1.angle(vec2) + + for j in range(n + 1): + new_angle = rot_ang * j / n + mtrx = Matrix.Rotation(new_angle, 3, axis) + if out is False: + if flip is False: + tmp = p4 - rp + tmp1 = mtrx @ tmp + tmp2 = tmp1 + rp + elif flip is True: + p3 = p - (vec1.normalized() * opp) + tmp = p3 - p + tmp1 = mtrx @ tmp + tmp2 = tmp1 + p + elif out is True: + p4 = p - (vec2.normalized() * opp) + tmp = p4 - p + tmp1 = mtrx @ tmp + tmp2 = tmp1 + p + + v = bm.verts.new(tmp2) + list_2.append(v) + + if flip is True: + list_3[1:2] = list_2 + else: + list_2.reverse() + list_3[1:2] = list_2 + + list_clear_(list_2) + + n1 = len(list_3) + + for t in range(n1 - 1): + bm.edges.new([list_3[t], list_3[(t + 1) % n1]]) + + v = bm.verts.new(p) + bm.edges.new([v, p_]) + + bm.edges.ensure_lookup_table() + + if face is not None: + for l in face.loops: + if l.vert == list_3[0]: + startl = l + break + vertlist2 = [] + + if startl.link_loop_next.vert == startv: + l = startl.link_loop_prev + while len(vertlist) > 0: + vertlist2.insert(0, l.vert) + vertlist.pop(vertlist.index(l.vert)) + l = l.link_loop_prev + else: + l = startl.link_loop_next + while len(vertlist) > 0: + vertlist2.insert(0, l.vert) + vertlist.pop(vertlist.index(l.vert)) + l = l.link_loop_next + + for v in list_3: + vertlist2.append(v) + bm.faces.new(vertlist2) + if startv.is_valid: + bm.verts.remove(startv) + else: + print("\n[Function fillets Error]\n" + "Starting vertex (startv var) couldn't be removed\n") + return False + bm.verts.ensure_lookup_table() + bm.edges.ensure_lookup_table() + bm.faces.ensure_lookup_table() + list_3[1].select = 1 + list_3[-2].select = 1 + bm.edges.get([list_3[0], list_3[1]]).select = 1 + bm.edges.get([list_3[-1], list_3[-2]]).select = 1 + bm.verts.index_update() + bm.edges.index_update() + bm.faces.index_update() + + me.update(calc_edges=True, calc_loop_triangles=True) + bmesh.ops.recalc_face_normals(bm, faces=bm.faces) + + except Exception as e: + print("\n[Function fillets Error]\n{}\n".format(e)) + return False + + +def do_filletplus(self, pair): + is_finished = True + try: + startv = None + global inaction + global flip + list_0 = [list([e.verts[0].index, e.verts[1].index]) for e in pair] + + vertset = set([]) + bm.verts.ensure_lookup_table() + bm.edges.ensure_lookup_table() + bm.faces.ensure_lookup_table() + vertset.add(bm.verts[list_0[0][0]]) + vertset.add(bm.verts[list_0[0][1]]) + vertset.add(bm.verts[list_0[1][0]]) + vertset.add(bm.verts[list_0[1][1]]) + + v1, v2, v3 = vertset + + if len(list_0) != 2: + self.report({'WARNING'}, "Two adjacent edges must be selected") + is_finished = False + else: + inaction = 1 + vertlist = [] + found = 0 + for f in v1.link_faces: + if v2 in f.verts and v3 in f.verts: + found = 1 + if not found: + for v in [v1, v2, v3]: + if v.index in list_0[0] and v.index in list_0[1]: + startv = v + face = None + else: + for f in v1.link_faces: + if v2 in f.verts and v3 in f.verts: + for v in f.verts: + if not(v in vertset): + vertlist.append(v) + if (v in vertset and v.link_loops[0].link_loop_prev.vert in vertset and + v.link_loops[0].link_loop_next.vert in vertset): + startv = v + face = f + if out is True: + flip = False + if startv: + fills = fillets(list_0, startv, vertlist, face, adj, n, out, flip, radius) + if not fills: + is_finished = False + else: + is_finished = False + except Exception as e: + print("\n[Function do_filletplus Error]\n{}\n".format(e)) + is_finished = False + return is_finished + + +def check_is_not_coplanar(bm_data): + from mathutils import Vector + check = False + angles, norm_angle = 0, 0 + z_vec = Vector((0, 0, 1)) + try: + bm_data.faces.ensure_lookup_table() + + for f in bm_data.faces: + norm_angle = f.normal.angle(z_vec) + if angles == 0: + angles = norm_angle + if angles != norm_angle: + check = True + break + except Exception as e: + print("\n[Function check_is_not_coplanar Error]\n{}\n".format(e)) + check = True + return check + + +# Operator + +class MESH_OT_fillet_plus(Operator): + bl_idname = "mesh.fillet_plus" + bl_label = "Fillet Plus" + bl_description = ("Fillet adjoining edges\n" + "Note: Works on a mesh whose all faces share the same normal") + bl_options = {"REGISTER", "UNDO"} + + adj: FloatProperty( + name="", + description="Size of the filleted corners", + default=0.1, + min=0.00001, max=100.0, + step=1, + precision=3 + ) + n: IntProperty( + name="", + description="Subdivision of the filleted corners", + default=3, + min=1, max=50, + step=1 + ) + out: BoolProperty( + name="Outside", + description="Fillet towards outside", + default=False + ) + flip: BoolProperty( + name="Flip", + description="Flip the direction of the Fillet\n" + "Only available if Outside option is not active", + default=False + ) + radius: BoolProperty( + name="Radius", + description="Use radius for the size of the filleted corners", + default=False + ) + + @classmethod + def poll(cls, context): + obj = context.active_object + return (obj and obj.type == 'MESH' and context.mode == 'EDIT_MESH') + + def draw(self, context): + layout = self.layout + + if f_buf.check is False: + layout.label(text="Angle is equal to 0 or 180", icon="INFO") + layout.label(text="Can not fillet", icon="BLANK1") + else: + layout.prop(self, "radius") + if self.radius is True: + layout.label(text="Radius:") + elif self.radius is False: + layout.label(text="Distance:") + layout.prop(self, "adj") + layout.label(text="Number of sides:") + layout.prop(self, "n") + + if self.n > 1: + row = layout.row(align=False) + row.prop(self, "out") + if self.out is False: + row.prop(self, "flip") + + def execute(self, context): + global inaction + global bm, me, adj, n, out, flip, radius, f_buf + + adj = self.adj + n = self.n + out = self.out + flip = self.flip + radius = self.radius + + inaction = 0 + f_buf.check = False + + ob_act = context.active_object + try: + me = ob_act.data + bm = bmesh.from_edit_mesh(me) + warn_obj = bool(check_is_not_coplanar(bm)) + if warn_obj is False: + tempset = set([]) + bm.verts.ensure_lookup_table() + bm.edges.ensure_lookup_table() + bm.faces.ensure_lookup_table() + for v in bm.verts: + if v.select and v.is_boundary: + tempset.add(v) + for v in tempset: + edgeset = set([]) + for e in v.link_edges: + if e.select and e.is_boundary: + edgeset.add(e) + if len(edgeset) == 2: + is_finished = do_filletplus(self, edgeset) + if not is_finished: + break + + if inaction == 1: + bpy.ops.mesh.select_all(action="DESELECT") + for v in bm.verts: + if len(v.link_edges) == 0: + bm.verts.remove(v) + bpy.ops.object.editmode_toggle() + bpy.ops.object.editmode_toggle() + else: + self.report({'WARNING'}, "Filletplus operation could not be performed") + return {'CANCELLED'} + else: + self.report({'WARNING'}, "Mesh is not a coplanar surface. Operation cancelled") + return {'CANCELLED'} + except: + self.report({'WARNING'}, "Filletplus operation could not be performed") + return {'CANCELLED'} + + return {'FINISHED'} + +# define classes for registration +classes = ( + MESH_OT_fillet_plus, + ) + +# registering and menu integration +def register(): + for cls in classes: + bpy.utils.register_class(cls) + + +# unregistering and removing menus +def unregister(): + for cls in reversed(classes): + bpy.utils.unregister_class(cls) + +if __name__ == "__main__": + register() |