1 # SPDX-FileCopyrightText: 2019-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
7 "author": "Gert De Roost - original by zmj100",
10 "location": "View3D > Tool Shelf",
19 from bpy
.props
import (
24 from bpy
.types
import Operator
26 from mathutils
import Matrix
39 def get_adj_v_(list_
):
43 tmp
[i
[0]].append(i
[1])
47 tmp
[i
[1]].append(i
[0])
53 # one of the angles was not 0 or 180
57 def fillets(list_0
, startv
, vertlist
, face
, adj
, n
, out
, flip
, radius
):
59 dict_0
= get_adj_v_(list_0
)
60 list_1
= [[dict_0
[i
][0], i
, dict_0
[i
][1]] for i
in dict_0
if (len(dict_0
[i
]) == 2)][0]
63 list_3
.append(bm
.verts
[elem
])
67 p
= (list_3
[1].co
).copy()
68 p1
= (list_3
[0].co
).copy()
69 p2
= (list_3
[2].co
).copy()
74 ang
= vec1
.angle(vec2
, any
)
75 check_angle
= round(degrees(ang
))
77 if check_angle
== 180 or check_angle
== 0.0:
85 h
= adj
* (1 / cos(ang
* 0.5))
88 h
= opp
/ sin(ang
* 0.5)
89 adj_
= opp
/ tan(ang
* 0.5)
91 p3
= p
- (vec1
.normalized() * adj_
)
92 p4
= p
- (vec2
.normalized() * adj_
)
93 rp
= p
- ((p
- ((p3
+ p4
) * 0.5)).normalized() * h
)
98 axis
= vec1
.cross(vec2
)
102 rot_ang
= vec3
.angle(vec4
)
104 rot_ang
= vec1
.angle(vec2
)
106 rot_ang
= (2 * pi
) - vec1
.angle(vec2
)
108 for j
in range(n
+ 1):
109 new_angle
= rot_ang
* j
/ n
110 mtrx
= Matrix
.Rotation(new_angle
, 3, axis
)
117 p3
= p
- (vec1
.normalized() * opp
)
122 p4
= p
- (vec2
.normalized() * opp
)
127 v
= bm
.verts
.new(tmp2
)
140 for t
in range(n1
- 1):
141 bm
.edges
.new([list_3
[t
], list_3
[(t
+ 1) % n1
]])
144 bm
.edges
.new([v
, p_
])
146 bm
.edges
.ensure_lookup_table()
150 if l
.vert
== list_3
[0]:
155 if startl
.link_loop_next
.vert
== startv
:
156 l
= startl
.link_loop_prev
157 while len(vertlist
) > 0:
158 vertlist2
.insert(0, l
.vert
)
159 vertlist
.pop(vertlist
.index(l
.vert
))
162 l
= startl
.link_loop_next
163 while len(vertlist
) > 0:
164 vertlist2
.insert(0, l
.vert
)
165 vertlist
.pop(vertlist
.index(l
.vert
))
170 bm
.faces
.new(vertlist2
)
172 bm
.verts
.remove(startv
)
174 print("\n[Function fillets Error]\n"
175 "Starting vertex (startv var) couldn't be removed\n")
177 bm
.verts
.ensure_lookup_table()
178 bm
.edges
.ensure_lookup_table()
179 bm
.faces
.ensure_lookup_table()
181 list_3
[-2].select
= 1
182 bm
.edges
.get([list_3
[0], list_3
[1]]).select
= 1
183 bm
.edges
.get([list_3
[-1], list_3
[-2]]).select
= 1
184 bm
.verts
.index_update()
185 bm
.edges
.index_update()
186 bm
.faces
.index_update()
188 me
.update(calc_edges
=True, calc_loop_triangles
=True)
189 bmesh
.ops
.recalc_face_normals(bm
, faces
=bm
.faces
)
191 except Exception as e
:
192 print("\n[Function fillets Error]\n{}\n".format(e
))
196 def do_filletplus(self
, pair
):
202 list_0
= [list([e
.verts
[0].index
, e
.verts
[1].index
]) for e
in pair
]
205 bm
.verts
.ensure_lookup_table()
206 bm
.edges
.ensure_lookup_table()
207 bm
.faces
.ensure_lookup_table()
208 vertset
.add(bm
.verts
[list_0
[0][0]])
209 vertset
.add(bm
.verts
[list_0
[0][1]])
210 vertset
.add(bm
.verts
[list_0
[1][0]])
211 vertset
.add(bm
.verts
[list_0
[1][1]])
216 self
.report({'WARNING'}, "Two adjacent edges must be selected")
222 for f
in v1
.link_faces
:
223 if v2
in f
.verts
and v3
in f
.verts
:
226 for v
in [v1
, v2
, v3
]:
227 if v
.index
in list_0
[0] and v
.index
in list_0
[1]:
231 for f
in v1
.link_faces
:
232 if v2
in f
.verts
and v3
in f
.verts
:
234 if not(v
in vertset
):
236 if (v
in vertset
and v
.link_loops
[0].link_loop_prev
.vert
in vertset
and
237 v
.link_loops
[0].link_loop_next
.vert
in vertset
):
243 fills
= fillets(list_0
, startv
, vertlist
, face
, adj
, n
, out
, flip
, radius
)
248 except Exception as e
:
249 print("\n[Function do_filletplus Error]\n{}\n".format(e
))
254 def check_is_not_coplanar(bm_data
):
255 from mathutils
import Vector
257 angles
, norm_angle
= 0, 0
258 z_vec
= Vector((0, 0, 1))
260 bm_data
.faces
.ensure_lookup_table()
262 for f
in bm_data
.faces
:
263 norm_angle
= f
.normal
.angle(z_vec
)
266 if angles
!= norm_angle
:
269 except Exception as e
:
270 print("\n[Function check_is_not_coplanar Error]\n{}\n".format(e
))
277 class MESH_OT_fillet_plus(Operator
):
278 bl_idname
= "mesh.fillet_plus"
279 bl_label
= "Fillet Plus"
280 bl_description
= ("Fillet adjoining edges\n"
281 "Note: Works on a mesh whose all faces share the same normal")
282 bl_options
= {"REGISTER", "UNDO"}
286 description
="Size of the filleted corners",
288 min=0.00001, max=100.0,
294 description
="Subdivision of the filleted corners",
301 description
="Fillet towards outside",
306 description
="Flip the direction of the Fillet\n"
307 "Only available if Outside option is not active",
310 radius
: BoolProperty(
312 description
="Use radius for the size of the filleted corners",
317 def poll(cls
, context
):
318 obj
= context
.active_object
319 return (obj
and obj
.type == 'MESH' and context
.mode
== 'EDIT_MESH')
321 def draw(self
, context
):
324 if f_buf
.check
is False:
325 layout
.label(text
="Angle is equal to 0 or 180", icon
="INFO")
326 layout
.label(text
="Can not fillet", icon
="BLANK1")
328 layout
.prop(self
, "radius")
329 if self
.radius
is True:
330 layout
.label(text
="Radius:")
331 elif self
.radius
is False:
332 layout
.label(text
="Distance:")
333 layout
.prop(self
, "adj")
334 layout
.label(text
="Number of sides:")
335 layout
.prop(self
, "n")
338 row
= layout
.row(align
=False)
339 row
.prop(self
, "out")
340 if self
.out
is False:
341 row
.prop(self
, "flip")
343 def execute(self
, context
):
345 global bm
, me
, adj
, n
, out
, flip
, radius
, f_buf
356 ob_act
= context
.active_object
359 bm
= bmesh
.from_edit_mesh(me
)
360 warn_obj
= bool(check_is_not_coplanar(bm
))
361 if warn_obj
is False:
363 bm
.verts
.ensure_lookup_table()
364 bm
.edges
.ensure_lookup_table()
365 bm
.faces
.ensure_lookup_table()
367 if v
.select
and v
.is_boundary
:
371 for e
in v
.link_edges
:
372 if e
.select
and e
.is_boundary
:
374 if len(edgeset
) == 2:
375 is_finished
= do_filletplus(self
, edgeset
)
380 bpy
.ops
.mesh
.select_all(action
="DESELECT")
382 if len(v
.link_edges
) == 0:
384 bpy
.ops
.object.editmode_toggle()
385 bpy
.ops
.object.editmode_toggle()
387 self
.report({'WARNING'}, "Filletplus operation could not be performed")
390 self
.report({'WARNING'}, "Mesh is not a coplanar surface. Operation cancelled")
393 self
.report({'WARNING'}, "Filletplus operation could not be performed")
398 # define classes for registration
403 # registering and menu integration
406 bpy
.utils
.register_class(cls
)
409 # unregistering and removing menus
411 for cls
in reversed(classes
):
412 bpy
.utils
.unregister_class(cls
)
414 if __name__
== "__main__":