Cleanup: quiet character escape warnings
[blender-addons.git] / greasepencil_tools / line_reshape.py
blobb994b8d6a76fe285b8627676f46c159edd5101cb
1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
19 '''Based on GP_refine_stroke 0.2.4 - Author: Samuel Bernou'''
21 import bpy
23 ### --- Vector utils
25 def mean(*args):
26 '''
27 return mean of all passed value (multiple)
28 If it's a list or tuple return mean of it (only on first list passed).
29 '''
30 if isinstance(args[0], list) or isinstance(args[0], tuple):
31 return mean(*args[0])#send the first list UNPACKED (else infinite recursion as it always evaluate as list)
32 return sum(args) / len(args)
34 def vector_len_from_coord(a, b):
35 '''
36 Get two points (that has coordinate 'co' attribute) or Vectors (2D or 3D)
37 Return length as float
38 '''
39 from mathutils import Vector
40 if type(a) is Vector:
41 return (a - b).length
42 else:
43 return (a.co - b.co).length
45 def point_from_dist_in_segment_3d(a, b, ratio):
46 '''return the tuple coords of a point on 3D segment ab according to given ratio (some distance divided by total segment lenght)'''
47 ## ref:https://math.stackexchange.com/questions/175896/finding-a-point-along-a-line-a-certain-distance-away-from-another-point
48 # ratio = dist / seglenght
49 return ( ((1 - ratio) * a[0] + (ratio*b[0])), ((1 - ratio) * a[1] + (ratio*b[1])), ((1 - ratio) * a[2] + (ratio*b[2])) )
51 def get_stroke_length(s):
52 '''return 3D total length of the stroke'''
53 all_len = 0.0
54 for i in range(0, len(s.points)-1):
55 #print(vector_len_from_coord(s.points[i],s.points[i+1]))
56 all_len += vector_len_from_coord(s.points[i],s.points[i+1])
57 return (all_len)
59 ### --- Functions
61 def to_straight_line(s, keep_points=True, influence=100, straight_pressure=True):
62 '''
63 keep points : if false only start and end point stay
64 straight_pressure : (not available with keep point) take the mean pressure of all points and apply to stroke.
65 '''
67 p_len = len(s.points)
68 if p_len <= 2: # 1 or 2 points only, cancel
69 return
71 if not keep_points:
72 if straight_pressure: mean_pressure = mean([p.pressure for p in s.points])#can use a foreach_get but might not be faster.
73 for i in range(p_len-2):
74 s.points.pop(index=1)
75 if straight_pressure:
76 for p in s.points:
77 p.pressure = mean_pressure
79 else:
80 A = s.points[0].co
81 B = s.points[-1].co
82 # ab_dist = vector_len_from_coord(A,B)
83 full_dist = get_stroke_length(s)
84 dist_from_start = 0.0
85 coord_list = []
87 for i in range(1, p_len-1):#all but first and last
88 dist_from_start += vector_len_from_coord(s.points[i-1],s.points[i])
89 ratio = dist_from_start / full_dist
90 # dont apply directly (change line as we measure it in loop)
91 coord_list.append( point_from_dist_in_segment_3d(A, B, ratio) )
93 # apply change
94 for i in range(1, p_len-1):
95 ## Direct super straight 100%
96 #s.points[i].co = coord_list[i-1]
98 ## With influence
99 s.points[i].co = point_from_dist_in_segment_3d(s.points[i].co, coord_list[i-1], influence / 100)
101 return
103 def get_last_index(context=None):
104 if not context:
105 context = bpy.context
106 return 0 if context.tool_settings.use_gpencil_draw_onback else -1
108 ### --- OPS
110 class GP_OT_straightStroke(bpy.types.Operator):
111 bl_idname = "gp.straight_stroke"
112 bl_label = "Straight Stroke"
113 bl_description = "Make stroke a straight line between first and last point, tweak influence in the redo panel\
114 \nshift+click to reset infuence to 100%"
115 bl_options = {"REGISTER", "UNDO"}
117 @classmethod
118 def poll(cls, context):
119 return context.active_object is not None and context.object.type == 'GPENCIL'
120 #and context.mode in ('PAINT_GPENCIL', 'EDIT_GPENCIL')
122 influence_val : bpy.props.FloatProperty(name="Straight force", description="Straight interpolation percentage",
123 default=100, min=0, max=100, step=2, precision=1, subtype='PERCENTAGE', unit='NONE')
125 def execute(self, context):
126 gp = context.object.data
127 gpl = gp.layers
128 if not gpl:
129 return {"CANCELLED"}
131 if context.mode == 'PAINT_GPENCIL':
132 if not gpl.active or not gpl.active.active_frame:
133 self.report({'ERROR'}, 'No Grease pencil frame found')
134 return {"CANCELLED"}
136 if not len(gpl.active.active_frame.strokes):
137 self.report({'ERROR'}, 'No strokes found.')
138 return {"CANCELLED"}
140 s = gpl.active.active_frame.strokes[get_last_index(context)]
141 to_straight_line(s, keep_points=True, influence=self.influence_val)
143 elif context.mode == 'EDIT_GPENCIL':
144 ct = 0
145 for l in gpl:
146 if l.lock or l.hide or not l.active_frame:
147 # avoid locked, hided, empty layers
148 continue
149 if gp.use_multiedit:
150 target_frames = [f for f in l.frames if f.select]
151 else:
152 target_frames = [l.active_frame]
154 for f in target_frames:
155 for s in f.strokes:
156 if s.select:
157 ct += 1
158 to_straight_line(s, keep_points=True, influence=self.influence_val)
160 if not ct:
161 self.report({'ERROR'}, 'No selected stroke found.')
162 return {"CANCELLED"}
164 ## filter method
165 # if context.mode == 'PAINT_GPENCIL':
166 # L, F, S = 'ACTIVE', 'ACTIVE', 'LAST'
167 # elif context.mode == 'EDIT_GPENCIL'
168 # L, F, S = 'ALL', 'ACTIVE', 'SELECT'
169 # if gp.use_multiedit: F = 'SELECT'
170 # else : return {"CANCELLED"}
171 # for s in strokelist(t_layer=L, t_frame=F, t_stroke=S):
172 # to_straight_line(s, keep_points=True, influence = self.influence_val)#, straight_pressure=True
174 return {"FINISHED"}
176 def draw(self, context):
177 layout = self.layout
178 layout.prop(self, "influence_val")
180 def invoke(self, context, event):
181 if context.mode not in ('PAINT_GPENCIL', 'EDIT_GPENCIL'):
182 return {"CANCELLED"}
183 if event.shift:
184 self.influence_val = 100
185 return self.execute(context)
188 def register():
189 bpy.utils.register_class(GP_OT_straightStroke)
191 def unregister():
192 bpy.utils.unregister_class(GP_OT_straightStroke)