1 # -*- coding: utf-8 -*-
3 # ##### BEGIN GPL LICENSE BLOCK #####
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software Foundation,
17 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 # ##### END GPL LICENSE BLOCK #####
21 # based completely on addon by zmj100
22 # added some distance limits to prevent overlap - max12345
27 from bpy
.types
import Operator
28 from bpy
.props
import (
38 from mathutils
import Matrix
42 bpy
.ops
.object.mode_set(mode
='OBJECT')
46 bpy
.ops
.object.mode_set(mode
='EDIT')
49 def angle_rotation(rp
, q
, axis
, angle
):
50 # returns the vector made by the rotation of the vector q
51 # rp by angle around axis and then adds rp
53 return (Matrix
.Rotation(angle
, 3, axis
) * (q
- rp
)) + rp
56 def face_inset_fillet(bme
, face_index_list
, inset_amount
, distance
,
57 number_of_sides
, out
, radius
, type_enum
, kp
):
60 for faceindex
in face_index_list
:
62 bme
.faces
.ensure_lookup_table()
63 # loops through the faces...
64 f
= bme
.faces
[faceindex
]
68 vertex_index_list
= [v
.index
for v
in f
.verts
]
70 orientation_vertex_list
= []
71 n
= len(vertex_index_list
)
73 # loops through the vertices
75 bme
.verts
.ensure_lookup_table()
76 p
= (bme
.verts
[vertex_index_list
[i
]].co
).copy()
77 p1
= (bme
.verts
[vertex_index_list
[(i
- 1) % n
]].co
).copy()
78 p2
= (bme
.verts
[vertex_index_list
[(i
+ 1) % n
]].co
).copy()
79 # copies some vert coordinates, always the 3 around i
80 dict_0
[i
].append(bme
.verts
[vertex_index_list
[i
]])
81 # appends the bmesh vert of the appropriate index to the dict
84 # vectors for the other corner points to the cornerpoint
85 # corresponding to i / p
86 angle
= vec1
.angle(vec2
)
88 adj
= inset_amount
/ tan(angle
* 0.5)
89 h
= (adj
** 2 + inset_amount
** 2) ** 0.5
90 if round(degrees(angle
)) == 180 or round(degrees(angle
)) == 0.0:
91 # if the corner is a straight line...
92 # I think this creates some new points...
94 val
= ((f
.normal
).normalized() * inset_amount
)
96 val
= -((f
.normal
).normalized() * inset_amount
)
97 p6
= angle_rotation(p
, p
+ val
, vec1
, radians(90))
99 # if the corner is an actual corner
100 val
= ((f
.normal
).normalized() * h
)
102 # this -(p - (vec2.normalized() * adj))) is just the freaking axis afaik...
105 -(p
- (vec2
.normalized() * adj
)),
111 ((p
- (vec1
.normalized() * adj
)) - (p
- (vec2
.normalized() * adj
))),
115 orientation_vertex_list
.append(p6
)
118 orientation_vertex_list_length
= len(orientation_vertex_list
)
119 ovll
= orientation_vertex_list_length
121 for j
in range(ovll
):
122 q
= orientation_vertex_list
[j
]
123 q1
= orientation_vertex_list
[(j
- 1) % ovll
]
124 q2
= orientation_vertex_list
[(j
+ 1) % ovll
]
125 # again, these are just vectors between somewhat displaced corner vertices
128 ang_
= vec1_
.angle(vec2_
)
130 # the angle between them
131 if round(degrees(ang_
)) == 180 or round(degrees(ang_
)) == 0.0:
132 # again... if it's really a line...
134 new_inner_face
.append(v
)
139 h_
= distance
* (1 / cos(ang_
* 0.5))
142 h_
= distance
/ sin(ang_
* 0.5)
143 d
= distance
/ tan(ang_
* 0.5)
144 # max(d) is vec1_.magnitude * 0.5
145 # or vec2_.magnitude * 0.5 respectively
147 # only functional difference v
148 if d
> vec1_
.magnitude
* 0.5:
149 d
= vec1_
.magnitude
* 0.5
151 if d
> vec2_
.magnitude
* 0.5:
152 d
= vec2_
.magnitude
* 0.5
153 # only functional difference ^
155 q3
= q
- (vec1_
.normalized() * d
)
156 q4
= q
- (vec2_
.normalized() * d
)
157 # these are new verts somewhat offset from the corners
158 rp_
= q
- ((q
- ((q3
+ q4
) * 0.5)).normalized() * h_
)
159 # reference point inside the curvature
160 axis_
= vec1_
.cross(vec2_
)
161 # this should really be just the face normal
164 rot_ang
= vec3_
.angle(vec4_
)
167 for o
in range(number_of_sides
+ 1):
168 # this calculates the actual new vertices
169 q5
= angle_rotation(rp_
, q4
, axis_
, rot_ang
* o
/ number_of_sides
)
170 v
= bme
.verts
.new(q5
)
172 # creates new bmesh vertices from it
173 bme
.verts
.index_update()
176 cornerverts
.append(v
)
178 cornerverts
.reverse()
179 new_inner_face
.extend(cornerverts
)
182 f
= bme
.faces
.new(new_inner_face
)
184 elif out
is True and kp
is True:
185 f
= bme
.faces
.new(new_inner_face
)
189 # these are the new side faces, those that don't depend on cornertype
192 list_b
= dict_0
[(o
+ 1) % n2_
]
193 bme
.faces
.new([list_a
[0], list_b
[0], list_b
[-1], list_a
[1]])
194 bme
.faces
.index_update()
195 # cornertype 1 - ngon faces
196 if type_enum
== 'opt0':
198 if len(dict_0
[k
]) > 2:
199 bme
.faces
.new(dict_0
[k
])
200 bme
.faces
.index_update()
201 # cornertype 2 - triangulated faces
202 if type_enum
== 'opt1':
206 n3_
= len(dict_0
[k_
])
207 for kk
in range(n3_
- 1):
208 bme
.faces
.new([dict_0
[k_
][kk
], dict_0
[k_
][(kk
+ 1) % n3_
], q_
])
209 bme
.faces
.index_update()
211 del_
= [bme
.faces
.remove(f
) for f
in list_del
]
219 class MESH_OT_face_inset_fillet(Operator
):
220 bl_idname
= "mesh.face_inset_fillet"
221 bl_label
= "Face Inset Fillet"
222 bl_description
= ("Inset selected and Fillet (make round) the corners \n"
223 "of the newly created Faces")
224 bl_options
= {"REGISTER", "UNDO"}
227 inset_amount
= FloatProperty(
229 description
="Define the size of the Inset relative to the selection",
236 number_of_sides
= IntProperty(
237 name
="Number of sides",
238 description
="Define the roundness of the corners by specifying\n"
239 "the subdivision count",
244 distance
= FloatProperty(
246 description
="Use distance or radius for corners' size calculation",
248 min=0.00001, max=100.0,
254 description
="Inset the Faces outwards in relation to the selection\n"
255 "Note: depending on the geometry, can give unsatisfactory results",
258 radius
= BoolProperty(
260 description
="Use radius for corners' size calculation",
263 type_enum
= EnumProperty(
264 items
=(('opt0', "N-gon", "N-gon corners - Keep the corner Faces uncut"),
265 ('opt1', "Triangle", "Triangulate corners")),
271 description
="Do not delete the inside Faces\n"
272 "Only available if the Out option is checked",
276 def draw(self
, context
):
279 layout
.label("Corner Type:")
282 row
.prop(self
, "type_enum", text
="")
284 row
= layout
.row(align
=True)
285 row
.prop(self
, "out")
291 row
.prop(self
, "inset_amount")
294 row
.prop(self
, "number_of_sides")
297 row
.prop(self
, "radius")
300 dist_rad
= "Radius" if self
.radius
else "Distance"
301 row
.prop(self
, "distance", text
=dist_rad
)
303 def execute(self
, context
):
304 # this really just prepares everything for the main function
305 inset_amount
= self
.inset_amount
306 number_of_sides
= self
.number_of_sides
307 distance
= self
.distance
310 type_enum
= self
.type_enum
314 ob_act
= context
.active_object
316 bme
.from_mesh(ob_act
.data
)
318 face_index_list
= [f
.index
for f
in bme
.faces
if f
.select
and f
.is_valid
]
320 if len(face_index_list
) == 0:
321 self
.report({'WARNING'},
322 "No suitable Face selection found. Operation cancelled")
327 elif len(face_index_list
) != 0:
328 face_inset_fillet(bme
, face_index_list
,
329 inset_amount
, distance
, number_of_sides
,
330 out
, radius
, type_enum
, kp
)
332 bme
.to_mesh(ob_act
.data
)