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