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