1# ##### BEGIN GPL LICENSE BLOCK #####
2#
3#  This program is free software; you can redistribute it and/or
4#  modify it under the terms of the GNU General Public License
5#  as published by the Free Software Foundation; either version 2
6#  of the License, or (at your option) any later version.
7#
8#  This program is distributed in the hope that it will be useful,
9#  but WITHOUT ANY WARRANTY; without even the implied warranty of
10#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11#  GNU General Public License for more details.
12#
13#  You should have received a copy of the GNU General Public License
14#  along with this program; if not, write to the Free Software Foundation,
15#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16#
17# ##### END GPL LICENSE BLOCK #####
18# Contributed to by:
19# meta-androcto,  Hidesato Ikeya, zmj100, Gert De Roost, TrumanBlending, PKHG, #
20# Oscurart, Greg, Stanislav Blinov, komi3D, BlenderLab, Paul Marshall (brikbot), #
21# metalliandy, macouno, CoDEmanX, dustractor, Liero, lijenstina, Germano Cavalcante #
22# Pistiwique, Jimmy Hazevoet #
23
24bl_info = {
25    "name": "Edit Mesh Tools",
26    "author": "Meta-Androcto",
27    "version": (0, 3, 6),
28    "blender": (2, 80, 0),
29    "location": "View3D > Sidebar > Edit Tab / Edit Mode Context Menu",
30    "warning": "",
31    "description": "Mesh modelling toolkit. Several tools to aid modelling",
32    "doc_url": "{BLENDER_MANUAL_URL}/addons/mesh/edit_mesh_tools.html",
33    "category": "Mesh",
34}
35
36# Import From Files
37if "bpy" in locals():
38    import importlib
39    importlib.reload(mesh_offset_edges)
40    importlib.reload(split_solidify)
41    importlib.reload(mesh_filletplus)
42    importlib.reload(mesh_vertex_chamfer)
43    importlib.reload(random_vertices)
44#    importlib.reload(mesh_extrude_and_reshape)
45    importlib.reload(mesh_edge_roundifier)
46    importlib.reload(mesh_edgetools)
47    importlib.reload(mesh_edges_floor_plan)
48    importlib.reload(mesh_edges_length)
49    importlib.reload(pkhg_faces)
50    importlib.reload(mesh_cut_faces)
51    importlib.reload(mesh_relax)
52
53else:
54    from . import mesh_offset_edges
55    from . import split_solidify
56    from . import mesh_filletplus
57    from . import mesh_vertex_chamfer
58    from . import random_vertices
59#    from . import mesh_extrude_and_reshape
60    from . import mesh_edge_roundifier
61    from . import mesh_edgetools
62    from . import mesh_edges_floor_plan
63    from . import mesh_edges_length
64    from . import pkhg_faces
65    from . import mesh_cut_faces
66    from . import mesh_relax
67
68
69import bmesh
70import bpy
71import collections
72import mathutils
73import random
74from math import (
75        sin, cos, tan,
76        degrees, radians, pi,
77        )
78from random import gauss
79from mathutils import Matrix, Euler, Vector
80from bpy_extras import view3d_utils
81from bpy.types import (
82        Operator,
83        Menu,
84        Panel,
85        PropertyGroup,
86        AddonPreferences,
87        )
88from bpy.props import (
89        BoolProperty,
90        BoolVectorProperty,
91        EnumProperty,
92        FloatProperty,
93        FloatVectorProperty,
94        IntVectorProperty,
95        PointerProperty,
96        StringProperty,
97        IntProperty
98        )
99
100# ########################################
101# ##### General functions ################
102# ########################################
103
104
105# Multi extrude
106def gloc(self, r):
107    return Vector((self.offx, self.offy, self.offz))
108
109
110def vloc(self, r):
111    random.seed(self.ran + r)
112    return self.off * (1 + gauss(0, self.var1 / 3))
113
114
115def nrot(self, n):
116    return Euler((radians(self.nrotx) * n[0],
117                  radians(self.nroty) * n[1],
118                  radians(self.nrotz) * n[2]), 'XYZ')
119
120
121def vrot(self, r):
122    random.seed(self.ran + r)
123    return Euler((radians(self.rotx) + gauss(0, self.var2 / 3),
124                  radians(self.roty) + gauss(0, self.var2 / 3),
125                  radians(self.rotz) + gauss(0, self.var2 / 3)), 'XYZ')
126
127
128def vsca(self, r):
129    random.seed(self.ran + r)
130    return self.sca * (1 + gauss(0, self.var3 / 3))
131
132
133class ME_OT_MExtrude(Operator):
134    bl_idname = "object.mextrude"
135    bl_label = "Multi Extrude"
136    bl_description = ("Extrude selected Faces with Rotation,\n"
137                      "Scaling, Variation, Randomization")
138    bl_options = {"REGISTER", "UNDO", "PRESET"}
139
140    off : FloatProperty(
141            name="Offset",
142            soft_min=0.001, soft_max=10,
143            min=-100, max=100,
144            default=1.0,
145            description="Translation"
146            )
147    offx : FloatProperty(
148            name="Loc X",
149            soft_min=-10.0, soft_max=10.0,
150            min=-100.0, max=100.0,
151            default=0.0,
152            description="Global Translation X"
153            )
154    offy : FloatProperty(
155            name="Loc Y",
156            soft_min=-10.0, soft_max=10.0,
157            min=-100.0, max=100.0,
158            default=0.0,
159            description="Global Translation Y"
160            )
161    offz : FloatProperty(
162            name="Loc Z",
163            soft_min=-10.0, soft_max=10.0,
164            min=-100.0, max=100.0,
165            default=0.0,
166            description="Global Translation Z"
167            )
168    rotx : FloatProperty(
169            name="Rot X",
170            min=-85, max=85,
171            soft_min=-30, soft_max=30,
172            default=0,
173            description="X Rotation"
174            )
175    roty : FloatProperty(
176            name="Rot Y",
177            min=-85, max=85,
178            soft_min=-30,
179            soft_max=30,
180            default=0,
181            description="Y Rotation"
182            )
183    rotz : FloatProperty(
184            name="Rot Z",
185            min=-85, max=85,
186            soft_min=-30, soft_max=30,
187            default=-0,
188            description="Z Rotation"
189            )
190    nrotx : FloatProperty(
191            name="N Rot X",
192            min=-85, max=85,
193            soft_min=-30, soft_max=30,
194            default=0,
195            description="Normal X Rotation"
196            )
197    nroty : FloatProperty(
198            name="N Rot Y",
199            min=-85, max=85,
200            soft_min=-30, soft_max=30,
201            default=0,
202            description="Normal Y Rotation"
203            )
204    nrotz : FloatProperty(
205            name="N Rot Z",
206            min=-85, max=85,
207            soft_min=-30, soft_max=30,
208            default=-0,
209            description="Normal Z Rotation"
210            )
211    sca : FloatProperty(
212            name="Scale",
213            min=0.01, max=10,
214            soft_min=0.5, soft_max=1.5,
215            default=1.0,
216            description="Scaling of the selected faces after extrusion"
217            )
218    var1 : FloatProperty(
219            name="Offset Var", min=-10, max=10,
220            soft_min=-1, soft_max=1,
221            default=0,
222            description="Offset variation"
223            )
224    var2 : FloatProperty(
225            name="Rotation Var",
226            min=-10, max=10,
227            soft_min=-1, soft_max=1,
228            default=0,
229            description="Rotation variation"
230            )
231    var3 : FloatProperty(
232            name="Scale Noise",
233            min=-10, max=10,
234            soft_min=-1, soft_max=1,
235            default=0,
236            description="Scaling noise"
237            )
238    var4 : IntProperty(
239            name="Probability",
240            min=0, max=100,
241            default=100,
242            description="Probability, chance of extruding a face"
243            )
244    num : IntProperty(
245            name="Repeat",
246            min=1, max=500,
247            soft_max=100,
248            default=1,
249            description="Repetitions"
250            )
251    ran : IntProperty(
252            name="Seed",
253            min=-9999, max=9999,
254            default=0,
255            description="Seed to feed random values"
256            )
257    opt1 : BoolProperty(
258            name="Polygon coordinates",
259            default=True,
260            description="Polygon coordinates, Object coordinates"
261            )
262    opt2 : BoolProperty(
263            name="Proportional offset",
264            default=False,
265            description="Scale * Offset"
266            )
267    opt3 : BoolProperty(
268            name="Per step rotation noise",
269            default=False,
270            description="Per step rotation noise, Initial rotation noise"
271            )
272    opt4 : BoolProperty(
273            name="Per step scale noise",
274            default=False,
275            description="Per step scale noise, Initial scale noise"
276            )
277
278    @classmethod
279    def poll(cls, context):
280        obj = context.object
281        return (obj and obj.type == 'MESH')
282
283    def draw(self, context):
284        layout = self.layout
285        col = layout.column(align=True)
286        col.label(text="Transformations:")
287        col.prop(self, "off", slider=True)
288        col.prop(self, "offx", slider=True)
289        col.prop(self, "offy", slider=True)
290        col.prop(self, "offz", slider=True)
291
292        col = layout.column(align=True)
293        col.prop(self, "rotx", slider=True)
294        col.prop(self, "roty", slider=True)
295        col.prop(self, "rotz", slider=True)
296        col.prop(self, "nrotx", slider=True)
297        col.prop(self, "nroty", slider=True)
298        col.prop(self, "nrotz", slider=True)
299        col = layout.column(align=True)
300        col.prop(self, "sca", slider=True)
301
302        col = layout.column(align=True)
303        col.label(text="Variation settings:")
304        col.prop(self, "var1", slider=True)
305        col.prop(self, "var2", slider=True)
306        col.prop(self, "var3", slider=True)
307        col.prop(self, "var4", slider=True)
308        col.prop(self, "ran")
309        col = layout.column(align=False)
310        col.prop(self, 'num')
311
312        col = layout.column(align=True)
313        col.label(text="Options:")
314        col.prop(self, "opt1")
315        col.prop(self, "opt2")
316        col.prop(self, "opt3")
317        col.prop(self, "opt4")
318
319    def execute(self, context):
320        obj = bpy.context.object
321        om = obj.mode
322        bpy.context.tool_settings.mesh_select_mode = [False, False, True]
323        origin = Vector([0.0, 0.0, 0.0])
324
325        # bmesh operations
326        bpy.ops.object.mode_set()
327        bm = bmesh.new()
328        bm.from_mesh(obj.data)
329        sel = [f for f in bm.faces if f.select]
330
331        after = []
332
333        # faces loop
334        for i, of in enumerate(sel):
335            nro = nrot(self, of.normal)
336            off = vloc(self, i)
337            loc = gloc(self, i)
338            of.normal_update()
339
340            # initial rotation noise
341            if self.opt3 is False:
342                rot = vrot(self, i)
343            # initial scale noise
344            if self.opt4 is False:
345                s = vsca(self, i)
346
347            # extrusion loop
348            for r in range(self.num):
349                # random probability % for extrusions
350                if self.var4 > int(random.random() * 100):
351                    nf = of.copy()
352                    nf.normal_update()
353                    no = nf.normal.copy()
354
355                    # face/obj coordinates
356                    if self.opt1 is True:
357                        ce = nf.calc_center_bounds()
358                    else:
359                        ce = origin
360
361                    # per step rotation noise
362                    if self.opt3 is True:
363                        rot = vrot(self, i + r)
364                    # per step scale noise
365                    if self.opt4 is True:
366                        s = vsca(self, i + r)
367
368                    # proportional, scale * offset
369                    if self.opt2 is True:
370                        off = s * off
371
372                    for v in nf.verts:
373                        v.co -= ce
374                        v.co.rotate(nro)
375                        v.co.rotate(rot)
376                        v.co += ce + loc + no * off
377                        v.co = v.co.lerp(ce, 1 - s)
378
379                    # extrude code from TrumanBlending
380                    for a, b in zip(of.loops, nf.loops):
381                        sf = bm.faces.new((a.vert, a.link_loop_next.vert,
382                                           b.link_loop_next.vert, b.vert))
383                        sf.normal_update()
384                    bm.faces.remove(of)
385                    of = nf
386
387            after.append(of)
388
389        for v in bm.verts:
390            v.select = False
391        for e in bm.edges:
392            e.select = False
393
394        for f in after:
395            if f not in sel:
396                f.select = True
397            else:
398                f.select = False
399
400        bm.to_mesh(obj.data)
401        obj.data.update()
402
403        # restore user settings
404        bpy.ops.object.mode_set(mode=om)
405
406        if not len(sel):
407            self.report({"WARNING"},
408                        "No suitable Face selection found. Operation cancelled")
409            return {'CANCELLED'}
410
411        return {'FINISHED'}
412
413# Face inset fillet
414def edit_mode_out():
415    bpy.ops.object.mode_set(mode='OBJECT')
416
417
418def edit_mode_in():
419    bpy.ops.object.mode_set(mode='EDIT')
420
421
422def angle_rotation(rp, q, axis, angle):
423    # returns the vector made by the rotation of the vector q
424    # rp by angle around axis and then adds rp
425
426    return (Matrix.Rotation(angle, 3, axis) @ (q - rp)) + rp
427
428
429def face_inset_fillet(bme, face_index_list, inset_amount, distance,
430                      number_of_sides, out, radius, type_enum, kp):
431    list_del = []
432
433    for faceindex in face_index_list:
434
435        bme.faces.ensure_lookup_table()
436        # loops through the faces...
437        f = bme.faces[faceindex]
438        f.select_set(False)
439        list_del.append(f)
440        f.normal_update()
441        vertex_index_list = [v.index for v in f.verts]
442        dict_0 = {}
443        orientation_vertex_list = []
444        n = len(vertex_index_list)
445        for i in range(n):
446            # loops through the vertices
447            dict_0[i] = []
448            bme.verts.ensure_lookup_table()
449            p = (bme.verts[vertex_index_list[i]].co).copy()
450            p1 = (bme.verts[vertex_index_list[(i - 1) % n]].co).copy()
451            p2 = (bme.verts[vertex_index_list[(i + 1) % n]].co).copy()
452            # copies some vert coordinates, always the 3 around i
453            dict_0[i].append(bme.verts[vertex_index_list[i]])
454            # appends the bmesh vert of the appropriate index to the dict
455            vec1 = p - p1
456            vec2 = p - p2
457            # vectors for the other corner points to the cornerpoint
458            # corresponding to i / p
459            angle = vec1.angle(vec2)
460
461            adj = inset_amount / tan(angle * 0.5)
462            h = (adj ** 2 + inset_amount ** 2) ** 0.5
463            if round(degrees(angle)) == 180 or round(degrees(angle)) == 0.0:
464                # if the corner is a straight line...
465                # I think this creates some new points...
466                if out is True:
467                    val = ((f.normal).normalized() * inset_amount)
468                else:
469                    val = -((f.normal).normalized() * inset_amount)
470                p6 = angle_rotation(p, p + val, vec1, radians(90))
471            else:
472                # if the corner is an actual corner
473                val = ((f.normal).normalized() * h)
474                if out is True:
475                    # this -(p - (vec2.normalized() * adj))) is just the freaking axis afaik...
476                    p6 = angle_rotation(
477                                p, p + val,
478                                -(p - (vec2.normalized() * adj)),
479                                -radians(90)
480                                )
481                else:
482                    p6 = angle_rotation(
483                                p, p - val,
484                                ((p - (vec1.normalized() * adj)) - (p - (vec2.normalized() * adj))),
485                                -radians(90)
486                                )
487
488                orientation_vertex_list.append(p6)
489
490        new_inner_face = []
491        orientation_vertex_list_length = len(orientation_vertex_list)
492        ovll = orientation_vertex_list_length
493
494        for j in range(ovll):
495            q = orientation_vertex_list[j]
496            q1 = orientation_vertex_list[(j - 1) % ovll]
497            q2 = orientation_vertex_list[(j + 1) % ovll]
498            # again, these are just vectors between somewhat displaced corner vertices
499            vec1_ = q - q1
500            vec2_ = q - q2
501            ang_ = vec1_.angle(vec2_)
502
503            # the angle between them
504            if round(degrees(ang_)) == 180 or round(degrees(ang_)) == 0.0:
505                # again... if it's really a line...
506                v = bme.verts.new(q)
507                new_inner_face.append(v)
508                dict_0[j].append(v)
509            else:
510                # s.a.
511                if radius is False:
512                    h_ = distance * (1 / cos(ang_ * 0.5))
513                    d = distance
514                elif radius is True:
515                    h_ = distance / sin(ang_ * 0.5)
516                    d = distance / tan(ang_ * 0.5)
517                # max(d) is vec1_.magnitude * 0.5
518                # or vec2_.magnitude * 0.5 respectively
519
520                # only functional difference v
521                if d > vec1_.magnitude * 0.5:
522                    d = vec1_.magnitude * 0.5
523
524                if d > vec2_.magnitude * 0.5:
525                    d = vec2_.magnitude * 0.5
526                # only functional difference ^
527
528                q3 = q - (vec1_.normalized() * d)
529                q4 = q - (vec2_.normalized() * d)
530                # these are new verts somewhat offset from the corners
531                rp_ = q - ((q - ((q3 + q4) * 0.5)).normalized() * h_)
532                # reference point inside the curvature
533                axis_ = vec1_.cross(vec2_)
534                # this should really be just the face normal
535                vec3_ = rp_ - q3
536                vec4_ = rp_ - q4
537                rot_ang = vec3_.angle(vec4_)
538                cornerverts = []
539
540                for o in range(number_of_sides + 1):
541                    # this calculates the actual new vertices
542                    q5 = angle_rotation(rp_, q4, axis_, rot_ang * o / number_of_sides)
543                    v = bme.verts.new(q5)
544
545                    # creates new bmesh vertices from it
546                    bme.verts.index_update()
547
548                    dict_0[j].append(v)
549                    cornerverts.append(v)
550
551                cornerverts.reverse()
552                new_inner_face.extend(cornerverts)
553
554        if out is False:
555            f = bme.faces.new(new_inner_face)
556            f.select_set(True)
557        elif out is True and kp is True:
558            f = bme.faces.new(new_inner_face)
559            f.select_set(True)
560
561        n2_ = len(dict_0)
562        # these are the new side faces, those that don't depend on cornertype
563        for o in range(n2_):
564            list_a = dict_0[o]
565            list_b = dict_0[(o + 1) % n2_]
566            bme.faces.new([list_a[0], list_b[0], list_b[-1], list_a[1]])
567            bme.faces.index_update()
568        # cornertype 1 - ngon faces
569        if type_enum == 'opt0':
570            for k in dict_0:
571                if len(dict_0[k]) > 2:
572                    bme.faces.new(dict_0[k])
573                    bme.faces.index_update()
574        # cornertype 2 - triangulated faces
575        if type_enum == 'opt1':
576            for k_ in dict_0:
577                q_ = dict_0[k_][0]
578                dict_0[k_].pop(0)
579                n3_ = len(dict_0[k_])
580                for kk in range(n3_ - 1):
581                    bme.faces.new([dict_0[k_][kk], dict_0[k_][(kk + 1) % n3_], q_])
582                    bme.faces.index_update()
583
584    del_ = [bme.faces.remove(f) for f in list_del]
585
586    if del_:
587        del del_
588
589
590# Operator
591
592class MESH_OT_face_inset_fillet(Operator):
593    bl_idname = "mesh.face_inset_fillet"
594    bl_label = "Face Inset Fillet"
595    bl_description = ("Inset selected and Fillet (make round) the corners \n"
596                     "of the newly created Faces")
597    bl_options = {"REGISTER", "UNDO"}
598
599    # inset amount
600    inset_amount : bpy.props.FloatProperty(
601            name="Inset amount",
602            description="Define the size of the Inset relative to the selection",
603            default=0.04,
604            min=0, max=100.0,
605            step=1,
606            precision=3
607            )
608    # number of sides
609    number_of_sides : bpy.props.IntProperty(
610            name="Number of sides",
611            description="Define the roundness of the corners by specifying\n"
612                        "the subdivision count",
613            default=4,
614            min=1, max=100,
615            step=1
616            )
617    distance : bpy.props.FloatProperty(
618            name="",
619            description="Use distance or radius for corners' size calculation",
620            default=0.04,
621            min=0.00001, max=100.0,
622            step=1,
623            precision=3
624            )
625    out : bpy.props.BoolProperty(
626            name="Outside",
627            description="Inset the Faces outwards in relation to the selection\n"
628                        "Note: depending on the geometry, can give unsatisfactory results",
629            default=False
630            )
631    radius : bpy.props.BoolProperty(
632            name="Radius",
633            description="Use radius for corners' size calculation",
634            default=False
635            )
636    type_enum : bpy.props.EnumProperty(
637            items=[('opt0', "N-gon", "N-gon corners - Keep the corner Faces uncut"),
638                   ('opt1', "Triangle", "Triangulate corners")],
639            name="Corner Type",
640            default="opt0"
641            )
642    kp : bpy.props.BoolProperty(
643            name="Keep faces",
644            description="Do not delete the inside Faces\n"
645                        "Only available if the Out option is checked",
646            default=False
647            )
648
649    def draw(self, context):
650        layout = self.layout
651
652        layout.label(text="Corner Type:")
653
654        row = layout.row()
655        row.prop(self, "type_enum", text="")
656
657        row = layout.row(align=True)
658        row.prop(self, "out")
659
660        if self.out is True:
661            row.prop(self, "kp")
662
663        row = layout.row()
664        row.prop(self, "inset_amount")
665
666        row = layout.row()
667        row.prop(self, "number_of_sides")
668
669        row = layout.row()
670        row.prop(self, "radius")
671
672        row = layout.row()
673        dist_rad = "Radius" if self.radius else "Distance"
674        row.prop(self, "distance", text=dist_rad)
675
676    def execute(self, context):
677        # this really just prepares everything for the main function
678        inset_amount = self.inset_amount
679        number_of_sides = self.number_of_sides
680        distance = self.distance
681        out = self.out
682        radius = self.radius
683        type_enum = self.type_enum
684        kp = self.kp
685
686        edit_mode_out()
687        ob_act = context.active_object
688        bme = bmesh.new()
689        bme.from_mesh(ob_act.data)
690        # this
691        face_index_list = [f.index for f in bme.faces if f.select and f.is_valid]
692
693        if len(face_index_list) == 0:
694            self.report({'WARNING'},
695                        "No suitable Face selection found. Operation cancelled")
696            edit_mode_in()
697
698            return {'CANCELLED'}
699
700        elif len(face_index_list) != 0:
701            face_inset_fillet(bme, face_index_list,
702                              inset_amount, distance, number_of_sides,
703                              out, radius, type_enum, kp)
704
705        bme.to_mesh(ob_act.data)
706        edit_mode_in()
707
708        return {'FINISHED'}
709
710# ********** Edit Multiselect **********
711class VIEW3D_MT_Edit_MultiMET(Menu):
712    bl_label = "Multi Select"
713
714    def draw(self, context):
715        layout = self.layout
716        layout.operator_context = 'INVOKE_REGION_WIN'
717
718        layout.operator("multiedit.allselect", text="All Select Modes", icon='RESTRICT_SELECT_OFF')
719
720
721# Select Tools
722class VIEW3D_MT_Select_Vert(Menu):
723    bl_label = "Select Vert"
724
725    def draw(self, context):
726        layout = self.layout
727        layout.operator_context = 'INVOKE_REGION_WIN'
728
729        layout.operator("multiedit.vertexselect", text="Vertex Select Mode", icon='VERTEXSEL')
730        layout.operator("multiedit.vertedgeselect", text="Vert & Edge Select", icon='EDGESEL')
731        layout.operator("multiedit.vertfaceselect", text="Vert & Face Select", icon='FACESEL')
732
733
734class VIEW3D_MT_Select_Edge(Menu):
735    bl_label = "Select Edge"
736
737    def draw(self, context):
738        layout = self.layout
739        layout.operator_context = 'INVOKE_REGION_WIN'
740
741        layout.operator("multiedit.edgeselect", text="Edge Select Mode", icon='EDGESEL')
742        layout.operator("multiedit.vertedgeselect", text="Edge & Vert Select", icon='VERTEXSEL')
743        layout.operator("multiedit.edgefaceselect", text="Edge & Face Select", icon='FACESEL')
744
745
746class VIEW3D_MT_Select_Face(Menu):
747    bl_label = "Select Face"
748
749    def draw(self, context):
750        layout = self.layout
751        layout.operator_context = 'INVOKE_REGION_WIN'
752
753        layout.operator("multiedit.faceselect", text="Face Select Mode", icon='FACESEL')
754        layout.operator("multiedit.vertfaceselect", text="Face & Vert Select", icon='VERTEXSEL')
755        layout.operator("multiedit.edgefaceselect", text="Face & Edge Select", icon='EDGESEL')
756
757
758 # multiple edit select modes.
759class VIEW3D_OT_multieditvertex(Operator):
760    bl_idname = "multiedit.vertexselect"
761    bl_label = "Vertex Mode"
762    bl_description = "Vert Select Mode On"
763    bl_options = {'REGISTER', 'UNDO'}
764
765    def execute(self, context):
766        if context.object.mode != "EDIT":
767            bpy.ops.object.mode_set(mode="EDIT")
768            bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT')
769        if bpy.ops.mesh.select_mode != "EDGE, FACE":
770            bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT')
771            return {'FINISHED'}
772
773
774class VIEW3D_OT_multieditedge(Operator):
775    bl_idname = "multiedit.edgeselect"
776    bl_label = "Edge Mode"
777    bl_description = "Edge Select Mode On"
778    bl_options = {'REGISTER', 'UNDO'}
779
780    def execute(self, context):
781        if context.object.mode != "EDIT":
782            bpy.ops.object.mode_set(mode="EDIT")
783            bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE')
784        if bpy.ops.mesh.select_mode != "VERT, FACE":
785            bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE')
786            return {'FINISHED'}
787
788
789class VIEW3D_OT_multieditface(Operator):
790    bl_idname = "multiedit.faceselect"
791    bl_label = "Multiedit Face"
792    bl_description = "Face Select Mode On"
793    bl_options = {'REGISTER', 'UNDO'}
794
795    def execute(self, context):
796        if context.object.mode != "EDIT":
797            bpy.ops.object.mode_set(mode="EDIT")
798            bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE')
799        if bpy.ops.mesh.select_mode != "VERT, EDGE":
800            bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE')
801            return {'FINISHED'}
802
803class VIEW3D_OT_multieditvertedge(Operator):
804    bl_idname = "multiedit.vertedgeselect"
805    bl_label = "Multiedit Face"
806    bl_description = "Vert & Edge Select Modes On"
807    bl_options = {'REGISTER', 'UNDO'}
808
809    def execute(self, context):
810        if context.object.mode != "EDIT":
811            bpy.ops.object.mode_set(mode="EDIT")
812            bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT')
813        if bpy.ops.mesh.select_mode != "VERT, EDGE, FACE":
814            bpy.ops.object.mode_set(mode="EDIT")
815            bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT')
816            bpy.ops.mesh.select_mode(use_extend=True, use_expand=False, type='EDGE')
817            return {'FINISHED'}
818
819class VIEW3D_OT_multieditvertface(Operator):
820    bl_idname = "multiedit.vertfaceselect"
821    bl_label = "Multiedit Face"
822    bl_description = "Vert & Face Select Modes On"
823    bl_options = {'REGISTER', 'UNDO'}
824
825    def execute(self, context):
826        if context.object.mode != "EDIT":
827            bpy.ops.object.mode_set(mode="EDIT")
828            bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT')
829        if bpy.ops.mesh.select_mode != "VERT, EDGE, FACE":
830            bpy.ops.object.mode_set(mode="EDIT")
831            bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT')
832            bpy.ops.mesh.select_mode(use_extend=True, use_expand=False, type='FACE')
833            return {'FINISHED'}
834
835
836class VIEW3D_OT_multieditedgeface(Operator):
837    bl_idname = "multiedit.edgefaceselect"
838    bl_label = "Mode Face Edge"
839    bl_description = "Edge & Face Select Modes On"
840    bl_options = {'REGISTER', 'UNDO'}
841
842    def execute(self, context):
843        if context.object.mode != "EDIT":
844            bpy.ops.object.mode_set(mode="EDIT")
845            bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE')
846        if bpy.ops.mesh.select_mode != "VERT, EDGE, FACE":
847            bpy.ops.object.mode_set(mode="EDIT")
848            bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE')
849            bpy.ops.mesh.select_mode(use_extend=True, use_expand=False, type='FACE')
850            return {'FINISHED'}
851
852
853class VIEW3D_OT_multieditall(Operator):
854    bl_idname = "multiedit.allselect"
855    bl_label = "All Edit Select Modes"
856    bl_description = "Vert & Edge & Face Select Modes On"
857    bl_options = {'REGISTER', 'UNDO'}
858
859    def execute(self, context):
860        if context.object.mode != "EDIT":
861            bpy.ops.object.mode_set(mode="EDIT")
862            bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT')
863        if bpy.ops.mesh.select_mode != "VERT, EDGE, FACE":
864            bpy.ops.object.mode_set(mode="EDIT")
865            bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT')
866            bpy.ops.mesh.select_mode(use_extend=True, use_expand=False, type='EDGE')
867            bpy.ops.mesh.select_mode(use_extend=True, use_expand=False, type='FACE')
868            return {'FINISHED'}
869
870
871# ########################################
872# ##### GUI and registration #############
873# ########################################
874
875# menu containing all tools
876class VIEW3D_MT_edit_mesh_tools(Menu):
877    bl_label = "Mesh Tools"
878
879    def draw(self, context):
880        layout = self.layout
881        layout.operator("mesh.remove_doubles")
882        layout.operator("mesh.dissolve_limited")
883        layout.operator("mesh.flip_normals")
884        props = layout.operator("mesh.quads_convert_to_tris")
885        props.quad_method = props.ngon_method = 'BEAUTY'
886        layout.operator("mesh.tris_convert_to_quads")
887        layout.operator('mesh.vertex_chamfer', text="Vertex Chamfer")
888        layout.operator("mesh.bevel", text="Bevel Vertices").affect = 'VERTICES'
889        layout.operator('mesh.offset_edges', text="Offset Edges")
890        layout.operator('mesh.fillet_plus', text="Fillet Edges")
891        layout.operator("mesh.face_inset_fillet",
892                            text="Face Inset Fillet")
893#        layout.operator("mesh.extrude_reshape",
894#                        text="Push/Pull Faces")
895        layout.operator("object.mextrude",
896                        text="Multi Extrude")
897        layout.operator('mesh.split_solidify', text="Split Solidify")
898
899
900
901# panel containing all tools
902class VIEW3D_PT_edit_mesh_tools(Panel):
903    bl_space_type = 'VIEW_3D'
904    bl_region_type = 'UI'
905    bl_category = 'Edit'
906    bl_context = "mesh_edit"
907    bl_label = "Mesh Tools"
908    bl_options = {'DEFAULT_CLOSED'}
909
910    def draw(self, context):
911        layout = self.layout
912        col = layout.column(align=True)
913        et = context.window_manager.edittools
914
915        # vert - first line
916        split = col.split(factor=0.80, align=True)
917        if et.display_vert:
918            split.prop(et, "display_vert", text="Vert Tools", icon='DOWNARROW_HLT')
919        else:
920            split.prop(et, "display_vert", text="Vert tools", icon='RIGHTARROW')
921        split.menu("VIEW3D_MT_Select_Vert", text="", icon='VERTEXSEL')
922        # vert - settings
923        if et.display_vert:
924            box = col.column(align=True).box().column()
925            col_top = box.column(align=True)
926            row = col_top.row(align=True)
927            row.operator('mesh.vertex_chamfer', text="Vertex Chamfer")
928            row = col_top.row(align=True)
929            row.operator("mesh.extrude_vertices_move", text="Extrude Vertices")
930            row = col_top.row(align=True)
931            row.operator("mesh.random_vertices", text="Random Vertices")
932            row = col_top.row(align=True)
933            row.operator("mesh.bevel", text="Bevel Vertices").affect = 'VERTICES'
934
935        # edge - first line
936        split = col.split(factor=0.80, align=True)
937        if et.display_edge:
938            split.prop(et, "display_edge", text="Edge Tools", icon='DOWNARROW_HLT')
939        else:
940            split.prop(et, "display_edge", text="Edge Tools", icon='RIGHTARROW')
941        split.menu("VIEW3D_MT_Select_Edge", text="", icon='EDGESEL')
942        # Edge - settings
943        if et.display_edge:
944            box = col.column(align=True).box().column()
945            col_top = box.column(align=True)
946            row = col_top.row(align=True)
947            row.operator('mesh.offset_edges', text="Offset Edges")
948            row = col_top.row(align=True)
949            row.operator('mesh.fillet_plus', text="Fillet Edges")
950            row = col_top.row(align=True)
951            row.operator('mesh.edge_roundifier', text="Edge Roundify")
952            row = col_top.row(align=True)
953            row.operator('object.mesh_edge_length_set', text="Set Edge Length")
954            row = col_top.row(align=True)
955            row.operator('mesh.edges_floor_plan', text="Edges Floor Plan")
956            row = col_top.row(align=True)
957            row.operator("mesh.extrude_edges_move", text="Extrude Edges")
958            row = col_top.row(align=True)
959            row.operator("mesh.bevel", text="Bevel Edges").affect = 'EDGES'
960
961        # face - first line
962        split = col.split(factor=0.80, align=True)
963        if et.display_face:
964            split.prop(et, "display_face", text="Face Tools", icon='DOWNARROW_HLT')
965        else:
966            split.prop(et, "display_face", text="Face Tools", icon='RIGHTARROW')
967        split.menu("VIEW3D_MT_Select_Face", text="", icon='FACESEL')
968        # face - settings
969        if et.display_face:
970            box = col.column(align=True).box().column()
971            col_top = box.column(align=True)
972            row = col_top.row(align=True)
973            row.operator("mesh.face_inset_fillet",
974                            text="Face Inset Fillet")
975            row = col_top.row(align=True)
976            row.operator("mesh.ext_cut_faces",
977                            text="Cut Faces")
978            row = col_top.row(align=True)
979#            row.operator("mesh.extrude_reshape",
980#                            text="Push/Pull Faces")
981            row = col_top.row(align=True)
982            row.operator("object.mextrude",
983                            text="Multi Extrude")
984            row = col_top.row(align=True)
985            row.operator('mesh.split_solidify', text="Split Solidify")
986            row = col_top.row(align=True)
987            row.operator('mesh.add_faces_to_object', text="Face Shape")
988            row = col_top.row(align=True)
989            row.operator("mesh.inset")
990            row = col_top.row(align=True)
991            row.operator("mesh.extrude_faces_move", text="Extrude Individual Faces")
992
993        # util - first line
994        split = col.split(factor=0.80, align=True)
995        if et.display_util:
996            split.prop(et, "display_util", text="Utility Tools", icon='DOWNARROW_HLT')
997        else:
998            split.prop(et, "display_util", text="Utility Tools", icon='RIGHTARROW')
999        split.menu("VIEW3D_MT_Edit_MultiMET", text="", icon='RESTRICT_SELECT_OFF')
1000        # util - settings
1001        if et.display_util:
1002            box = col.column(align=True).box().column()
1003            col_top = box.column(align=True)
1004            row = col_top.row(align=True)
1005            row.operator("mesh.subdivide")
1006            row = col_top.row(align=True)
1007            row.operator("mesh.remove_doubles")
1008            row = col_top.row(align=True)
1009            row.operator("mesh.dissolve_limited")
1010            row = col_top.row(align=True)
1011            row.operator("mesh.flip_normals")
1012            row = col_top.row(align=True)
1013            props = row.operator("mesh.quads_convert_to_tris")
1014            props.quad_method = props.ngon_method = 'BEAUTY'
1015            row = col_top.row(align=True)
1016            row.operator("mesh.tris_convert_to_quads")
1017            row = col_top.row(align=True)
1018            row.operator("mesh.relax")
1019
1020# property group containing all properties for the gui in the panel
1021class EditToolsProps(PropertyGroup):
1022    """
1023    Fake module like class
1024    bpy.context.window_manager.edittools
1025    """
1026    # general display properties
1027    display_vert: BoolProperty(
1028        name="Bridge settings",
1029        description="Display settings of the Vert tool",
1030        default=False
1031        )
1032    display_edge: BoolProperty(
1033        name="Edge settings",
1034        description="Display settings of the Edge tool",
1035        default=False
1036        )
1037    display_face: BoolProperty(
1038        name="Face settings",
1039        description="Display settings of the Face tool",
1040        default=False
1041        )
1042    display_util: BoolProperty(
1043        name="Face settings",
1044        description="Display settings of the Face tool",
1045        default=False
1046        )
1047
1048# draw function for integration in menus
1049def menu_func(self, context):
1050    self.layout.menu("VIEW3D_MT_edit_mesh_tools")
1051    self.layout.separator()
1052
1053# Add-ons Preferences Update Panel
1054
1055# Define Panel classes for updating
1056panels = (
1057        VIEW3D_PT_edit_mesh_tools,
1058        )
1059
1060
1061def update_panel(self, context):
1062    message = "LoopTools: Updating Panel locations has failed"
1063    try:
1064        for panel in panels:
1065            if "bl_rna" in panel.__dict__:
1066                bpy.utils.unregister_class(panel)
1067
1068        for panel in panels:
1069            panel.bl_category = context.preferences.addons[__name__].preferences.category
1070            bpy.utils.register_class(panel)
1071
1072    except Exception as e:
1073        print("\n[{}]\n{}\n\nError:\n{}".format(__name__, message, e))
1074        pass
1075
1076
1077class EditToolsPreferences(AddonPreferences):
1078    # this must match the addon name, use '__package__'
1079    # when defining this in a submodule of a python package.
1080    bl_idname = __name__
1081
1082    category: StringProperty(
1083            name="Tab Category",
1084            description="Choose a name for the category of the panel",
1085            default="Edit",
1086            update=update_panel
1087            )
1088
1089    def draw(self, context):
1090        layout = self.layout
1091
1092        row = layout.row()
1093        col = row.column()
1094        col.label(text="Tab Category:")
1095        col.prop(self, "category", text="")
1096
1097
1098# define classes for registration
1099classes = (
1100    VIEW3D_MT_edit_mesh_tools,
1101    VIEW3D_PT_edit_mesh_tools,
1102    VIEW3D_MT_Edit_MultiMET,
1103    VIEW3D_MT_Select_Vert,
1104    VIEW3D_MT_Select_Edge,
1105    VIEW3D_MT_Select_Face,
1106    EditToolsProps,
1107    EditToolsPreferences,
1108    MESH_OT_face_inset_fillet,
1109    ME_OT_MExtrude,
1110    VIEW3D_OT_multieditvertex,
1111    VIEW3D_OT_multieditedge,
1112    VIEW3D_OT_multieditface,
1113    VIEW3D_OT_multieditvertedge,
1114    VIEW3D_OT_multieditvertface,
1115    VIEW3D_OT_multieditedgeface,
1116    VIEW3D_OT_multieditall
1117    )
1118
1119
1120# registering and menu integration
1121def register():
1122    for cls in classes:
1123        bpy.utils.register_class(cls)
1124    bpy.types.VIEW3D_MT_edit_mesh_context_menu.prepend(menu_func)
1125    bpy.types.WindowManager.edittools = PointerProperty(type=EditToolsProps)
1126    update_panel(None, bpy.context)
1127
1128    mesh_filletplus.register()
1129    mesh_offset_edges.register()
1130    split_solidify.register()
1131    mesh_vertex_chamfer.register()
1132    random_vertices.register()
1133#    mesh_extrude_and_reshape.register()
1134    mesh_edge_roundifier.register()
1135    mesh_edgetools.register()
1136    mesh_edges_floor_plan.register()
1137    mesh_edges_length.register()
1138    pkhg_faces.register()
1139    mesh_cut_faces.register()
1140    mesh_relax.register()
1141
1142
1143# unregistering and removing menus
1144def unregister():
1145    for cls in reversed(classes):
1146        bpy.utils.unregister_class(cls)
1147    bpy.types.VIEW3D_MT_edit_mesh_context_menu.remove(menu_func)
1148    try:
1149        del bpy.types.WindowManager.edittools
1150    except Exception as e:
1151        print('unregister fail:\n', e)
1152        pass
1153
1154    mesh_filletplus.unregister()
1155    mesh_offset_edges.unregister()
1156    split_solidify.unregister()
1157    mesh_vertex_chamfer.unregister()
1158    random_vertices.unregister()
1159#    mesh_extrude_and_reshape.unregister()
1160    mesh_edge_roundifier.unregister()
1161    mesh_edgetools.unregister()
1162    mesh_edges_floor_plan.unregister()
1163    mesh_edges_length.unregister()
1164    pkhg_faces.unregister()
1165    mesh_cut_faces.unregister()
1166    mesh_relax.unregister()
1167
1168
1169if __name__ == "__main__":
1170    register()
1171