1 # -*- coding: utf-8 -*-
3 # ##### END 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 #####
23 "author": "Gert De Roost - original by zmj100",
25 "blender": (2, 61, 0),
26 "location": "View3D > Tool Shelf",
34 from bpy
.props
import (
39 from bpy
.types
import Operator
41 from mathutils
import Matrix
54 def get_adj_v_(list_
):
58 tmp
[i
[0]].append(i
[1])
62 tmp
[i
[1]].append(i
[0])
69 # one of the angles was not 0 or 180
73 def fillets(list_0
, startv
, vertlist
, face
, adj
, n
, out
, flip
, radius
):
75 dict_0
= get_adj_v_(list_0
)
76 list_1
= [[dict_0
[i
][0], i
, dict_0
[i
][1]] for i
in dict_0
if (len(dict_0
[i
]) == 2)][0]
79 list_3
.append(bm
.verts
[elem
])
83 p
= (list_3
[1].co
).copy()
84 p1
= (list_3
[0].co
).copy()
85 p2
= (list_3
[2].co
).copy()
90 ang
= vec1
.angle(vec2
, any
)
91 check_angle
= round(degrees(ang
))
93 if check_angle
== 180 or check_angle
== 0.0:
101 h
= adj
* (1 / cos(ang
* 0.5))
104 h
= opp
/ sin(ang
* 0.5)
105 adj_
= opp
/ tan(ang
* 0.5)
107 p3
= p
- (vec1
.normalized() * adj_
)
108 p4
= p
- (vec2
.normalized() * adj_
)
109 rp
= p
- ((p
- ((p3
+ p4
) * 0.5)).normalized() * h
)
114 axis
= vec1
.cross(vec2
)
118 rot_ang
= vec3
.angle(vec4
)
120 rot_ang
= vec1
.angle(vec2
)
122 rot_ang
= (2 * pi
) - vec1
.angle(vec2
)
124 for j
in range(n
+ 1):
125 new_angle
= rot_ang
* j
/ n
126 mtrx
= Matrix
.Rotation(new_angle
, 3, axis
)
133 p3
= p
- (vec1
.normalized() * opp
)
138 p4
= p
- (vec2
.normalized() * opp
)
143 v
= bm
.verts
.new(tmp2
)
156 for t
in range(n1
- 1):
157 bm
.edges
.new([list_3
[t
], list_3
[(t
+ 1) % n1
]])
160 bm
.edges
.new([v
, p_
])
162 bm
.edges
.ensure_lookup_table()
166 if l
.vert
== list_3
[0]:
171 if startl
.link_loop_next
.vert
== startv
:
172 l
= startl
.link_loop_prev
173 while len(vertlist
) > 0:
174 vertlist2
.insert(0, l
.vert
)
175 vertlist
.pop(vertlist
.index(l
.vert
))
178 l
= startl
.link_loop_next
179 while len(vertlist
) > 0:
180 vertlist2
.insert(0, l
.vert
)
181 vertlist
.pop(vertlist
.index(l
.vert
))
186 bm
.faces
.new(vertlist2
)
188 bm
.verts
.remove(startv
)
190 print("\n[Function fillets Error]\n"
191 "Starting vertex (startv var) couldn't be removed\n")
193 bm
.verts
.ensure_lookup_table()
194 bm
.edges
.ensure_lookup_table()
195 bm
.faces
.ensure_lookup_table()
197 list_3
[-2].select
= 1
198 bm
.edges
.get([list_3
[0], list_3
[1]]).select
= 1
199 bm
.edges
.get([list_3
[-1], list_3
[-2]]).select
= 1
200 bm
.verts
.index_update()
201 bm
.edges
.index_update()
202 bm
.faces
.index_update()
204 me
.update(calc_edges
=True, calc_loop_triangles
=True)
205 bmesh
.ops
.recalc_face_normals(bm
, faces
=bm
.faces
)
207 except Exception as e
:
208 print("\n[Function fillets Error]\n{}\n".format(e
))
212 def do_filletplus(self
, pair
):
218 list_0
= [list([e
.verts
[0].index
, e
.verts
[1].index
]) for e
in pair
]
221 bm
.verts
.ensure_lookup_table()
222 bm
.edges
.ensure_lookup_table()
223 bm
.faces
.ensure_lookup_table()
224 vertset
.add(bm
.verts
[list_0
[0][0]])
225 vertset
.add(bm
.verts
[list_0
[0][1]])
226 vertset
.add(bm
.verts
[list_0
[1][0]])
227 vertset
.add(bm
.verts
[list_0
[1][1]])
232 self
.report({'WARNING'}, "Two adjacent edges must be selected")
238 for f
in v1
.link_faces
:
239 if v2
in f
.verts
and v3
in f
.verts
:
242 for v
in [v1
, v2
, v3
]:
243 if v
.index
in list_0
[0] and v
.index
in list_0
[1]:
247 for f
in v1
.link_faces
:
248 if v2
in f
.verts
and v3
in f
.verts
:
250 if not(v
in vertset
):
252 if (v
in vertset
and v
.link_loops
[0].link_loop_prev
.vert
in vertset
and
253 v
.link_loops
[0].link_loop_next
.vert
in vertset
):
259 fills
= fillets(list_0
, startv
, vertlist
, face
, adj
, n
, out
, flip
, radius
)
264 except Exception as e
:
265 print("\n[Function do_filletplus Error]\n{}\n".format(e
))
270 def check_is_not_coplanar(bm_data
):
271 from mathutils
import Vector
273 angles
, norm_angle
= 0, 0
274 z_vec
= Vector((0, 0, 1))
276 bm_data
.faces
.ensure_lookup_table()
278 for f
in bm_data
.faces
:
279 norm_angle
= f
.normal
.angle(z_vec
)
282 if angles
!= norm_angle
:
285 except Exception as e
:
286 print("\n[Function check_is_not_coplanar Error]\n{}\n".format(e
))
293 class MESH_OT_fillet_plus(Operator
):
294 bl_idname
= "mesh.fillet_plus"
295 bl_label
= "Fillet Plus"
296 bl_description
= ("Fillet adjoining edges\n"
297 "Note: Works on a mesh whose all faces share the same normal")
298 bl_options
= {"REGISTER", "UNDO"}
302 description
="Size of the filleted corners",
304 min=0.00001, max=100.0,
310 description
="Subdivision of the filleted corners",
317 description
="Fillet towards outside",
322 description
="Flip the direction of the Fillet\n"
323 "Only available if Outside option is not active",
326 radius
= BoolProperty(
328 description
="Use radius for the size of the filleted corners",
333 def poll(cls
, context
):
334 obj
= context
.active_object
335 return (obj
and obj
.type == 'MESH' and context
.mode
== 'EDIT_MESH')
337 def draw(self
, context
):
340 if f_buf
.check
is False:
341 layout
.label(text
="Angle is equal to 0 or 180", icon
="INFO")
342 layout
.label(text
="Can not fillet", icon
="BLANK1")
344 layout
.prop(self
, "radius")
345 if self
.radius
is True:
346 layout
.label("Radius:")
347 elif self
.radius
is False:
348 layout
.label("Distance:")
349 layout
.prop(self
, "adj")
350 layout
.label("Number of sides:")
351 layout
.prop(self
, "n")
354 row
= layout
.row(align
=False)
355 row
.prop(self
, "out")
356 if self
.out
is False:
357 row
.prop(self
, "flip")
359 def execute(self
, context
):
361 global bm
, me
, adj
, n
, out
, flip
, radius
372 ob_act
= context
.active_object
375 bm
= bmesh
.from_edit_mesh(me
)
376 warn_obj
= bool(check_is_not_coplanar(bm
))
377 if warn_obj
is False:
379 bm
.verts
.ensure_lookup_table()
380 bm
.edges
.ensure_lookup_table()
381 bm
.faces
.ensure_lookup_table()
383 if v
.select
and v
.is_boundary
:
387 for e
in v
.link_edges
:
388 if e
.select
and e
.is_boundary
:
390 if len(edgeset
) == 2:
391 is_finished
= do_filletplus(self
, edgeset
)
396 bpy
.ops
.mesh
.select_all(action
="DESELECT")
398 if len(v
.link_edges
) == 0:
400 bpy
.ops
.object.editmode_toggle()
401 bpy
.ops
.object.editmode_toggle()
403 self
.report({'WARNING'}, "Filletplus operation could not be performed")
406 self
.report({'WARNING'}, "Mesh is not a coplanar surface. Operation cancelled")
409 self
.report({'WARNING'}, "Filletplus operation could not be performed")