1 # SPDX-License-Identifier: GPL-2.0-or-later
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
10 "name": "Edit Mesh Tools",
11 "author": "Meta-Androcto",
13 "blender": (2, 80, 0),
14 "location": "View3D > Sidebar > Edit Tab / Edit Mode Context Menu",
16 "description": "Mesh modelling toolkit. Several tools to aid modelling",
17 "doc_url": "{BLENDER_MANUAL_URL}/addons/mesh/edit_mesh_tools.html",
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
)
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
63 from random
import gauss
64 from mathutils
import Matrix
, Euler
, Vector
65 from bpy_extras
import view3d_utils
66 from bpy
.types
import (
73 from bpy
.props
import (
85 # ########################################
86 # ##### General functions ################
87 # ########################################
92 return Vector((self
.offx
, self
.offy
, self
.offz
))
96 random
.seed(self
.ran
+ r
)
97 return self
.off
* (1 + gauss(0, self
.var1
/ 3))
101 return Euler((radians(self
.nrotx
) * n
[0],
102 radians(self
.nroty
) * n
[1],
103 radians(self
.nrotz
) * n
[2]), 'XYZ')
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')
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"}
127 soft_min
=0.001, soft_max
=10,
130 description
="Translation"
132 offx
: FloatProperty(
134 soft_min
=-10.0, soft_max
=10.0,
135 min=-100.0, max=100.0,
137 description
="Global Translation X"
139 offy
: FloatProperty(
141 soft_min
=-10.0, soft_max
=10.0,
142 min=-100.0, max=100.0,
144 description
="Global Translation Y"
146 offz
: FloatProperty(
148 soft_min
=-10.0, soft_max
=10.0,
149 min=-100.0, max=100.0,
151 description
="Global Translation Z"
153 rotx
: FloatProperty(
156 soft_min
=-30, soft_max
=30,
158 description
="X Rotation"
160 roty
: FloatProperty(
166 description
="Y Rotation"
168 rotz
: FloatProperty(
171 soft_min
=-30, soft_max
=30,
173 description
="Z Rotation"
175 nrotx
: FloatProperty(
178 soft_min
=-30, soft_max
=30,
180 description
="Normal X Rotation"
182 nroty
: FloatProperty(
185 soft_min
=-30, soft_max
=30,
187 description
="Normal Y Rotation"
189 nrotz
: FloatProperty(
192 soft_min
=-30, soft_max
=30,
194 description
="Normal Z Rotation"
199 soft_min
=0.5, soft_max
=1.5,
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,
207 description
="Offset variation"
209 var2
: FloatProperty(
212 soft_min
=-1, soft_max
=1,
214 description
="Rotation variation"
216 var3
: FloatProperty(
219 soft_min
=-1, soft_max
=1,
221 description
="Scaling noise"
227 description
="Probability, chance of extruding a face"
234 description
="Repetitions"
240 description
="Seed to feed random values"
243 name
="Polygon coordinates",
245 description
="Polygon coordinates, Object coordinates"
248 name
="Proportional offset",
250 description
="Scale * Offset"
253 name
="Per step rotation noise",
255 description
="Per step rotation noise, Initial rotation noise"
258 name
="Per step scale noise",
260 description
="Per step scale noise, Initial scale noise"
264 def poll(cls
, context
):
266 return (obj
and obj
.type == 'MESH')
268 def draw(self
, context
):
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
307 bpy
.context
.tool_settings
.mesh_select_mode
= [False, False, True]
308 origin
= Vector([0.0, 0.0, 0.0])
311 bpy
.ops
.object.mode_set()
313 bm
.from_mesh(obj
.data
)
314 sel
= [f
for f
in bm
.faces
if f
.select
]
319 for i
, of
in enumerate(sel
):
320 nro
= nrot(self
, of
.normal
)
325 # initial rotation noise
326 if self
.opt3
is False:
328 # initial scale noise
329 if self
.opt4
is False:
333 for r
in range(self
.num
):
334 # random probability % for extrusions
335 if self
.var4
> int(random
.random() * 100):
338 no
= nf
.normal
.copy()
340 # face/obj coordinates
341 if self
.opt1
is True:
342 ce
= nf
.calc_center_bounds()
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:
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
))
388 # restore user settings
389 bpy
.ops
.object.mode_set(mode
=om
)
392 self
.report({"WARNING"},
393 "No suitable Face selection found. Operation cancelled")
400 bpy
.ops
.object.mode_set(mode
='OBJECT')
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
):
418 for faceindex
in face_index_list
:
420 bme
.faces
.ensure_lookup_table()
421 # loops through the faces...
422 f
= bme
.faces
[faceindex
]
426 vertex_index_list
= [v
.index
for v
in f
.verts
]
428 orientation_vertex_list
= []
429 n
= len(vertex_index_list
)
431 # loops through the vertices
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
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...
452 val
= ((f
.normal
).normalized() * inset_amount
)
454 val
= -((f
.normal
).normalized() * inset_amount
)
455 p6
= angle_rotation(p
, p
+ val
, vec1
, radians(90))
457 # if the corner is an actual corner
458 val
= ((f
.normal
).normalized() * h
)
460 # this -(p - (vec2.normalized() * adj))) is just the freaking axis afaik...
463 -(p
- (vec2
.normalized() * adj
)),
469 ((p
- (vec1
.normalized() * adj
)) - (p
- (vec2
.normalized() * adj
))),
473 orientation_vertex_list
.append(p6
)
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
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...
492 new_inner_face
.append(v
)
497 h_
= distance
* (1 / cos(ang_
* 0.5))
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
522 rot_ang
= vec3_
.angle(vec4_
)
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()
534 cornerverts
.append(v
)
536 cornerverts
.reverse()
537 new_inner_face
.extend(cornerverts
)
540 f
= bme
.faces
.new(new_inner_face
)
542 elif out
is True and kp
is True:
543 f
= bme
.faces
.new(new_inner_face
)
547 # these are the new side faces, those that don't depend on cornertype
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':
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':
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
]
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"}
585 inset_amount
: bpy
.props
.FloatProperty(
587 description
="Define the size of the Inset relative to the selection",
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",
602 distance
: bpy
.props
.FloatProperty(
604 description
="Use distance or radius for corners' size calculation",
606 min=0.00001, max=100.0,
610 out
: bpy
.props
.BoolProperty(
612 description
="Inset the Faces outwards in relation to the selection\n"
613 "Note: depending on the geometry, can give unsatisfactory results",
616 radius
: bpy
.props
.BoolProperty(
618 description
="Use radius for corners' size calculation",
621 type_enum
: bpy
.props
.EnumProperty(
622 items
=[('opt0', "N-gon", "N-gon corners - Keep the corner Faces uncut"),
623 ('opt1', "Triangle", "Triangulate corners")],
627 kp
: bpy
.props
.BoolProperty(
629 description
="Do not delete the inside Faces\n"
630 "Only available if the Out option is checked",
634 def draw(self
, context
):
637 layout
.label(text
="Corner Type:")
640 row
.prop(self
, "type_enum", text
="")
642 row
= layout
.row(align
=True)
643 row
.prop(self
, "out")
649 row
.prop(self
, "inset_amount")
652 row
.prop(self
, "number_of_sides")
655 row
.prop(self
, "radius")
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
668 type_enum
= self
.type_enum
672 ob_act
= context
.active_object
674 bme
.from_mesh(ob_act
.data
)
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")
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
)
695 # ********** Edit Multiselect **********
696 class VIEW3D_MT_Edit_MultiMET(Menu
):
697 bl_label
= "Multi Select"
699 def draw(self
, context
):
701 layout
.operator_context
= 'INVOKE_REGION_WIN'
703 layout
.operator("multiedit.allselect", text
="All Select Modes", icon
='RESTRICT_SELECT_OFF')
707 class VIEW3D_MT_Select_Vert(Menu
):
708 bl_label
= "Select Vert"
710 def draw(self
, context
):
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
):
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
):
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')
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')
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')
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')
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')
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')
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')
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
):
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'
891 bl_context
= "mesh_edit"
892 bl_label
= "Mesh Tools"
893 bl_options
= {'DEFAULT_CLOSED'}
895 def draw(self
, context
):
897 col
= layout
.column(align
=True)
898 et
= context
.window_manager
.edittools
901 split
= col
.split(factor
=0.80, align
=True)
903 split
.prop(et
, "display_vert", text
="Vert Tools", icon
='DOWNARROW_HLT')
905 split
.prop(et
, "display_vert", text
="Vert tools", icon
='RIGHTARROW')
906 split
.menu("VIEW3D_MT_Select_Vert", text
="", icon
='VERTEXSEL')
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'
921 split
= col
.split(factor
=0.80, align
=True)
923 split
.prop(et
, "display_edge", text
="Edge Tools", icon
='DOWNARROW_HLT')
925 split
.prop(et
, "display_edge", text
="Edge Tools", icon
='RIGHTARROW')
926 split
.menu("VIEW3D_MT_Select_Edge", text
="", icon
='EDGESEL')
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'
947 split
= col
.split(factor
=0.80, align
=True)
949 split
.prop(et
, "display_face", text
="Face Tools", icon
='DOWNARROW_HLT')
951 split
.prop(et
, "display_face", text
="Face Tools", icon
='RIGHTARROW')
952 split
.menu("VIEW3D_MT_Select_Face", text
="", icon
='FACESEL')
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",
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")
979 split
= col
.split(factor
=0.80, align
=True)
981 split
.prop(et
, "display_util", text
="Utility Tools", icon
='DOWNARROW_HLT')
983 split
.prop(et
, "display_util", text
="Utility Tools", icon
='RIGHTARROW')
984 split
.menu("VIEW3D_MT_Edit_MultiMET", text
="", icon
='RESTRICT_SELECT_OFF')
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",
1017 display_edge
: BoolProperty(
1018 name
="Edge settings",
1019 description
="Display settings of the Edge tool",
1022 display_face
: BoolProperty(
1023 name
="Face settings",
1024 description
="Display settings of the Face tool",
1027 display_util
: BoolProperty(
1028 name
="Face settings",
1029 description
="Display settings of the Face tool",
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
1042 VIEW3D_PT_edit_mesh_tools
,
1046 def update_panel(self
, context
):
1047 message
= "LoopTools: Updating Panel locations has failed"
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
))
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",
1074 def draw(self
, context
):
1075 layout
= self
.layout
1079 col
.label(text
="Tab Category:")
1080 col
.prop(self
, "category", text
="")
1083 # define classes for registration
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
,
1092 EditToolsPreferences
,
1093 MESH_OT_face_inset_fillet
,
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
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
1130 for cls
in reversed(classes
):
1131 bpy
.utils
.unregister_class(cls
)
1132 bpy
.types
.VIEW3D_MT_edit_mesh_context_menu
.remove(menu_func
)
1134 del bpy
.types
.WindowManager
.edittools
1135 except Exception as e
:
1136 print('unregister fail:\n', e
)
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__":