Node Wrangler: do not add reroutes to unavailable outputs
[blender-addons.git] / paint_palette.py
blobf438217939f117ad97771d0dffa9c83164afd0c7
1 # SPDX-FileCopyrightText: 2011 Dany Lebel (Axon_D)
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 bl_info = {
6 "name": "Paint Palettes",
7 "author": "Dany Lebel (Axon D)",
8 "version": (0, 9, 4),
9 "blender": (2, 80, 0),
10 "location": "Image Editor and 3D View > Any Paint mode > Color Palette or Weight Palette panel",
11 "description": "Palettes for color and weight paint modes",
12 "warning": "",
13 "doc_url": "{BLENDER_MANUAL_URL}/addons/paint/paint_palettes.html",
14 "category": "Paint",
17 """
18 This add-on brings palettes to the paint modes.
20 * Color Palette for Image Painting, Texture Paint and Vertex Paint modes.
21 * Weight Palette for the Weight Paint mode.
23 Set a number of colors (or weights according to the mode) and then associate it
24 with the brush by using the button under the color.
25 """
27 import bpy
28 from bpy.types import (
29 Operator,
30 Menu,
31 Panel,
32 PropertyGroup,
34 from bpy.props import (
35 BoolProperty,
36 FloatProperty,
37 FloatVectorProperty,
38 IntProperty,
39 StringProperty,
40 PointerProperty,
41 CollectionProperty,
45 def update_panels():
46 pp = bpy.context.scene.palette_props
47 current_color = pp.colors[pp.current_color_index].color
48 pp.color_name = pp.colors[pp.current_color_index].name
49 brush = current_brush()
50 brush.color = current_color
51 pp.index = pp.current_color_index
54 def sample():
55 pp = bpy.context.scene.palette_props
56 current_color = pp.colors[pp.current_color_index]
57 brush = current_brush()
58 current_color.color = brush.color
59 return None
62 def current_brush():
63 context = bpy.context
64 if context.area.type == 'VIEW_3D' and context.vertex_paint_object:
65 brush = context.tool_settings.vertex_paint.brush
66 elif context.area.type == 'VIEW_3D' and context.image_paint_object:
67 brush = context.tool_settings.image_paint.brush
68 elif context.area.type == 'IMAGE_EDITOR' and context.space_data.mode == 'PAINT':
69 brush = context.tool_settings.image_paint.brush
70 else:
71 brush = None
72 return brush
75 def update_weight_value():
76 pp = bpy.context.scene.palette_props
77 tt = bpy.context.tool_settings
78 tt.unified_paint_settings.weight = pp.weight_value
79 return None
82 def check_path_return():
83 from os.path import normpath
84 preset_path = bpy.path.abspath(bpy.context.scene.palette_props.presets_folder)
85 paths = normpath(preset_path)
87 return paths if paths else ""
90 class PALETTE_MT_menu(Menu):
91 bl_label = "Presets"
92 preset_subdir = ""
93 preset_operator = "palette.load_gimp_palette"
95 def path_menu(self, searchpaths, operator, props_default={}):
96 layout = self.layout
97 # hard coded to set the operators 'filepath' to the filename.
98 import os
99 import bpy.utils
101 layout = self.layout
103 if bpy.data.filepath == "":
104 layout.label(text="*Please save the .blend file first*")
105 return
107 if not searchpaths[0]:
108 layout.label(text="* Missing Paths *")
109 return
111 # collect paths
112 files = []
113 for directory in searchpaths:
114 files.extend([(f, os.path.join(directory, f)) for f in os.listdir(directory)])
116 files.sort()
118 for f, filepath in files:
120 if f.startswith("."):
121 continue
122 # do not load everything from the given folder, only .gpl files
123 if f[-4:] != ".gpl":
124 continue
126 preset_name = bpy.path.display_name(f)
127 props = layout.operator(operator, text=preset_name)
129 for attr, value in props_default.items():
130 setattr(props, attr, value)
132 props.filepath = filepath
133 if operator == "palette.load_gimp_palette":
134 props.menu_idname = self.bl_idname
136 def draw_preset(self, context):
137 paths = check_path_return()
138 self.path_menu([paths], self.preset_operator)
140 draw = draw_preset
143 class PALETTE_OT_load_gimp_palette(Operator):
144 """Execute a preset"""
145 bl_idname = "palette.load_gimp_palette"
146 bl_label = "Load a Gimp palette"
148 filepath: StringProperty(
149 name="Path",
150 description="Path of the .gpl file to load",
151 default=""
153 menu_idname: StringProperty(
154 name="Menu ID Name",
155 description="ID name of the menu this was called from",
156 default=""
159 def execute(self, context):
160 from os.path import basename
161 import re
162 filepath = self.filepath
164 palette_props = bpy.context.scene.palette_props
165 palette_props.current_color_index = 0
167 # change the menu title to the most recently chosen option
168 preset_class = getattr(bpy.types, self.menu_idname)
169 preset_class.bl_label = bpy.path.display_name(basename(filepath))
171 palette_props.columns = 0
172 error_palette = False # errors found
173 error_import = [] # collect exception messages
174 start_color_index = 0 # store the starting line for color definitions
176 if filepath[-4:] != ".gpl":
177 error_palette = True
178 else:
179 gpl = open(filepath, "r")
180 lines = gpl.readlines()
181 palette_props.notes = ''
182 has_color = False
183 for index_0, line in enumerate(lines):
184 if not line or (line[:12] == "GIMP Palette"):
185 pass
186 elif line[:5] == "Name:":
187 palette_props.palette_name = line[5:]
188 elif line[:8] == "Columns:":
189 palette_props.columns = int(line[8:])
190 elif line[0] == "#":
191 palette_props.notes += line
192 elif line[0] == "\n":
193 pass
194 else:
195 has_color = True
196 start_color_index = index_0
197 break
198 i = -1
199 if has_color:
200 for i, ln in enumerate(lines[start_color_index:]):
201 try:
202 palette_props.colors[i]
203 except IndexError:
204 palette_props.colors.add()
205 try:
206 # get line - find keywords with re.split, remove the empty ones with filter
207 get_line = list(filter(None, re.split(r'\t+|\s+', ln.rstrip('\n'))))
208 extract_colors = get_line[:3]
209 get_color_name = [str(name) for name in get_line[3:]]
210 color = [float(rgb) / 255 for rgb in extract_colors]
211 palette_props.colors[i].color = color
212 palette_props.colors[i].name = " ".join(get_color_name) or "Color " + str(i)
213 except Exception as e:
214 error_palette = True
215 error_import.append(".gpl file line: {}, error: {}".format(i + 1 + start_color_index, e))
216 pass
218 exceeding = i + 1
219 while palette_props.colors.__len__() > exceeding:
220 palette_props.colors.remove(exceeding)
222 if has_color:
223 update_panels()
224 gpl.close()
225 pass
227 message = "Loaded palette from file: {}".format(filepath)
229 if error_palette:
230 message = "Not supported palette format for file: {}".format(filepath)
231 if error_import:
232 message = "Some of the .gpl palette data can not be parsed. See Console for more info"
233 print("\n[Paint Palette]\nOperator: palette.load_gimp_palette\nErrors: %s\n" %
234 ('\n'.join(error_import)))
236 self.report({'INFO'}, message)
238 return {'FINISHED'}
241 class WriteGimpPalette():
242 """Base preset class, only for subclassing
243 subclasses must define
244 - preset_values
245 - preset_subdir """
246 bl_options = {'REGISTER'} # only because invoke_props_popup requires
248 name: StringProperty(
249 name="Name",
250 description="Name of the preset, used to make the path name",
251 maxlen=64,
252 options={'SKIP_SAVE'},
253 default=""
255 remove_active: BoolProperty(
256 default=False,
257 options={'HIDDEN'}
260 @staticmethod
261 def as_filename(name): # could reuse for other presets
262 for char in " !@#$%^&*(){}:\";'[]<>,.\\/?":
263 name = name.replace(char, '_')
264 return name.lower().strip()
266 def execute(self, context):
267 import os
268 pp = bpy.context.scene.palette_props
270 if hasattr(self, "pre_cb"):
271 self.pre_cb(context)
273 preset_menu_class = getattr(bpy.types, self.preset_menu)
274 target_path = check_path_return()
276 if not target_path:
277 self.report({'WARNING'}, "Failed to create presets path")
278 return {'CANCELLED'}
280 if not os.path.exists(target_path):
281 self.report({'WARNING'},
282 "Failure to open the saved Palettes Folder. Check if the path exists")
283 return {'CANCELLED'}
285 if not self.remove_active:
286 if not self.name:
287 self.report({'INFO'},
288 "No name is given for the preset entry. Operation Cancelled")
289 return {'FINISHED'}
291 filename = self.as_filename(self.name)
292 filepath = os.path.join(target_path, filename) + ".gpl"
293 file_preset = open(filepath, 'wb')
294 gpl = "GIMP Palette\n"
295 gpl += "Name: %s\n" % filename
296 gpl += "Columns: %d\n" % pp.columns
297 gpl += pp.notes
298 if pp.colors.items():
299 for i, color in enumerate(pp.colors):
300 gpl += "%3d%4d%4d %s" % (color.color.r * 255, color.color.g * 255,
301 color.color.b * 255, color.name + '\n')
302 file_preset.write(bytes(gpl, 'UTF-8'))
304 file_preset.close()
306 pp.palette_name = filename
307 preset_menu_class.bl_label = bpy.path.display_name(filename)
309 self.report({'INFO'}, "Created Palette: {}".format(filepath))
311 else:
312 preset_active = preset_menu_class.bl_label
313 filename = self.as_filename(preset_active)
315 filepath = os.path.join(target_path, filename) + ".gpl"
317 if not filepath or not os.path.exists(filepath):
318 self.report({'WARNING'}, "Preset could not be found. Operation Cancelled")
319 self.reset_preset_name(preset_menu_class, pp)
320 return {'CANCELLED'}
322 if hasattr(self, "remove"):
323 self.remove(context, filepath)
324 else:
325 try:
326 os.remove(filepath)
327 self.report({'INFO'}, "Deleted palette: {}".format(filepath))
328 except:
329 import traceback
330 traceback.print_exc()
332 self.reset_preset_name(preset_menu_class, pp)
334 if hasattr(self, "post_cb"):
335 self.post_cb(context)
337 return {'FINISHED'}
339 @staticmethod
340 def reset_preset_name(presets, props):
341 # XXX, still stupid!
342 presets.bl_label = "Presets"
343 props.palette_name = ""
345 def check(self, context):
346 self.name = self.as_filename(self.name)
348 def invoke(self, context, event):
349 if not self.remove_active:
350 wm = context.window_manager
351 return wm.invoke_props_dialog(self)
353 return self.execute(context)
356 class PALETTE_OT_preset_add(WriteGimpPalette, Operator):
357 bl_idname = "palette.preset_add"
358 bl_label = "Add Palette Preset"
359 preset_menu = "PALETTE_MT_menu"
360 bl_description = "Add a Palette Preset"
362 preset_defines = []
363 preset_values = []
364 preset_subdir = "palette"
367 class PALETTE_OT_add_color(Operator):
368 bl_idname = "palette_props.add_color"
369 bl_label = ""
370 bl_description = "Add a Color to the Palette"
372 def execute(self, context):
373 pp = bpy.context.scene.palette_props
374 new_index = 0
375 if pp.colors.items():
376 new_index = pp.current_color_index + 1
377 pp.colors.add()
379 last = pp.colors.__len__() - 1
381 pp.colors.move(last, new_index)
382 pp.current_color_index = new_index
383 sample()
384 update_panels()
386 return {'FINISHED'}
389 class PALETTE_OT_remove_color(Operator):
390 bl_idname = "palette_props.remove_color"
391 bl_label = ""
392 bl_description = "Remove Selected Color"
394 @classmethod
395 def poll(cls, context):
396 pp = bpy.context.scene.palette_props
397 return bool(pp.colors.items())
399 def execute(self, context):
400 pp = context.scene.palette_props
401 i = pp.current_color_index
402 pp.colors.remove(i)
404 if pp.current_color_index >= pp.colors.__len__():
405 pp.index = pp.current_color_index = pp.colors.__len__() - 1
407 return {'FINISHED'}
410 class PALETTE_OT_sample_tool_color(Operator):
411 bl_idname = "palette_props.sample_tool_color"
412 bl_label = ""
413 bl_description = "Sample Tool Color"
415 def execute(self, context):
416 pp = context.scene.palette_props
417 brush = current_brush()
418 pp.colors[pp.current_color_index].color = brush.color
420 return {'FINISHED'}
423 class IMAGE_OT_select_color(Operator):
424 bl_idname = "paint.select_color"
425 bl_label = ""
426 bl_description = "Select this color"
427 bl_options = {'UNDO'}
429 color_index: IntProperty()
431 def invoke(self, context, event):
432 palette_props = context.scene.palette_props
433 palette_props.current_color_index = self.color_index
435 update_panels()
437 return {'FINISHED'}
440 def color_palette_draw(self, context):
441 palette_props = context.scene.palette_props
443 layout = self.layout
445 row = layout.row(align=True)
446 row.menu("PALETTE_MT_menu", text=PALETTE_MT_menu.bl_label)
447 row.operator("palette.preset_add", text="", icon='ADD').remove_active = False
448 row.operator("palette.preset_add", text="", icon='REMOVE').remove_active = True
450 col = layout.column(align=True)
451 row = col.row(align=True)
452 row.operator("palette_props.add_color", icon='ADD')
453 row.prop(palette_props, "index")
454 row.operator("palette_props.remove_color", icon="PANEL_CLOSE")
456 row = col.row(align=True)
457 row.prop(palette_props, "columns")
458 if palette_props.colors.items():
459 layout = col.box()
460 row = layout.row(align=True)
461 row.prop(palette_props, "color_name")
462 row.operator("palette_props.sample_tool_color", icon="COLOR")
464 laycol = layout.column(align=False)
466 if palette_props.columns:
467 columns = palette_props.columns
468 else:
469 columns = 16
471 for i, color in enumerate(palette_props.colors):
472 if not i % columns:
473 row1 = laycol.row(align=True)
474 row1.scale_y = 0.8
475 row2 = laycol.row(align=True)
476 row2.scale_y = 0.8
478 active = True if i == palette_props.current_color_index else False
479 icons = "LAYER_ACTIVE" if active else "LAYER_USED"
480 row1.prop(palette_props.colors[i], "color", event=True, toggle=True)
481 row2.operator("paint.select_color", text=" ",
482 emboss=active, icon=icons).color_index = i
484 layout = self.layout
485 row = layout.row()
486 row.prop(palette_props, "presets_folder", text="")
489 class BrushButtonsPanel():
490 bl_space_type = 'IMAGE_EDITOR'
491 bl_region_type = 'UI'
493 @classmethod
494 def poll(cls, context):
495 sima = context.space_data
496 toolsettings = context.tool_settings.image_paint
497 return sima.show_paint and toolsettings.brush
500 class PaintPanel():
501 bl_space_type = 'VIEW_3D'
502 bl_region_type = 'UI'
503 bl_category = 'Paint'
505 @staticmethod
506 def paint_settings(context):
507 ts = context.tool_settings
509 if context.vertex_paint_object:
510 return ts.vertex_paint
511 elif context.weight_paint_object:
512 return ts.weight_paint
513 elif context.texture_paint_object:
514 return ts.image_paint
515 return None
518 class IMAGE_PT_color_palette(BrushButtonsPanel, Panel):
519 bl_label = "Color Palette"
520 bl_options = {'DEFAULT_CLOSED'}
522 def draw(self, context):
523 color_palette_draw(self, context)
526 class VIEW3D_PT_color_palette(PaintPanel, Panel):
527 bl_label = "Color Palette"
528 bl_options = {'DEFAULT_CLOSED'}
530 @classmethod
531 def poll(cls, context):
532 return (context.image_paint_object or context.vertex_paint_object)
534 def draw(self, context):
535 color_palette_draw(self, context)
538 class VIEW3D_OT_select_weight(Operator):
539 bl_idname = "paint.select_weight"
540 bl_label = ""
541 bl_description = "Select this weight value slot"
542 bl_options = {'UNDO'}
544 weight_index: IntProperty()
546 def current_weight(self):
547 pp = bpy.context.scene.palette_props
548 if self.weight_index == 0:
549 weight = pp.weight_0
550 elif self.weight_index == 1:
551 weight = pp.weight_1
552 elif self.weight_index == 2:
553 weight = pp.weight_2
554 elif self.weight_index == 3:
555 weight = pp.weight_3
556 elif self.weight_index == 4:
557 weight = pp.weight_4
558 elif self.weight_index == 5:
559 weight = pp.weight_5
560 elif self.weight_index == 6:
561 weight = pp.weight_6
562 elif self.weight_index == 7:
563 weight = pp.weight_7
564 elif self.weight_index == 8:
565 weight = pp.weight_8
566 elif self.weight_index == 9:
567 weight = pp.weight_9
568 elif self.weight_index == 10:
569 weight = pp.weight_10
570 return weight
572 def invoke(self, context, event):
573 palette_props = context.scene.palette_props
574 palette_props.current_weight_index = self.weight_index
576 if self.weight_index == 0:
577 weight = palette_props.weight_0
578 elif self.weight_index == 1:
579 weight = palette_props.weight_1
580 elif self.weight_index == 2:
581 weight = palette_props.weight_2
582 elif self.weight_index == 3:
583 weight = palette_props.weight_3
584 elif self.weight_index == 4:
585 weight = palette_props.weight_4
586 elif self.weight_index == 5:
587 weight = palette_props.weight_5
588 elif self.weight_index == 6:
589 weight = palette_props.weight_6
590 elif self.weight_index == 7:
591 weight = palette_props.weight_7
592 elif self.weight_index == 8:
593 weight = palette_props.weight_8
594 elif self.weight_index == 9:
595 weight = palette_props.weight_9
596 elif self.weight_index == 10:
597 weight = palette_props.weight_10
598 palette_props.weight = weight
600 return {'FINISHED'}
603 class VIEW3D_OT_reset_weight_palette(Operator):
604 bl_idname = "paint.reset_weight_palette"
605 bl_label = ""
606 bl_description = "Reset the active Weight slot to it's default value"
608 def execute(self, context):
609 try:
610 palette_props = context.scene.palette_props
611 dict_defs = {
612 0: 0.0, 1: 0.1, 2: 0.25,
613 3: 0.333, 4: 0.4, 5: 0.5,
614 6: 0.6, 7: 0.6666, 8: 0.75,
615 9: 0.9, 10: 1.0
617 current_idx = palette_props.current_weight_index
618 palette_props.weight = dict_defs[current_idx]
620 var_name = "weight_" + str(current_idx)
621 var_to_change = getattr(palette_props, var_name, None)
622 if var_to_change:
623 var_to_change = dict_defs[current_idx]
625 return {'FINISHED'}
627 except Exception as e:
628 self.report({'WARNING'},
629 "Reset Weight palette could not be completed (See Console for more info)")
630 print("\n[Paint Palette]\nOperator: paint.reset_weight_palette\nError: %s\n" % e)
632 return {'CANCELLED'}
635 class VIEW3D_PT_weight_palette(PaintPanel, Panel):
636 bl_label = "Weight Palette"
637 bl_options = {'DEFAULT_CLOSED'}
639 @classmethod
640 def poll(cls, context):
641 return context.weight_paint_object
643 def draw(self, context):
644 palette_props = context.scene.palette_props
646 layout = self.layout
647 row = layout.row()
648 row.prop(palette_props, "weight", slider=True)
649 box = layout.box()
651 selected_weight = palette_props.current_weight_index
652 for props in range(0, 11):
653 embossed = False if props == selected_weight else True
654 prop_name = "weight_" + str(props)
655 prop_value = getattr(palette_props, prop_name, "")
656 if props in (0, 10):
657 row = box.row(align=True)
658 elif (props + 2) % 3 == 0:
659 col = box.column(align=True)
660 row = col.row(align=True)
661 else:
662 if props == 1:
663 row = box.row(align=True)
664 row = row.row(align=True)
666 row.operator("paint.select_weight", text="%.2f" % prop_value,
667 emboss=embossed).weight_index = props
669 row = layout.row()
670 row.operator("paint.reset_weight_palette", text="Reset")
673 class PALETTE_Colors(PropertyGroup):
674 """Class for colors CollectionProperty"""
675 color: FloatVectorProperty(
676 name="",
677 description="",
678 default=(0.8, 0.8, 0.8),
679 min=0, max=1,
680 step=1, precision=3,
681 subtype='COLOR_GAMMA',
682 size=3
686 class PALETTE_Props(PropertyGroup):
688 def update_color_name(self, context):
689 pp = bpy.context.scene.palette_props
690 pp.colors[pp.current_color_index].name = pp.color_name
691 return None
693 def move_color(self, context):
694 pp = bpy.context.scene.palette_props
695 if pp.colors.items() and pp.current_color_index != pp.index:
696 if pp.index >= pp.colors.__len__():
697 pp.index = pp.colors.__len__() - 1
699 pp.colors.move(pp.current_color_index, pp.index)
700 pp.current_color_index = pp.index
701 return None
703 def update_weight(self, context):
704 pp = context.scene.palette_props
705 weight = pp.weight
706 if pp.current_weight_index == 0:
707 pp.weight_0 = weight
708 elif pp.current_weight_index == 1:
709 pp.weight_1 = weight
710 elif pp.current_weight_index == 2:
711 pp.weight_2 = weight
712 elif pp.current_weight_index == 3:
713 pp.weight_3 = weight
714 elif pp.current_weight_index == 4:
715 pp.weight_4 = weight
716 elif pp.current_weight_index == 5:
717 pp.weight_5 = weight
718 elif pp.current_weight_index == 6:
719 pp.weight_6 = weight
720 elif pp.current_weight_index == 7:
721 pp.weight_7 = weight
722 elif pp.current_weight_index == 8:
723 pp.weight_8 = weight
724 elif pp.current_weight_index == 9:
725 pp.weight_9 = weight
726 elif pp.current_weight_index == 10:
727 pp.weight_10 = weight
728 bpy.context.tool_settings.unified_paint_settings.weight = weight
729 return None
731 palette_name: StringProperty(
732 name="Palette Name",
733 default="Preset",
734 subtype='FILE_NAME'
736 color_name: StringProperty(
737 name="",
738 description="Color Name",
739 default="Untitled",
740 update=update_color_name
742 columns: IntProperty(
743 name="Columns",
744 description="Number of Columns",
745 min=0, max=16,
746 default=0
748 index: IntProperty(
749 name="Index",
750 description="Move Selected Color",
751 min=0,
752 update=move_color
754 notes: StringProperty(
755 name="Palette Notes",
756 default="#\n"
758 current_color_index: IntProperty(
759 name="Current Color Index",
760 description="",
761 default=0,
762 min=0
764 current_weight_index: IntProperty(
765 name="Current Color Index",
766 description="",
767 default=10,
768 min=-1
770 presets_folder: StringProperty(name="",
771 description="Palettes Folder",
772 subtype="DIR_PATH",
773 default="//"
775 colors: CollectionProperty(
776 type=PALETTE_Colors
778 weight: FloatProperty(
779 name="Weight",
780 description="Modify the active Weight preset slot value",
781 default=0.0,
782 min=0.0, max=1.0,
783 precision=3,
784 update=update_weight
786 weight_0: FloatProperty(
787 default=0.0,
788 min=0.0, max=1.0,
789 precision=3
791 weight_1: FloatProperty(
792 default=0.1,
793 min=0.0, max=1.0,
794 precision=3
796 weight_2: FloatProperty(
797 default=0.25,
798 min=0.0, max=1.0,
799 precision=3
801 weight_3: FloatProperty(
802 default=0.333,
803 min=0.0, max=1.0,
804 precision=3
806 weight_4: FloatProperty(
807 default=0.4,
808 min=0.0, max=1.0,
809 precision=3
811 weight_5: FloatProperty(
812 default=0.5,
813 min=0.0, max=1.0,
814 precision=3
816 weight_6: FloatProperty(
817 default=0.6,
818 min=0.0, max=1.0,
819 precision=3
821 weight_7: FloatProperty(
822 default=0.6666,
823 min=0.0, max=1.0,
824 precision=3
826 weight_8: FloatProperty(
827 default=0.75,
828 min=0.0, max=1.0,
829 precision=3
831 weight_9: FloatProperty(
832 default=0.9,
833 min=0.0, max=1.0,
834 precision=3
836 weight_10: FloatProperty(
837 default=1.0,
838 min=0.0, max=1.0,
839 precision=3
843 classes = (
844 PALETTE_MT_menu,
845 PALETTE_OT_load_gimp_palette,
846 PALETTE_OT_preset_add,
847 PALETTE_OT_add_color,
848 PALETTE_OT_remove_color,
849 PALETTE_OT_sample_tool_color,
850 IMAGE_OT_select_color,
851 IMAGE_PT_color_palette,
852 VIEW3D_PT_color_palette,
853 VIEW3D_OT_select_weight,
854 VIEW3D_OT_reset_weight_palette,
855 VIEW3D_PT_weight_palette,
856 PALETTE_Colors,
857 PALETTE_Props,
861 def register():
862 for cls in classes:
863 bpy.utils.register_class(cls)
865 bpy.types.Scene.palette_props = PointerProperty(
866 type=PALETTE_Props,
867 name="Palette Props",
868 description=""
872 def unregister():
873 for cls in reversed(classes):
874 bpy.utils.unregister_class(cls)
876 del bpy.types.Scene.palette_props
879 if __name__ == "__main__":
880 register()