Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCampbell Barton <ideasman42@gmail.com>2019-02-28 08:34:05 +0300
committerCampbell Barton <ideasman42@gmail.com>2019-02-28 08:42:06 +0300
commit5e6515149f47b2aae9a356eb2744170d8b3acbb8 (patch)
tree6d0cee2d032dbb4a35868f0fc15184a251a30d3b /object_color_rules.py
parentaaf97075b4ee65eeb22f9832695cb716f1319ab3 (diff)
object_color_rules: update for 2.8x
Add to add-ons since color objects/wireframe is now supported.
Diffstat (limited to 'object_color_rules.py')
-rw-r--r--object_color_rules.py478
1 files changed, 478 insertions, 0 deletions
diff --git a/object_color_rules.py b/object_color_rules.py
new file mode 100644
index 00000000..dfa835aa
--- /dev/null
+++ b/object_color_rules.py
@@ -0,0 +1,478 @@
+# ***** 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": "Object Color Rules",
+ "author": "Campbell Barton",
+ "version": (0, 0, 2),
+ "blender": (2, 80, 0),
+ "location": "Properties > Object Buttons",
+ "description": "Rules for assigning object color (for object & wireframe colors).",
+ "category": "Object",
+}
+
+
+def test_name(rule, needle, haystack, cache):
+ if rule.use_match_regex:
+ if not cache:
+ import re
+ re_needle = re.compile(needle)
+ cache[:] = [re_needle]
+ else:
+ re_needle = cache[0]
+ return (re_needle.match(haystack) is not None)
+ else:
+ return (needle in haystack)
+
+
+class rule_test:
+ __slots__ = ()
+
+ def __new__(cls, *args, **kwargs):
+ raise RuntimeError("%s should not be instantiated" % cls)
+
+ @staticmethod
+ def NAME(obj, rule, cache):
+ match_name = rule.match_name
+ return test_name(rule, match_name, obj.name, cache)
+
+ def DATA(obj, rule, cache):
+ match_name = rule.match_name
+ obj_data = obj.data
+ if obj_data is not None:
+ return test_name(rule, match_name, obj_data.name, cache)
+ else:
+ return False
+
+ @staticmethod
+ def COLLECTION(obj, rule, cache):
+ if not cache:
+ match_name = rule.match_name
+ objects = {o for g in bpy.data.collections if test_name(rule, match_name, g.name, cache) for o in g.objects}
+ cache["objects"] = objects
+ else:
+ objects = cache["objects"]
+
+ return obj in objects
+
+ @staticmethod
+ def MATERIAL(obj, rule, cache):
+ match_name = rule.match_name
+ materials = getattr(obj.data, "materials", None)
+
+ return ((materials is not None) and
+ (any((test_name(rule, match_name, m.name) for m in materials if m is not None))))
+
+ @staticmethod
+ def TYPE(obj, rule, cache):
+ return (obj.type == rule.match_object_type)
+
+ @staticmethod
+ def EXPR(obj, rule, cache):
+ if not cache:
+ match_expr = rule.match_expr
+ expr = compile(match_expr, rule.name, 'eval')
+
+ namespace = {}
+ namespace.update(__import__("math").__dict__)
+
+ cache["expr"] = expr
+ cache["namespace"] = namespace
+ else:
+ expr = cache["expr"]
+ namespace = cache["namespace"]
+
+ try:
+ return bool(eval(expr, {}, {"self": obj}))
+ except:
+ import traceback
+ traceback.print_exc()
+ return False
+
+
+class rule_draw:
+ __slots__ = ()
+
+ def __new__(cls, *args, **kwargs):
+ raise RuntimeError("%s should not be instantiated" % cls)
+
+ @staticmethod
+ def _generic_match_name(layout, rule):
+ layout.label(text="Match Name:")
+ row = layout.row(align=True)
+ row.prop(rule, "match_name", text="")
+ row.prop(rule, "use_match_regex", text="", icon='SORTALPHA')
+
+ @staticmethod
+ def NAME(layout, rule):
+ rule_draw._generic_match_name(layout, rule)
+
+ @staticmethod
+ def DATA(layout, rule):
+ rule_draw._generic_match_name(layout, rule)
+
+ @staticmethod
+ def COLLECTION(layout, rule):
+ rule_draw._generic_match_name(layout, rule)
+
+ @staticmethod
+ def MATERIAL(layout, rule):
+ rule_draw._generic_match_name(layout, rule)
+
+ @staticmethod
+ def TYPE(layout, rule):
+ row = layout.row()
+ row.prop(rule, "match_object_type")
+
+ @staticmethod
+ def EXPR(layout, rule):
+ col = layout.column()
+ col.label(text="Scripted Expression:")
+ col.prop(rule, "match_expr", text="")
+
+
+def object_colors_calc(rules, objects):
+ from mathutils import Color
+
+ rules_cb = [getattr(rule_test, rule.type) for rule in rules]
+ rules_blend = [(1.0 - rule.factor, rule.factor) for rule in rules]
+ rules_color = [Color(rule.color) for rule in rules]
+ rules_cache = [{} for i in range(len(rules))]
+ rules_inv = [rule.use_invert for rule in rules]
+ changed_count = 0
+
+ for obj in objects:
+ is_set = False
+ obj_color = Color(obj.color[0:3])
+
+ for (rule, test_cb, color, blend, cache, use_invert) \
+ in zip(rules, rules_cb, rules_color, rules_blend, rules_cache, rules_inv):
+
+ if test_cb(obj, rule, cache) is not use_invert:
+ if is_set is False:
+ obj_color = color
+ else:
+ # prevent mixing colors loosing saturation
+ obj_color_s = obj_color.s
+ obj_color = (obj_color * blend[0]) + (color * blend[1])
+ obj_color.s = (obj_color_s * blend[0]) + (color.s * blend[1])
+
+ is_set = True
+
+ if is_set:
+ obj.color[0:3] = obj_color
+ changed_count += 1
+ return changed_count
+
+
+def object_colors_select(rule, objects):
+ cache = {}
+
+ rule_type = rule.type
+ test_cb = getattr(rule_test, rule_type)
+
+ for obj in objects:
+ obj.select = test_cb(obj, rule, cache)
+
+
+def object_colors_rule_validate(rule, report):
+ rule_type = rule.type
+
+ if rule_type in {'NAME', 'DATA', 'COLLECTION', 'MATERIAL'}:
+ if rule.use_match_regex:
+ import re
+ try:
+ re.compile(rule.match_name)
+ except Exception as e:
+ report({'ERROR'}, "Rule %r: %s" % (rule.name, str(e)))
+ return False
+
+ elif rule_type == 'EXPR':
+ try:
+ compile(rule.match_expr, rule.name, 'eval')
+ except Exception as e:
+ report({'ERROR'}, "Rule %r: %s" % (rule.name, str(e)))
+ return False
+
+ return True
+
+
+
+import bpy
+from bpy.types import (
+ Operator,
+ Panel,
+ UIList,
+)
+from bpy.props import (
+ StringProperty,
+ BoolProperty,
+ IntProperty,
+ FloatProperty,
+ EnumProperty,
+ CollectionProperty,
+ BoolVectorProperty,
+ FloatVectorProperty,
+)
+
+
+class OBJECT_PT_color_rules(Panel):
+ bl_label = "Color Rules"
+ bl_space_type = 'PROPERTIES'
+ bl_region_type = 'WINDOW'
+ bl_context = "object"
+
+ def draw(self, context):
+ layout = self.layout
+
+ scene = context.scene
+
+ # Rig type list
+ row = layout.row()
+ row.template_list(
+ "OBJECT_UL_color_rule", "color_rules",
+ scene, "color_rules",
+ scene, "color_rules_active_index",
+ )
+
+ col = row.column()
+ colsub = col.column(align=True)
+ colsub.operator("object.color_rules_add", icon='ADD', text="")
+ colsub.operator("object.color_rules_remove", icon='REMOVE', text="")
+
+ colsub = col.column(align=True)
+ colsub.operator("object.color_rules_move", text="", icon='TRIA_UP').direction = -1
+ colsub.operator("object.color_rules_move", text="", icon='TRIA_DOWN').direction = 1
+
+ colsub = col.column(align=True)
+ colsub.operator("object.color_rules_select", text="", icon='RESTRICT_SELECT_OFF')
+
+ if scene.color_rules:
+ index = scene.color_rules_active_index
+ rule = scene.color_rules[index]
+
+ box = layout.box()
+ row = box.row(align=True)
+ row.prop(rule, "name", text="")
+ row.prop(rule, "type", text="")
+ row.prop(rule, "use_invert", text="", icon='ARROW_LEFTRIGHT')
+
+ draw_cb = getattr(rule_draw, rule.type)
+ draw_cb(box, rule)
+
+ row = layout.split(factor=0.75, align=True)
+ props = row.operator("object.color_rules_assign", text="Assign Selected")
+ props.use_selection = True
+ props = row.operator("object.color_rules_assign", text="All")
+ props.use_selection = False
+
+
+class OBJECT_UL_color_rule(UIList):
+ def draw_item(self, context, layout, data, rule, icon, active_data, active_propname, index):
+ # assert(isinstance(rule, bpy.types.ShapeKey))
+ # scene = active_data
+ split = layout.split(factor=0.5)
+ row = split.split(align=False)
+ row.label(text="%s (%s)" % (rule.name, rule.type.lower()))
+ split = split.split(factor=0.7)
+ split.prop(rule, "factor", text="", emboss=False)
+ split.prop(rule, "color", text="")
+
+
+class OBJECT_OT_color_rules_assign(Operator):
+ """Assign colors to objects based on user rules"""
+ bl_idname = "object.color_rules_assign"
+ bl_label = "Assign Colors"
+ bl_options = {'UNDO'}
+
+ use_selection: BoolProperty(
+ name="Selected",
+ description="Apply to selected (otherwise all objects in the scene)",
+ default=True,
+ )
+
+ def execute(self, context):
+ scene = context.scene
+
+ if self.use_selection:
+ objects = context.selected_editable_objects
+ else:
+ objects = scene.objects
+
+ rules = scene.color_rules[:]
+ for rule in rules:
+ if not object_colors_rule_validate(rule, self.report):
+ return {'CANCELLED'}
+
+ changed_count = object_colors_calc(rules, objects)
+ self.report({'INFO'}, "Set colors for {} of {} objects".format(changed_count, len(objects)))
+ return {'FINISHED'}
+
+
+class OBJECT_OT_color_rules_select(Operator):
+ """Select objects matching the current rule"""
+ bl_idname = "object.color_rules_select"
+ bl_label = "Select Rule"
+ bl_options = {'UNDO'}
+
+ def execute(self, context):
+ scene = context.scene
+ rule = scene.color_rules[scene.color_rules_active_index]
+
+ if not object_colors_rule_validate(rule, self.report):
+ return {'CANCELLED'}
+
+ objects = context.visible_objects
+ object_colors_select(rule, objects)
+ return {'FINISHED'}
+
+
+class OBJECT_OT_color_rules_add(Operator):
+ bl_idname = "object.color_rules_add"
+ bl_label = "Add Color Layer"
+ bl_options = {'UNDO'}
+
+ def execute(self, context):
+ scene = context.scene
+ rules = scene.color_rules
+ rule = rules.add()
+ rule.name = "Rule.%.3d" % len(rules)
+ scene.color_rules_active_index = len(rules) - 1
+ return {'FINISHED'}
+
+
+class OBJECT_OT_color_rules_remove(Operator):
+ bl_idname = "object.color_rules_remove"
+ bl_label = "Remove Color Layer"
+ bl_options = {'UNDO'}
+
+ def execute(self, context):
+ scene = context.scene
+ rules = scene.color_rules
+ rules.remove(scene.color_rules_active_index)
+ if scene.color_rules_active_index > len(rules) - 1:
+ scene.color_rules_active_index = len(rules) - 1
+ return {'FINISHED'}
+
+
+class OBJECT_OT_color_rules_move(Operator):
+ bl_idname = "object.color_rules_move"
+ bl_label = "Remove Color Layer"
+ bl_options = {'UNDO'}
+ direction: IntProperty()
+
+ def execute(self, context):
+ scene = context.scene
+ rules = scene.color_rules
+ index = scene.color_rules_active_index
+ index_new = index + self.direction
+ if index_new < len(rules) and index_new >= 0:
+ rules.move(index, index_new)
+ scene.color_rules_active_index = index_new
+ return {'FINISHED'}
+ else:
+ return {'CANCELLED'}
+
+
+class ColorRule(bpy.types.PropertyGroup):
+ name: StringProperty(
+ name="Rule Name",
+ )
+ color: FloatVectorProperty(
+ name="Color",
+ description="Color to assign",
+ subtype='COLOR', size=3, min=0, max=1, precision=3, step=0.1,
+ default=(0.5, 0.5, 0.5),
+ )
+ factor: FloatProperty(
+ name="Opacity",
+ description="Color to assign",
+ min=0, max=1, precision=1, step=0.1,
+ default=1.0,
+ )
+ type: EnumProperty(
+ name="Rule Type",
+ items=(
+ ('NAME', "Name", "Object name contains this text (or matches regex)"),
+ ('DATA', "Data Name", "Object data name contains this text (or matches regex)"),
+ ('COLLECTION', "Collection Name", "Object in collection that contains this text (or matches regex)"),
+ ('MATERIAL', "Material Name", "Object uses a material name that contains this text (or matches regex)"),
+ ('TYPE', "Type", "Object type"),
+ ('EXPR', "Expression", (
+ "Scripted expression (using 'self' for the object) eg:\n"
+ " self.type == 'MESH' and len(self.data.vertices) > 20"
+ )
+ ),
+ ),
+ )
+
+ use_invert: BoolProperty(
+ name="Invert",
+ description="Match when the rule isn't met",
+ )
+
+ # ------------------
+ # Matching Variables
+
+ # shared by all name matching
+ match_name: StringProperty(
+ name="Match Name",
+ )
+ use_match_regex: BoolProperty(
+ name="Regex",
+ description="Use regular expressions for pattern matching",
+ )
+ # type == 'TYPE'
+ match_object_type: EnumProperty(
+ name="Object Type",
+ items=([(i.identifier, i.name, "")
+ for i in bpy.types.Object.bl_rna.properties['type'].enum_items]
+ )
+ )
+ # type == 'EXPR'
+ match_expr: StringProperty(
+ name="Expression",
+ description="Python expression, where 'self' is the object variable"
+ )
+
+
+classes = (
+ OBJECT_PT_color_rules,
+ OBJECT_OT_color_rules_add,
+ OBJECT_OT_color_rules_remove,
+ OBJECT_OT_color_rules_move,
+ OBJECT_OT_color_rules_assign,
+ OBJECT_OT_color_rules_select,
+ OBJECT_UL_color_rule,
+ ColorRule,
+)
+
+
+def register():
+ for cls in classes:
+ bpy.utils.register_class(cls)
+
+ bpy.types.Scene.color_rules = CollectionProperty(type=ColorRule)
+ bpy.types.Scene.color_rules_active_index = IntProperty()
+
+
+def unregister():
+ for cls in classes:
+ bpy.utils.unregister_class(cls)
+
+ del bpy.types.Scene.color_rules