properties_paint_common.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  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. # <pep8 compliant>
  19. from bpy.types import Menu
  20. class UnifiedPaintPanel:
  21. # subclass must set
  22. # bl_space_type = 'IMAGE_EDITOR'
  23. # bl_region_type = 'UI'
  24. @staticmethod
  25. def paint_settings(context):
  26. tool_settings = context.tool_settings
  27. if context.sculpt_object:
  28. return tool_settings.sculpt
  29. elif context.vertex_paint_object:
  30. return tool_settings.vertex_paint
  31. elif context.weight_paint_object:
  32. return tool_settings.weight_paint
  33. elif context.image_paint_object:
  34. if (tool_settings.image_paint and tool_settings.image_paint.detect_data()):
  35. return tool_settings.image_paint
  36. return None
  37. elif context.particle_edit_object:
  38. return tool_settings.particle_edit
  39. return None
  40. @staticmethod
  41. def unified_paint_settings(parent, context):
  42. ups = context.tool_settings.unified_paint_settings
  43. flow = parent.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
  44. col = flow.column()
  45. col.prop(ups, "use_unified_size", text="Size")
  46. col = flow.column()
  47. col.prop(ups, "use_unified_strength", text="Strength")
  48. if context.weight_paint_object:
  49. col = flow.column()
  50. col.prop(ups, "use_unified_weight", text="Weight")
  51. elif context.vertex_paint_object or context.image_paint_object:
  52. col = flow.column()
  53. col.prop(ups, "use_unified_color", text="Color")
  54. else:
  55. col = flow.column()
  56. col.prop(ups, "use_unified_color", text="Color")
  57. @staticmethod
  58. def prop_unified_size(parent, context, brush, prop_name, *, icon='NONE', text=None, slider=False):
  59. ups = context.tool_settings.unified_paint_settings
  60. ptr = ups if ups.use_unified_size else brush
  61. parent.prop(ptr, prop_name, icon=icon, text=text, slider=slider)
  62. @staticmethod
  63. def prop_unified_strength(parent, context, brush, prop_name, *, icon='NONE', text=None, slider=False):
  64. ups = context.tool_settings.unified_paint_settings
  65. ptr = ups if ups.use_unified_strength else brush
  66. parent.prop(ptr, prop_name, icon=icon, text=text, slider=slider)
  67. @staticmethod
  68. def prop_unified_weight(parent, context, brush, prop_name, *, icon='NONE', text=None, slider=False):
  69. ups = context.tool_settings.unified_paint_settings
  70. ptr = ups if ups.use_unified_weight else brush
  71. parent.prop(ptr, prop_name, icon=icon, text=text, slider=slider)
  72. @staticmethod
  73. def prop_unified_color(parent, context, brush, prop_name, *, text=None):
  74. ups = context.tool_settings.unified_paint_settings
  75. ptr = ups if ups.use_unified_color else brush
  76. parent.prop(ptr, prop_name, text=text)
  77. @staticmethod
  78. def prop_unified_color_picker(parent, context, brush, prop_name, value_slider=True):
  79. ups = context.tool_settings.unified_paint_settings
  80. ptr = ups if ups.use_unified_color else brush
  81. parent.template_color_picker(ptr, prop_name, value_slider=value_slider)
  82. class VIEW3D_MT_tools_projectpaint_clone(Menu):
  83. bl_label = "Clone Layer"
  84. def draw(self, context):
  85. layout = self.layout
  86. for i, uv_layer in enumerate(context.active_object.data.uv_layers):
  87. props = layout.operator("wm.context_set_int", text=uv_layer.name, translate=False)
  88. props.data_path = "active_object.data.uv_layer_clone_index"
  89. props.value = i
  90. def brush_texpaint_common(panel, context, layout, brush, _settings, projpaint=False):
  91. capabilities = brush.image_paint_capabilities
  92. col = layout.column()
  93. if capabilities.has_color:
  94. if brush.blend in {'ERASE_ALPHA', 'ADD_ALPHA'}:
  95. if brush.image_tool == 'FILL' and not projpaint:
  96. col.prop(brush, "fill_threshold")
  97. elif brush.image_tool == 'SOFTEN':
  98. col.row().prop(brush, "direction", expand=True)
  99. col.prop(brush, "sharp_threshold")
  100. if not projpaint:
  101. col.prop(brush, "blur_kernel_radius")
  102. col.prop(brush, "blur_mode")
  103. elif brush.image_tool == 'MASK':
  104. col.prop(brush, "weight", text="Mask Value", slider=True)
  105. elif brush.image_tool == 'CLONE':
  106. if not projpaint:
  107. col.prop(brush, "clone_image", text="Image")
  108. col.prop(brush, "clone_alpha", text="Alpha")
  109. if not panel.is_popover:
  110. brush_basic_texpaint_settings(col, context, brush)
  111. def brush_texpaint_common_clone(_panel, context, layout, _brush, settings, projpaint=False):
  112. ob = context.active_object
  113. col = layout.column()
  114. if settings.mode == 'MATERIAL':
  115. if len(ob.material_slots) > 1:
  116. col.label(text="Materials")
  117. col.template_list("MATERIAL_UL_matslots", "",
  118. ob, "material_slots",
  119. ob, "active_material_index", rows=2)
  120. mat = ob.active_material
  121. if mat:
  122. col.label(text="Source Clone Slot")
  123. col.template_list("TEXTURE_UL_texpaintslots", "",
  124. mat, "texture_paint_images",
  125. mat, "paint_clone_slot", rows=2)
  126. elif settings.mode == 'IMAGE':
  127. mesh = ob.data
  128. clone_text = mesh.uv_layer_clone.name if mesh.uv_layer_clone else ""
  129. col.label(text="Source Clone Image")
  130. col.template_ID(settings, "clone_image")
  131. col.label(text="Source Clone UV Map")
  132. col.menu("VIEW3D_MT_tools_projectpaint_clone", text=clone_text, translate=False)
  133. def brush_texpaint_common_color(_panel, context, layout, brush, _settings, projpaint=False):
  134. UnifiedPaintPanel.prop_unified_color_picker(layout, context, brush, "color", value_slider=True)
  135. row = layout.row(align=True)
  136. UnifiedPaintPanel.prop_unified_color(row, context, brush, "color", text="")
  137. UnifiedPaintPanel.prop_unified_color(row, context, brush, "secondary_color", text="")
  138. row.separator()
  139. row.operator("paint.brush_colors_flip", icon='FILE_REFRESH', text="", emboss=False)
  140. def brush_texpaint_common_gradient(_panel, context, layout, brush, _settings, projpaint=False):
  141. layout.template_color_ramp(brush, "gradient", expand=True)
  142. layout.use_property_split = True
  143. col = layout.column()
  144. if brush.image_tool == 'DRAW':
  145. UnifiedPaintPanel.prop_unified_color(col, context, brush, "secondary_color", text="Background Color")
  146. col.prop(brush, "gradient_stroke_mode", text="Mode")
  147. if brush.gradient_stroke_mode in {'SPACING_REPEAT', 'SPACING_CLAMP'}:
  148. col.prop(brush, "grad_spacing")
  149. else: # if brush.image_tool == 'FILL':
  150. col.prop(brush, "gradient_fill_mode")
  151. def brush_texpaint_common_options(_panel, _context, layout, brush, _settings, projpaint=False):
  152. capabilities = brush.image_paint_capabilities
  153. col = layout.column()
  154. if capabilities.has_accumulate:
  155. col.prop(brush, "use_accumulate")
  156. if capabilities.has_space_attenuation:
  157. col.prop(brush, "use_space_attenuation")
  158. if projpaint:
  159. col.prop(brush, "use_alpha")
  160. # Used in both the View3D toolbar and texture properties
  161. def brush_texture_settings(layout, brush, sculpt):
  162. tex_slot = brush.texture_slot
  163. layout.use_property_split = True
  164. layout.use_property_decorate = False
  165. # map_mode
  166. if sculpt:
  167. layout.prop(tex_slot, "map_mode", text="Mapping")
  168. else:
  169. layout.prop(tex_slot, "tex_paint_map_mode", text="Mapping")
  170. layout.separator()
  171. if tex_slot.map_mode == 'STENCIL':
  172. if brush.texture and brush.texture.type == 'IMAGE':
  173. layout.operator("brush.stencil_fit_image_aspect")
  174. layout.operator("brush.stencil_reset_transform")
  175. # angle and texture_angle_source
  176. if tex_slot.has_texture_angle:
  177. col = layout.column()
  178. col.prop(tex_slot, "angle", text="Angle")
  179. if tex_slot.has_texture_angle_source:
  180. col.prop(tex_slot, "use_rake", text="Rake")
  181. if brush.brush_capabilities.has_random_texture_angle and tex_slot.has_random_texture_angle:
  182. if sculpt:
  183. if brush.sculpt_capabilities.has_random_texture_angle:
  184. col.prop(tex_slot, "use_random", text="Random")
  185. if tex_slot.use_random:
  186. col.prop(tex_slot, "random_angle", text="Random Angle")
  187. else:
  188. col.prop(tex_slot, "use_random", text="Random")
  189. if tex_slot.use_random:
  190. col.prop(tex_slot, "random_angle", text="Random Angle")
  191. # scale and offset
  192. layout.prop(tex_slot, "offset")
  193. layout.prop(tex_slot, "scale")
  194. if sculpt:
  195. # texture_sample_bias
  196. layout.prop(brush, "texture_sample_bias", slider=True, text="Sample Bias")
  197. def brush_mask_texture_settings(layout, brush):
  198. mask_tex_slot = brush.mask_texture_slot
  199. layout.use_property_split = True
  200. layout.use_property_decorate = False
  201. # map_mode
  202. layout.row().prop(mask_tex_slot, "mask_map_mode", text="Mask Mapping")
  203. if mask_tex_slot.map_mode == 'STENCIL':
  204. if brush.mask_texture and brush.mask_texture.type == 'IMAGE':
  205. layout.operator("brush.stencil_fit_image_aspect").mask = True
  206. layout.operator("brush.stencil_reset_transform").mask = True
  207. col = layout.column()
  208. col.prop(brush, "use_pressure_masking", text="Pressure Masking")
  209. # angle and texture_angle_source
  210. if mask_tex_slot.has_texture_angle:
  211. col = layout.column()
  212. col.prop(mask_tex_slot, "angle", text="Angle")
  213. if mask_tex_slot.has_texture_angle_source:
  214. col.prop(mask_tex_slot, "use_rake", text="Rake")
  215. if brush.brush_capabilities.has_random_texture_angle and mask_tex_slot.has_random_texture_angle:
  216. col.prop(mask_tex_slot, "use_random", text="Random")
  217. if mask_tex_slot.use_random:
  218. col.prop(mask_tex_slot, "random_angle", text="Random Angle")
  219. # scale and offset
  220. col.prop(mask_tex_slot, "offset")
  221. col.prop(mask_tex_slot, "scale")
  222. # Basic Brush Options
  223. #
  224. # Share between topbar and brush panel.
  225. def brush_basic_wpaint_settings(layout, context, brush, *, compact=False):
  226. capabilities = brush.weight_paint_capabilities
  227. if capabilities.has_weight:
  228. row = layout.row(align=True)
  229. UnifiedPaintPanel.prop_unified_weight(row, context, brush, "weight", slider=True)
  230. row = layout.row(align=True)
  231. UnifiedPaintPanel.prop_unified_size(row, context, brush, "size", slider=True)
  232. UnifiedPaintPanel.prop_unified_size(row, context, brush, "use_pressure_size", text="")
  233. row = layout.row(align=True)
  234. UnifiedPaintPanel.prop_unified_strength(row, context, brush, "strength")
  235. UnifiedPaintPanel.prop_unified_strength(row, context, brush, "use_pressure_strength", text="")
  236. layout.prop(brush, "blend", text="" if compact else "Blend")
  237. def brush_basic_vpaint_settings(layout, context, brush, *, compact=False):
  238. capabilities = brush.vertex_paint_capabilities
  239. row = layout.row(align=True)
  240. UnifiedPaintPanel.prop_unified_size(row, context, brush, "size", slider=True)
  241. UnifiedPaintPanel.prop_unified_size(row, context, brush, "use_pressure_size", text="")
  242. row = layout.row(align=True)
  243. UnifiedPaintPanel.prop_unified_strength(row, context, brush, "strength")
  244. UnifiedPaintPanel.prop_unified_strength(row, context, brush, "use_pressure_strength", text="")
  245. if capabilities.has_color:
  246. layout.prop(brush, "blend", text="" if compact else "Blend")
  247. def brush_basic_texpaint_settings(layout, context, brush, *, compact=False):
  248. capabilities = brush.image_paint_capabilities
  249. if capabilities.has_radius:
  250. row = layout.row(align=True)
  251. UnifiedPaintPanel.prop_unified_size(row, context, brush, "size", slider=True)
  252. UnifiedPaintPanel.prop_unified_size(row, context, brush, "use_pressure_size", text="")
  253. row = layout.row(align=True)
  254. UnifiedPaintPanel.prop_unified_strength(row, context, brush, "strength")
  255. UnifiedPaintPanel.prop_unified_strength(row, context, brush, "use_pressure_strength", text="")
  256. if capabilities.has_color:
  257. layout.prop(brush, "blend", text="" if compact else "Blend")
  258. def brush_basic_sculpt_settings(layout, context, brush, *, compact=False):
  259. tool_settings = context.tool_settings
  260. capabilities = brush.sculpt_capabilities
  261. row = layout.row(align=True)
  262. ups = tool_settings.unified_paint_settings
  263. if (
  264. (ups.use_unified_size and ups.use_locked_size == 'SCENE') or
  265. ((not ups.use_unified_size) and brush.use_locked_size == 'SCENE')
  266. ):
  267. UnifiedPaintPanel.prop_unified_size(row, context, brush, "unprojected_radius", slider=True, text="Radius")
  268. else:
  269. UnifiedPaintPanel.prop_unified_size(row, context, brush, "size", slider=True)
  270. UnifiedPaintPanel.prop_unified_size(row, context, brush, "use_pressure_size", text="")
  271. # strength, use_strength_pressure, and use_strength_attenuation
  272. row = layout.row(align=True)
  273. UnifiedPaintPanel.prop_unified_strength(row, context, brush, "strength")
  274. if capabilities.has_strength_pressure:
  275. UnifiedPaintPanel.prop_unified_strength(row, context, brush, "use_pressure_strength", text="")
  276. # direction
  277. if not capabilities.has_direction:
  278. layout.row().prop(brush, "direction", expand=True, **({"text": ""} if compact else {}))
  279. def brush_basic_gpencil_paint_settings(layout, _context, brush, *, compact=True):
  280. gp_settings = brush.gpencil_settings
  281. # Brush details
  282. if brush.gpencil_tool == 'ERASE':
  283. row = layout.row(align=True)
  284. row.prop(brush, "size", text="Radius")
  285. row.prop(gp_settings, "use_pressure", text="", icon='STYLUS_PRESSURE')
  286. row.prop(gp_settings, "use_occlude_eraser", text="", icon='XRAY')
  287. if gp_settings.eraser_mode == 'SOFT':
  288. row = layout.row(align=True)
  289. row.prop(gp_settings, "pen_strength", slider=True)
  290. row.prop(gp_settings, "use_strength_pressure", text="", icon='STYLUS_PRESSURE')
  291. row = layout.row(align=True)
  292. row.prop(gp_settings, "eraser_strength_factor")
  293. row = layout.row(align=True)
  294. row.prop(gp_settings, "eraser_thickness_factor")
  295. elif brush.gpencil_tool == 'FILL':
  296. row = layout.row(align=True)
  297. row.prop(gp_settings, "fill_leak", text="Leak Size")
  298. row = layout.row(align=True)
  299. row.prop(brush, "size", text="Thickness")
  300. row = layout.row(align=True)
  301. row.prop(gp_settings, "fill_simplify_level", text="Simplify")
  302. row = layout.row(align=True)
  303. row.prop(gp_settings, "fill_draw_mode", text="Boundary")
  304. row.prop(gp_settings, "show_fill_boundary", text="", icon='GRID')
  305. else: # brush.gpencil_tool == 'DRAW':
  306. row = layout.row(align=True)
  307. row.prop(brush, "size", text="Radius")
  308. row.prop(gp_settings, "use_pressure", text="", icon='STYLUS_PRESSURE')
  309. row = layout.row(align=True)
  310. row.prop(gp_settings, "pen_strength", slider=True)
  311. row.prop(gp_settings, "use_strength_pressure", text="", icon='STYLUS_PRESSURE')
  312. def brush_basic_gpencil_sculpt_settings(layout, context, brush, *, compact=False):
  313. tool_settings = context.tool_settings
  314. settings = tool_settings.gpencil_sculpt
  315. tool = settings.sculpt_tool
  316. row = layout.row(align=True)
  317. row.prop(brush, "size", slider=True)
  318. sub = row.row(align=True)
  319. sub.enabled = tool not in {'GRAB', 'CLONE'}
  320. sub.prop(brush, "use_pressure_radius", text="")
  321. row = layout.row(align=True)
  322. row.prop(brush, "strength", slider=True)
  323. row.prop(brush, "use_pressure_strength", text="")
  324. layout.prop(brush, "use_falloff")
  325. if compact:
  326. if tool in {'THICKNESS', 'STRENGTH', 'PINCH', 'TWIST'}:
  327. row.separator()
  328. row.prop(brush, "direction", expand=True, text="")
  329. else:
  330. use_property_split_prev = layout.use_property_split
  331. layout.use_property_split = False
  332. if tool in {'THICKNESS', 'STRENGTH'}:
  333. layout.row().prop(brush, "direction", expand=True)
  334. elif tool == 'PINCH':
  335. row = layout.row(align=True)
  336. row.prop_enum(brush, "direction", value='ADD', text="Pinch")
  337. row.prop_enum(brush, "direction", value='SUBTRACT', text="Inflate")
  338. elif tool == 'TWIST':
  339. row = layout.row(align=True)
  340. row.prop_enum(brush, "direction", value='ADD', text="CCW")
  341. row.prop_enum(brush, "direction", value='SUBTRACT', text="CW")
  342. layout.use_property_split = use_property_split_prev
  343. def brush_basic_gpencil_weight_settings(layout, _context, brush, *, compact=False):
  344. layout.prop(brush, "size", slider=True)
  345. row = layout.row(align=True)
  346. row.prop(brush, "strength", slider=True)
  347. row.prop(brush, "use_pressure_strength", text="")
  348. layout.prop(brush, "use_falloff")
  349. layout.prop(brush, "weight", slider=True)
  350. classes = (
  351. VIEW3D_MT_tools_projectpaint_clone,
  352. )
  353. if __name__ == "__main__": # only for live edit.
  354. from bpy.utils import register_class
  355. for cls in classes:
  356. register_class(cls)