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