Merge branch 'blender-v2.81-release'
[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=None, 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=None, 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=None, 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=None):
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     col = layout.column()
113
114     if brush.image_tool == 'FILL' and not projpaint:
115         col.prop(brush, "fill_threshold", text="Gradient Type", slider=True)
116
117     elif brush.image_tool == 'SOFTEN':
118         col.row().prop(brush, "direction", expand=True)
119         col.prop(brush, "sharp_threshold")
120         if not projpaint:
121             col.prop(brush, "blur_kernel_radius")
122         col.prop(brush, "blur_mode")
123     elif brush.image_tool == 'MASK':
124         col.prop(brush, "weight", text="Mask Value", slider=True)
125
126     elif brush.image_tool == 'CLONE':
127         if not projpaint:
128             col.prop(brush, "clone_image", text="Image")
129             col.prop(brush, "clone_alpha", text="Alpha")
130
131     if not panel.is_popover:
132         brush_basic_texpaint_settings(col, context, brush)
133
134
135 def brush_texpaint_common_clone(_panel, context, layout, _brush, settings, *, projpaint=False):
136     ob = context.active_object
137     col = layout.column()
138
139     if settings.mode == 'MATERIAL':
140         if len(ob.material_slots) > 1:
141             col.label(text="Materials")
142             col.template_list("MATERIAL_UL_matslots", "",
143                               ob, "material_slots",
144                               ob, "active_material_index", rows=2)
145
146         mat = ob.active_material
147         if mat:
148             col.label(text="Source Clone Slot")
149             col.template_list("TEXTURE_UL_texpaintslots", "",
150                               mat, "texture_paint_images",
151                               mat, "paint_clone_slot", rows=2)
152
153     elif settings.mode == 'IMAGE':
154         mesh = ob.data
155
156         clone_text = mesh.uv_layer_clone.name if mesh.uv_layer_clone else ""
157         col.label(text="Source Clone Image")
158         col.template_ID(settings, "clone_image")
159         col.label(text="Source Clone UV Map")
160         col.menu("VIEW3D_MT_tools_projectpaint_clone", text=clone_text, translate=False)
161
162
163 def brush_texpaint_common_color(_panel, context, layout, brush, _settings, *, projpaint=False):
164     UnifiedPaintPanel.prop_unified_color_picker(layout, context, brush, "color", value_slider=True)
165
166     row = layout.row(align=True)
167     UnifiedPaintPanel.prop_unified_color(row, context, brush, "color", text="")
168     UnifiedPaintPanel.prop_unified_color(row, context, brush, "secondary_color", text="")
169     row.separator()
170     row.operator("paint.brush_colors_flip", icon='FILE_REFRESH', text="", emboss=False)
171
172
173 def brush_texpaint_common_gradient(_panel, context, layout, brush, _settings, *, projpaint=False):
174     layout.template_color_ramp(brush, "gradient", expand=True)
175
176     layout.use_property_split = True
177
178     col = layout.column()
179
180     if brush.image_tool == 'DRAW':
181         UnifiedPaintPanel.prop_unified_color(col, context, brush, "secondary_color", text="Background Color")
182         col.prop(brush, "gradient_stroke_mode", text="Gradient Mapping")
183         if brush.gradient_stroke_mode in {'SPACING_REPEAT', 'SPACING_CLAMP'}:
184             col.prop(brush, "grad_spacing")
185     else:  # if brush.image_tool == 'FILL':
186         col.prop(brush, "gradient_fill_mode")
187
188
189 def brush_texpaint_common_options(_panel, _context, layout, brush, _settings, *, projpaint=False):
190     capabilities = brush.image_paint_capabilities
191
192     col = layout.column()
193
194     if capabilities.has_accumulate:
195         col.prop(brush, "use_accumulate")
196
197     if capabilities.has_space_attenuation:
198         col.prop(brush, "use_space_attenuation")
199
200     if projpaint:
201         col.prop(brush, "use_alpha")
202     else:
203         col.prop(brush, "use_paint_antialiasing")
204
205
206 # Used in both the View3D toolbar and texture properties
207 def brush_texture_settings(layout, brush, sculpt):
208     tex_slot = brush.texture_slot
209
210     layout.use_property_split = True
211     layout.use_property_decorate = False
212
213     # map_mode
214     if sculpt:
215         layout.prop(tex_slot, "map_mode", text="Mapping")
216     else:
217         layout.prop(tex_slot, "tex_paint_map_mode", text="Mapping")
218
219     layout.separator()
220
221     if tex_slot.map_mode == 'STENCIL':
222         if brush.texture and brush.texture.type == 'IMAGE':
223             layout.operator("brush.stencil_fit_image_aspect")
224         layout.operator("brush.stencil_reset_transform")
225
226     # angle and texture_angle_source
227     if tex_slot.has_texture_angle:
228         col = layout.column()
229         col.prop(tex_slot, "angle", text="Angle")
230         if tex_slot.has_texture_angle_source:
231             col.prop(tex_slot, "use_rake", text="Rake")
232
233             if brush.brush_capabilities.has_random_texture_angle and tex_slot.has_random_texture_angle:
234                 if sculpt:
235                     if brush.sculpt_capabilities.has_random_texture_angle:
236                         col.prop(tex_slot, "use_random", text="Random")
237                         if tex_slot.use_random:
238                             col.prop(tex_slot, "random_angle", text="Random Angle")
239                 else:
240                     col.prop(tex_slot, "use_random", text="Random")
241                     if tex_slot.use_random:
242                         col.prop(tex_slot, "random_angle", text="Random Angle")
243
244     # scale and offset
245     layout.prop(tex_slot, "offset")
246     layout.prop(tex_slot, "scale")
247
248     if sculpt:
249         # texture_sample_bias
250         layout.prop(brush, "texture_sample_bias", slider=True, text="Sample Bias")
251
252
253 def brush_mask_texture_settings(layout, brush):
254     mask_tex_slot = brush.mask_texture_slot
255
256     layout.use_property_split = True
257     layout.use_property_decorate = False
258
259     # map_mode
260     layout.row().prop(mask_tex_slot, "mask_map_mode", text="Mask Mapping")
261
262     if mask_tex_slot.map_mode == 'STENCIL':
263         if brush.mask_texture and brush.mask_texture.type == 'IMAGE':
264             layout.operator("brush.stencil_fit_image_aspect").mask = True
265         layout.operator("brush.stencil_reset_transform").mask = True
266
267     col = layout.column()
268     col.prop(brush, "use_pressure_masking", text="Pressure Masking")
269     # angle and texture_angle_source
270     if mask_tex_slot.has_texture_angle:
271         col = layout.column()
272         col.prop(mask_tex_slot, "angle", text="Angle")
273         if mask_tex_slot.has_texture_angle_source:
274             col.prop(mask_tex_slot, "use_rake", text="Rake")
275
276             if brush.brush_capabilities.has_random_texture_angle and mask_tex_slot.has_random_texture_angle:
277                 col.prop(mask_tex_slot, "use_random", text="Random")
278                 if mask_tex_slot.use_random:
279                     col.prop(mask_tex_slot, "random_angle", text="Random Angle")
280
281     # scale and offset
282     col.prop(mask_tex_slot, "offset")
283     col.prop(mask_tex_slot, "scale")
284
285 # Basic Brush Options
286 #
287 # Share between topbar and brush panel.
288
289
290 def brush_basic_wpaint_settings(layout, context, brush, *, compact=False):
291     capabilities = brush.weight_paint_capabilities
292
293     if capabilities.has_weight:
294         row = layout.row(align=True)
295         UnifiedPaintPanel.prop_unified_weight(row, context, brush, "weight", slider=True)
296
297     row = layout.row(align=True)
298     UnifiedPaintPanel.prop_unified_size(row, context, brush, "size", slider=True)
299     UnifiedPaintPanel.prop_unified_size(row, context, brush, "use_pressure_size", text="")
300
301     row = layout.row(align=True)
302     UnifiedPaintPanel.prop_unified_strength(row, context, brush, "strength")
303     UnifiedPaintPanel.prop_unified_strength(row, context, brush, "use_pressure_strength", text="")
304
305     layout.prop(brush, "blend", text="" if compact else "Blend")
306
307
308 def brush_basic_vpaint_settings(layout, context, brush, *, compact=False):
309     capabilities = brush.vertex_paint_capabilities
310
311     row = layout.row(align=True)
312     UnifiedPaintPanel.prop_unified_size(row, context, brush, "size", slider=True)
313     UnifiedPaintPanel.prop_unified_size(row, context, brush, "use_pressure_size", text="")
314
315     row = layout.row(align=True)
316     UnifiedPaintPanel.prop_unified_strength(row, context, brush, "strength")
317     UnifiedPaintPanel.prop_unified_strength(row, context, brush, "use_pressure_strength", text="")
318
319     if capabilities.has_color:
320         layout.prop(brush, "blend", text="" if compact else "Blend")
321
322
323 def brush_basic_texpaint_settings(layout, context, brush, *, compact=False):
324     capabilities = brush.image_paint_capabilities
325
326     if capabilities.has_radius:
327         row = layout.row(align=True)
328         UnifiedPaintPanel.prop_unified_size(row, context, brush, "size", slider=True)
329         UnifiedPaintPanel.prop_unified_size(row, context, brush, "use_pressure_size", text="")
330
331     row = layout.row(align=True)
332
333     UnifiedPaintPanel.prop_unified_strength(row, context, brush, "strength")
334     UnifiedPaintPanel.prop_unified_strength(row, context, brush, "use_pressure_strength", text="")
335
336     if capabilities.has_color:
337         layout.prop(brush, "blend", text="" if compact else "Blend")
338
339
340 def brush_basic_sculpt_settings(layout, context, brush, *, compact=False):
341     tool_settings = context.tool_settings
342     capabilities = brush.sculpt_capabilities
343
344     row = layout.row(align=True)
345
346     ups = tool_settings.unified_paint_settings
347     if (
348             (ups.use_unified_size and ups.use_locked_size == 'SCENE') or
349             ((not ups.use_unified_size) and brush.use_locked_size == 'SCENE')
350     ):
351         UnifiedPaintPanel.prop_unified_size(row, context, brush, "unprojected_radius", slider=True, text="Radius")
352     else:
353         UnifiedPaintPanel.prop_unified_size(row, context, brush, "size", slider=True)
354
355     UnifiedPaintPanel.prop_unified_size(row, context, brush, "use_pressure_size", text="")
356
357     # strength, use_strength_pressure, and use_strength_attenuation
358     row = layout.row(align=True)
359
360     UnifiedPaintPanel.prop_unified_strength(row, context, brush, "strength")
361
362     if capabilities.has_strength_pressure:
363         UnifiedPaintPanel.prop_unified_strength(row, context, brush, "use_pressure_strength", text="")
364
365     # direction
366     if not capabilities.has_direction:
367         layout.row().prop(brush, "direction", expand=True, **({"text": ""} if compact else {}))
368
369
370 def brush_basic_gpencil_paint_settings(layout, _context, brush, tool, *, compact=True, is_toolbar=False):
371     gp_settings = brush.gpencil_settings
372
373     # Brush details
374     if brush.gpencil_tool == 'ERASE':
375         row = layout.row(align=True)
376         row.prop(brush, "size", text="Radius")
377         row.prop(gp_settings, "use_pressure", text="", icon='STYLUS_PRESSURE')
378         row.prop(gp_settings, "use_occlude_eraser", text="", icon='XRAY')
379
380         if gp_settings.eraser_mode == 'SOFT':
381             row = layout.row(align=True)
382             row.prop(gp_settings, "pen_strength", slider=True)
383             row.prop(gp_settings, "use_strength_pressure", text="", icon='STYLUS_PRESSURE')
384             row = layout.row(align=True)
385             row.prop(gp_settings, "eraser_strength_factor")
386             row = layout.row(align=True)
387             row.prop(gp_settings, "eraser_thickness_factor")
388     elif brush.gpencil_tool == 'FILL':
389         row = layout.row(align=True)
390         row.prop(gp_settings, "fill_leak", text="Leak Size")
391         row = layout.row(align=True)
392         row.prop(brush, "size", text="Thickness")
393         row = layout.row(align=True)
394         row.prop(gp_settings, "fill_simplify_level", text="Simplify")
395         row = layout.row(align=True)
396         row.prop(gp_settings, "fill_draw_mode", text="Boundary")
397         row.prop(gp_settings, "show_fill_boundary", text="", icon='GRID')
398         # Fill options
399         if is_toolbar:
400             settings = _context.tool_settings.gpencil_sculpt
401             row = layout.row(align=True)
402             sub = row.row(align=True)
403             sub.popover(
404                 panel="TOPBAR_PT_gpencil_fill",
405                 text="Fill Options",
406             )
407         else:
408             row = layout.row(align=True)
409             row.prop(gp_settings, "fill_factor", text="Resolution")
410             if gp_settings.fill_draw_mode != 'STROKE':
411                 row = layout.row(align=True)
412                 row.prop(gp_settings, "show_fill", text="Ignore Transparent Strokes")
413                 row = layout.row(align=True)
414                 row.prop(gp_settings, "fill_threshold", text="Threshold")
415     else:  # brush.gpencil_tool == 'DRAW':
416         row = layout.row(align=True)
417         row.prop(brush, "size", text="Radius")
418         row.prop(gp_settings, "use_pressure", text="", icon='STYLUS_PRESSURE')
419         row = layout.row(align=True)
420         row.prop(gp_settings, "pen_strength", slider=True)
421         row.prop(gp_settings, "use_strength_pressure", text="", icon='STYLUS_PRESSURE')
422
423     # FIXME: tools must use their own UI drawing!
424     if tool.idname in {
425             "builtin.arc",
426             "builtin.curve",
427             "builtin.line",
428             "builtin.box",
429             "builtin.circle",
430             "builtin.polyline",
431     }:
432         settings = _context.tool_settings.gpencil_sculpt
433         if is_toolbar:
434             row = layout.row(align=True)
435             row.prop(settings, "use_thickness_curve", text="", icon='CURVE_DATA')
436             sub = row.row(align=True)
437             sub.active = settings.use_thickness_curve
438             sub.popover(
439                 panel="TOPBAR_PT_gpencil_primitive",
440                 text="Thickness Profile",
441             )
442         else:
443             row = layout.row(align=True)
444             row.prop(settings, "use_thickness_curve", text="Use Thickness Profile")
445             sub = row.row(align=True)
446             if settings.use_thickness_curve:
447                 # Curve
448                 layout.template_curve_mapping(settings, "thickness_primitive_curve", brush=True)
449
450
451 def brush_basic_gpencil_sculpt_settings(layout, context, brush, *, compact=False):
452     tool_settings = context.tool_settings
453     settings = tool_settings.gpencil_sculpt
454     tool = settings.sculpt_tool
455
456     row = layout.row(align=True)
457     row.prop(brush, "size", slider=True)
458     sub = row.row(align=True)
459     sub.enabled = tool not in {'GRAB', 'CLONE'}
460     sub.prop(brush, "use_pressure_radius", text="")
461
462     row = layout.row(align=True)
463     row.prop(brush, "strength", slider=True)
464     row.prop(brush, "use_pressure_strength", text="")
465
466     layout.prop(brush, "use_falloff")
467
468     if compact:
469         if tool in {'THICKNESS', 'STRENGTH', 'PINCH', 'TWIST'}:
470             row.separator()
471             row.prop(brush, "direction", expand=True, text="")
472     else:
473         use_property_split_prev = layout.use_property_split
474         layout.use_property_split = False
475         if tool in {'THICKNESS', 'STRENGTH'}:
476             layout.row().prop(brush, "direction", expand=True)
477         elif tool == 'PINCH':
478             row = layout.row(align=True)
479             row.prop_enum(brush, "direction", value='ADD', text="Pinch")
480             row.prop_enum(brush, "direction", value='SUBTRACT', text="Inflate")
481         elif tool == 'TWIST':
482             row = layout.row(align=True)
483             row.prop_enum(brush, "direction", value='ADD', text="CCW")
484             row.prop_enum(brush, "direction", value='SUBTRACT', text="CW")
485         layout.use_property_split = use_property_split_prev
486
487
488 def brush_basic_gpencil_weight_settings(layout, _context, brush, *, compact=False):
489     layout.prop(brush, "size", slider=True)
490
491     row = layout.row(align=True)
492     row.prop(brush, "strength", slider=True)
493     row.prop(brush, "use_pressure_strength", text="")
494
495     layout.prop(brush, "use_falloff")
496
497     layout.prop(brush, "weight", slider=True)
498
499
500 classes = (
501     VIEW3D_MT_tools_projectpaint_clone,
502 )
503
504 if __name__ == "__main__":  # only for live edit.
505     from bpy.utils import register_class
506     for cls in classes:
507         register_class(cls)