From 40a7e4b8ba097a73ab000eea49eb8230048d1fd0 Mon Sep 17 00:00:00 2001 From: Michel Fedde <35878897+Neintonine@users.noreply.github.com> Date: Thu, 1 Feb 2024 00:48:05 +0100 Subject: [PATCH] adds id editor --- docu/planned_features.md | 6 ++ src/__init__.py | 38 +++++--- src/operators/bake_to_id_map.py | 3 - src/operators/create_id_mask.py | 38 ++++++++ src/operators/id_editor_create_id.py | 33 +++++++ src/operators/id_editor_paint.py | 38 ++++++++ src/operators/id_editor_remove_id.py | 41 +++++++++ src/panels/{panel.py => bake_panel.py} | 2 +- .../{panel_info.py => bake_panel_info.py} | 0 ...panel_options.py => bake_panel_options.py} | 0 src/panels/id_mask_editor.py | 90 +++++++++++++++++++ src/panels/id_mask_editor_id_list.py | 17 ++++ .../{bake_to_id.py => bake_properties.py} | 0 src/properties/id_mask_editor_properties.py | 58 ++++++++++++ .../id_mask_editor_value_properties.py | 17 ++++ src/types/targets/vertex_colors.py | 4 - 16 files changed, 367 insertions(+), 18 deletions(-) create mode 100644 docu/planned_features.md create mode 100644 src/operators/create_id_mask.py create mode 100644 src/operators/id_editor_create_id.py create mode 100644 src/operators/id_editor_paint.py create mode 100644 src/operators/id_editor_remove_id.py rename src/panels/{panel.py => bake_panel.py} (96%) rename src/panels/{panel_info.py => bake_panel_info.py} (100%) rename src/panels/{panel_options.py => bake_panel_options.py} (100%) create mode 100644 src/panels/id_mask_editor.py create mode 100644 src/panels/id_mask_editor_id_list.py rename src/properties/{bake_to_id.py => bake_properties.py} (100%) create mode 100644 src/properties/id_mask_editor_properties.py create mode 100644 src/properties/id_mask_editor_value_properties.py diff --git a/docu/planned_features.md b/docu/planned_features.md new file mode 100644 index 0000000..547bd7a --- /dev/null +++ b/docu/planned_features.md @@ -0,0 +1,6 @@ +## Planned features + +### ID Mask - Editor +- [ ] A way to automatically get already used colors, f.E. you already did it by hand +- [ ] Select by color +- [ ] Update after color change \ No newline at end of file diff --git a/src/__init__.py b/src/__init__.py index a62002d..d04dfa1 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -1,5 +1,22 @@ +import bpy + +from src.operators.create_id_mask import CreateIDMaskOperator +from src.operators.id_editor_create_id import IDEDITOR_CreateIDOperator +from src.operators.id_editor_paint import IDEDITOR_PaintIDMaskOperator +from src.operators.id_editor_remove_id import IDEDITOR_RemoveIDOperator +from src.panels.id_mask_editor_id_list import IDMaskEditorIDList +from src.properties.id_mask_editor_value_properties import IDMaskEditorValueProperties +from . panels.bake_panel_options import BakeToIDOptionsPanel +from . operators.bake_to_id_map import BakeToIDMapOperator +from . panels.bake_panel import BakeToIDMapPanel +from . panels.bake_panel_info import BakeToIDInfoPanel +from . panels.id_mask_editor import IDMaskEditorPanel +from . properties.bake_properties import BakeToIDProperties + +from src.properties.id_mask_editor_properties import IDMaskEditorProperties + bl_info = { - "name": "Bake ID Mask", + "name": "ID Mask - Tools", "author": "iedSoftworks", "description": "", # !VERSION @@ -7,20 +24,21 @@ bl_info = { "category": "Object" } -import bpy - -from . panels.panel_options import BakeToIDOptionsPanel -from . operators.bake_to_id_map import BakeToIDMapOperator -from . panels.panel import BakeToIDMapPanel -from . panels.panel_info import BakeToIDInfoPanel -from . properties.bake_to_id import BakeToIDProperties - classes = ( BakeToIDMapOperator, BakeToIDMapPanel, BakeToIDInfoPanel, BakeToIDOptionsPanel, BakeToIDProperties, + + IDMaskEditorValueProperties, + IDMaskEditorProperties, + IDMaskEditorPanel, + CreateIDMaskOperator, + IDMaskEditorIDList, + IDEDITOR_CreateIDOperator, + IDEDITOR_RemoveIDOperator, + IDEDITOR_PaintIDMaskOperator ) @@ -29,7 +47,7 @@ def register(): bpy.utils.register_class(cls) setattr(bpy.types.Scene, 'bake_to_id_props', bpy.props.PointerProperty(type=BakeToIDProperties)) - + setattr(bpy.types.Mesh, 'id_mask_editor_properties', bpy.props.PointerProperty(type=IDMaskEditorProperties)) def unregister(): for cls in reversed(classes): diff --git a/src/operators/bake_to_id_map.py b/src/operators/bake_to_id_map.py index 8446d06..6f58564 100644 --- a/src/operators/bake_to_id_map.py +++ b/src/operators/bake_to_id_map.py @@ -1,6 +1,3 @@ -import colorsys -import math - import bpy from ..types.colors import get_color diff --git a/src/operators/create_id_mask.py b/src/operators/create_id_mask.py new file mode 100644 index 0000000..de8652d --- /dev/null +++ b/src/operators/create_id_mask.py @@ -0,0 +1,38 @@ +import bpy + + +class CreateIDMaskOperator(bpy.types.Operator): + bl_idname = "id_mask_editor.create_id_mask_attribute" + bl_label = "" + bl_options = {'INTERNAL'} + + @classmethod + def poll(cls, context): + + obj = context.active_object + if obj.type != 'MESH': + return False + + if not obj.data: + return False + + mesh = obj.data + return 'ID_MASK' not in mesh.color_attributes + + def execute(self, context): + obj = context.active_object + if obj.type != 'MESH': + return {'FINISHED'} + + if not obj.data: + return {'FINISHED'} + + mesh = obj.data + + if 'ID_MASK' in mesh.color_attributes: + return {'FINISHED'} + + bpy.ops.geometry.color_attribute_add(name='ID_MASK', data_type='FLOAT_COLOR', domain='CORNER') + mesh.id_mask_editor_properties.target_attribute = 'ID_MASK' + + return {'FINISHED'} diff --git a/src/operators/id_editor_create_id.py b/src/operators/id_editor_create_id.py new file mode 100644 index 0000000..1e26fc3 --- /dev/null +++ b/src/operators/id_editor_create_id.py @@ -0,0 +1,33 @@ +import bpy + +from src.properties.id_mask_editor_value_properties import IDMaskEditorValueProperties +from src.types.colors import get_color + + +class IDEDITOR_CreateIDOperator(bpy.types.Operator): + bl_idname = "id_mask_editor.create_id_mask" + bl_label = "id_mask_editor.create_id_mask" + + bl_options = {'INTERNAL'} + + def execute(self, context): + obj = context.active_object + if obj.type != 'MESH': + return {'FINISHED'} + + if not obj.data: + return {'FINISHED'} + + mesh = obj.data + properties = mesh.id_mask_editor_properties + collection = properties.possible_ids + new_id = collection.add() + + color = get_color(properties.colors) + colors = color.get_colors(properties) + colorCount = len(colors) + current_id_color = colors[properties.current_color_id % colorCount] + new_id.color = current_id_color + properties.current_color_id += 1 + + return {'FINISHED'} diff --git a/src/operators/id_editor_paint.py b/src/operators/id_editor_paint.py new file mode 100644 index 0000000..a8c97b5 --- /dev/null +++ b/src/operators/id_editor_paint.py @@ -0,0 +1,38 @@ +import bpy + +class IDEDITOR_PaintIDMaskOperator(bpy.types.Operator): + bl_idname = "id_mask_editor.paint_id_mask" + bl_label = "Paint" + bl_options = {'INTERNAL'} + + def execute(self, context): + obj = context.active_object + if obj.type != 'MESH': + return {'FINISHED'} + + if not obj.data: + return {'FINISHED'} + + old_mode = bpy.context.object.mode + if old_mode != "OBJECT": + bpy.ops.object.mode_set(mode='OBJECT', toggle=False) + + mesh = obj.data + properties = mesh.id_mask_editor_properties + collection = properties.possible_ids + color = collection[properties.active_id].color + + color_attribute = mesh.color_attributes.get(properties.target_attribute) + + selected_polygons = [] + for polygon in mesh.polygons: + if not polygon.select: + continue + + for idx in polygon.loop_indices: + color_attribute.data[idx].color = (color.r, color.g, color.b, 1.0) + + if old_mode != "OBJECT": + bpy.ops.object.mode_set(mode=old_mode, toggle=False) + + return {'FINISHED'} diff --git a/src/operators/id_editor_remove_id.py b/src/operators/id_editor_remove_id.py new file mode 100644 index 0000000..0455986 --- /dev/null +++ b/src/operators/id_editor_remove_id.py @@ -0,0 +1,41 @@ +import bpy + +from src.properties.id_mask_editor_value_properties import IDMaskEditorValueProperties +from src.types.colors import get_color + + +class IDEDITOR_RemoveIDOperator(bpy.types.Operator): + bl_idname = "id_mask_editor.remove_id_mask" + bl_label = "id_mask_editor.remove_id_mask" + + bl_options = {'INTERNAL'} + + @classmethod + def poll(cls, context): + + obj = context.active_object + if obj.type != 'MESH': + return False + + if not obj.data: + return False + + mesh = obj.data + properties = mesh.id_mask_editor_properties + collection = properties.possible_ids + return 0 <= properties.active_id < len(collection) + + def execute(self, context): + obj = context.active_object + if obj.type != 'MESH': + return {'FINISHED'} + + if not obj.data: + return {'FINISHED'} + + mesh = obj.data + properties = mesh.id_mask_editor_properties + collection = properties.possible_ids + collection.remove(properties.active_id) + + return {'FINISHED'} diff --git a/src/panels/panel.py b/src/panels/bake_panel.py similarity index 96% rename from src/panels/panel.py rename to src/panels/bake_panel.py index 50f99e8..c89fcf2 100644 --- a/src/panels/panel.py +++ b/src/panels/bake_panel.py @@ -3,7 +3,7 @@ import bpy class BakeToIDMapPanel(bpy.types.Panel): bl_idname = "PANEL.BAKE_TO_ID_MAP_PT_SETTINGS" - bl_label = "Bake to ID Map" + bl_label = "Bake ID Mask" bl_space_type = "VIEW_3D" bl_region_type = "UI" bl_category = "Tool" diff --git a/src/panels/panel_info.py b/src/panels/bake_panel_info.py similarity index 100% rename from src/panels/panel_info.py rename to src/panels/bake_panel_info.py diff --git a/src/panels/panel_options.py b/src/panels/bake_panel_options.py similarity index 100% rename from src/panels/panel_options.py rename to src/panels/bake_panel_options.py diff --git a/src/panels/id_mask_editor.py b/src/panels/id_mask_editor.py new file mode 100644 index 0000000..f1fd474 --- /dev/null +++ b/src/panels/id_mask_editor.py @@ -0,0 +1,90 @@ +import bpy + +from src.operators.create_id_mask import CreateIDMaskOperator +from src.operators.id_editor_create_id import IDEDITOR_CreateIDOperator +from src.operators.id_editor_paint import IDEDITOR_PaintIDMaskOperator +from src.operators.id_editor_remove_id import IDEDITOR_RemoveIDOperator +from src.types.colors import get_color + + +class IDMaskEditorPanel(bpy.types.Panel): + bl_idname = "ID_MASK_EDITOR_PT_Panel" + bl_label = "ID Mask Editor" + bl_category = "Tool" + bl_space_type = "VIEW_3D" + bl_region_type = "UI" + + @classmethod + def poll(cls, context): + if not context.object: + return False + + if not context.object.type == 'MESH': + return False + + return context.object.mode == "EDIT" + + def draw(self, context): + layout = self.layout + layout.use_property_decorate = True + + mesh = context.object.data + properties = mesh.id_mask_editor_properties + + target_attribute_row = layout.row(align=True) + has_attribute = properties.target_attribute + + target_attribute_row.prop_search( + properties, + 'target_attribute', + mesh, + 'color_attributes', + icon='GROUP_VCOL' + ) + target_attribute_row.operator(CreateIDMaskOperator.bl_idname, icon='ADD', text="") + + if not has_attribute: + return + + layout.prop(properties, "colors") + color = get_color(properties.colors) + self.draw_options(context, layout, properties, color) + + layout.separator() + + row = layout.row() + + col = row.column() + col.operator(IDEDITOR_PaintIDMaskOperator.bl_idname, icon='VPAINT_HLT') + col.template_list( + 'IDMaskEditorIDList', + 'IDMaskEditorIDList', + properties, + 'possible_ids', + properties, + 'active_id', + rows=3 + ) + + col = row.column(align=True) + col.operator(IDEDITOR_CreateIDOperator.bl_idname, icon='ADD', text="") + col.operator(IDEDITOR_RemoveIDOperator.bl_idname, icon='REMOVE', text="") + + + + def draw_options(self, context, layout, props, element): + + has_render_ui = 'render_ui' in dir(element) + has_connected_properties = 'connected_properties' in dir(element) and len(element.connected_properties) > 0 + + if not has_render_ui and not has_connected_properties: + return + + object_box = layout.box() + + if has_render_ui: + element.render_ui(context, object_box, props) + return + + for setting in element.connected_properties: + object_box.prop(props, setting) \ No newline at end of file diff --git a/src/panels/id_mask_editor_id_list.py b/src/panels/id_mask_editor_id_list.py new file mode 100644 index 0000000..680403d --- /dev/null +++ b/src/panels/id_mask_editor_id_list.py @@ -0,0 +1,17 @@ +import bpy + +from src.operators.id_editor_paint import IDEDITOR_PaintIDMaskOperator + + +class IDMaskEditorIDList(bpy.types.UIList): + def draw_item(self, _context, layout, data, attribute, _icon, _active_data, _active_propname, _index): + layout.alignment = 'EXPAND' + + split = layout.split(factor=0.15) + split.alignment = 'LEFT' + split.prop(attribute, 'color', text='') + + split.emboss = 'NONE' + split2 = split.split(factor=0.75) + split2.prop(attribute, "name", text="") + split2.emboss = 'NORMAL' \ No newline at end of file diff --git a/src/properties/bake_to_id.py b/src/properties/bake_properties.py similarity index 100% rename from src/properties/bake_to_id.py rename to src/properties/bake_properties.py diff --git a/src/properties/id_mask_editor_properties.py b/src/properties/id_mask_editor_properties.py new file mode 100644 index 0000000..44b4461 --- /dev/null +++ b/src/properties/id_mask_editor_properties.py @@ -0,0 +1,58 @@ +import bpy + +from bpy.types import (PropertyGroup, Palette, FloatColorAttribute) +from bpy.props import (EnumProperty, BoolProperty, IntProperty, StringProperty, PointerProperty, CollectionProperty) + +from src.properties.id_mask_editor_value_properties import IDMaskEditorValueProperties +from src.types.colors import get_color_enum + +def on_target_attribute_set(self, value): + return None + +def on_target_attribute_get(self): + if bpy.context.active_object is None: + return None + + if bpy.context.active_object.data is None: + return None + + if bpy.context.active_object.type != 'MESH': + return None + + color_attributes = bpy.context.active_object.data.color_attributes[0] + + return color_attributes + +class IDMaskEditorProperties(PropertyGroup): + colors: EnumProperty( + items=get_color_enum(), + name="Color Source", + description="From where to take the colors", + default=get_color_enum()[0][0] + ) + + colors_color_palette_palette: PointerProperty( + type=Palette, + name='Color Palette', + description="The Color Palette used for colors" + ) + + target_attribute: StringProperty( + name='', + description="The attribute to write the ID Mask to" + ) + + possible_ids: CollectionProperty( + type=IDMaskEditorValueProperties, + name='Entries', + ) + + active_id: IntProperty( + name="", + description="", + default=0 + ) + + current_color_id: IntProperty( + default=0 + ) \ No newline at end of file diff --git a/src/properties/id_mask_editor_value_properties.py b/src/properties/id_mask_editor_value_properties.py new file mode 100644 index 0000000..f2cbd9c --- /dev/null +++ b/src/properties/id_mask_editor_value_properties.py @@ -0,0 +1,17 @@ +from bpy.types import (PropertyGroup) +from bpy.props import (StringProperty, FloatVectorProperty) + +class IDMaskEditorValueProperties(PropertyGroup): + name: StringProperty( + name="Name", + description="ID-Name", + default='ID' + ) + + color: FloatVectorProperty( + name="Color", + subtype='COLOR', + default=[1.0, 1.0, 1.0], + min=0, + max=1 + ) \ No newline at end of file diff --git a/src/types/targets/vertex_colors.py b/src/types/targets/vertex_colors.py index d4aaeb0..b343d01 100644 --- a/src/types/targets/vertex_colors.py +++ b/src/types/targets/vertex_colors.py @@ -23,10 +23,6 @@ def paint_targets(props, targets, colors): layer_name = props.target_vertex_color_attribute_name for mesh in sorted_targets: - - if layer_name in mesh.attributes: - mesh.attributes.remove(mesh.attributes[layer_name]) - color_attribute = get_color_attribute(props, mesh.attributes, layer_name) for (indecies, color) in sorted_targets[mesh]: