-
Campbell Barton authoredCampbell Barton authored
paint_palette.py 27.52 KiB
# paint_palette.py (c) 2011 Dany Lebel (Axon_D)
#
# ***** BEGIN GPL LICENSE BLOCK *****
#
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ***** END GPL LICENCE BLOCK *****
bl_info = {
"name": "Paint Palettes",
"author": "Dany Lebel (Axon D)",
"version": (0,9,1),
"blender": (2, 63, 12),
"location": "Image Editor and 3D View > Any Paint mode > Color Palette or Weight Palette panel",
"description": "Palettes for color and weight paint modes",
"warning": "",
"wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Paint/Palettes",
"tracker_url": "http://projects.blender.org/tracker/index.php?func=detail&aid=25908",
"category": "Paint"}
"""
This addon brings palettes to the paint modes.
* Color Palette for Image Painting, Texture Paint and Vertex Paint modes.
* Weight Palette for the Weight Paint mode.
Set a number of colors (or weights according to the mode) and then associate it
with the brush by using the button under the color.
"""
import bpy
from bpy.props import *
def update_panels():
pp = bpy.context.scene.palette_props
current_color = pp.colors[pp.current_color_index].color
pp.color_name = pp.colors[pp.current_color_index].name
brush = current_brush()
brush.color = current_color
pp.index = pp.current_color_index
def sample():
pp = bpy.context.scene.palette_props
current_color = pp.colors[pp.current_color_index]
brush = current_brush()
current_color.color = brush.color
return None
def current_brush():
context = bpy.context
if context.area.type == 'VIEW_3D' and context.vertex_paint_object:
brush = context.tool_settings.vertex_paint.brush
elif context.area.type == 'VIEW_3D' and context.image_paint_object:
brush = context.tool_settings.image_paint.brush
elif context.area.type == 'IMAGE_EDITOR' and context.space_data.mode == 'PAINT':
brush = context.tool_settings.image_paint.brush
else :
brush = None
return brush
def update_weight_value():
pp = bpy.context.scene.palette_props
tt = bpy.context.tool_settings
tt.unified_paint_settings.weight = pp.weight_value
return None
class PALETTE_MT_menu(bpy.types.Menu):
bl_label = "Presets"
preset_subdir = ""
preset_operator = "palette.load_gimp_palette"
def path_menu(self, searchpaths, operator, props_default={}):
layout = self.layout
# hard coded to set the operators 'filepath' to the filename.
import os
import bpy.utils
layout = self.layout
if not searchpaths[0]:
layout.label("* Missing Paths *")
# collect paths
else :
files = []
for directory in searchpaths:
files.extend([(f, os.path.join(directory, f)) for f in os.listdir(directory)])
files.sort()
for f, filepath in files:
if f.startswith("."):
continue
preset_name = bpy.path.display_name(f)
props = layout.operator(operator, text=preset_name)
for attr, value in props_default.items():
setattr(props, attr, value)
props.filepath = filepath
if operator == "palette.load_gimp_palette":
props.menu_idname = self.bl_idname
def draw_preset(self, context):
"""Define these on the subclass
- preset_operator
- preset_subdir
"""
import bpy
self.path_menu([bpy.context.scene.palette_props.presets_folder], self.preset_operator)
draw = draw_preset
class LoadGimpPalette(bpy.types.Operator):
"""Execute a preset"""
bl_idname = "palette.load_gimp_palette"
bl_label = "Load a Gimp palette"
filepath = bpy.props.StringProperty(name="Path",
description="Path of the Python file to execute",
maxlen=512, default="")
menu_idname = bpy.props.StringProperty(name="Menu ID Name",
description="ID name of the menu this was called from", default="")
def execute(self, context):
from os.path import basename
filepath = self.filepath
palette_props = bpy.context.scene.palette_props
palette_props.current_color_index = 0
# change the menu title to the most recently chosen option
preset_class = getattr(bpy.types, self.menu_idname)
preset_class.bl_label = bpy.path.display_name(basename(filepath))
ts = bpy.context.tool_settings
palette_props.columns = 0
if filepath[-4:] == ".gpl":
gpl = open(filepath, "r")
lines = gpl.readlines()
palette_props.notes = ''
has_color = False
for index_0, line in enumerate(lines):
if not line or (line[:12] == "GIMP Palette"):
pass
elif line[:5] == "Name:":
palette_props.palette_name = line[5:]
elif line[:8] == "Columns:":
palette_props.columns = int(line[8:])
elif line[0] == "#":
palette_props.notes += line
else :
has_color = True
#index_0 = i
break
i = -1
if has_color:
for i, ln in enumerate(lines[index_0:]):
try:
palette_props.colors[i]
except IndexError:
palette_props.colors.add()
color = [float(rgb)/255 for rgb in [ln[0:3], ln[4:7], ln[8:11]]]
palette_props.colors[i].color = color
palette_props.colors[i].name = ln[12:-1]
exceeding = i + 1
while palette_props.colors.__len__() > exceeding:
palette_props.colors.remove(exceeding)
if has_color:
update_panels()
gpl.close()
pass
else :
self.report({'INFO'}, "Not a supported palette format")
return {'FINISHED'}
class WriteGimpPalette():
"""Base preset class, only for subclassing
subclasses must define
- preset_values
- preset_subdir """
bl_options = {'REGISTER'} # only because invoke_props_popup requires.
name = bpy.props.StringProperty(name="Name",
description="Name of the preset, used to make the path name",
maxlen=64, default="")
remove_active = bpy.props.BoolProperty(default=False, options={'HIDDEN'})
@staticmethod
def as_filename(name): # could reuse for other presets
for char in " !@#$%^&*(){}:\";'[]<>,.\\/?":
name = name.replace(char, '_')
return name.lower().strip()
def execute(self, context):
import os
pp = bpy.context.scene.palette_props
if hasattr(self, "pre_cb"):
self.pre_cb(context)
preset_menu_class = getattr(bpy.types, self.preset_menu)
if not self.remove_active:
if not self.name:
return {'FINISHED'}
filename = self.as_filename(self.name)
target_path = pp.presets_folder
if not target_path:
self.report({'WARNING'}, "Failed to create presets path")
return {'CANCELLED'}
filepath = os.path.join(target_path, filename) + ".gpl"
file_preset = open(filepath, 'wb')
gpl = "GIMP Palette\n"
gpl += "Name: %s\n" % filename
gpl += "Columns: %d\n" % pp.columns
gpl += pp.notes
if pp.colors.items():
for i, color in enumerate(pp.colors):
gpl += "%3d%4d%4d %s" % (color.color.r * 255, color.color.g * 255, color.color.b * 255, color.name + '\n')
file_preset.write(bytes(gpl, 'UTF-8'))
file_preset.close()
pp.palette_name = filename
else:
preset_active = preset_menu_class.bl_label
# fairly sloppy but convenient.
filepath = bpy.utils.preset_find(preset_active, self.preset_subdir)
if not filepath:
filepath = bpy.utils.preset_find(preset_active,
self.preset_subdir, display_name=True)
if not filepath:
return {'CANCELLED'}
if hasattr(self, "remove"):
self.remove(context, filepath)
else:
try:
os.remove(filepath)
except:
import traceback
traceback.print_exc()
# XXX, stupid!
preset_menu_class.bl_label = "Presets"
if hasattr(self, "post_cb"):
self.post_cb(context)
return {'FINISHED'}
def check(self, context):
self.name = self.as_filename(self.name)
def invoke(self, context, event):
if not self.remove_active:
wm = context.window_manager
return wm.invoke_props_dialog(self)
else:
return self.execute(context)
class AddPresetPalette(WriteGimpPalette, bpy.types.Operator):
"""Add a Palette Preset"""
bl_idname = "palette.preset_add"
bl_label = "Add Palette Preset"
preset_menu = "PALETTE_MT_menu"
preset_defines = []
preset_values = []
preset_subdir = "palette"
class PALETTE_OT_add_color(bpy.types.Operator):
bl_label = ""
bl_description = "Add a Color to the Palette"
bl_idname = "palette_props.add_color"
def execute(self, context):
pp = bpy.context.scene.palette_props
new_index = 0
if pp.colors.items():
new_index = pp.current_color_index + 1
pp.colors.add()
last = pp.colors.__len__() - 1
pp.colors.move(last, new_index)
pp.current_color_index = new_index
sample()
update_panels()
return {'FINISHED'}
class PALETTE_OT_remove_color(bpy.types.Operator):
bl_label = ""
bl_description = "Remove Selected Color"
bl_idname = "palette_props.remove_color"
@classmethod
def poll(cls, context):
pp = bpy.context.scene.palette_props
return bool(pp.colors.items())
def execute(self, context):
pp = bpy.context.scene.palette_props
i = pp.current_color_index
pp.colors.remove(i)
if pp.current_color_index >= pp.colors.__len__():
pp.index = pp.current_color_index = pp.colors.__len__() - 1
return {'FINISHED'}
class PALETTE_OT_sample_tool_color(bpy.types.Operator):
bl_label = ""
bl_description = "Sample Tool Color"
bl_idname = "palette_props.sample_tool_color"
def execute(self, context):
pp = bpy.context.scene.palette_props
brush = current_brush()
pp.colors[pp.current_color_index].color = brush.color
return {'FINISHED'}
class IMAGE_OT_select_color(bpy.types.Operator):
bl_label = ""
bl_description = "Select this color"
bl_idname = "paint.select_color"
color_index = IntProperty()
def invoke(self, context, event):
palette_props = bpy.context.scene.palette_props
palette_props.current_color_index = self.color_index
update_panels()
return {'FINISHED'}
def color_palette_draw(self, context):
palette_props = bpy.context.scene.palette_props
layout = self.layout
bpy.types.PALETTE_MT_menu.preset_subdir = palette_props.presets_folder
row = layout.row(align=True)
row.menu("PALETTE_MT_menu", text=palette_props.palette_name.rstrip())
row.operator("palette.preset_add", text="", icon="ZOOMIN")
row.operator("palette.preset_add", text="", icon="ZOOMOUT").remove_active = True
col = layout.column(align=True)
row = col.row(align=True)
row.operator("palette_props.add_color", icon="ZOOMIN")
row.prop(palette_props, "index")
row.operator("palette_props.remove_color", icon="PANEL_CLOSE")
row = col.row(align=True)
row.prop(palette_props, "columns")
if palette_props.colors.items():
layout = col.box()
row = layout.row(align=True)
row.prop(palette_props, "color_name")
row.operator("palette_props.sample_tool_color", icon="COLOR")
laycol = layout.column(align=False)
if palette_props.columns:
columns = palette_props.columns
else :
columns = 16
for i, color in enumerate(palette_props.colors):
if not i % columns:
row1 = laycol.row(align=True)
row1.scale_y = 0.8
row2 = laycol.row(align=True)
row2.scale_y = 0.5
if i == palette_props.current_color_index:
row1.prop(palette_props.colors[i], "color", event=True, toggle=True)
row2.operator("paint.select_color", emboss=False).color_index = i
else :
row1.prop(palette_props.colors[i], "color", event=True, toggle=True)
row2.operator("paint.select_color").color_index = i
layout = self.layout
row = layout.row()
row.prop(palette_props, "presets_folder", text="")
pass
class BrushButtonsPanel():
bl_space_type = 'IMAGE_EDITOR'
bl_region_type = 'UI'
@classmethod
def poll(cls, context):
sima = context.space_data
toolsettings = context.tool_settings.image_paint
return sima.show_paint and toolsettings.brush
class PaintPanel():
bl_space_type = 'VIEW_3D'
bl_region_type = 'TOOLS'
@staticmethod
def paint_settings(context):
ts = context.tool_settings
if context.vertex_paint_object:
return ts.vertex_paint
elif context.weight_paint_object:
return ts.weight_paint
elif context.texture_paint_object:
return ts.image_paint
return None
class IMAGE_PT_color_palette(BrushButtonsPanel, bpy.types.Panel):
bl_label = "Color Palette"
bl_options = {'DEFAULT_CLOSED'}
def draw(self, context):
color_palette_draw(self, context)
class VIEW3D_PT_color_palette(PaintPanel, bpy.types.Panel):
bl_label = "Color Palette"
bl_options = {'DEFAULT_CLOSED'}
@classmethod
def poll(cls, context):
return (context.image_paint_object or context.vertex_paint_object)
def draw(self, context):
color_palette_draw(self, context)
class VIEW3D_OT_select_weight(bpy.types.Operator):
bl_label = ""
bl_description = "Select this weight"
bl_idname = "paint.select_weight"
weight_index = IntProperty()
def current_weight(self):
pp = bpy.context.scene.palette_props
if self.weight_index == 0:
weight = pp.weight_0
elif self.weight_index == 1:
weight = pp.weight_1
elif self.weight_index == 2:
weight = pp.weight_2
elif self.weight_index == 3:
weight = pp.weight_3
elif self.weight_index == 4:
weight = pp.weight_4
elif self.weight_index == 5:
weight = pp.weight_5
elif self.weight_index == 6:
weight = pp.weight_6
elif self.weight_index == 7:
weight = pp.weight_7
elif self.weight_index == 8:
weight = pp.weight_8
elif self.weight_index == 9:
weight = pp.weight_9
elif self.weight_index == 10:
weight = pp.weight_10
return weight
def invoke(self, context, event):
palette_props = bpy.context.scene.palette_props
palette_props.current_weight_index = self.weight_index
if self.weight_index == 0:
weight = palette_props.weight_0
elif self.weight_index == 1:
weight = palette_props.weight_1
elif self.weight_index == 2:
weight = palette_props.weight_2
elif self.weight_index == 3:
weight = palette_props.weight_3
elif self.weight_index == 4:
weight = palette_props.weight_4
elif self.weight_index == 5:
weight = palette_props.weight_5
elif self.weight_index == 6:
weight = palette_props.weight_6
elif self.weight_index == 7:
weight = palette_props.weight_7
elif self.weight_index == 8:
weight = palette_props.weight_8
elif self.weight_index == 9:
weight = palette_props.weight_9
elif self.weight_index == 10:
weight = palette_props.weight_10
palette_props.weight = weight
#bpy.context.tool_settings.vertex_group_weight = weight
return {'FINISHED'}
class VIEW3D_OT_reset_weight_palette(bpy.types.Operator):
bl_label = ""
bl_idname = "paint.reset_weight_palette"
def execute(self, context):
palette_props = context.scene.palette_props
if palette_props.current_weight_index == 0:
palette_props.weight = 0.0
palette_props.weight_0 = 0.0
palette_props.weight_1 = 0.1
if palette_props.current_weight_index == 1:
palette_props.weight = 0.1
if palette_props.current_weight_index == 2:
palette_props.weight = 0.25
palette_props.weight_2 = 0.25
if palette_props.current_weight_index == 3:
palette_props.weight = 0.3333
palette_props.weight_3 = 0.3333
if palette_props.current_weight_index == 4:
palette_props.weight = 0.4
palette_props.weight_4 = 0.4
if palette_props.current_weight_index == 5:
palette_props.weight = 0.5
palette_props.weight_5 = 0.5
if palette_props.current_weight_index == 6:
palette_props.weight = 0.6
palette_props.weight_6 = 0.6
if palette_props.current_weight_index == 7:
palette_props.weight = 0.6666
palette_props.weight_7 = 0.6666
if palette_props.current_weight_index == 8:
palette_props.weight = 0.75
palette_props.weight_8 = 0.75
if palette_props.current_weight_index == 9:
palette_props.weight = 0.9
palette_props.weight_9 = 0.9
if palette_props.current_weight_index == 10:
palette_props.weight = 1.0
palette_props.weight_10 = 1.0
return {'FINISHED'}
class VIEW3D_PT_weight_palette(PaintPanel, bpy.types.Panel):
bl_label = "Weight Palette"
bl_options = {'DEFAULT_CLOSED'}
@classmethod
def poll(cls, context):
return context.weight_paint_object
def draw(self, context):
palette_props = bpy.context.scene.palette_props
#vertex_group_weight = bpy.context.tool_settings.unified_paint_settings.weight
layout = self.layout
row = layout.row()
row.prop(palette_props, "weight", slider=True)
box = layout.box()
row = box.row()
if palette_props.current_weight_index == 0:
row.operator("paint.select_weight", text="%.2f" % palette_props.weight_0,
emboss=False).weight_index = 0
else :
row.operator("paint.select_weight", text="%.2f" % palette_props.weight_0,
emboss=True).weight_index = 0
row = box.row(align=True)
if palette_props.current_weight_index == 1:
row.operator("paint.select_weight", text="%.2f" % palette_props.weight_1,
emboss=False).weight_index = 1
else :
row.operator("paint.select_weight", text="%.2f" % palette_props.weight_1,
emboss=True).weight_index = 1
if palette_props.current_weight_index == 2:
row.operator("paint.select_weight", text="%.2f" % palette_props.weight_2,
emboss=False).weight_index = 2
else :
row.operator("paint.select_weight", text="%.2f" % palette_props.weight_2,
emboss=True).weight_index = 2
if palette_props.current_weight_index == 3:
row.operator("paint.select_weight", text="%.2f" % palette_props.weight_3,
emboss=False).weight_index = 3
else :
row.operator("paint.select_weight", text="%.2f" % palette_props.weight_3,
emboss=True).weight_index = 3
row = box.row(align=True)
if palette_props.current_weight_index == 4:
row.operator("paint.select_weight", text="%.2f" % palette_props.weight_4,
emboss=False).weight_index = 4
else :
row.operator("paint.select_weight", text="%.2f" % palette_props.weight_4,
emboss=True).weight_index = 4
if palette_props.current_weight_index == 5:
row.operator("paint.select_weight", text="%.2f" % palette_props.weight_5,
emboss=False).weight_index = 5
else :
row.operator("paint.select_weight", text="%.2f" % palette_props.weight_5,
emboss=True).weight_index = 5
if palette_props.current_weight_index == 6:
row.operator("paint.select_weight", text="%.2f" % palette_props.weight_6,
emboss=False).weight_index = 6
else :
row.operator("paint.select_weight", text="%.2f" % palette_props.weight_6,
emboss=True).weight_index = 6
row = box.row(align=True)
if palette_props.current_weight_index == 7:
row.operator("paint.select_weight", text="%.2f" % palette_props.weight_7,
emboss=False).weight_index = 7
else :
row.operator("paint.select_weight", text="%.2f" % palette_props.weight_7,
emboss=True).weight_index = 7
if palette_props.current_weight_index == 8:
row.operator("paint.select_weight", text="%.2f" % palette_props.weight_8,
emboss=False).weight_index = 8
else :
row.operator("paint.select_weight", text="%.2f" % palette_props.weight_8,
emboss=True).weight_index = 8
if palette_props.current_weight_index == 9:
row.operator("paint.select_weight", text="%.2f" % palette_props.weight_9,
emboss=False).weight_index = 9
else :
row.operator("paint.select_weight", text="%.2f" % palette_props.weight_9,
emboss=True).weight_index = 9
row = box.row(align=True)
if palette_props.current_weight_index == 10:
row.operator("paint.select_weight", text="%.2f" % palette_props.weight_10,
emboss=False).weight_index = 10
else :
row.operator("paint.select_weight", text="%.2f" % palette_props.weight_10,
emboss=True).weight_index = 10
row = layout.row()
row.operator("paint.reset_weight_palette", text="Reset")
class Colors(bpy.types.PropertyGroup):
"""Class for colors CollectionProperty"""
color = bpy.props.FloatVectorProperty(
name="", description="", default=(0.8, 0.8, 0.8), min=0, max=1,
step=1, precision=3, subtype='COLOR_GAMMA', size=3)
class Weights(bpy.types.PropertyGroup):
"""Class for Weights Collection Property"""
weight = bpy.props.FloatProperty(
default=0.0, min=0.0, max=1.0, precision=3)
class PaletteProps(bpy.types.PropertyGroup):
def update_color_name(self, context):
pp = bpy.context.scene.palette_props
pp.colors[pp.current_color_index].name = pp.color_name
return None
def move_color(self, context):
pp = bpy.context.scene.palette_props
if pp.colors.items() and pp.current_color_index != pp.index:
if pp.index >= pp.colors.__len__():
pp.index = pp.colors.__len__() - 1
pp.colors.move(pp.current_color_index, pp.index)
pp.current_color_index = pp.index
return None
def update_weight(self, context):
pp = context.scene.palette_props
weight = pp.weight
if pp.current_weight_index == 0:
pp.weight_0 = weight
elif pp.current_weight_index == 1:
pp.weight_1 = weight
elif pp.current_weight_index == 2:
pp.weight_2 = weight
elif pp.current_weight_index == 3:
pp.weight_3 = weight
elif pp.current_weight_index == 4:
pp.weight_4 = weight
elif pp.current_weight_index == 5:
pp.weight_5 = weight
elif pp.current_weight_index == 6:
pp.weight_6 = weight
elif pp.current_weight_index == 7:
pp.weight_7 = weight
elif pp.current_weight_index == 8:
pp.weight_8 = weight
elif pp.current_weight_index == 9:
pp.weight_9 = weight
elif pp.current_weight_index == 10:
pp.weight_10 = weight
bpy.context.tool_settings.unified_paint_settings.weight = weight
#bpy.context.tool_settings.vertex_group_weight = weight
return None
palette_name = StringProperty(
name="Palette Name", default="Preset", subtype='FILE_NAME')
color_name = StringProperty(
name="", description="Color Name", default="Untitled", update=update_color_name)
columns = IntProperty(
name="Columns",
description="Number of Columns",
min=0, max=16, default=0)
index = IntProperty(
name="Index",
description="Move Selected Color",
min=0,
update=move_color)
notes = StringProperty(
name="Palette Notes", default="#\n")
current_color_index = IntProperty(
name="Current Color Index", description="", default=0, min=0)
current_weight_index = IntProperty(
name="Current Color Index", description="", default=10, min=-1)
presets_folder = StringProperty(name="",
description="Palettes Folder",
subtype="DIR_PATH")
colors = bpy.props.CollectionProperty(type=Colors)
weight = bpy.props.FloatProperty(name="Weight",
default=0.0, min=0.0, max=1.0, precision=3, update=update_weight)
weight_0 = bpy.props.FloatProperty(
default=0.0, min=0.0, max=1.0, precision=3)
weight_1 = bpy.props.FloatProperty(
default=0.1, min=0.0, max=1.0, precision=3)
weight_2 = bpy.props.FloatProperty(
default=0.25, min=0.0, max=1.0, precision=3)
weight_3 = bpy.props.FloatProperty(
default=0.333, min=0.0, max=1.0, precision=3)
weight_4 = bpy.props.FloatProperty(
default=0.4, min=0.0, max=1.0, precision=3)
weight_5 = bpy.props.FloatProperty(
default=0.5, min=0.0, max=1.0, precision=3)
weight_6 = bpy.props.FloatProperty(
default=0.6, min=0.0, max=1.0, precision=3)
weight_7 = bpy.props.FloatProperty(
default=0.6666, min=0.0, max=1.0, precision=3)
weight_8 = bpy.props.FloatProperty(
default=0.75, min=0.0, max=1.0, precision=3)
weight_9 = bpy.props.FloatProperty(
default=0.9, min=0.0, max=1.0, precision=3)
weight_10 = bpy.props.FloatProperty(
default=1.0, min=0.0, max=1.0, precision=3)
pass
def register():
bpy.utils.register_module(__name__)
bpy.types.Scene.palette_props = PointerProperty(
type=PaletteProps, name="Palette Props", description="")
pass
def unregister():
bpy.utils.unregister_module(__name__)
del bpy.types.Scene.palette_props
pass
if __name__ == "__main__":
register()
# To Do List
# ToDo1 Overiting the current file
# ToDo3 Foreground Background