Merge branch 'blender-v3.3-release'
[blender-addons.git] / paint_palette.py
blob789ab5626d1bdda1826c7cda094b00971851ed0e
1 # SPDX-License-Identifier: GPL-2.0-or-later
2 # Copyright 2011 Dany Lebel (Axon_D)
4 bl_info = {
5 "name": "Paint Palettes",
6 "author": "Dany Lebel (Axon D)",
7 "version": (0, 9, 4),
8 "blender": (2, 80, 0),
9 "location": "Image Editor and 3D View > Any Paint mode > Color Palette or Weight Palette panel",
10 "description": "Palettes for color and weight paint modes",
11 "warning": "",
12 "doc_url": "{BLENDER_MANUAL_URL}/addons/paint/paint_palettes.html",
13 "category": "Paint",
16 """
17 This add-on brings palettes to the paint modes.
19 * Color Palette for Image Painting, Texture Paint and Vertex Paint modes.
20 * Weight Palette for the Weight Paint mode.
22 Set a number of colors (or weights according to the mode) and then associate it
23 with the brush by using the button under the color.
24 """
26 import bpy
27 from bpy.types import (
28 Operator,
29 Menu,
30 Panel,
31 PropertyGroup,
33 from bpy.props import (
34 BoolProperty,
35 FloatProperty,
36 FloatVectorProperty,
37 IntProperty,
38 StringProperty,
39 PointerProperty,
40 CollectionProperty,
44 def update_panels():
45 pp = bpy.context.scene.palette_props
46 current_color = pp.colors[pp.current_color_index].color
47 pp.color_name = pp.colors[pp.current_color_index].name
48 brush = current_brush()
49 brush.color = current_color
50 pp.index = pp.current_color_index
53 def sample():
54 pp = bpy.context.scene.palette_props
55 current_color = pp.colors[pp.current_color_index]
56 brush = current_brush()
57 current_color.color = brush.color
58 return None
61 def current_brush():
62 context = bpy.context
63 if context.area.type == 'VIEW_3D' and context.vertex_paint_object:
64 brush = context.tool_settings.vertex_paint.brush
65 elif context.area.type == 'VIEW_3D' and context.image_paint_object:
66 brush = context.tool_settings.image_paint.brush
67 elif context.area.type == 'IMAGE_EDITOR' and context.space_data.mode == 'PAINT':
68 brush = context.tool_settings.image_paint.brush
69 else:
70 brush = None
71 return brush
74 def update_weight_value():
75 pp = bpy.context.scene.palette_props
76 tt = bpy.context.tool_settings
77 tt.unified_paint_settings.weight = pp.weight_value
78 return None
81 def check_path_return():
82 from os.path import normpath
83 preset_path = bpy.path.abspath(bpy.context.scene.palette_props.presets_folder)
84 paths = normpath(preset_path)
86 return paths if paths else ""
89 class PALETTE_MT_menu(Menu):
90 bl_label = "Presets"
91 preset_subdir = ""
92 preset_operator = "palette.load_gimp_palette"
94 def path_menu(self, searchpaths, operator, props_default={}):
95 layout = self.layout
96 # hard coded to set the operators 'filepath' to the filename.
97 import os
98 import bpy.utils
100 layout = self.layout
102 if bpy.data.filepath == "":
103 layout.label(text="*Please save the .blend file first*")
104 return
106 if not searchpaths[0]:
107 layout.label(text="* Missing Paths *")
108 return
110 # collect paths
111 files = []
112 for directory in searchpaths:
113 files.extend([(f, os.path.join(directory, f)) for f in os.listdir(directory)])
115 files.sort()
117 for f, filepath in files:
119 if f.startswith("."):
120 continue
121 # do not load everything from the given folder, only .gpl files
122 if f[-4:] != ".gpl":
123 continue
125 preset_name = bpy.path.display_name(f)
126 props = layout.operator(operator, text=preset_name)
128 for attr, value in props_default.items():
129 setattr(props, attr, value)
131 props.filepath = filepath
132 if operator == "palette.load_gimp_palette":
133 props.menu_idname = self.bl_idname
135 def draw_preset(self, context):
136 paths = check_path_return()
137 self.path_menu([paths], self.preset_operator)
139 draw = draw_preset
142 class PALETTE_OT_load_gimp_palette(Operator):
143 """Execute a preset"""
144 bl_idname = "palette.load_gimp_palette"
145 bl_label = "Load a Gimp palette"
147 filepath: StringProperty(
148 name="Path",
149 description="Path of the .gpl file to load",
150 default=""
152 menu_idname: StringProperty(
153 name="Menu ID Name",
154 description="ID name of the menu this was called from",
155 default=""
158 def execute(self, context):
159 from os.path import basename
160 import re
161 filepath = self.filepath
163 palette_props = bpy.context.scene.palette_props
164 palette_props.current_color_index = 0
166 # change the menu title to the most recently chosen option
167 preset_class = getattr(bpy.types, self.menu_idname)
168 preset_class.bl_label = bpy.path.display_name(basename(filepath))
170 palette_props.columns = 0
171 error_palette = False # errors found
172 error_import = [] # collect exception messages
173 start_color_index = 0 # store the starting line for color definitions
175 if filepath[-4:] != ".gpl":
176 error_palette = True
177 else:
178 gpl = open(filepath, "r")
179 lines = gpl.readlines()
180 palette_props.notes = ''
181 has_color = False
182 for index_0, line in enumerate(lines):
183 if not line or (line[:12] == "GIMP Palette"):
184 pass
185 elif line[:5] == "Name:":
186 palette_props.palette_name = line[5:]
187 elif line[:8] == "Columns:":
188 palette_props.columns = int(line[8:])
189 elif line[0] == "#":
190 palette_props.notes += line
191 elif line[0] == "\n":
192 pass
193 else:
194 has_color = True
195 start_color_index = index_0
196 break
197 i = -1
198 if has_color:
199 for i, ln in enumerate(lines[start_color_index:]):
200 try:
201 palette_props.colors[i]
202 except IndexError:
203 palette_props.colors.add()
204 try:
205 # get line - find keywords with re.split, remove the empty ones with filter
206 get_line = list(filter(None, re.split(r'\t+|\s+', ln.rstrip('\n'))))
207 extract_colors = get_line[:3]
208 get_color_name = [str(name) for name in get_line[3:]]
209 color = [float(rgb) / 255 for rgb in extract_colors]
210 palette_props.colors[i].color = color
211 palette_props.colors[i].name = " ".join(get_color_name) or "Color " + str(i)
212 except Exception as e:
213 error_palette = True
214 error_import.append(".gpl file line: {}, error: {}".format(i + 1 + start_color_index, e))
215 pass
217 exceeding = i + 1
218 while palette_props.colors.__len__() > exceeding:
219 palette_props.colors.remove(exceeding)
221 if has_color:
222 update_panels()
223 gpl.close()
224 pass
226 message = "Loaded palette from file: {}".format(filepath)
228 if error_palette:
229 message = "Not supported palette format for file: {}".format(filepath)
230 if error_import:
231 message = "Some of the .gpl palette data can not be parsed. See Console for more info"
232 print("\n[Paint Palette]\nOperator: palette.load_gimp_palette\nErrors: %s\n" %
233 ('\n'.join(error_import)))
235 self.report({'INFO'}, message)
237 return {'FINISHED'}
240 class WriteGimpPalette():
241 """Base preset class, only for subclassing
242 subclasses must define
243 - preset_values
244 - preset_subdir """
245 bl_options = {'REGISTER'} # only because invoke_props_popup requires
247 name: StringProperty(
248 name="Name",
249 description="Name of the preset, used to make the path name",
250 maxlen=64,
251 options={'SKIP_SAVE'},
252 default=""
254 remove_active: BoolProperty(
255 default=False,
256 options={'HIDDEN'}
259 @staticmethod
260 def as_filename(name): # could reuse for other presets
261 for char in " !@#$%^&*(){}:\";'[]<>,.\\/?":
262 name = name.replace(char, '_')
263 return name.lower().strip()
265 def execute(self, context):
266 import os
267 pp = bpy.context.scene.palette_props
269 if hasattr(self, "pre_cb"):
270 self.pre_cb(context)
272 preset_menu_class = getattr(bpy.types, self.preset_menu)
273 target_path = check_path_return()
275 if not target_path:
276 self.report({'WARNING'}, "Failed to create presets path")
277 return {'CANCELLED'}
279 if not os.path.exists(target_path):
280 self.report({'WARNING'},
281 "Failure to open the saved Palettes Folder. Check if the path exists")
282 return {'CANCELLED'}
284 if not self.remove_active:
285 if not self.name:
286 self.report({'INFO'},
287 "No name is given for the preset entry. Operation Cancelled")
288 return {'FINISHED'}
290 filename = self.as_filename(self.name)
291 filepath = os.path.join(target_path, filename) + ".gpl"
292 file_preset = open(filepath, 'wb')
293 gpl = "GIMP Palette\n"
294 gpl += "Name: %s\n" % filename
295 gpl += "Columns: %d\n" % pp.columns
296 gpl += pp.notes
297 if pp.colors.items():
298 for i, color in enumerate(pp.colors):
299 gpl += "%3d%4d%4d %s" % (color.color.r * 255, color.color.g * 255,
300 color.color.b * 255, color.name + '\n')
301 file_preset.write(bytes(gpl, 'UTF-8'))
303 file_preset.close()
305 pp.palette_name = filename
306 preset_menu_class.bl_label = bpy.path.display_name(filename)
308 self.report({'INFO'}, "Created Palette: {}".format(filepath))
310 else:
311 preset_active = preset_menu_class.bl_label
312 filename = self.as_filename(preset_active)
314 filepath = os.path.join(target_path, filename) + ".gpl"
316 if not filepath or not os.path.exists(filepath):
317 self.report({'WARNING'}, "Preset could not be found. Operation Cancelled")
318 self.reset_preset_name(preset_menu_class, pp)
319 return {'CANCELLED'}
321 if hasattr(self, "remove"):
322 self.remove(context, filepath)
323 else:
324 try:
325 os.remove(filepath)
326 self.report({'INFO'}, "Deleted palette: {}".format(filepath))
327 except:
328 import traceback
329 traceback.print_exc()
331 self.reset_preset_name(preset_menu_class, pp)
333 if hasattr(self, "post_cb"):
334 self.post_cb(context)
336 return {'FINISHED'}
338 @staticmethod
339 def reset_preset_name(presets, props):
340 # XXX, still stupid!
341 presets.bl_label = "Presets"
342 props.palette_name = ""
344 def check(self, context):
345 self.name = self.as_filename(self.name)
347 def invoke(self, context, event):
348 if not self.remove_active:
349 wm = context.window_manager
350 return wm.invoke_props_dialog(self)
352 return self.execute(context)
355 class PALETTE_OT_preset_add(WriteGimpPalette, Operator):
356 bl_idname = "palette.preset_add"
357 bl_label = "Add Palette Preset"
358 preset_menu = "PALETTE_MT_menu"
359 bl_description = "Add a Palette Preset"
361 preset_defines = []
362 preset_values = []
363 preset_subdir = "palette"
366 class PALETTE_OT_add_color(Operator):
367 bl_idname = "palette_props.add_color"
368 bl_label = ""
369 bl_description = "Add a Color to the Palette"
371 def execute(self, context):
372 pp = bpy.context.scene.palette_props
373 new_index = 0
374 if pp.colors.items():
375 new_index = pp.current_color_index + 1
376 pp.colors.add()
378 last = pp.colors.__len__() - 1
380 pp.colors.move(last, new_index)
381 pp.current_color_index = new_index
382 sample()
383 update_panels()
385 return {'FINISHED'}
388 class PALETTE_OT_remove_color(Operator):
389 bl_idname = "palette_props.remove_color"
390 bl_label = ""
391 bl_description = "Remove Selected Color"
393 @classmethod
394 def poll(cls, context):
395 pp = bpy.context.scene.palette_props
396 return bool(pp.colors.items())
398 def execute(self, context):
399 pp = context.scene.palette_props
400 i = pp.current_color_index
401 pp.colors.remove(i)
403 if pp.current_color_index >= pp.colors.__len__():
404 pp.index = pp.current_color_index = pp.colors.__len__() - 1
406 return {'FINISHED'}
409 class PALETTE_OT_sample_tool_color(Operator):
410 bl_idname = "palette_props.sample_tool_color"
411 bl_label = ""
412 bl_description = "Sample Tool Color"
414 def execute(self, context):
415 pp = context.scene.palette_props
416 brush = current_brush()
417 pp.colors[pp.current_color_index].color = brush.color
419 return {'FINISHED'}
422 class IMAGE_OT_select_color(Operator):
423 bl_idname = "paint.select_color"
424 bl_label = ""
425 bl_description = "Select this color"
426 bl_options = {'UNDO'}
428 color_index: IntProperty()
430 def invoke(self, context, event):
431 palette_props = context.scene.palette_props
432 palette_props.current_color_index = self.color_index
434 update_panels()
436 return {'FINISHED'}
439 def color_palette_draw(self, context):
440 palette_props = context.scene.palette_props
442 layout = self.layout
444 row = layout.row(align=True)
445 row.menu("PALETTE_MT_menu", text=PALETTE_MT_menu.bl_label)
446 row.operator("palette.preset_add", text="", icon='ADD').remove_active = False
447 row.operator("palette.preset_add", text="", icon='REMOVE').remove_active = True
449 col = layout.column(align=True)
450 row = col.row(align=True)
451 row.operator("palette_props.add_color", icon='ADD')
452 row.prop(palette_props, "index")
453 row.operator("palette_props.remove_color", icon="PANEL_CLOSE")
455 row = col.row(align=True)
456 row.prop(palette_props, "columns")
457 if palette_props.colors.items():
458 layout = col.box()
459 row = layout.row(align=True)
460 row.prop(palette_props, "color_name")
461 row.operator("palette_props.sample_tool_color", icon="COLOR")
463 laycol = layout.column(align=False)
465 if palette_props.columns:
466 columns = palette_props.columns
467 else:
468 columns = 16
470 for i, color in enumerate(palette_props.colors):
471 if not i % columns:
472 row1 = laycol.row(align=True)
473 row1.scale_y = 0.8
474 row2 = laycol.row(align=True)
475 row2.scale_y = 0.8
477 active = True if i == palette_props.current_color_index else False
478 icons = "LAYER_ACTIVE" if active else "LAYER_USED"
479 row1.prop(palette_props.colors[i], "color", event=True, toggle=True)
480 row2.operator("paint.select_color", text=" ",
481 emboss=active, icon=icons).color_index = i
483 layout = self.layout
484 row = layout.row()
485 row.prop(palette_props, "presets_folder", text="")
488 class BrushButtonsPanel():
489 bl_space_type = 'IMAGE_EDITOR'
490 bl_region_type = 'UI'
492 @classmethod
493 def poll(cls, context):
494 sima = context.space_data
495 toolsettings = context.tool_settings.image_paint
496 return sima.show_paint and toolsettings.brush
499 class PaintPanel():
500 bl_space_type = 'VIEW_3D'
501 bl_region_type = 'UI'
502 bl_category = 'Paint'
504 @staticmethod
505 def paint_settings(context):
506 ts = context.tool_settings
508 if context.vertex_paint_object:
509 return ts.vertex_paint
510 elif context.weight_paint_object:
511 return ts.weight_paint
512 elif context.texture_paint_object:
513 return ts.image_paint
514 return None
517 class IMAGE_PT_color_palette(BrushButtonsPanel, Panel):
518 bl_label = "Color Palette"
519 bl_options = {'DEFAULT_CLOSED'}
521 def draw(self, context):
522 color_palette_draw(self, context)
525 class VIEW3D_PT_color_palette(PaintPanel, Panel):
526 bl_label = "Color Palette"
527 bl_options = {'DEFAULT_CLOSED'}
529 @classmethod
530 def poll(cls, context):
531 return (context.image_paint_object or context.vertex_paint_object)
533 def draw(self, context):
534 color_palette_draw(self, context)
537 class VIEW3D_OT_select_weight(Operator):
538 bl_idname = "paint.select_weight"
539 bl_label = ""
540 bl_description = "Select this weight value slot"
541 bl_options = {'UNDO'}
543 weight_index: IntProperty()
545 def current_weight(self):
546 pp = bpy.context.scene.palette_props
547 if self.weight_index == 0:
548 weight = pp.weight_0
549 elif self.weight_index == 1:
550 weight = pp.weight_1
551 elif self.weight_index == 2:
552 weight = pp.weight_2
553 elif self.weight_index == 3:
554 weight = pp.weight_3
555 elif self.weight_index == 4:
556 weight = pp.weight_4
557 elif self.weight_index == 5:
558 weight = pp.weight_5
559 elif self.weight_index == 6:
560 weight = pp.weight_6
561 elif self.weight_index == 7:
562 weight = pp.weight_7
563 elif self.weight_index == 8:
564 weight = pp.weight_8
565 elif self.weight_index == 9:
566 weight = pp.weight_9
567 elif self.weight_index == 10:
568 weight = pp.weight_10
569 return weight
571 def invoke(self, context, event):
572 palette_props = context.scene.palette_props
573 palette_props.current_weight_index = self.weight_index
575 if self.weight_index == 0:
576 weight = palette_props.weight_0
577 elif self.weight_index == 1:
578 weight = palette_props.weight_1
579 elif self.weight_index == 2:
580 weight = palette_props.weight_2
581 elif self.weight_index == 3:
582 weight = palette_props.weight_3
583 elif self.weight_index == 4:
584 weight = palette_props.weight_4
585 elif self.weight_index == 5:
586 weight = palette_props.weight_5
587 elif self.weight_index == 6:
588 weight = palette_props.weight_6
589 elif self.weight_index == 7:
590 weight = palette_props.weight_7
591 elif self.weight_index == 8:
592 weight = palette_props.weight_8
593 elif self.weight_index == 9:
594 weight = palette_props.weight_9
595 elif self.weight_index == 10:
596 weight = palette_props.weight_10
597 palette_props.weight = weight
599 return {'FINISHED'}
602 class VIEW3D_OT_reset_weight_palette(Operator):
603 bl_idname = "paint.reset_weight_palette"
604 bl_label = ""
605 bl_description = "Reset the active Weight slot to it's default value"
607 def execute(self, context):
608 try:
609 palette_props = context.scene.palette_props
610 dict_defs = {
611 0: 0.0, 1: 0.1, 2: 0.25,
612 3: 0.333, 4: 0.4, 5: 0.5,
613 6: 0.6, 7: 0.6666, 8: 0.75,
614 9: 0.9, 10: 1.0
616 current_idx = palette_props.current_weight_index
617 palette_props.weight = dict_defs[current_idx]
619 var_name = "weight_" + str(current_idx)
620 var_to_change = getattr(palette_props, var_name, None)
621 if var_to_change:
622 var_to_change = dict_defs[current_idx]
624 return {'FINISHED'}
626 except Exception as e:
627 self.report({'WARNING'},
628 "Reset Weight palette could not be completed (See Console for more info)")
629 print("\n[Paint Palette]\nOperator: paint.reset_weight_palette\nError: %s\n" % e)
631 return {'CANCELLED'}
634 class VIEW3D_PT_weight_palette(PaintPanel, Panel):
635 bl_label = "Weight Palette"
636 bl_options = {'DEFAULT_CLOSED'}
638 @classmethod
639 def poll(cls, context):
640 return context.weight_paint_object
642 def draw(self, context):
643 palette_props = context.scene.palette_props
645 layout = self.layout
646 row = layout.row()
647 row.prop(palette_props, "weight", slider=True)
648 box = layout.box()
650 selected_weight = palette_props.current_weight_index
651 for props in range(0, 11):
652 embossed = False if props == selected_weight else True
653 prop_name = "weight_" + str(props)
654 prop_value = getattr(palette_props, prop_name, "")
655 if props in (0, 10):
656 row = box.row(align=True)
657 elif (props + 2) % 3 == 0:
658 col = box.column(align=True)
659 row = col.row(align=True)
660 else:
661 if props == 1:
662 row = box.row(align=True)
663 row = row.row(align=True)
665 row.operator("paint.select_weight", text="%.2f" % prop_value,
666 emboss=embossed).weight_index = props
668 row = layout.row()
669 row.operator("paint.reset_weight_palette", text="Reset")
672 class PALETTE_Colors(PropertyGroup):
673 """Class for colors CollectionProperty"""
674 color: FloatVectorProperty(
675 name="",
676 description="",
677 default=(0.8, 0.8, 0.8),
678 min=0, max=1,
679 step=1, precision=3,
680 subtype='COLOR_GAMMA',
681 size=3
685 class PALETTE_Props(PropertyGroup):
687 def update_color_name(self, context):
688 pp = bpy.context.scene.palette_props
689 pp.colors[pp.current_color_index].name = pp.color_name
690 return None
692 def move_color(self, context):
693 pp = bpy.context.scene.palette_props
694 if pp.colors.items() and pp.current_color_index != pp.index:
695 if pp.index >= pp.colors.__len__():
696 pp.index = pp.colors.__len__() - 1
698 pp.colors.move(pp.current_color_index, pp.index)
699 pp.current_color_index = pp.index
700 return None
702 def update_weight(self, context):
703 pp = context.scene.palette_props
704 weight = pp.weight
705 if pp.current_weight_index == 0:
706 pp.weight_0 = weight
707 elif pp.current_weight_index == 1:
708 pp.weight_1 = weight
709 elif pp.current_weight_index == 2:
710 pp.weight_2 = weight
711 elif pp.current_weight_index == 3:
712 pp.weight_3 = weight
713 elif pp.current_weight_index == 4:
714 pp.weight_4 = weight
715 elif pp.current_weight_index == 5:
716 pp.weight_5 = weight
717 elif pp.current_weight_index == 6:
718 pp.weight_6 = weight
719 elif pp.current_weight_index == 7:
720 pp.weight_7 = weight
721 elif pp.current_weight_index == 8:
722 pp.weight_8 = weight
723 elif pp.current_weight_index == 9:
724 pp.weight_9 = weight
725 elif pp.current_weight_index == 10:
726 pp.weight_10 = weight
727 bpy.context.tool_settings.unified_paint_settings.weight = weight
728 return None
730 palette_name: StringProperty(
731 name="Palette Name",
732 default="Preset",
733 subtype='FILE_NAME'
735 color_name: StringProperty(
736 name="",
737 description="Color Name",
738 default="Untitled",
739 update=update_color_name
741 columns: IntProperty(
742 name="Columns",
743 description="Number of Columns",
744 min=0, max=16,
745 default=0
747 index: IntProperty(
748 name="Index",
749 description="Move Selected Color",
750 min=0,
751 update=move_color
753 notes: StringProperty(
754 name="Palette Notes",
755 default="#\n"
757 current_color_index: IntProperty(
758 name="Current Color Index",
759 description="",
760 default=0,
761 min=0
763 current_weight_index: IntProperty(
764 name="Current Color Index",
765 description="",
766 default=10,
767 min=-1
769 presets_folder: StringProperty(name="",
770 description="Palettes Folder",
771 subtype="DIR_PATH",
772 default="//"
774 colors: CollectionProperty(
775 type=PALETTE_Colors
777 weight: FloatProperty(
778 name="Weight",
779 description="Modify the active Weight preset slot value",
780 default=0.0,
781 min=0.0, max=1.0,
782 precision=3,
783 update=update_weight
785 weight_0: FloatProperty(
786 default=0.0,
787 min=0.0, max=1.0,
788 precision=3
790 weight_1: FloatProperty(
791 default=0.1,
792 min=0.0, max=1.0,
793 precision=3
795 weight_2: FloatProperty(
796 default=0.25,
797 min=0.0, max=1.0,
798 precision=3
800 weight_3: FloatProperty(
801 default=0.333,
802 min=0.0, max=1.0,
803 precision=3
805 weight_4: FloatProperty(
806 default=0.4,
807 min=0.0, max=1.0,
808 precision=3
810 weight_5: FloatProperty(
811 default=0.5,
812 min=0.0, max=1.0,
813 precision=3
815 weight_6: FloatProperty(
816 default=0.6,
817 min=0.0, max=1.0,
818 precision=3
820 weight_7: FloatProperty(
821 default=0.6666,
822 min=0.0, max=1.0,
823 precision=3
825 weight_8: FloatProperty(
826 default=0.75,
827 min=0.0, max=1.0,
828 precision=3
830 weight_9: FloatProperty(
831 default=0.9,
832 min=0.0, max=1.0,
833 precision=3
835 weight_10: FloatProperty(
836 default=1.0,
837 min=0.0, max=1.0,
838 precision=3
842 classes = (
843 PALETTE_MT_menu,
844 PALETTE_OT_load_gimp_palette,
845 PALETTE_OT_preset_add,
846 PALETTE_OT_add_color,
847 PALETTE_OT_remove_color,
848 PALETTE_OT_sample_tool_color,
849 IMAGE_OT_select_color,
850 IMAGE_PT_color_palette,
851 VIEW3D_PT_color_palette,
852 VIEW3D_OT_select_weight,
853 VIEW3D_OT_reset_weight_palette,
854 VIEW3D_PT_weight_palette,
855 PALETTE_Colors,
856 PALETTE_Props,
860 def register():
861 for cls in classes:
862 bpy.utils.register_class(cls)
864 bpy.types.Scene.palette_props = PointerProperty(
865 type=PALETTE_Props,
866 name="Palette Props",
867 description=""
871 def unregister():
872 for cls in reversed(classes):
873 bpy.utils.unregister_class(cls)
875 del bpy.types.Scene.palette_props
878 if __name__ == "__main__":
879 register()