mirror of
https://github.com/blender/blender-addons.git
synced 2025-08-16 15:35:05 +00:00
1172 lines
40 KiB
Python
1172 lines
40 KiB
Python
# ##### BEGIN GPL LICENSE BLOCK #####
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License
|
|
# as published by the Free Software Foundation; either version 2
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software Foundation,
|
|
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
#
|
|
# ##### END GPL LICENSE BLOCK #####
|
|
# Contributed to by:
|
|
# meta-androcto, Hidesato Ikeya, zmj100, Gert De Roost, TrumanBlending, PKHG, #
|
|
# Oscurart, Greg, Stanislav Blinov, komi3D, BlenderLab, Paul Marshall (brikbot), #
|
|
# metalliandy, macouno, CoDEmanX, dustractor, Liero, lijenstina, Germano Cavalcante #
|
|
# Pistiwique, Jimmy Hazevoet #
|
|
|
|
bl_info = {
|
|
"name": "Edit Mesh Tools",
|
|
"author": "Meta-Androcto",
|
|
"version": (0, 3, 6),
|
|
"blender": (2, 80, 0),
|
|
"location": "View3D > Sidebar > Edit Tab / Edit Mode Context Menu",
|
|
"warning": "",
|
|
"description": "Mesh modelling toolkit. Several tools to aid modelling",
|
|
"doc_url": "https://docs.blender.org/manual/en/dev/addons/"
|
|
"mesh/edit_mesh_tools.html",
|
|
"category": "Mesh",
|
|
}
|
|
|
|
# Import From Files
|
|
if "bpy" in locals():
|
|
import importlib
|
|
importlib.reload(mesh_offset_edges)
|
|
importlib.reload(split_solidify)
|
|
importlib.reload(mesh_filletplus)
|
|
importlib.reload(mesh_vertex_chamfer)
|
|
importlib.reload(random_vertices)
|
|
# importlib.reload(mesh_extrude_and_reshape)
|
|
importlib.reload(mesh_edge_roundifier)
|
|
importlib.reload(mesh_edgetools)
|
|
importlib.reload(mesh_edges_floor_plan)
|
|
importlib.reload(mesh_edges_length)
|
|
importlib.reload(pkhg_faces)
|
|
importlib.reload(mesh_cut_faces)
|
|
importlib.reload(mesh_relax)
|
|
|
|
else:
|
|
from . import mesh_offset_edges
|
|
from . import split_solidify
|
|
from . import mesh_filletplus
|
|
from . import mesh_vertex_chamfer
|
|
from . import random_vertices
|
|
# from . import mesh_extrude_and_reshape
|
|
from . import mesh_edge_roundifier
|
|
from . import mesh_edgetools
|
|
from . import mesh_edges_floor_plan
|
|
from . import mesh_edges_length
|
|
from . import pkhg_faces
|
|
from . import mesh_cut_faces
|
|
from . import mesh_relax
|
|
|
|
|
|
import bmesh
|
|
import bpy
|
|
import collections
|
|
import mathutils
|
|
import random
|
|
from math import (
|
|
sin, cos, tan,
|
|
degrees, radians, pi,
|
|
)
|
|
from random import gauss
|
|
from mathutils import Matrix, Euler, Vector
|
|
from bpy_extras import view3d_utils
|
|
from bpy.types import (
|
|
Operator,
|
|
Menu,
|
|
Panel,
|
|
PropertyGroup,
|
|
AddonPreferences,
|
|
)
|
|
from bpy.props import (
|
|
BoolProperty,
|
|
BoolVectorProperty,
|
|
EnumProperty,
|
|
FloatProperty,
|
|
FloatVectorProperty,
|
|
IntVectorProperty,
|
|
PointerProperty,
|
|
StringProperty,
|
|
IntProperty
|
|
)
|
|
|
|
# ########################################
|
|
# ##### General functions ################
|
|
# ########################################
|
|
|
|
|
|
# Multi extrude
|
|
def gloc(self, r):
|
|
return Vector((self.offx, self.offy, self.offz))
|
|
|
|
|
|
def vloc(self, r):
|
|
random.seed(self.ran + r)
|
|
return self.off * (1 + gauss(0, self.var1 / 3))
|
|
|
|
|
|
def nrot(self, n):
|
|
return Euler((radians(self.nrotx) * n[0],
|
|
radians(self.nroty) * n[1],
|
|
radians(self.nrotz) * n[2]), 'XYZ')
|
|
|
|
|
|
def vrot(self, r):
|
|
random.seed(self.ran + r)
|
|
return Euler((radians(self.rotx) + gauss(0, self.var2 / 3),
|
|
radians(self.roty) + gauss(0, self.var2 / 3),
|
|
radians(self.rotz) + gauss(0, self.var2 / 3)), 'XYZ')
|
|
|
|
|
|
def vsca(self, r):
|
|
random.seed(self.ran + r)
|
|
return self.sca * (1 + gauss(0, self.var3 / 3))
|
|
|
|
|
|
class ME_OT_MExtrude(Operator):
|
|
bl_idname = "object.mextrude"
|
|
bl_label = "Multi Extrude"
|
|
bl_description = ("Extrude selected Faces with Rotation,\n"
|
|
"Scaling, Variation, Randomization")
|
|
bl_options = {"REGISTER", "UNDO", "PRESET"}
|
|
|
|
off : FloatProperty(
|
|
name="Offset",
|
|
soft_min=0.001, soft_max=10,
|
|
min=-100, max=100,
|
|
default=1.0,
|
|
description="Translation"
|
|
)
|
|
offx : FloatProperty(
|
|
name="Loc X",
|
|
soft_min=-10.0, soft_max=10.0,
|
|
min=-100.0, max=100.0,
|
|
default=0.0,
|
|
description="Global Translation X"
|
|
)
|
|
offy : FloatProperty(
|
|
name="Loc Y",
|
|
soft_min=-10.0, soft_max=10.0,
|
|
min=-100.0, max=100.0,
|
|
default=0.0,
|
|
description="Global Translation Y"
|
|
)
|
|
offz : FloatProperty(
|
|
name="Loc Z",
|
|
soft_min=-10.0, soft_max=10.0,
|
|
min=-100.0, max=100.0,
|
|
default=0.0,
|
|
description="Global Translation Z"
|
|
)
|
|
rotx : FloatProperty(
|
|
name="Rot X",
|
|
min=-85, max=85,
|
|
soft_min=-30, soft_max=30,
|
|
default=0,
|
|
description="X Rotation"
|
|
)
|
|
roty : FloatProperty(
|
|
name="Rot Y",
|
|
min=-85, max=85,
|
|
soft_min=-30,
|
|
soft_max=30,
|
|
default=0,
|
|
description="Y Rotation"
|
|
)
|
|
rotz : FloatProperty(
|
|
name="Rot Z",
|
|
min=-85, max=85,
|
|
soft_min=-30, soft_max=30,
|
|
default=-0,
|
|
description="Z Rotation"
|
|
)
|
|
nrotx : FloatProperty(
|
|
name="N Rot X",
|
|
min=-85, max=85,
|
|
soft_min=-30, soft_max=30,
|
|
default=0,
|
|
description="Normal X Rotation"
|
|
)
|
|
nroty : FloatProperty(
|
|
name="N Rot Y",
|
|
min=-85, max=85,
|
|
soft_min=-30, soft_max=30,
|
|
default=0,
|
|
description="Normal Y Rotation"
|
|
)
|
|
nrotz : FloatProperty(
|
|
name="N Rot Z",
|
|
min=-85, max=85,
|
|
soft_min=-30, soft_max=30,
|
|
default=-0,
|
|
description="Normal Z Rotation"
|
|
)
|
|
sca : FloatProperty(
|
|
name="Scale",
|
|
min=0.01, max=10,
|
|
soft_min=0.5, soft_max=1.5,
|
|
default=1.0,
|
|
description="Scaling of the selected faces after extrusion"
|
|
)
|
|
var1 : FloatProperty(
|
|
name="Offset Var", min=-10, max=10,
|
|
soft_min=-1, soft_max=1,
|
|
default=0,
|
|
description="Offset variation"
|
|
)
|
|
var2 : FloatProperty(
|
|
name="Rotation Var",
|
|
min=-10, max=10,
|
|
soft_min=-1, soft_max=1,
|
|
default=0,
|
|
description="Rotation variation"
|
|
)
|
|
var3 : FloatProperty(
|
|
name="Scale Noise",
|
|
min=-10, max=10,
|
|
soft_min=-1, soft_max=1,
|
|
default=0,
|
|
description="Scaling noise"
|
|
)
|
|
var4 : IntProperty(
|
|
name="Probability",
|
|
min=0, max=100,
|
|
default=100,
|
|
description="Probability, chance of extruding a face"
|
|
)
|
|
num : IntProperty(
|
|
name="Repeat",
|
|
min=1, max=500,
|
|
soft_max=100,
|
|
default=1,
|
|
description="Repetitions"
|
|
)
|
|
ran : IntProperty(
|
|
name="Seed",
|
|
min=-9999, max=9999,
|
|
default=0,
|
|
description="Seed to feed random values"
|
|
)
|
|
opt1 : BoolProperty(
|
|
name="Polygon coordinates",
|
|
default=True,
|
|
description="Polygon coordinates, Object coordinates"
|
|
)
|
|
opt2 : BoolProperty(
|
|
name="Proportional offset",
|
|
default=False,
|
|
description="Scale * Offset"
|
|
)
|
|
opt3 : BoolProperty(
|
|
name="Per step rotation noise",
|
|
default=False,
|
|
description="Per step rotation noise, Initial rotation noise"
|
|
)
|
|
opt4 : BoolProperty(
|
|
name="Per step scale noise",
|
|
default=False,
|
|
description="Per step scale noise, Initial scale noise"
|
|
)
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
obj = context.object
|
|
return (obj and obj.type == 'MESH')
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
col = layout.column(align=True)
|
|
col.label(text="Transformations:")
|
|
col.prop(self, "off", slider=True)
|
|
col.prop(self, "offx", slider=True)
|
|
col.prop(self, "offy", slider=True)
|
|
col.prop(self, "offz", slider=True)
|
|
|
|
col = layout.column(align=True)
|
|
col.prop(self, "rotx", slider=True)
|
|
col.prop(self, "roty", slider=True)
|
|
col.prop(self, "rotz", slider=True)
|
|
col.prop(self, "nrotx", slider=True)
|
|
col.prop(self, "nroty", slider=True)
|
|
col.prop(self, "nrotz", slider=True)
|
|
col = layout.column(align=True)
|
|
col.prop(self, "sca", slider=True)
|
|
|
|
col = layout.column(align=True)
|
|
col.label(text="Variation settings:")
|
|
col.prop(self, "var1", slider=True)
|
|
col.prop(self, "var2", slider=True)
|
|
col.prop(self, "var3", slider=True)
|
|
col.prop(self, "var4", slider=True)
|
|
col.prop(self, "ran")
|
|
col = layout.column(align=False)
|
|
col.prop(self, 'num')
|
|
|
|
col = layout.column(align=True)
|
|
col.label(text="Options:")
|
|
col.prop(self, "opt1")
|
|
col.prop(self, "opt2")
|
|
col.prop(self, "opt3")
|
|
col.prop(self, "opt4")
|
|
|
|
def execute(self, context):
|
|
obj = bpy.context.object
|
|
om = obj.mode
|
|
bpy.context.tool_settings.mesh_select_mode = [False, False, True]
|
|
origin = Vector([0.0, 0.0, 0.0])
|
|
|
|
# bmesh operations
|
|
bpy.ops.object.mode_set()
|
|
bm = bmesh.new()
|
|
bm.from_mesh(obj.data)
|
|
sel = [f for f in bm.faces if f.select]
|
|
|
|
after = []
|
|
|
|
# faces loop
|
|
for i, of in enumerate(sel):
|
|
nro = nrot(self, of.normal)
|
|
off = vloc(self, i)
|
|
loc = gloc(self, i)
|
|
of.normal_update()
|
|
|
|
# initial rotation noise
|
|
if self.opt3 is False:
|
|
rot = vrot(self, i)
|
|
# initial scale noise
|
|
if self.opt4 is False:
|
|
s = vsca(self, i)
|
|
|
|
# extrusion loop
|
|
for r in range(self.num):
|
|
# random probability % for extrusions
|
|
if self.var4 > int(random.random() * 100):
|
|
nf = of.copy()
|
|
nf.normal_update()
|
|
no = nf.normal.copy()
|
|
|
|
# face/obj coordinates
|
|
if self.opt1 is True:
|
|
ce = nf.calc_center_bounds()
|
|
else:
|
|
ce = origin
|
|
|
|
# per step rotation noise
|
|
if self.opt3 is True:
|
|
rot = vrot(self, i + r)
|
|
# per step scale noise
|
|
if self.opt4 is True:
|
|
s = vsca(self, i + r)
|
|
|
|
# proportional, scale * offset
|
|
if self.opt2 is True:
|
|
off = s * off
|
|
|
|
for v in nf.verts:
|
|
v.co -= ce
|
|
v.co.rotate(nro)
|
|
v.co.rotate(rot)
|
|
v.co += ce + loc + no * off
|
|
v.co = v.co.lerp(ce, 1 - s)
|
|
|
|
# extrude code from TrumanBlending
|
|
for a, b in zip(of.loops, nf.loops):
|
|
sf = bm.faces.new((a.vert, a.link_loop_next.vert,
|
|
b.link_loop_next.vert, b.vert))
|
|
sf.normal_update()
|
|
bm.faces.remove(of)
|
|
of = nf
|
|
|
|
after.append(of)
|
|
|
|
for v in bm.verts:
|
|
v.select = False
|
|
for e in bm.edges:
|
|
e.select = False
|
|
|
|
for f in after:
|
|
if f not in sel:
|
|
f.select = True
|
|
else:
|
|
f.select = False
|
|
|
|
bm.to_mesh(obj.data)
|
|
obj.data.update()
|
|
|
|
# restore user settings
|
|
bpy.ops.object.mode_set(mode=om)
|
|
|
|
if not len(sel):
|
|
self.report({"WARNING"},
|
|
"No suitable Face selection found. Operation cancelled")
|
|
return {'CANCELLED'}
|
|
|
|
return {'FINISHED'}
|
|
|
|
# Face inset fillet
|
|
def edit_mode_out():
|
|
bpy.ops.object.mode_set(mode='OBJECT')
|
|
|
|
|
|
def edit_mode_in():
|
|
bpy.ops.object.mode_set(mode='EDIT')
|
|
|
|
|
|
def angle_rotation(rp, q, axis, angle):
|
|
# returns the vector made by the rotation of the vector q
|
|
# rp by angle around axis and then adds rp
|
|
|
|
return (Matrix.Rotation(angle, 3, axis) @ (q - rp)) + rp
|
|
|
|
|
|
def face_inset_fillet(bme, face_index_list, inset_amount, distance,
|
|
number_of_sides, out, radius, type_enum, kp):
|
|
list_del = []
|
|
|
|
for faceindex in face_index_list:
|
|
|
|
bme.faces.ensure_lookup_table()
|
|
# loops through the faces...
|
|
f = bme.faces[faceindex]
|
|
f.select_set(False)
|
|
list_del.append(f)
|
|
f.normal_update()
|
|
vertex_index_list = [v.index for v in f.verts]
|
|
dict_0 = {}
|
|
orientation_vertex_list = []
|
|
n = len(vertex_index_list)
|
|
for i in range(n):
|
|
# loops through the vertices
|
|
dict_0[i] = []
|
|
bme.verts.ensure_lookup_table()
|
|
p = (bme.verts[vertex_index_list[i]].co).copy()
|
|
p1 = (bme.verts[vertex_index_list[(i - 1) % n]].co).copy()
|
|
p2 = (bme.verts[vertex_index_list[(i + 1) % n]].co).copy()
|
|
# copies some vert coordinates, always the 3 around i
|
|
dict_0[i].append(bme.verts[vertex_index_list[i]])
|
|
# appends the bmesh vert of the appropriate index to the dict
|
|
vec1 = p - p1
|
|
vec2 = p - p2
|
|
# vectors for the other corner points to the cornerpoint
|
|
# corresponding to i / p
|
|
angle = vec1.angle(vec2)
|
|
|
|
adj = inset_amount / tan(angle * 0.5)
|
|
h = (adj ** 2 + inset_amount ** 2) ** 0.5
|
|
if round(degrees(angle)) == 180 or round(degrees(angle)) == 0.0:
|
|
# if the corner is a straight line...
|
|
# I think this creates some new points...
|
|
if out is True:
|
|
val = ((f.normal).normalized() * inset_amount)
|
|
else:
|
|
val = -((f.normal).normalized() * inset_amount)
|
|
p6 = angle_rotation(p, p + val, vec1, radians(90))
|
|
else:
|
|
# if the corner is an actual corner
|
|
val = ((f.normal).normalized() * h)
|
|
if out is True:
|
|
# this -(p - (vec2.normalized() * adj))) is just the freaking axis afaik...
|
|
p6 = angle_rotation(
|
|
p, p + val,
|
|
-(p - (vec2.normalized() * adj)),
|
|
-radians(90)
|
|
)
|
|
else:
|
|
p6 = angle_rotation(
|
|
p, p - val,
|
|
((p - (vec1.normalized() * adj)) - (p - (vec2.normalized() * adj))),
|
|
-radians(90)
|
|
)
|
|
|
|
orientation_vertex_list.append(p6)
|
|
|
|
new_inner_face = []
|
|
orientation_vertex_list_length = len(orientation_vertex_list)
|
|
ovll = orientation_vertex_list_length
|
|
|
|
for j in range(ovll):
|
|
q = orientation_vertex_list[j]
|
|
q1 = orientation_vertex_list[(j - 1) % ovll]
|
|
q2 = orientation_vertex_list[(j + 1) % ovll]
|
|
# again, these are just vectors between somewhat displaced corner vertices
|
|
vec1_ = q - q1
|
|
vec2_ = q - q2
|
|
ang_ = vec1_.angle(vec2_)
|
|
|
|
# the angle between them
|
|
if round(degrees(ang_)) == 180 or round(degrees(ang_)) == 0.0:
|
|
# again... if it's really a line...
|
|
v = bme.verts.new(q)
|
|
new_inner_face.append(v)
|
|
dict_0[j].append(v)
|
|
else:
|
|
# s.a.
|
|
if radius is False:
|
|
h_ = distance * (1 / cos(ang_ * 0.5))
|
|
d = distance
|
|
elif radius is True:
|
|
h_ = distance / sin(ang_ * 0.5)
|
|
d = distance / tan(ang_ * 0.5)
|
|
# max(d) is vec1_.magnitude * 0.5
|
|
# or vec2_.magnitude * 0.5 respectively
|
|
|
|
# only functional difference v
|
|
if d > vec1_.magnitude * 0.5:
|
|
d = vec1_.magnitude * 0.5
|
|
|
|
if d > vec2_.magnitude * 0.5:
|
|
d = vec2_.magnitude * 0.5
|
|
# only functional difference ^
|
|
|
|
q3 = q - (vec1_.normalized() * d)
|
|
q4 = q - (vec2_.normalized() * d)
|
|
# these are new verts somewhat offset from the corners
|
|
rp_ = q - ((q - ((q3 + q4) * 0.5)).normalized() * h_)
|
|
# reference point inside the curvature
|
|
axis_ = vec1_.cross(vec2_)
|
|
# this should really be just the face normal
|
|
vec3_ = rp_ - q3
|
|
vec4_ = rp_ - q4
|
|
rot_ang = vec3_.angle(vec4_)
|
|
cornerverts = []
|
|
|
|
for o in range(number_of_sides + 1):
|
|
# this calculates the actual new vertices
|
|
q5 = angle_rotation(rp_, q4, axis_, rot_ang * o / number_of_sides)
|
|
v = bme.verts.new(q5)
|
|
|
|
# creates new bmesh vertices from it
|
|
bme.verts.index_update()
|
|
|
|
dict_0[j].append(v)
|
|
cornerverts.append(v)
|
|
|
|
cornerverts.reverse()
|
|
new_inner_face.extend(cornerverts)
|
|
|
|
if out is False:
|
|
f = bme.faces.new(new_inner_face)
|
|
f.select_set(True)
|
|
elif out is True and kp is True:
|
|
f = bme.faces.new(new_inner_face)
|
|
f.select_set(True)
|
|
|
|
n2_ = len(dict_0)
|
|
# these are the new side faces, those that don't depend on cornertype
|
|
for o in range(n2_):
|
|
list_a = dict_0[o]
|
|
list_b = dict_0[(o + 1) % n2_]
|
|
bme.faces.new([list_a[0], list_b[0], list_b[-1], list_a[1]])
|
|
bme.faces.index_update()
|
|
# cornertype 1 - ngon faces
|
|
if type_enum == 'opt0':
|
|
for k in dict_0:
|
|
if len(dict_0[k]) > 2:
|
|
bme.faces.new(dict_0[k])
|
|
bme.faces.index_update()
|
|
# cornertype 2 - triangulated faces
|
|
if type_enum == 'opt1':
|
|
for k_ in dict_0:
|
|
q_ = dict_0[k_][0]
|
|
dict_0[k_].pop(0)
|
|
n3_ = len(dict_0[k_])
|
|
for kk in range(n3_ - 1):
|
|
bme.faces.new([dict_0[k_][kk], dict_0[k_][(kk + 1) % n3_], q_])
|
|
bme.faces.index_update()
|
|
|
|
del_ = [bme.faces.remove(f) for f in list_del]
|
|
|
|
if del_:
|
|
del del_
|
|
|
|
|
|
# Operator
|
|
|
|
class MESH_OT_face_inset_fillet(Operator):
|
|
bl_idname = "mesh.face_inset_fillet"
|
|
bl_label = "Face Inset Fillet"
|
|
bl_description = ("Inset selected and Fillet (make round) the corners \n"
|
|
"of the newly created Faces")
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
# inset amount
|
|
inset_amount : bpy.props.FloatProperty(
|
|
name="Inset amount",
|
|
description="Define the size of the Inset relative to the selection",
|
|
default=0.04,
|
|
min=0, max=100.0,
|
|
step=1,
|
|
precision=3
|
|
)
|
|
# number of sides
|
|
number_of_sides : bpy.props.IntProperty(
|
|
name="Number of sides",
|
|
description="Define the roundness of the corners by specifying\n"
|
|
"the subdivision count",
|
|
default=4,
|
|
min=1, max=100,
|
|
step=1
|
|
)
|
|
distance : bpy.props.FloatProperty(
|
|
name="",
|
|
description="Use distance or radius for corners' size calculation",
|
|
default=0.04,
|
|
min=0.00001, max=100.0,
|
|
step=1,
|
|
precision=3
|
|
)
|
|
out : bpy.props.BoolProperty(
|
|
name="Outside",
|
|
description="Inset the Faces outwards in relation to the selection\n"
|
|
"Note: depending on the geometry, can give unsatisfactory results",
|
|
default=False
|
|
)
|
|
radius : bpy.props.BoolProperty(
|
|
name="Radius",
|
|
description="Use radius for corners' size calculation",
|
|
default=False
|
|
)
|
|
type_enum : bpy.props.EnumProperty(
|
|
items=[('opt0', "N-gon", "N-gon corners - Keep the corner Faces uncut"),
|
|
('opt1', "Triangle", "Triangulate corners")],
|
|
name="Corner Type",
|
|
default="opt0"
|
|
)
|
|
kp : bpy.props.BoolProperty(
|
|
name="Keep faces",
|
|
description="Do not delete the inside Faces\n"
|
|
"Only available if the Out option is checked",
|
|
default=False
|
|
)
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
|
|
layout.label(text="Corner Type:")
|
|
|
|
row = layout.row()
|
|
row.prop(self, "type_enum", text="")
|
|
|
|
row = layout.row(align=True)
|
|
row.prop(self, "out")
|
|
|
|
if self.out is True:
|
|
row.prop(self, "kp")
|
|
|
|
row = layout.row()
|
|
row.prop(self, "inset_amount")
|
|
|
|
row = layout.row()
|
|
row.prop(self, "number_of_sides")
|
|
|
|
row = layout.row()
|
|
row.prop(self, "radius")
|
|
|
|
row = layout.row()
|
|
dist_rad = "Radius" if self.radius else "Distance"
|
|
row.prop(self, "distance", text=dist_rad)
|
|
|
|
def execute(self, context):
|
|
# this really just prepares everything for the main function
|
|
inset_amount = self.inset_amount
|
|
number_of_sides = self.number_of_sides
|
|
distance = self.distance
|
|
out = self.out
|
|
radius = self.radius
|
|
type_enum = self.type_enum
|
|
kp = self.kp
|
|
|
|
edit_mode_out()
|
|
ob_act = context.active_object
|
|
bme = bmesh.new()
|
|
bme.from_mesh(ob_act.data)
|
|
# this
|
|
face_index_list = [f.index for f in bme.faces if f.select and f.is_valid]
|
|
|
|
if len(face_index_list) == 0:
|
|
self.report({'WARNING'},
|
|
"No suitable Face selection found. Operation cancelled")
|
|
edit_mode_in()
|
|
|
|
return {'CANCELLED'}
|
|
|
|
elif len(face_index_list) != 0:
|
|
face_inset_fillet(bme, face_index_list,
|
|
inset_amount, distance, number_of_sides,
|
|
out, radius, type_enum, kp)
|
|
|
|
bme.to_mesh(ob_act.data)
|
|
edit_mode_in()
|
|
|
|
return {'FINISHED'}
|
|
|
|
# ********** Edit Multiselect **********
|
|
class VIEW3D_MT_Edit_MultiMET(Menu):
|
|
bl_label = "Multi Select"
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.operator_context = 'INVOKE_REGION_WIN'
|
|
|
|
layout.operator("multiedit.allselect", text="All Select Modes", icon='RESTRICT_SELECT_OFF')
|
|
|
|
|
|
# Select Tools
|
|
class VIEW3D_MT_Select_Vert(Menu):
|
|
bl_label = "Select Vert"
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.operator_context = 'INVOKE_REGION_WIN'
|
|
|
|
layout.operator("multiedit.vertexselect", text="Vertex Select Mode", icon='VERTEXSEL')
|
|
layout.operator("multiedit.vertedgeselect", text="Vert & Edge Select", icon='EDGESEL')
|
|
layout.operator("multiedit.vertfaceselect", text="Vert & Face Select", icon='FACESEL')
|
|
|
|
|
|
class VIEW3D_MT_Select_Edge(Menu):
|
|
bl_label = "Select Edge"
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.operator_context = 'INVOKE_REGION_WIN'
|
|
|
|
layout.operator("multiedit.edgeselect", text="Edge Select Mode", icon='EDGESEL')
|
|
layout.operator("multiedit.vertedgeselect", text="Edge & Vert Select", icon='VERTEXSEL')
|
|
layout.operator("multiedit.edgefaceselect", text="Edge & Face Select", icon='FACESEL')
|
|
|
|
|
|
class VIEW3D_MT_Select_Face(Menu):
|
|
bl_label = "Select Face"
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.operator_context = 'INVOKE_REGION_WIN'
|
|
|
|
layout.operator("multiedit.faceselect", text="Face Select Mode", icon='FACESEL')
|
|
layout.operator("multiedit.vertfaceselect", text="Face & Vert Select", icon='VERTEXSEL')
|
|
layout.operator("multiedit.edgefaceselect", text="Face & Edge Select", icon='EDGESEL')
|
|
|
|
|
|
# multiple edit select modes.
|
|
class VIEW3D_OT_multieditvertex(Operator):
|
|
bl_idname = "multiedit.vertexselect"
|
|
bl_label = "Vertex Mode"
|
|
bl_description = "Vert Select Mode On"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
def execute(self, context):
|
|
if context.object.mode != "EDIT":
|
|
bpy.ops.object.mode_set(mode="EDIT")
|
|
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT')
|
|
if bpy.ops.mesh.select_mode != "EDGE, FACE":
|
|
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT')
|
|
return {'FINISHED'}
|
|
|
|
|
|
class VIEW3D_OT_multieditedge(Operator):
|
|
bl_idname = "multiedit.edgeselect"
|
|
bl_label = "Edge Mode"
|
|
bl_description = "Edge Select Mode On"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
def execute(self, context):
|
|
if context.object.mode != "EDIT":
|
|
bpy.ops.object.mode_set(mode="EDIT")
|
|
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE')
|
|
if bpy.ops.mesh.select_mode != "VERT, FACE":
|
|
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE')
|
|
return {'FINISHED'}
|
|
|
|
|
|
class VIEW3D_OT_multieditface(Operator):
|
|
bl_idname = "multiedit.faceselect"
|
|
bl_label = "Multiedit Face"
|
|
bl_description = "Face Select Mode On"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
def execute(self, context):
|
|
if context.object.mode != "EDIT":
|
|
bpy.ops.object.mode_set(mode="EDIT")
|
|
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE')
|
|
if bpy.ops.mesh.select_mode != "VERT, EDGE":
|
|
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE')
|
|
return {'FINISHED'}
|
|
|
|
class VIEW3D_OT_multieditvertedge(Operator):
|
|
bl_idname = "multiedit.vertedgeselect"
|
|
bl_label = "Multiedit Face"
|
|
bl_description = "Vert & Edge Select Modes On"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
def execute(self, context):
|
|
if context.object.mode != "EDIT":
|
|
bpy.ops.object.mode_set(mode="EDIT")
|
|
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT')
|
|
if bpy.ops.mesh.select_mode != "VERT, EDGE, FACE":
|
|
bpy.ops.object.mode_set(mode="EDIT")
|
|
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT')
|
|
bpy.ops.mesh.select_mode(use_extend=True, use_expand=False, type='EDGE')
|
|
return {'FINISHED'}
|
|
|
|
class VIEW3D_OT_multieditvertface(Operator):
|
|
bl_idname = "multiedit.vertfaceselect"
|
|
bl_label = "Multiedit Face"
|
|
bl_description = "Vert & Face Select Modes On"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
def execute(self, context):
|
|
if context.object.mode != "EDIT":
|
|
bpy.ops.object.mode_set(mode="EDIT")
|
|
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT')
|
|
if bpy.ops.mesh.select_mode != "VERT, EDGE, FACE":
|
|
bpy.ops.object.mode_set(mode="EDIT")
|
|
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT')
|
|
bpy.ops.mesh.select_mode(use_extend=True, use_expand=False, type='FACE')
|
|
return {'FINISHED'}
|
|
|
|
|
|
class VIEW3D_OT_multieditedgeface(Operator):
|
|
bl_idname = "multiedit.edgefaceselect"
|
|
bl_label = "Mode Face Edge"
|
|
bl_description = "Edge & Face Select Modes On"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
def execute(self, context):
|
|
if context.object.mode != "EDIT":
|
|
bpy.ops.object.mode_set(mode="EDIT")
|
|
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE')
|
|
if bpy.ops.mesh.select_mode != "VERT, EDGE, FACE":
|
|
bpy.ops.object.mode_set(mode="EDIT")
|
|
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE')
|
|
bpy.ops.mesh.select_mode(use_extend=True, use_expand=False, type='FACE')
|
|
return {'FINISHED'}
|
|
|
|
|
|
class VIEW3D_OT_multieditall(Operator):
|
|
bl_idname = "multiedit.allselect"
|
|
bl_label = "All Edit Select Modes"
|
|
bl_description = "Vert & Edge & Face Select Modes On"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
def execute(self, context):
|
|
if context.object.mode != "EDIT":
|
|
bpy.ops.object.mode_set(mode="EDIT")
|
|
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT')
|
|
if bpy.ops.mesh.select_mode != "VERT, EDGE, FACE":
|
|
bpy.ops.object.mode_set(mode="EDIT")
|
|
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT')
|
|
bpy.ops.mesh.select_mode(use_extend=True, use_expand=False, type='EDGE')
|
|
bpy.ops.mesh.select_mode(use_extend=True, use_expand=False, type='FACE')
|
|
return {'FINISHED'}
|
|
|
|
|
|
# ########################################
|
|
# ##### GUI and registration #############
|
|
# ########################################
|
|
|
|
# menu containing all tools
|
|
class VIEW3D_MT_edit_mesh_tools(Menu):
|
|
bl_label = "Mesh Tools"
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.operator("mesh.remove_doubles")
|
|
layout.operator("mesh.dissolve_limited")
|
|
layout.operator("mesh.flip_normals")
|
|
props = layout.operator("mesh.quads_convert_to_tris")
|
|
props.quad_method = props.ngon_method = 'BEAUTY'
|
|
layout.operator("mesh.tris_convert_to_quads")
|
|
layout.operator('mesh.vertex_chamfer', text="Vertex Chamfer")
|
|
layout.operator("mesh.bevel", text="Bevel Vertices").vertex_only = True
|
|
layout.operator('mesh.offset_edges', text="Offset Edges")
|
|
layout.operator('mesh.fillet_plus', text="Fillet Edges")
|
|
layout.operator("mesh.face_inset_fillet",
|
|
text="Face Inset Fillet")
|
|
# layout.operator("mesh.extrude_reshape",
|
|
# text="Push/Pull Faces")
|
|
layout.operator("object.mextrude",
|
|
text="Multi Extrude")
|
|
layout.operator('mesh.split_solidify', text="Split Solidify")
|
|
|
|
|
|
|
|
# panel containing all tools
|
|
class VIEW3D_PT_edit_mesh_tools(Panel):
|
|
bl_space_type = 'VIEW_3D'
|
|
bl_region_type = 'UI'
|
|
bl_category = 'Edit'
|
|
bl_context = "mesh_edit"
|
|
bl_label = "Mesh Tools"
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
col = layout.column(align=True)
|
|
et = context.window_manager.edittools
|
|
|
|
# vert - first line
|
|
split = col.split(factor=0.80, align=True)
|
|
if et.display_vert:
|
|
split.prop(et, "display_vert", text="Vert Tools", icon='DOWNARROW_HLT')
|
|
else:
|
|
split.prop(et, "display_vert", text="Vert tools", icon='RIGHTARROW')
|
|
split.menu("VIEW3D_MT_Select_Vert", text="", icon='VERTEXSEL')
|
|
# vert - settings
|
|
if et.display_vert:
|
|
box = col.column(align=True).box().column()
|
|
col_top = box.column(align=True)
|
|
row = col_top.row(align=True)
|
|
row.operator('mesh.vertex_chamfer', text="Vertex Chamfer")
|
|
row = col_top.row(align=True)
|
|
row.operator("mesh.extrude_vertices_move", text="Extrude Vertices")
|
|
row = col_top.row(align=True)
|
|
row.operator("mesh.random_vertices", text="Random Vertices")
|
|
row = col_top.row(align=True)
|
|
row.operator("mesh.bevel", text="Bevel Vertices").vertex_only = True
|
|
|
|
# edge - first line
|
|
split = col.split(factor=0.80, align=True)
|
|
if et.display_edge:
|
|
split.prop(et, "display_edge", text="Edge Tools", icon='DOWNARROW_HLT')
|
|
else:
|
|
split.prop(et, "display_edge", text="Edge Tools", icon='RIGHTARROW')
|
|
split.menu("VIEW3D_MT_Select_Edge", text="", icon='EDGESEL')
|
|
# Edge - settings
|
|
if et.display_edge:
|
|
box = col.column(align=True).box().column()
|
|
col_top = box.column(align=True)
|
|
row = col_top.row(align=True)
|
|
row.operator('mesh.offset_edges', text="Offset Edges")
|
|
row = col_top.row(align=True)
|
|
row.operator('mesh.fillet_plus', text="Fillet Edges")
|
|
row = col_top.row(align=True)
|
|
row.operator('mesh.edge_roundifier', text="Edge Roundify")
|
|
row = col_top.row(align=True)
|
|
row.operator('object.mesh_edge_length_set', text="Set Edge Length")
|
|
row = col_top.row(align=True)
|
|
row.operator('mesh.edges_floor_plan', text="Edges Floor Plan")
|
|
row = col_top.row(align=True)
|
|
row.operator("mesh.extrude_edges_move", text="Extrude Edges")
|
|
row = col_top.row(align=True)
|
|
row.operator("mesh.bevel", text="Bevel Edges").vertex_only = False
|
|
|
|
# face - first line
|
|
split = col.split(factor=0.80, align=True)
|
|
if et.display_face:
|
|
split.prop(et, "display_face", text="Face Tools", icon='DOWNARROW_HLT')
|
|
else:
|
|
split.prop(et, "display_face", text="Face Tools", icon='RIGHTARROW')
|
|
split.menu("VIEW3D_MT_Select_Face", text="", icon='FACESEL')
|
|
# face - settings
|
|
if et.display_face:
|
|
box = col.column(align=True).box().column()
|
|
col_top = box.column(align=True)
|
|
row = col_top.row(align=True)
|
|
row.operator("mesh.face_inset_fillet",
|
|
text="Face Inset Fillet")
|
|
row = col_top.row(align=True)
|
|
row.operator("mesh.ext_cut_faces",
|
|
text="Cut Faces")
|
|
row = col_top.row(align=True)
|
|
# row.operator("mesh.extrude_reshape",
|
|
# text="Push/Pull Faces")
|
|
row = col_top.row(align=True)
|
|
row.operator("object.mextrude",
|
|
text="Multi Extrude")
|
|
row = col_top.row(align=True)
|
|
row.operator('mesh.split_solidify', text="Split Solidify")
|
|
row = col_top.row(align=True)
|
|
row.operator('mesh.add_faces_to_object', text="Face Shape")
|
|
row = col_top.row(align=True)
|
|
row.operator("mesh.inset")
|
|
row = col_top.row(align=True)
|
|
row.operator("mesh.extrude_faces_move", text="Extrude Individual Faces")
|
|
|
|
# util - first line
|
|
split = col.split(factor=0.80, align=True)
|
|
if et.display_util:
|
|
split.prop(et, "display_util", text="Utility Tools", icon='DOWNARROW_HLT')
|
|
else:
|
|
split.prop(et, "display_util", text="Utility Tools", icon='RIGHTARROW')
|
|
split.menu("VIEW3D_MT_Edit_MultiMET", text="", icon='RESTRICT_SELECT_OFF')
|
|
# util - settings
|
|
if et.display_util:
|
|
box = col.column(align=True).box().column()
|
|
col_top = box.column(align=True)
|
|
row = col_top.row(align=True)
|
|
row.operator("mesh.subdivide")
|
|
row = col_top.row(align=True)
|
|
row.operator("mesh.remove_doubles")
|
|
row = col_top.row(align=True)
|
|
row.operator("mesh.dissolve_limited")
|
|
row = col_top.row(align=True)
|
|
row.operator("mesh.flip_normals")
|
|
row = col_top.row(align=True)
|
|
props = row.operator("mesh.quads_convert_to_tris")
|
|
props.quad_method = props.ngon_method = 'BEAUTY'
|
|
row = col_top.row(align=True)
|
|
row.operator("mesh.tris_convert_to_quads")
|
|
row = col_top.row(align=True)
|
|
row.operator("mesh.relax")
|
|
|
|
# property group containing all properties for the gui in the panel
|
|
class EditToolsProps(PropertyGroup):
|
|
"""
|
|
Fake module like class
|
|
bpy.context.window_manager.edittools
|
|
"""
|
|
# general display properties
|
|
display_vert: BoolProperty(
|
|
name="Bridge settings",
|
|
description="Display settings of the Vert tool",
|
|
default=False
|
|
)
|
|
display_edge: BoolProperty(
|
|
name="Edge settings",
|
|
description="Display settings of the Edge tool",
|
|
default=False
|
|
)
|
|
display_face: BoolProperty(
|
|
name="Face settings",
|
|
description="Display settings of the Face tool",
|
|
default=False
|
|
)
|
|
display_util: BoolProperty(
|
|
name="Face settings",
|
|
description="Display settings of the Face tool",
|
|
default=False
|
|
)
|
|
|
|
# draw function for integration in menus
|
|
def menu_func(self, context):
|
|
self.layout.menu("VIEW3D_MT_edit_mesh_tools")
|
|
self.layout.separator()
|
|
|
|
# Add-ons Preferences Update Panel
|
|
|
|
# Define Panel classes for updating
|
|
panels = (
|
|
VIEW3D_PT_edit_mesh_tools,
|
|
)
|
|
|
|
|
|
def update_panel(self, context):
|
|
message = "LoopTools: Updating Panel locations has failed"
|
|
try:
|
|
for panel in panels:
|
|
if "bl_rna" in panel.__dict__:
|
|
bpy.utils.unregister_class(panel)
|
|
|
|
for panel in panels:
|
|
panel.bl_category = context.preferences.addons[__name__].preferences.category
|
|
bpy.utils.register_class(panel)
|
|
|
|
except Exception as e:
|
|
print("\n[{}]\n{}\n\nError:\n{}".format(__name__, message, e))
|
|
pass
|
|
|
|
|
|
class EditToolsPreferences(AddonPreferences):
|
|
# this must match the addon name, use '__package__'
|
|
# when defining this in a submodule of a python package.
|
|
bl_idname = __name__
|
|
|
|
category: StringProperty(
|
|
name="Tab Category",
|
|
description="Choose a name for the category of the panel",
|
|
default="Edit",
|
|
update=update_panel
|
|
)
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
|
|
row = layout.row()
|
|
col = row.column()
|
|
col.label(text="Tab Category:")
|
|
col.prop(self, "category", text="")
|
|
|
|
|
|
# define classes for registration
|
|
classes = (
|
|
VIEW3D_MT_edit_mesh_tools,
|
|
VIEW3D_PT_edit_mesh_tools,
|
|
VIEW3D_MT_Edit_MultiMET,
|
|
VIEW3D_MT_Select_Vert,
|
|
VIEW3D_MT_Select_Edge,
|
|
VIEW3D_MT_Select_Face,
|
|
EditToolsProps,
|
|
EditToolsPreferences,
|
|
MESH_OT_face_inset_fillet,
|
|
ME_OT_MExtrude,
|
|
VIEW3D_OT_multieditvertex,
|
|
VIEW3D_OT_multieditedge,
|
|
VIEW3D_OT_multieditface,
|
|
VIEW3D_OT_multieditvertedge,
|
|
VIEW3D_OT_multieditvertface,
|
|
VIEW3D_OT_multieditedgeface,
|
|
VIEW3D_OT_multieditall
|
|
)
|
|
|
|
|
|
# registering and menu integration
|
|
def register():
|
|
for cls in classes:
|
|
bpy.utils.register_class(cls)
|
|
bpy.types.VIEW3D_MT_edit_mesh_context_menu.prepend(menu_func)
|
|
bpy.types.WindowManager.edittools = PointerProperty(type=EditToolsProps)
|
|
update_panel(None, bpy.context)
|
|
|
|
mesh_filletplus.register()
|
|
mesh_offset_edges.register()
|
|
split_solidify.register()
|
|
mesh_vertex_chamfer.register()
|
|
random_vertices.register()
|
|
# mesh_extrude_and_reshape.register()
|
|
mesh_edge_roundifier.register()
|
|
mesh_edgetools.register()
|
|
mesh_edges_floor_plan.register()
|
|
mesh_edges_length.register()
|
|
pkhg_faces.register()
|
|
mesh_cut_faces.register()
|
|
mesh_relax.register()
|
|
|
|
|
|
# unregistering and removing menus
|
|
def unregister():
|
|
for cls in reversed(classes):
|
|
bpy.utils.unregister_class(cls)
|
|
bpy.types.VIEW3D_MT_edit_mesh_context_menu.remove(menu_func)
|
|
try:
|
|
del bpy.types.WindowManager.edittools
|
|
except Exception as e:
|
|
print('unregister fail:\n', e)
|
|
pass
|
|
|
|
mesh_filletplus.unregister()
|
|
mesh_offset_edges.unregister()
|
|
split_solidify.unregister()
|
|
mesh_vertex_chamfer.unregister()
|
|
random_vertices.unregister()
|
|
# mesh_extrude_and_reshape.unregister()
|
|
mesh_edge_roundifier.unregister()
|
|
mesh_edgetools.unregister()
|
|
mesh_edges_floor_plan.unregister()
|
|
mesh_edges_length.unregister()
|
|
pkhg_faces.unregister()
|
|
mesh_cut_faces.unregister()
|
|
mesh_relax.unregister()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
register()
|