1 # SPDX-FileCopyrightText: 2019-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
6 "name": "Edit Mesh Tools",
7 "author": "Meta-Androcto",
10 "location": "View3D > Sidebar > Edit Tab / Edit Mode Context Menu",
12 "description": "Mesh modelling toolkit. Several tools to aid modelling",
13 "doc_url": "{BLENDER_MANUAL_URL}/addons/mesh/edit_mesh_tools.html",
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
)
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
59 from random
import gauss
60 from mathutils
import Matrix
, Euler
, Vector
61 from bpy_extras
import view3d_utils
62 from bpy
.types
import (
69 from bpy
.props
import (
81 # ########################################
82 # ##### General functions ################
83 # ########################################
88 return Vector((self
.offx
, self
.offy
, self
.offz
))
92 random
.seed(self
.ran
+ r
)
93 return self
.off
* (1 + gauss(0, self
.var1
/ 3))
97 return Euler((radians(self
.nrotx
) * n
[0],
98 radians(self
.nroty
) * n
[1],
99 radians(self
.nrotz
) * n
[2]), 'XYZ')
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')
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"}
123 soft_min
=0.001, soft_max
=10,
126 description
="Translation"
128 offx
: FloatProperty(
130 soft_min
=-10.0, soft_max
=10.0,
131 min=-100.0, max=100.0,
133 description
="Global Translation X"
135 offy
: FloatProperty(
137 soft_min
=-10.0, soft_max
=10.0,
138 min=-100.0, max=100.0,
140 description
="Global Translation Y"
142 offz
: FloatProperty(
144 soft_min
=-10.0, soft_max
=10.0,
145 min=-100.0, max=100.0,
147 description
="Global Translation Z"
149 rotx
: FloatProperty(
152 soft_min
=-30, soft_max
=30,
154 description
="X Rotation"
156 roty
: FloatProperty(
162 description
="Y Rotation"
164 rotz
: FloatProperty(
167 soft_min
=-30, soft_max
=30,
169 description
="Z Rotation"
171 nrotx
: FloatProperty(
174 soft_min
=-30, soft_max
=30,
176 description
="Normal X Rotation"
178 nroty
: FloatProperty(
181 soft_min
=-30, soft_max
=30,
183 description
="Normal Y Rotation"
185 nrotz
: FloatProperty(
188 soft_min
=-30, soft_max
=30,
190 description
="Normal Z Rotation"
195 soft_min
=0.5, soft_max
=1.5,
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,
203 description
="Offset variation"
205 var2
: FloatProperty(
208 soft_min
=-1, soft_max
=1,
210 description
="Rotation variation"
212 var3
: FloatProperty(
215 soft_min
=-1, soft_max
=1,
217 description
="Scaling noise"
223 description
="Probability, chance of extruding a face"
230 description
="Repetitions"
236 description
="Seed to feed random values"
239 name
="Polygon coordinates",
241 description
="Polygon coordinates, Object coordinates"
244 name
="Proportional offset",
246 description
="Scale * Offset"
249 name
="Per step rotation noise",
251 description
="Per step rotation noise, Initial rotation noise"
254 name
="Per step scale noise",
256 description
="Per step scale noise, Initial scale noise"
260 def poll(cls
, context
):
262 return (obj
and obj
.type == 'MESH')
264 def draw(self
, context
):
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
303 bpy
.context
.tool_settings
.mesh_select_mode
= [False, False, True]
304 origin
= Vector([0.0, 0.0, 0.0])
307 bpy
.ops
.object.mode_set()
309 bm
.from_mesh(obj
.data
)
310 sel
= [f
for f
in bm
.faces
if f
.select
]
315 for i
, of
in enumerate(sel
):
316 nro
= nrot(self
, of
.normal
)
321 # initial rotation noise
322 if self
.opt3
is False:
324 # initial scale noise
325 if self
.opt4
is False:
329 for r
in range(self
.num
):
330 # random probability % for extrusions
331 if self
.var4
> int(random
.random() * 100):
334 no
= nf
.normal
.copy()
336 # face/obj coordinates
337 if self
.opt1
is True:
338 ce
= nf
.calc_center_bounds()
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:
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
))
384 # restore user settings
385 bpy
.ops
.object.mode_set(mode
=om
)
388 self
.report({"WARNING"},
389 "No suitable Face selection found. Operation cancelled")
396 bpy
.ops
.object.mode_set(mode
='OBJECT')
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
):
414 for faceindex
in face_index_list
:
416 bme
.faces
.ensure_lookup_table()
417 # loops through the faces...
418 f
= bme
.faces
[faceindex
]
422 vertex_index_list
= [v
.index
for v
in f
.verts
]
424 orientation_vertex_list
= []
425 n
= len(vertex_index_list
)
427 # loops through the vertices
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
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...
448 val
= ((f
.normal
).normalized() * inset_amount
)
450 val
= -((f
.normal
).normalized() * inset_amount
)
451 p6
= angle_rotation(p
, p
+ val
, vec1
, radians(90))
453 # if the corner is an actual corner
454 val
= ((f
.normal
).normalized() * h
)
456 # this -(p - (vec2.normalized() * adj))) is just the freaking axis afaik...
459 -(p
- (vec2
.normalized() * adj
)),
465 ((p
- (vec1
.normalized() * adj
)) - (p
- (vec2
.normalized() * adj
))),
469 orientation_vertex_list
.append(p6
)
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
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...
488 new_inner_face
.append(v
)
493 h_
= distance
* (1 / cos(ang_
* 0.5))
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
518 rot_ang
= vec3_
.angle(vec4_
)
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()
530 cornerverts
.append(v
)
532 cornerverts
.reverse()
533 new_inner_face
.extend(cornerverts
)
536 f
= bme
.faces
.new(new_inner_face
)
538 elif out
is True and kp
is True:
539 f
= bme
.faces
.new(new_inner_face
)
543 # these are the new side faces, those that don't depend on cornertype
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':
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':
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
]
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"}
581 inset_amount
: bpy
.props
.FloatProperty(
583 description
="Define the size of the Inset relative to the selection",
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",
598 distance
: bpy
.props
.FloatProperty(
600 description
="Use distance or radius for corners' size calculation",
602 min=0.00001, max=100.0,
606 out
: bpy
.props
.BoolProperty(
608 description
="Inset the Faces outwards in relation to the selection\n"
609 "Note: depending on the geometry, can give unsatisfactory results",
612 radius
: bpy
.props
.BoolProperty(
614 description
="Use radius for corners' size calculation",
617 type_enum
: bpy
.props
.EnumProperty(
618 items
=[('opt0', "N-gon", "N-gon corners - Keep the corner Faces uncut"),
619 ('opt1', "Triangle", "Triangulate corners")],
623 kp
: bpy
.props
.BoolProperty(
625 description
="Do not delete the inside Faces\n"
626 "Only available if the Out option is checked",
630 def draw(self
, context
):
633 layout
.label(text
="Corner Type:")
636 row
.prop(self
, "type_enum", text
="")
638 row
= layout
.row(align
=True)
639 row
.prop(self
, "out")
645 row
.prop(self
, "inset_amount")
648 row
.prop(self
, "number_of_sides")
651 row
.prop(self
, "radius")
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
664 type_enum
= self
.type_enum
668 ob_act
= context
.active_object
670 bme
.from_mesh(ob_act
.data
)
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")
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
)
691 # ********** Edit Multiselect **********
692 class VIEW3D_MT_Edit_MultiMET(Menu
):
693 bl_label
= "Multi Select"
695 def draw(self
, context
):
697 layout
.operator_context
= 'INVOKE_REGION_WIN'
699 layout
.operator("multiedit.allselect", text
="All Select Modes", icon
='RESTRICT_SELECT_OFF')
703 class VIEW3D_MT_Select_Vert(Menu
):
704 bl_label
= "Select Vert"
706 def draw(self
, context
):
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
):
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
):
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')
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')
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')
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')
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')
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')
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')
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
):
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'
887 bl_context
= "mesh_edit"
888 bl_label
= "Mesh Tools"
889 bl_options
= {'DEFAULT_CLOSED'}
891 def draw(self
, context
):
893 col
= layout
.column(align
=True)
894 et
= context
.window_manager
.edittools
897 split
= col
.split(factor
=0.80, align
=True)
899 split
.prop(et
, "display_vert", text
="Vert Tools", icon
='DOWNARROW_HLT')
901 split
.prop(et
, "display_vert", text
="Vert tools", icon
='RIGHTARROW')
902 split
.menu("VIEW3D_MT_Select_Vert", text
="", icon
='VERTEXSEL')
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'
917 split
= col
.split(factor
=0.80, align
=True)
919 split
.prop(et
, "display_edge", text
="Edge Tools", icon
='DOWNARROW_HLT')
921 split
.prop(et
, "display_edge", text
="Edge Tools", icon
='RIGHTARROW')
922 split
.menu("VIEW3D_MT_Select_Edge", text
="", icon
='EDGESEL')
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'
943 split
= col
.split(factor
=0.80, align
=True)
945 split
.prop(et
, "display_face", text
="Face Tools", icon
='DOWNARROW_HLT')
947 split
.prop(et
, "display_face", text
="Face Tools", icon
='RIGHTARROW')
948 split
.menu("VIEW3D_MT_Select_Face", text
="", icon
='FACESEL')
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",
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")
975 split
= col
.split(factor
=0.80, align
=True)
977 split
.prop(et
, "display_util", text
="Utility Tools", icon
='DOWNARROW_HLT')
979 split
.prop(et
, "display_util", text
="Utility Tools", icon
='RIGHTARROW')
980 split
.menu("VIEW3D_MT_Edit_MultiMET", text
="", icon
='RESTRICT_SELECT_OFF')
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",
1013 display_edge
: BoolProperty(
1014 name
="Edge settings",
1015 description
="Display settings of the Edge tool",
1018 display_face
: BoolProperty(
1019 name
="Face settings",
1020 description
="Display settings of the Face tool",
1023 display_util
: BoolProperty(
1024 name
="Face settings",
1025 description
="Display settings of the Face tool",
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
1038 VIEW3D_PT_edit_mesh_tools
,
1042 def update_panel(self
, context
):
1043 message
= "LoopTools: Updating Panel locations has failed"
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
))
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",
1070 def draw(self
, context
):
1071 layout
= self
.layout
1075 col
.label(text
="Tab Category:")
1076 col
.prop(self
, "category", text
="")
1079 # define classes for registration
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
,
1088 EditToolsPreferences
,
1089 MESH_OT_face_inset_fillet
,
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
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
1126 for cls
in reversed(classes
):
1127 bpy
.utils
.unregister_class(cls
)
1128 bpy
.types
.VIEW3D_MT_edit_mesh_context_menu
.remove(menu_func
)
1130 del bpy
.types
.WindowManager
.edittools
1131 except Exception as e
:
1132 print('unregister fail:\n', e
)
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__":