Welcome to mirror list, hosted at ThFree Co, Russian Federation.

line_reshape.py « greasepencil_tools - git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: dc14cfd4ea361a2e95dba1805a0a0e92b7f09efc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# SPDX-License-Identifier: GPL-2.0-or-later

'''Based on GP_refine_stroke 0.2.4 - Author: Samuel Bernou'''

import bpy

### --- Vector utils

def mean(*args):
    '''
    return mean of all passed value (multiple)
    If it's a list or tuple return mean of it (only on first list passed).
    '''
    if isinstance(args[0], list) or isinstance(args[0], tuple):
        return mean(*args[0])#send the first list UNPACKED (else infinite recursion as it always evaluate as list)
    return sum(args) / len(args)

def vector_len_from_coord(a, b):
    '''
    Get two points (that has coordinate 'co' attribute) or Vectors (2D or 3D)
    Return length as float
    '''
    from mathutils import Vector
    if type(a) is Vector:
        return (a - b).length
    else:
        return (a.co - b.co).length

def point_from_dist_in_segment_3d(a, b, ratio):
    '''return the tuple coords of a point on 3D segment ab according to given ratio (some distance divided by total segment length)'''
    ## ref:https://math.stackexchange.com/questions/175896/finding-a-point-along-a-line-a-certain-distance-away-from-another-point
    # ratio = dist / seglength
    return ( ((1 - ratio) * a[0] + (ratio*b[0])), ((1 - ratio) * a[1] + (ratio*b[1])), ((1 - ratio) * a[2] + (ratio*b[2])) )

def get_stroke_length(s):
    '''return 3D total length of the stroke'''
    all_len = 0.0
    for i in range(0, len(s.points)-1):
        #print(vector_len_from_coord(s.points[i],s.points[i+1]))
        all_len += vector_len_from_coord(s.points[i],s.points[i+1])
    return (all_len)

### --- Functions

def to_straight_line(s, keep_points=True, influence=100, straight_pressure=True):
    '''
    keep points : if false only start and end point stay
    straight_pressure : (not available with keep point) take the mean pressure of all points and apply to stroke.
    '''

    p_len = len(s.points)
    if p_len <= 2: # 1 or 2 points only, cancel
        return

    if not keep_points:
        if straight_pressure: mean_pressure = mean([p.pressure for p in s.points])#can use a foreach_get but might not be faster.
        for i in range(p_len-2):
            s.points.pop(index=1)
        if straight_pressure:
            for p in s.points:
                p.pressure = mean_pressure

    else:
        A = s.points[0].co
        B = s.points[-1].co
        # ab_dist = vector_len_from_coord(A,B)
        full_dist = get_stroke_length(s)
        dist_from_start = 0.0
        coord_list = []

        for i in range(1, p_len-1):#all but first and last
            dist_from_start += vector_len_from_coord(s.points[i-1],s.points[i])
            ratio = dist_from_start / full_dist
            # dont apply directly (change line as we measure it in loop)
            coord_list.append( point_from_dist_in_segment_3d(A, B, ratio) )

        # apply change
        for i in range(1, p_len-1):
            ## Direct super straight 100%
            #s.points[i].co = coord_list[i-1]

            ## With influence
            s.points[i].co = point_from_dist_in_segment_3d(s.points[i].co, coord_list[i-1], influence / 100)

    return

def get_last_index(context=None):
    if not context:
        context = bpy.context
    return 0 if context.tool_settings.use_gpencil_draw_onback else -1

### --- OPS

class GP_OT_straightStroke(bpy.types.Operator):
    bl_idname = "gp.straight_stroke"
    bl_label = "Straight Stroke"
    bl_description = "Make stroke a straight line between first and last point, tweak influence in the redo panel\
        \nshift+click to reset infuence to 100%"
    bl_options = {"REGISTER", "UNDO"}

    @classmethod
    def poll(cls, context):
        return context.active_object is not None and context.object.type == 'GPENCIL'
        #and context.mode in ('PAINT_GPENCIL', 'EDIT_GPENCIL')

    influence_val : bpy.props.FloatProperty(name="Straight force", description="Straight interpolation percentage",
    default=100, min=0, max=100, step=2, precision=1, subtype='PERCENTAGE', unit='NONE')

    def execute(self, context):
        gp = context.object.data
        gpl = gp.layers
        if not gpl:
            return {"CANCELLED"}

        if context.mode == 'PAINT_GPENCIL':
            if not gpl.active or not gpl.active.active_frame:
                self.report({'ERROR'}, 'No Grease pencil frame found')
                return {"CANCELLED"}

            if not len(gpl.active.active_frame.strokes):
                self.report({'ERROR'}, 'No strokes found.')
                return {"CANCELLED"}

            s = gpl.active.active_frame.strokes[get_last_index(context)]
            to_straight_line(s, keep_points=True, influence=self.influence_val)

        elif context.mode == 'EDIT_GPENCIL':
            ct = 0
            for l in gpl:
                if l.lock or l.hide or not l.active_frame:
                    # avoid locked, hidden, empty layers
                    continue
                if gp.use_multiedit:
                    target_frames = [f for f in l.frames if f.select]
                else:
                    target_frames = [l.active_frame]

                for f in target_frames:
                    for s in f.strokes:
                        if s.select:
                            ct += 1
                            to_straight_line(s, keep_points=True, influence=self.influence_val)

            if not ct:
                self.report({'ERROR'}, 'No selected stroke found.')
                return {"CANCELLED"}

        ## filter method
        # if context.mode == 'PAINT_GPENCIL':
        #     L, F, S = 'ACTIVE', 'ACTIVE', 'LAST'
        # elif context.mode == 'EDIT_GPENCIL'
        #     L, F, S = 'ALL', 'ACTIVE', 'SELECT'
        #     if gp.use_multiedit: F = 'SELECT'
        # else : return {"CANCELLED"}
        # for s in strokelist(t_layer=L, t_frame=F, t_stroke=S):
        #     to_straight_line(s, keep_points=True, influence = self.influence_val)#, straight_pressure=True

        return {"FINISHED"}

    def draw(self, context):
        layout = self.layout
        layout.prop(self, "influence_val")

    def invoke(self, context, event):
        if context.mode not in ('PAINT_GPENCIL', 'EDIT_GPENCIL'):
            return {"CANCELLED"}
        if event.shift:
            self.influence_val = 100
        return self.execute(context)


def register():
    bpy.utils.register_class(GP_OT_straightStroke)

def unregister():
    bpy.utils.unregister_class(GP_OT_straightStroke)