1 # SPDX-FileCopyrightText: 2020-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 '''Based on GP_refine_stroke 0.2.4 - Author: Samuel Bernou'''
13 return mean of all passed value (multiple)
14 If it's a list or tuple return mean of it (only on first list passed).
16 if isinstance(args
[0], list) or isinstance(args
[0], tuple):
17 return mean(*args
[0])#send the first list UNPACKED (else infinite recursion as it always evaluate as list)
18 return sum(args
) / len(args
)
20 def vector_len_from_coord(a
, b
):
22 Get two points (that has coordinate 'co' attribute) or Vectors (2D or 3D)
23 Return length as float
25 from mathutils
import Vector
29 return (a
.co
- b
.co
).length
31 def point_from_dist_in_segment_3d(a
, b
, ratio
):
32 '''return the tuple coords of a point on 3D segment ab according to given ratio (some distance divided by total segment length)'''
33 ## ref:https://math.stackexchange.com/questions/175896/finding-a-point-along-a-line-a-certain-distance-away-from-another-point
34 # ratio = dist / seglength
35 return ( ((1 - ratio
) * a
[0] + (ratio
*b
[0])), ((1 - ratio
) * a
[1] + (ratio
*b
[1])), ((1 - ratio
) * a
[2] + (ratio
*b
[2])) )
37 def get_stroke_length(s
):
38 '''return 3D total length of the stroke'''
40 for i
in range(0, len(s
.points
)-1):
41 #print(vector_len_from_coord(s.points[i],s.points[i+1]))
42 all_len
+= vector_len_from_coord(s
.points
[i
],s
.points
[i
+1])
47 def to_straight_line(s
, keep_points
=True, influence
=100, straight_pressure
=True):
49 keep points : if false only start and end point stay
50 straight_pressure : (not available with keep point) take the mean pressure of all points and apply to stroke.
54 if p_len
<= 2: # 1 or 2 points only, cancel
58 if straight_pressure
: mean_pressure
= mean([p
.pressure
for p
in s
.points
])#can use a foreach_get but might not be faster.
59 for i
in range(p_len
-2):
63 p
.pressure
= mean_pressure
68 # ab_dist = vector_len_from_coord(A,B)
69 full_dist
= get_stroke_length(s
)
73 for i
in range(1, p_len
-1):#all but first and last
74 dist_from_start
+= vector_len_from_coord(s
.points
[i
-1],s
.points
[i
])
75 ratio
= dist_from_start
/ full_dist
76 # dont apply directly (change line as we measure it in loop)
77 coord_list
.append( point_from_dist_in_segment_3d(A
, B
, ratio
) )
80 for i
in range(1, p_len
-1):
81 ## Direct super straight 100%
82 #s.points[i].co = coord_list[i-1]
85 s
.points
[i
].co
= point_from_dist_in_segment_3d(s
.points
[i
].co
, coord_list
[i
-1], influence
/ 100)
89 def get_last_index(context
=None):
92 return 0 if context
.tool_settings
.use_gpencil_draw_onback
else -1
96 class GPENCIL_OT_straight_stroke(bpy
.types
.Operator
):
97 bl_idname
= "gpencil.straight_stroke"
98 bl_label
= "Straight Stroke"
99 bl_description
= "Make stroke a straight line between first and last point,\
100 \nTweak influence in the redo panel\
101 \nShift+click to reset infuence to 100%"
102 bl_options
= {"REGISTER", "UNDO"}
105 def poll(cls
, context
):
106 return context
.active_object
is not None and context
.object.type == 'GPENCIL'
107 #and context.mode in ('PAINT_GPENCIL', 'EDIT_GPENCIL')
109 influence_val
: bpy
.props
.FloatProperty(name
="Straight force", description
="Straight interpolation percentage",
110 default
=100, min=0, max=100, step
=2, precision
=1, subtype
='PERCENTAGE', unit
='NONE')
112 def execute(self
, context
):
113 gp
= context
.object.data
118 if context
.mode
== 'PAINT_GPENCIL':
119 if not gpl
.active
or not gpl
.active
.active_frame
:
120 self
.report({'ERROR'}, 'No Grease pencil frame found')
123 if not len(gpl
.active
.active_frame
.strokes
):
124 self
.report({'ERROR'}, 'No strokes found.')
127 s
= gpl
.active
.active_frame
.strokes
[get_last_index(context
)]
128 to_straight_line(s
, keep_points
=True, influence
=self
.influence_val
)
130 elif context
.mode
== 'EDIT_GPENCIL':
133 if l
.lock
or l
.hide
or not l
.active_frame
:
134 # avoid locked, hidden, empty layers
137 target_frames
= [f
for f
in l
.frames
if f
.select
]
139 target_frames
= [l
.active_frame
]
141 for f
in target_frames
:
145 to_straight_line(s
, keep_points
=True, influence
=self
.influence_val
)
148 self
.report({'ERROR'}, 'No selected stroke found.')
152 # if context.mode == 'PAINT_GPENCIL':
153 # L, F, S = 'ACTIVE', 'ACTIVE', 'LAST'
154 # elif context.mode == 'EDIT_GPENCIL'
155 # L, F, S = 'ALL', 'ACTIVE', 'SELECT'
156 # if gp.use_multiedit: F = 'SELECT'
157 # else : return {"CANCELLED"}
158 # for s in strokelist(t_layer=L, t_frame=F, t_stroke=S):
159 # to_straight_line(s, keep_points=True, influence = self.influence_val)#, straight_pressure=True
163 def draw(self
, context
):
165 layout
.prop(self
, "influence_val")
167 def invoke(self
, context
, event
):
168 if context
.mode
not in ('PAINT_GPENCIL', 'EDIT_GPENCIL'):
171 self
.influence_val
= 100
172 return self
.execute(context
)
176 bpy
.utils
.register_class(GPENCIL_OT_straight_stroke
)
179 bpy
.utils
.unregister_class(GPENCIL_OT_straight_stroke
)