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