Import Images: bump minimum Blender version after API change
[blender-addons.git] / space_view3d_brush_menus / texture_menu.py
blobdc2a50be0a1a2a28b1ae4425217428aeeeaa6f3f
1 # SPDX-FileCopyrightText: 2017-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 import bpy
6 from bpy.types import Menu
7 from . import utils_core
10 class TextureMenu(Menu):
11 bl_label = "Texture Options"
12 bl_idname = "VIEW3D_MT_sv3_texture_menu"
14 @classmethod
15 def poll(self, context):
16 return utils_core.get_mode() in (
17 'SCULPT',
18 'VERTEX_PAINT',
19 'TEXTURE_PAINT'
22 def draw(self, context):
23 layout = self.layout
25 if utils_core.get_mode() == 'SCULPT':
26 self.sculpt(layout, context)
28 elif utils_core.get_mode() == 'VERTEX_PAINT':
29 self.vertpaint(layout, context)
31 else:
32 self.texpaint(layout, context)
34 def sculpt(self, layout, context):
35 has_brush = utils_core.get_brush_link(context, types="brush")
36 tex_slot = has_brush.texture_slot if has_brush else None
38 # Menus
39 layout.row().menu(Textures.bl_idname)
40 layout.row().menu(TextureMapMode.bl_idname)
41 layout.row().separator()
43 # Checkboxes
44 if tex_slot:
45 if tex_slot.map_mode != '3D':
46 if tex_slot.map_mode in ('RANDOM', 'VIEW_PLANE', 'AREA_PLANE'):
47 layout.row().prop(tex_slot, "use_rake", toggle=True)
48 layout.row().prop(tex_slot, "use_random", toggle=True)
50 # Sliders
51 layout.row().prop(tex_slot, "angle",
52 text=utils_core.PIW + "Angle", slider=True)
54 if tex_slot.map_mode in ('RANDOM', 'VIEW_PLANE') and tex_slot.use_random:
55 layout.row().prop(tex_slot, "random_angle",
56 text=utils_core.PIW + "Random Angle", slider=True)
58 # Operator
59 if tex_slot.map_mode == 'STENCIL':
60 if has_brush.texture and has_brush.texture.type == 'IMAGE':
61 layout.row().operator("brush.stencil_fit_image_aspect")
63 layout.row().operator("brush.stencil_reset_transform")
65 else:
66 layout.row().label(text="No Texture Slot available", icon="INFO")
68 def vertpaint(self, layout, context):
69 has_brush = utils_core.get_brush_link(context, types="brush")
70 tex_slot = has_brush.texture_slot if has_brush else None
72 # Menus
73 layout.row().menu(Textures.bl_idname)
74 layout.row().menu(TextureMapMode.bl_idname)
76 # Checkboxes
77 if tex_slot:
78 if tex_slot.map_mode != '3D':
79 if tex_slot.map_mode in ('RANDOM', 'VIEW_PLANE'):
80 layout.row().prop(tex_slot, "use_rake", toggle=True)
81 layout.row().prop(tex_slot, "use_random", toggle=True)
83 # Sliders
84 layout.row().prop(tex_slot, "angle",
85 text=utils_core.PIW + "Angle", slider=True)
87 if tex_slot.map_mode in ('RANDOM', 'VIEW_PLANE') and tex_slot.use_random:
88 layout.row().prop(tex_slot, "random_angle",
89 text=utils_core.PIW + "Random Angle", slider=True)
91 # Operator
92 if tex_slot.map_mode == 'STENCIL':
93 if has_brush.texture and has_brush.texture.type == 'IMAGE':
94 layout.row().operator("brush.stencil_fit_image_aspect")
96 layout.row().operator("brush.stencil_reset_transform")
98 else:
99 layout.row().label(text="No Texture Slot available", icon="INFO")
101 def texpaint(self, layout, context):
102 has_brush = utils_core.get_brush_link(context, types="brush")
103 tex_slot = has_brush.texture_slot if has_brush else None
104 mask_tex_slot = has_brush.mask_texture_slot if has_brush else None
106 # Texture Section
107 layout.row().label(text="Texture", icon='TEXTURE')
109 # Menus
110 layout.row().menu(Textures.bl_idname)
111 layout.row().menu(TextureMapMode.bl_idname)
113 # Checkboxes
114 if tex_slot:
115 if tex_slot.map_mode != '3D':
116 if tex_slot.map_mode in ('RANDOM', 'VIEW_PLANE'):
117 layout.row().prop(tex_slot, "use_rake", toggle=True)
118 layout.row().prop(tex_slot, "use_random", toggle=True)
120 # Sliders
121 layout.row().prop(tex_slot, "angle",
122 text=utils_core.PIW + "Angle", slider=True)
124 if tex_slot.map_mode in ('RANDOM', 'VIEW_PLANE') and tex_slot.use_random:
125 layout.row().prop(tex_slot, "random_angle",
126 text=utils_core.PIW + "Random Angle", slider=True)
128 # Operator
129 if tex_slot.map_mode == 'STENCIL':
130 if has_brush.texture and has_brush.texture.type == 'IMAGE':
131 layout.row().operator("brush.stencil_fit_image_aspect")
133 layout.row().operator("brush.stencil_reset_transform")
135 else:
136 layout.row().label(text="No Texture Slot available", icon="INFO")
138 layout.row().separator()
140 # Texture Mask Section
141 layout.row().label(text="Texture Mask", icon='MOD_MASK')
143 # Menus
144 layout.row().menu(MaskTextures.bl_idname)
145 layout.row().menu(MaskMapMode.bl_idname)
146 layout.row().menu(MaskPressureModeMenu.bl_idname)
148 # Checkboxes
149 if mask_tex_slot:
150 if mask_tex_slot.mask_map_mode in ('RANDOM', 'VIEW_PLANE'):
151 layout.row().prop(mask_tex_slot, "use_rake", toggle=True)
152 layout.row().prop(mask_tex_slot, "use_random", toggle=True)
154 # Sliders
155 layout.row().prop(mask_tex_slot, "angle",
156 text=utils_core.PIW + "Angle", icon_value=5, slider=True)
158 if mask_tex_slot.mask_map_mode in ('RANDOM', 'VIEW_PLANE') and mask_tex_slot.use_random:
159 layout.row().prop(mask_tex_slot, "random_angle",
160 text=utils_core.PIW + "Random Angle", slider=True)
162 # Operator
163 if mask_tex_slot.mask_map_mode == 'STENCIL':
164 if has_brush.mask_texture and has_brush.mask_texture.type == 'IMAGE':
165 layout.row().operator("brush.stencil_fit_image_aspect")
167 prop = layout.row().operator("brush.stencil_reset_transform")
168 prop.mask = True
170 else:
171 layout.row().label(text="Mask Texture not available", icon="INFO")
174 class Textures(Menu):
175 bl_label = "Brush Texture"
176 bl_idname = "VIEW3D_MT_sv3_texture_list"
178 def init(self):
179 if utils_core.get_mode() == 'SCULPT':
180 datapath = "tool_settings.sculpt.brush.texture"
182 elif utils_core.get_mode() == 'VERTEX_PAINT':
183 datapath = "tool_settings.vertex_paint.brush.texture"
185 elif utils_core.get_mode() == 'TEXTURE_PAINT':
186 datapath = "tool_settings.image_paint.brush.texture"
188 else:
189 datapath = ""
191 return datapath
193 def draw(self, context):
194 datapath = self.init()
195 has_brush = utils_core.get_brush_link(context, types="brush")
196 current_texture = eval("bpy.context.{}".format(datapath)) if \
197 has_brush else None
198 layout = self.layout
200 # get the current texture's name
201 if current_texture:
202 current_texture = current_texture.name
204 layout.row().label(text="Brush Texture")
205 layout.row().separator()
207 # add an item to set the texture to None
208 utils_core.menuprop(layout.row(), "None", "None",
209 datapath, icon='RADIOBUT_OFF', disable=True,
210 disable_icon='RADIOBUT_ON',
211 custom_disable_exp=(None, current_texture),
212 path=True)
214 # add the menu items
215 for item in bpy.data.textures:
216 utils_core.menuprop(layout.row(), item.name,
217 'bpy.data.textures["%s"]' % item.name,
218 datapath, icon='RADIOBUT_OFF',
219 disable=True,
220 disable_icon='RADIOBUT_ON',
221 custom_disable_exp=(item.name, current_texture),
222 path=True)
225 class TextureMapMode(Menu):
226 bl_label = "Brush Mapping"
227 bl_idname = "VIEW3D_MT_sv3_texture_map_mode"
229 def draw(self, context):
230 layout = self.layout
231 has_brush = utils_core.get_brush_link(context, types="brush")
233 layout.row().label(text="Brush Mapping")
234 layout.row().separator()
236 if has_brush:
237 if utils_core.get_mode() == 'SCULPT':
238 path = "tool_settings.sculpt.brush.texture_slot.map_mode"
240 # add the menu items
241 for item in has_brush. \
242 texture_slot.bl_rna.properties['map_mode'].enum_items:
243 utils_core.menuprop(
244 layout.row(), item.name, item.identifier, path,
245 icon='RADIOBUT_OFF',
246 disable=True,
247 disable_icon='RADIOBUT_ON'
249 elif utils_core.get_mode() == 'VERTEX_PAINT':
250 path = "tool_settings.vertex_paint.brush.texture_slot.map_mode"
252 # add the menu items
253 for item in has_brush. \
254 texture_slot.bl_rna.properties['map_mode'].enum_items:
255 utils_core.menuprop(
256 layout.row(), item.name, item.identifier, path,
257 icon='RADIOBUT_OFF',
258 disable=True,
259 disable_icon='RADIOBUT_ON'
261 else:
262 path = "tool_settings.image_paint.brush.texture_slot.map_mode"
264 # add the menu items
265 for item in has_brush. \
266 texture_slot.bl_rna.properties['map_mode'].enum_items:
267 utils_core.menuprop(
268 layout.row(), item.name, item.identifier, path,
269 icon='RADIOBUT_OFF',
270 disable=True,
271 disable_icon='RADIOBUT_ON'
273 else:
274 layout.row().label(text="No brushes available", icon="INFO")
277 class MaskTextures(Menu):
278 bl_label = "Mask Texture"
279 bl_idname = "VIEW3D_MT_sv3_mask_texture_list"
281 def draw(self, context):
282 layout = self.layout
283 datapath = "tool_settings.image_paint.brush.mask_texture"
284 has_brush = utils_core.get_brush_link(context, types="brush")
285 current_texture = eval("bpy.context.{}".format(datapath)) if \
286 has_brush else None
288 layout.row().label(text="Mask Texture")
289 layout.row().separator()
291 if has_brush:
292 # get the current texture's name
293 if current_texture:
294 current_texture = current_texture.name
296 # add an item to set the texture to None
297 utils_core.menuprop(
298 layout.row(), "None", "None",
299 datapath, icon='RADIOBUT_OFF', disable=True,
300 disable_icon='RADIOBUT_ON',
301 custom_disable_exp=(None, current_texture),
302 path=True
305 # add the menu items
306 for item in bpy.data.textures:
307 utils_core.menuprop(
308 layout.row(), item.name, 'bpy.data.textures["%s"]' % item.name,
309 datapath, icon='RADIOBUT_OFF', disable=True,
310 disable_icon='RADIOBUT_ON',
311 custom_disable_exp=(item.name, current_texture),
312 path=True
314 else:
315 layout.row().label(text="No brushes available", icon="INFO")
318 class MaskMapMode(Menu):
319 bl_label = "Mask Mapping"
320 bl_idname = "VIEW3D_MT_sv3_mask_map_mode"
322 def draw(self, context):
323 layout = self.layout
324 path = "tool_settings.image_paint.brush.mask_texture_slot.mask_map_mode"
325 has_brush = utils_core.get_brush_link(context, types="brush")
327 layout.row().label(text="Mask Mapping")
328 layout.row().separator()
329 if has_brush:
330 items = has_brush. \
331 mask_texture_slot.bl_rna.properties['mask_map_mode'].enum_items
332 # add the menu items
333 for item in items:
334 utils_core.menuprop(
335 layout.row(), item.name, item.identifier, path,
336 icon='RADIOBUT_OFF',
337 disable=True,
338 disable_icon='RADIOBUT_ON'
340 else:
341 layout.row().label(text="No brushes available", icon="INFO")
344 class TextureAngleSource(Menu):
345 bl_label = "Texture Angle Source"
346 bl_idname = "VIEW3D_MT_sv3_texture_angle_source"
348 def draw(self, context):
349 layout = self.layout
350 has_brush = utils_core.get_brush_link(context, types="brush")
352 if has_brush:
353 if utils_core.get_mode() == 'SCULPT':
354 items = has_brush. \
355 bl_rna.properties['texture_angle_source_random'].enum_items
356 path = "tool_settings.sculpt.brush.texture_angle_source_random"
358 elif utils_core.get_mode() == 'VERTEX_PAINT':
359 items = has_brush. \
360 bl_rna.properties['texture_angle_source_random'].enum_items
361 path = "tool_settings.vertex_paint.brush.texture_angle_source_random"
363 else:
364 items = has_brush. \
365 bl_rna.properties['texture_angle_source_random'].enum_items
366 path = "tool_settings.image_paint.brush.texture_angle_source_random"
368 # add the menu items
369 for item in items:
370 utils_core.menuprop(
371 layout.row(), item[0], item[1], path,
372 icon='RADIOBUT_OFF',
373 disable=True,
374 disable_icon='RADIOBUT_ON'
376 else:
377 layout.row().label(text="No brushes available", icon="INFO")
379 class MaskPressureModeMenu(Menu):
380 bl_label = "Mask Pressure Mode"
381 bl_idname = "VIEW3D_MT_sv3_mask_pressure_mode_menu"
383 def draw(self, context):
384 layout = self.layout
385 path = "tool_settings.image_paint.brush.use_pressure_masking"
387 layout.row().label(text="Mask Pressure Mode")
388 layout.row().separator()
390 # add the menu items
391 for item in context.tool_settings.image_paint.brush. \
392 bl_rna.properties['use_pressure_masking'].enum_items:
393 utils_core.menuprop(
394 layout.row(), item.name, item.identifier, path,
395 icon='RADIOBUT_OFF',
396 disable=True,
397 disable_icon='RADIOBUT_ON'
401 classes = (
402 TextureMenu,
403 Textures,
404 TextureMapMode,
405 MaskTextures,
406 MaskMapMode,
407 TextureAngleSource,
408 MaskPressureModeMenu
411 def register():
412 for cls in classes:
413 bpy.utils.register_class(cls)
415 def unregister():
416 for cls in classes:
417 bpy.utils.unregister_class(cls)