2222bl_info = {
2323 "name" : "Offset Edges" ,
2424 "author" : "Hidesato Ikeya" ,
25- "version" : (0 , 2 , 2 ),
25+ "version" : (0 , 2 , 3 ),
2626 "blender" : (2 , 70 , 0 ),
2727 "location" : "VIEW3D > Edge menu(CTRL-E) > Offset Edges" ,
2828 "description" : "Offset Edges" ,
@@ -67,7 +67,7 @@ def calc_normal_from_verts(verts, fallback=Z_UP):
6767
6868 return normal
6969
70- def get_corner_type (vec_up , vec_right2d , vec_left2d , threshold ):
70+ def get_corner_type (vec_up , vec_right2d , vec_left2d , threshold_edge ):
7171 # vec_right2d and vec_left2d should be perpendicular to vec_up.
7272 # All vectors in parameters should have been normalized.
7373 if vec_right2d == vec_left2d == ZERO_VEC :
@@ -76,22 +76,22 @@ def get_corner_type(vec_up, vec_right2d, vec_left2d, threshold):
7676 return 'STRAIGHT'
7777
7878 angle = vec_right2d .angle (vec_left2d )
79- if angle < threshold :
79+ if angle < threshold_edge :
8080 return 'FOLDING'
81- elif angle > ANGLE_180 - threshold :
81+ elif angle > ANGLE_180 - threshold_edge :
8282 return 'STRAIGHT'
8383 elif vec_right2d .cross (vec_left2d ).dot (vec_up ) > .0 :
8484 return 'CONVEX'
8585 else :
8686 return 'CONCAVE'
8787
88- def calc_tangent (vec_up , vec_right , vec_left , threshold ):
88+ def calc_tangent (vec_up , vec_right , vec_left , threshold_edge ):
8989 vec_right2d = vec_right - vec_right .project (vec_up )
9090 vec_right2d .normalize ()
9191 vec_left2d = vec_left - vec_left .project (vec_up )
9292 vec_right2d .normalize ()
9393
94- corner = get_corner_type (vec_up , vec_right2d , vec_left2d , threshold )
94+ corner = get_corner_type (vec_up , vec_right2d , vec_left2d , threshold_edge )
9595 if corner == 'FOLDING' :
9696 vec_tangent = ZERO_VEC
9797 elif corner == 'STRAIGHT' :
@@ -237,7 +237,7 @@ def get_adj_ix(ix_start, vec_edges, half_loop):
237237
238238 return ix_right , ix_left
239239
240- def get_normals (lp_normal , adj_faces , ix_r , ix_l , vert ):
240+ def get_normals (lp_normal , adj_faces , ix_r , ix_l , vert , threshold_face ):
241241 normal_r = normal_l = None
242242 if adj_faces :
243243 f_r , f_l = adj_faces [ix_r ], adj_faces [ix_l ]
@@ -252,7 +252,10 @@ def get_normals(lp_normal, adj_faces, ix_r, ix_l, vert):
252252
253253 if normal_r and normal_l :
254254 vec_up = (normal_r + normal_l ).normalized ()
255- if vec_up == ZERO_VEC :
255+ if normal_r .angle (normal_l ) < threshold_face :
256+ # Two normals are almost same, so assign None to normal_l
257+ normal_l = None
258+ elif vec_up == ZERO_VEC :
256259 vec_up = lp_normal .copy ()
257260 elif normal_r or normal_l :
258261 vec_up = (normal_r or normal_l ).copy ()
@@ -311,12 +314,8 @@ def get_edge_rail(vert, set_edges_orig):
311314 else :
312315 return None
313316
314- def get_cross_rail (vec_tan , vec_edge_r , vec_edge_l , normal_r , normal_l , threshold ):
317+ def get_cross_rail (vec_tan , vec_edge_r , vec_edge_l , normal_r , normal_l ):
315318 # Cross rail is a cross vector between normal_r and normal_l.
316- angle = normal_r .angle (normal_l )
317- if angle < threshold :
318- # normal_r and normal_l are almost same
319- return None
320319
321320 vec_cross = normal_r .cross (normal_l )
322321 if vec_cross .dot (vec_tan ) < .0 :
@@ -433,7 +432,8 @@ def get_directions(lp, vec_upward, normal_fallback, vert_mirror_pairs, **options
433432 opt_follow_face = options ['follow_face' ]
434433 opt_edge_rail = options ['edge_rail' ]
435434 opt_er_only_end = options ['edge_rail_only_end' ]
436- opt_threshold = options ['threshold' ]
435+ opt_threshold_edge = options ['threshold_edge' ]
436+ opt_threshold_face = options ['threshold_face' ]
437437
438438 verts , edges = lp [::2 ], lp [1 ::2 ]
439439 set_edges = set (edges )
@@ -480,24 +480,30 @@ def get_directions(lp, vec_upward, normal_fallback, vert_mirror_pairs, **options
480480 vec_edge_r = vec_edges [ix_r ]
481481 vec_edge_l = - vec_edges [ix_l ]
482482
483- vec_up , normal_r , normal_l = get_normals (lp_normal , adj_faces , ix_r , ix_l , v )
484- vec_tan = calc_tangent (vec_up , vec_edge_r , vec_edge_l , opt_threshold )
483+ vec_up , normal_r , normal_l = \
484+ get_normals (lp_normal , adj_faces , ix_r , ix_l , v , opt_threshold_face )
485+ vec_tan = calc_tangent (vec_up , vec_edge_r , vec_edge_l , opt_threshold_edge )
485486
486487 if vec_tan != ZERO_VEC :
488+ if normal_r and normal_l :
489+ two_normals = True
490+ else :
491+ two_normals = False
492+
487493 rail = None
488494 if vert_mirror_pairs and VERT_END :
489495 if v in vert_mirror_pairs :
490496 rail , vec_up = get_mirror_rail (vert_mirror_pairs [v ], vec_up )
491- if opt_edge_rail :
497+ if two_normals or opt_edge_rail :
492498 # Get edge rail.
493499 # edge rail is a vector of an inner edge.
494- if (not opt_er_only_end ) or VERT_END :
500+ if two_normals or (not opt_er_only_end ) or VERT_END :
495501 rail = get_edge_rail (v , set_edges )
496- if (not rail ) and normal_r and normal_l :
502+ if (not rail ) and two_normals :
497503 # Get cross rail.
498504 # Cross rail is a cross vector between normal_r and normal_l.
499505 rail = get_cross_rail (
500- vec_tan , vec_edge_r , vec_edge_l , normal_r , normal_l , opt_threshold )
506+ vec_tan , vec_edge_r , vec_edge_l , normal_r , normal_l )
501507 if rail :
502508 vec_tan = vec_tan .project (rail )
503509 vec_tan .normalize ()
@@ -541,7 +547,7 @@ class OffsetEdges(bpy.types.Operator):
541547 ('depth' , "Depth" , "Depth" )],
542548 name = "Depth mode" , default = 'angle' , update = use_cashes )
543549 angle = bpy .props .FloatProperty (
544- name = "Angle" , default = 0 , step = .1 , min = - 4 * pi , max = 4 * pi ,
550+ name = "Angle" , default = 0 , precision = 3 , step = .1 , min = - 4 * pi , max = 4 * pi ,
545551 subtype = 'ANGLE' , description = "Angle" , update = use_cashes )
546552 flip_angle = bpy .props .BoolProperty (
547553 name = "Flip Angle" , default = False ,
@@ -558,9 +564,13 @@ class OffsetEdges(bpy.types.Operator):
558564 edge_rail_only_end = bpy .props .BoolProperty (
559565 name = "Edge Rail Only End" , default = False ,
560566 description = "Apply edge rail to end verts only" )
561- threshold = bpy .props .FloatProperty (
562- name = "Threshold" , default = 1.0e-5 , step = .1 , subtype = 'ANGLE' ,
563- description = "Angle threshold which determines straight or folding edges" ,
567+ threshold_face = bpy .props .FloatProperty (
568+ name = "Face Threshold" , default = 1.0e-3 , precision = 7 , step = 1.0e-4 , subtype = 'ANGLE' ,
569+ description = "Threshold of normal angle which determines flat faces" ,
570+ options = {'HIDDEN' })
571+ threshold_edge = bpy .props .FloatProperty (
572+ name = "Edge Threshold" , default = 1.0e-4 , precision = 7 , step = 1.0e-4 , subtype = 'ANGLE' ,
573+ description = "Threshold of Edge angle which determines straight or folding" ,
564574 options = {'HIDDEN' })
565575 interactive = bpy .props .BoolProperty (
566576 name = "Interactive" , default = False ,
@@ -607,6 +617,11 @@ def draw(self, context):
607617
608618 layout .prop (self , 'mirror_modifier' )
609619
620+ layout .separator ()
621+ box = layout .box ()
622+ box .label ("Threshold:" )
623+ box .prop (self , 'threshold_face' , text = "Face" )
624+ box .prop (self , 'threshold_edge' , text = "Edge" )
610625 #layout.operator('mesh.offset_edges', text='Repeat')
611626
612627 def get_offset_infos (self , bm , edit_object ):
@@ -651,14 +666,16 @@ def get_offset_infos(self, bm, edit_object):
651666 follow_face = self .follow_face
652667 edge_rail = self .edge_rail
653668 er_only_end = self .edge_rail_only_end
654- threshold = self .threshold
669+ threshold_f = self .threshold_face
670+ threshold_e = self .threshold_edge
655671
656672 offset_infos = []
657673 for lp in loops :
658674 verts , directions = get_directions (
659675 lp , vec_upward , normal_fallback , vert_mirror_pairs ,
660676 follow_face = follow_face , edge_rail = edge_rail ,
661- edge_rail_only_end = er_only_end , threshold = threshold )
677+ edge_rail_only_end = er_only_end ,
678+ threshold_face = threshold_f , threshold_edge = threshold_e )
662679 if verts :
663680 offset_infos .append ((verts , directions ))
664681
@@ -731,6 +748,8 @@ def execute(self, context):
731748
732749 offset_infos , edges_orig = self .get_offset_infos (bm , edit_object )
733750 if offset_infos is False :
751+ bpy .ops .object .editmode_toggle ()
752+ # In object mode
734753 return {'CANCELLED' }
735754
736755 self .do_offset_and_free (bm , me , offset_infos , edges_orig )
0 commit comments