Cleanup: rename target_weight -> weight
[blender.git] / release / scripts / startup / bl_ui / properties_paint_common.py
1 # ##### BEGIN GPL LICENSE BLOCK #####
2 #
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.
7 #
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.
12 #
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.
16 #
17 # ##### END GPL LICENSE BLOCK #####
18
19 # <pep8 compliant>
20 from bpy.types import Menu
21
22
23 class UnifiedPaintPanel:
24     # subclass must set
25     # bl_space_type = 'IMAGE_EDITOR'
26     # bl_region_type = 'UI'
27
28     @staticmethod
29     def paint_settings(context):
30         tool_settings = context.tool_settings
31
32         if context.sculpt_object:
33             return tool_settings.sculpt
34         elif context.vertex_paint_object:
35             return tool_settings.vertex_paint
36         elif context.weight_paint_object:
37             return tool_settings.weight_paint
38         elif context.image_paint_object:
39             if (tool_settings.image_paint and tool_settings.image_paint.detect_data()):
40                 return tool_settings.image_paint
41
42             return None
43         elif context.particle_edit_object:
44             return tool_settings.particle_edit
45
46         return None
47
48     @staticmethod
49     def unified_paint_settings(parent, context):
50         ups = context.tool_settings.unified_paint_settings
51
52         flow = parent.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
53
54         col = flow.column()
55         col.prop(ups, "use_unified_size", text="Size")
56         col = flow.column()
57         col.prop(ups, "use_unified_strength", text="Strength")
58         if context.weight_paint_object:
59             col = flow.column()
60             col.prop(ups, "use_unified_weight", text="Weight")
61         elif context.vertex_paint_object or context.image_paint_object:
62             col = flow.column()
63             col.prop(ups, "use_unified_color", text="Color")
64         else:
65             col = flow.column()
66             col.prop(ups, "use_unified_color", text="Color")
67
68     @staticmethod
69     def prop_unified_size(parent, context, brush, prop_name, icon='NONE', text="", slider=False):
70         ups = context.tool_settings.unified_paint_settings
71         ptr = ups if ups.use_unified_size else brush
72         parent.prop(ptr, prop_name, icon=icon, text=text, slider=slider)
73
74     @staticmethod
75     def prop_unified_strength(parent, context, brush, prop_name, icon='NONE', text="", slider=False):
76         ups = context.tool_settings.unified_paint_settings
77         ptr = ups if ups.use_unified_strength else brush
78         parent.prop(ptr, prop_name, icon=icon, text=text, slider=slider)
79
80     @staticmethod
81     def prop_unified_weight(parent, context, brush, prop_name, icon='NONE', text="", slider=False):
82         ups = context.tool_settings.unified_paint_settings
83         ptr = ups if ups.use_unified_weight else brush
84         parent.prop(ptr, prop_name, icon=icon, text=text, slider=slider)
85
86     @staticmethod
87     def prop_unified_color(parent, context, brush, prop_name, text=""):
88         ups = context.tool_settings.unified_paint_settings
89         ptr = ups if ups.use_unified_color else brush
90         parent.prop(ptr, prop_name, text=text)
91
92     @staticmethod
93     def prop_unified_color_picker(parent, context, brush, prop_name, value_slider=True):
94         ups = context.tool_settings.unified_paint_settings
95         ptr = ups if ups.use_unified_color else brush
96         parent.template_color_picker(ptr, prop_name, value_slider=value_slider)
97
98
99 class VIEW3D_MT_tools_projectpaint_clone(Menu):
100     bl_label = "Clone Layer"
101
102     def draw(self, context):
103         layout = self.layout
104
105         for i, uv_layer in enumerate(context.active_object.data.uv_layers):
106             props = layout.operator("wm.context_set_int", text=uv_layer.name, translate=False)
107             props.data_path = "active_object.data.uv_layer_clone_index"
108             props.value = i
109
110
111 def brush_texpaint_common(panel, context, layout, brush, settings, projpaint=False):
112     capabilities = brush.image_paint_capabilities
113
114     col = layout.column()
115
116     if brush.image_tool in {'DRAW', 'FILL'}:
117         if brush.blend not in {'ERASE_ALPHA', 'ADD_ALPHA'}:
118             if not brush.use_gradient:
119                 panel.prop_unified_color_picker(col, context, brush, "color", value_slider=True)
120
121             if settings.palette:
122                 col.template_palette(settings, "palette", color=True)
123
124             if brush.use_gradient:
125                 col.label(text="Gradient Colors")
126                 col.template_color_ramp(brush, "gradient", expand=True)
127
128                 if brush.image_tool == 'DRAW':
129                     col.label(text="Background Color")
130                     row = col.row(align=True)
131                     panel.prop_unified_color(row, context, brush, "secondary_color", text="")
132                     col.prop(brush, "gradient_stroke_mode", text="Mode")
133                     if brush.gradient_stroke_mode in {'SPACING_REPEAT', 'SPACING_CLAMP'}:
134                         col.prop(brush, "grad_spacing")
135                 else:  # if brush.image_tool == 'FILL':
136                     col.prop(brush, "gradient_fill_mode")
137             else:
138                 row = col.row(align=True)
139                 panel.prop_unified_color(row, context, brush, "color", text="")
140                 if brush.image_tool == 'FILL' and not projpaint:
141                     col.prop(brush, "fill_threshold")
142                 else:
143                     panel.prop_unified_color(row, context, brush, "secondary_color", text="")
144                     row.separator()
145                     row.operator("paint.brush_colors_flip", icon='FILE_REFRESH', text="")
146         else:
147             if brush.image_tool == 'FILL' and not projpaint:
148                 col.prop(brush, "fill_threshold")
149
150     elif brush.image_tool == 'SOFTEN':
151         col = layout.column(align=True)
152         col.row().prop(brush, "direction", expand=True)
153         col.separator()
154         col.prop(brush, "sharp_threshold")
155         if not projpaint:
156             col.prop(brush, "blur_kernel_radius")
157         col.separator()
158         col.prop(brush, "blur_mode")
159     elif brush.image_tool == 'MASK':
160         col.prop(brush, "weight", text="Mask Value", slider=True)
161
162     elif brush.image_tool == 'CLONE':
163         col.separator()
164         if projpaint:
165             if settings.mode == 'MATERIAL':
166                 col.prop(settings, "use_clone_layer", text="Clone from Paint Slot")
167             elif settings.mode == 'IMAGE':
168                 col.prop(settings, "use_clone_layer", text="Clone from Image/UV Map")
169
170             if settings.use_clone_layer:
171                 ob = context.active_object
172                 col = layout.column()
173
174                 if settings.mode == 'MATERIAL':
175                     if len(ob.material_slots) > 1:
176                         col.label(text="Materials")
177                         col.template_list("MATERIAL_UL_matslots", "",
178                                           ob, "material_slots",
179                                           ob, "active_material_index", rows=2)
180
181                     mat = ob.active_material
182                     if mat:
183                         col.label(text="Source Clone Slot")
184                         col.template_list("TEXTURE_UL_texpaintslots", "",
185                                           mat, "texture_paint_images",
186                                           mat, "paint_clone_slot", rows=2)
187
188                 elif settings.mode == 'IMAGE':
189                     mesh = ob.data
190
191                     clone_text = mesh.uv_layer_clone.name if mesh.uv_layer_clone else ""
192                     col.label(text="Source Clone Image")
193                     col.template_ID(settings, "clone_image")
194                     col.label(text="Source Clone UV Map")
195                     col.menu("VIEW3D_MT_tools_projectpaint_clone", text=clone_text, translate=False)
196         else:
197             col.prop(brush, "clone_image", text="Image")
198             col.prop(brush, "clone_alpha", text="Alpha")
199
200     col.separator()
201
202     if not panel.is_popover:
203         brush_basic_texpaint_settings(col, context, brush)
204
205     col = layout.column()
206
207     # use_accumulate
208     if capabilities.has_accumulate:
209         col = layout.column(align=True)
210         col.prop(brush, "use_accumulate")
211
212     if projpaint:
213         col.prop(brush, "use_alpha")
214
215     col.prop(brush, "use_gradient")
216
217     col.separator()
218     col.template_ID(settings, "palette", new="palette.new")
219
220
221 # Used in both the View3D toolbar and texture properties
222 def brush_texture_settings(layout, brush, sculpt):
223     tex_slot = brush.texture_slot
224
225     layout.use_property_split = True
226     layout.use_property_decorate = False
227
228     # map_mode
229     if sculpt:
230         layout.prop(tex_slot, "map_mode", text="Mapping")
231     else:
232         layout.prop(tex_slot, "tex_paint_map_mode", text="Mapping")
233
234     layout.separator()
235
236     if tex_slot.map_mode == 'STENCIL':
237         if brush.texture and brush.texture.type == 'IMAGE':
238             layout.operator("brush.stencil_fit_image_aspect")
239         layout.operator("brush.stencil_reset_transform")
240
241     # angle and texture_angle_source
242     if tex_slot.has_texture_angle:
243         col = layout.column()
244         col.prop(tex_slot, "angle", text="Angle")
245         if tex_slot.has_texture_angle_source:
246             col.prop(tex_slot, "use_rake", text="Rake")
247
248             if brush.brush_capabilities.has_random_texture_angle and tex_slot.has_random_texture_angle:
249                 if sculpt:
250                     if brush.sculpt_capabilities.has_random_texture_angle:
251                         col.prop(tex_slot, "use_random", text="Random")
252                         if tex_slot.use_random:
253                             col.prop(tex_slot, "random_angle", text="Random Angle")
254                 else:
255                     col.prop(tex_slot, "use_random", text="Random")
256                     if tex_slot.use_random:
257                         col.prop(tex_slot, "random_angle", text="Random Angle")
258
259     # scale and offset
260     layout.prop(tex_slot, "offset")
261     layout.prop(tex_slot, "scale")
262
263     if sculpt:
264         # texture_sample_bias
265         layout.prop(brush, "texture_sample_bias", slider=True, text="Sample Bias")
266
267
268 def brush_mask_texture_settings(layout, brush):
269     mask_tex_slot = brush.mask_texture_slot
270
271     layout.use_property_split = True
272     layout.use_property_decorate = False
273
274     # map_mode
275     layout.row().prop(mask_tex_slot, "mask_map_mode", text="Mask Mapping")
276
277     if mask_tex_slot.map_mode == 'STENCIL':
278         if brush.mask_texture and brush.mask_texture.type == 'IMAGE':
279             layout.operator("brush.stencil_fit_image_aspect").mask = True
280         layout.operator("brush.stencil_reset_transform").mask = True
281
282     col = layout.column()
283     col.prop(brush, "use_pressure_masking", text="Pressure Masking")
284     # angle and texture_angle_source
285     if mask_tex_slot.has_texture_angle:
286         col = layout.column()
287         col.prop(mask_tex_slot, "angle", text="Angle")
288         if mask_tex_slot.has_texture_angle_source:
289             col.prop(mask_tex_slot, "use_rake", text="Rake")
290
291             if brush.brush_capabilities.has_random_texture_angle and mask_tex_slot.has_random_texture_angle:
292                 col.prop(mask_tex_slot, "use_random", text="Random")
293                 if mask_tex_slot.use_random:
294                     col.prop(mask_tex_slot, "random_angle", text="Random Angle")
295
296     # scale and offset
297     col.prop(mask_tex_slot, "offset")
298     col.prop(mask_tex_slot, "scale")
299
300 # Basic Brush Options
301 #
302 # Share between topbar and brush panel.
303
304 def brush_basic_wpaint_settings(layout, context, brush, *, compact=False):
305     row = layout.row(align=True)
306     UnifiedPaintPanel.prop_unified_weight(row, context, brush, "weight", slider=True, text="Weight")
307
308     row = layout.row(align=True)
309     UnifiedPaintPanel.prop_unified_size(row, context, brush, "size", slider=True, text="Radius")
310     UnifiedPaintPanel.prop_unified_size(row, context, brush, "use_pressure_size")
311
312     row = layout.row(align=True)
313     UnifiedPaintPanel.prop_unified_strength(row, context, brush, "strength", text="Strength")
314     UnifiedPaintPanel.prop_unified_strength(row, context, brush, "use_pressure_strength")
315
316     layout.separator()
317     layout.prop(brush, "blend", text="" if compact else "Blend")
318
319
320 def brush_basic_vpaint_settings(layout, context, brush, *, compact=False):
321     row = layout.row(align=True)
322     UnifiedPaintPanel.prop_unified_size(row, context, brush, "size", slider=True, text="Radius")
323     UnifiedPaintPanel.prop_unified_size(row, context, brush, "use_pressure_size")
324
325     row = layout.row(align=True)
326     UnifiedPaintPanel.prop_unified_strength(row, context, brush, "strength", text="Strength")
327     UnifiedPaintPanel.prop_unified_strength(row, context, brush, "use_pressure_strength")
328
329     layout.separator()
330     layout.prop(brush, "blend", text="" if compact else "Blend")
331
332
333 def brush_basic_texpaint_settings(layout, context, brush, *, compact=False):
334     capabilities = brush.image_paint_capabilities
335
336     if capabilities.has_radius:
337         row = layout.row(align=True)
338         UnifiedPaintPanel.prop_unified_size(row, context, brush, "size", slider=True, text="Radius")
339         UnifiedPaintPanel.prop_unified_size(row, context, brush, "use_pressure_size")
340
341     row = layout.row(align=True)
342
343     if capabilities.has_space_attenuation:
344         row.prop(brush, "use_space_attenuation", toggle=True, icon_only=True)
345
346     UnifiedPaintPanel.prop_unified_strength(row, context, brush, "strength", text="Strength")
347     UnifiedPaintPanel.prop_unified_strength(row, context, brush, "use_pressure_strength")
348
349     if brush.image_tool in {'DRAW', 'FILL'}:
350         layout.separator()
351         layout.prop(brush, "blend", text="" if compact else "Blend")
352
353
354 def brush_basic_sculpt_settings(layout, context, brush, *, compact=False):
355     tool_settings = context.tool_settings
356     capabilities = brush.sculpt_capabilities
357
358     row = layout.row(align=True)
359     ups = tool_settings.unified_paint_settings
360     if (
361             (ups.use_unified_size and ups.use_locked_size) or
362             ((not ups.use_unified_size) and brush.use_locked_size)
363     ):
364         UnifiedPaintPanel.prop_unified_size(row, context, brush, "use_locked_size", icon='LOCKED')
365         UnifiedPaintPanel.prop_unified_size(row, context, brush, "unprojected_radius", slider=True, text="Radius")
366     else:
367         UnifiedPaintPanel.prop_unified_size(row, context, brush, "use_locked_size", icon='UNLOCKED')
368         UnifiedPaintPanel.prop_unified_size(row, context, brush, "size", slider=True, text="Radius")
369
370     UnifiedPaintPanel.prop_unified_size(row, context, brush, "use_pressure_size")
371
372     # strength, use_strength_pressure, and use_strength_attenuation
373     layout.separator()
374     row = layout.row(align=True)
375
376     if capabilities.has_space_attenuation:
377         row.prop(brush, "use_space_attenuation", toggle=True, icon_only=True)
378
379     UnifiedPaintPanel.prop_unified_strength(row, context, brush, "strength", text="Strength")
380
381     if capabilities.has_strength_pressure:
382         UnifiedPaintPanel.prop_unified_strength(row, context, brush, "use_pressure_strength")
383
384     # direction
385     layout.separator()
386     layout.row().prop(brush, "direction", expand=True, **({"text": ""} if compact else {}))
387
388
389 def brush_basic_gpencil_paint_settings(layout, context, brush, *, compact=False):
390     gp_settings = brush.gpencil_settings
391
392     # Brush details
393     if brush.gpencil_tool == 'ERASE':
394         row = layout.row(align=True)
395         row.prop(brush, "size", text="Radius")
396         row.prop(gp_settings, "use_pressure", text="", icon='STYLUS_PRESSURE')
397         row.prop(gp_settings, "use_occlude_eraser", text="", icon='XRAY')
398
399         if gp_settings.eraser_mode == 'SOFT':
400             row = layout.row(align=True)
401             row.prop(gp_settings, "pen_strength", slider=True)
402             row.prop(gp_settings, "use_strength_pressure", text="", icon='STYLUS_PRESSURE')
403             row = layout.row(align=True)
404             row.prop(gp_settings, "eraser_strength_factor")
405             row = layout.row(align=True)
406             row.prop(gp_settings, "eraser_thickness_factor")
407     elif brush.gpencil_tool == 'FILL':
408         col = layout.column(align=True)
409         col.prop(gp_settings, "fill_leak", text="Leak Size")
410         col.separator()
411         col.prop(brush, "size", text="Thickness")
412         col.prop(gp_settings, "fill_simplify_level", text="Simplify")
413
414         row = layout.row(align=True)
415         row.prop(gp_settings, "fill_draw_mode", text="Boundary Draw Mode")
416         row.prop(gp_settings, "show_fill_boundary", text="", icon='GRID')
417
418         col = layout.column(align=True)
419         col.enabled = gp_settings.fill_draw_mode != 'STROKE'
420         col.prop(gp_settings, "show_fill", text="Ignore Transparent Strokes")
421         sub = col.row(align=True)
422         sub.enabled = not gp_settings.show_fill
423         sub.prop(gp_settings, "fill_threshold", text="Threshold")
424     else:  # brush.gpencil_tool == 'DRAW':
425         row = layout.row(align=True)
426         row.prop(brush, "size", text="Radius")
427         row.prop(gp_settings, "use_pressure", text="", icon='STYLUS_PRESSURE')
428         row = layout.row(align=True)
429         row.prop(gp_settings, "pen_strength", slider=True)
430         row.prop(gp_settings, "use_strength_pressure", text="", icon='STYLUS_PRESSURE')
431
432
433 def brush_basic_gpencil_sculpt_settings(layout, context, brush, *, compact=False):
434     tool_settings = context.tool_settings
435     settings = tool_settings.gpencil_sculpt
436     tool = settings.sculpt_tool
437
438     row = layout.row(align=True)
439     row.prop(brush, "size", slider=True)
440     sub = row.row(align=True)
441     sub.enabled = tool not in {'GRAB', 'CLONE'}
442     sub.prop(brush, "use_pressure_radius", text="")
443
444     row = layout.row(align=True)
445     row.prop(brush, "strength", slider=True)
446     row.prop(brush, "use_pressure_strength", text="")
447
448     layout.prop(brush, "use_falloff")
449
450     if compact:
451         if tool in {'THICKNESS', 'STRENGTH', 'PINCH', 'TWIST'}:
452             row.separator()
453             row.prop(brush, "direction", expand=True, text="")
454     else:
455         use_property_split_prev = layout.use_property_split
456         layout.use_property_split = False
457         if tool in {'THICKNESS', 'STRENGTH'}:
458             layout.row().prop(brush, "direction", expand=True)
459         elif tool == 'PINCH':
460             row = layout.row(align=True)
461             row.prop_enum(brush, "direction", value='ADD', text="Pinch")
462             row.prop_enum(brush, "direction", value='SUBTRACT', text="Inflate")
463         elif tool == 'TWIST':
464             row = layout.row(align=True)
465             row.prop_enum(brush, "direction", value='ADD', text="CCW")
466             row.prop_enum(brush, "direction", value='SUBTRACT', text="CW")
467         layout.use_property_split = use_property_split_prev
468
469
470 def brush_basic_gpencil_weight_settings(layout, context, brush, *, compact=False):
471     layout.prop(brush, "size", slider=True)
472
473     row = layout.row(align=True)
474     row.prop(brush, "strength", slider=True)
475     row.prop(brush, "use_pressure_strength", text="")
476
477     layout.prop(brush, "use_falloff")
478
479     layout.prop(brush, "weight", slider=True)
480
481
482 classes = (
483     VIEW3D_MT_tools_projectpaint_clone,
484 )
485
486 if __name__ == "__main__":  # only for live edit.
487     from bpy.utils import register_class
488     for cls in classes:
489         register_class(cls)