diff --git a/build/.version b/build/.version index 1cc5f65..d4d0d90 100644 --- a/build/.version +++ b/build/.version @@ -1 +1 @@ -1.1.0 \ No newline at end of file +1.1.0+build.1 \ No newline at end of file diff --git a/build/bump_minor.py b/build/bump_minor.py index 95053ae..449da17 100644 --- a/build/bump_minor.py +++ b/build/bump_minor.py @@ -5,4 +5,3 @@ if __name__ == "__main__": currentVersion = versioning.get_version() nextVersion = currentVersion.bump_minor() versioning.save_version(nextVersion) - diff --git a/src/__init__.py b/src/__init__.py index 65b5393..72db146 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -21,7 +21,6 @@ classes = ( BakeToIDMapPanel, BakeToIDInfoPanel, BakeToIDOptionsPanel, - BakeToIDAdvancedMenu, BakeToIDProperties, ) diff --git a/src/operators/bake_to_id_map.py b/src/operators/bake_to_id_map.py index dcd6643..6751d5b 100644 --- a/src/operators/bake_to_id_map.py +++ b/src/operators/bake_to_id_map.py @@ -3,7 +3,9 @@ import math import bpy -from .. types import (get_source, get_target) +from ..types.colors import get_color +from .. types.sources import get_source +from .. types.targets import get_target class BakeToIDMapOperator(bpy.types.Operator): @@ -27,27 +29,44 @@ class BakeToIDMapOperator(bpy.types.Operator): def paint_id_mask(self, context, props): source = get_source(props.source) - targets = source.get_targets(context) + targets = self.get_targets(context, source, props) if len(targets) < 1: return - totalTargets = len(targets) - colors = [] - - total_hues = props.adv_total_hues - total_satuations = props.adv_total_satuations - total_brightnesses = props.adv_total_brightnesses - - satuations_break_point = math.pow(total_brightnesses, total_hues) - - for i in range(totalTargets): - h = (i / total_hues) % 1 - l = (math.ceil(i / total_hues) % total_brightnesses) / total_brightnesses - s = math.ceil(i / satuations_break_point) / total_satuations - - colors.append(colorsys.hls_to_rgb(h, l, s)) + color = get_color(props.colors) + colors = color.get_colors(props) target = get_target(props.target) - target.paint_targets(props, targets, colors) - source.after_painting(context, props) \ No newline at end of file + self.paint_targets(props, target, targets, colors) + if 'after_painting' in dir(source): + source.after_painting(context, props) + + def get_targets(self, context, source, props): + if props.selection_mode == 'SINGLE': + return source.get_targets([context.active_object]) + + if props.selection_mode == 'MULTIPLE_COMBINED': + return source.get_targets(context.selected_objects) + + if props.selection_mode == 'MULTIPLE_SEPARATE': + result = [] + for obj in context.selected_objects: + if obj.type != 'MESH': + continue + + obj_targets = source.get_targets([obj]) + result.append(obj_targets) + + return result + + raise ValueError('Invalid selection_mode') + + def paint_targets(self, props, target, targets, colors): + if props.selection_mode == 'MULTIPLE_SEPARATE': + for targetList in targets: + target.paint_targets(props,targetList, colors) + + return + + target.paint_targets(props, targets, colors) \ No newline at end of file diff --git a/src/panels/panel.py b/src/panels/panel.py index 23a37d5..50f99e8 100644 --- a/src/panels/panel.py +++ b/src/panels/panel.py @@ -13,4 +13,19 @@ class BakeToIDMapPanel(bpy.types.Panel): layout.use_property_split = True layout.use_property_decorate = False # No animation. - self.layout.operator("object.bake_to_id_map", text="Bake") + props = context.scene.bake_to_id_props + + operator_row = layout.row() + operator_row.operator("object.bake_to_id_map", text="Bake") + operator_row.enabled = self.check_if_props_valid(context, props) + + layout.prop(props, "selection_mode") + + def check_if_props_valid(self, context, props): + if (props.selection_mode == "SINGLE" and context.active_object is None): + return False + + if (props.selection_mode != "SINGLE" and len(context.selected_objects) < 1): + return False + + return True \ No newline at end of file diff --git a/src/panels/panel_advanced.py b/src/panels/panel_advanced.py index 9accb61..139923a 100644 --- a/src/panels/panel_advanced.py +++ b/src/panels/panel_advanced.py @@ -21,4 +21,4 @@ class BakeToIDAdvancedMenu(bpy.types.Panel): layout.prop(props, "adv_total_satuations") layout.prop(props, "adv_total_brightnesses") layout.label(text="Max ID-count: " + str( - math.pow(math.pow(props.adv_total_hues, props.adv_total_satuations), props.adv_total_brightnesses))) + )) diff --git a/src/panels/panel_info.py b/src/panels/panel_info.py index 7ac9a0f..ed79817 100644 --- a/src/panels/panel_info.py +++ b/src/panels/panel_info.py @@ -1,7 +1,7 @@ import bpy -from .. operators.bake_to_id_map import BakeToIDMapOperator -from ..types import get_source +from ..types.colors import get_color +from .. types.sources import get_source class BakeToIDInfoPanel(bpy.types.Panel): @@ -19,5 +19,31 @@ class BakeToIDInfoPanel(bpy.types.Panel): source = get_source(props.source) - layout.label(text="Selected Object-Count: " + str(len(context.selected_objects))) - layout.label(text="Estimated ID-Count: " + str(source.estimate_ids(context, props))) \ No newline at end of file + if props.selection_mode != 'SINGLE': + layout.label(text="Selected Object-Count: " + str(len(context.selected_objects))) + + if props.selection_mode == 'SINGLE': + layout.label(text="ID-Total: " + str(source.estimate_ids([context.active_object]))) + + if props.selection_mode == 'MULTIPLE_SEPARATE': + total = 0 + count = 0 + for obj in context.selected_objects: + if (obj.type != 'MESH'): + continue + + total += source.estimate_ids([obj]) + count += 1 + + layout.label(text="Estimated ID-Total: " + str(total)) + try: + layout.label(text="Estimated ID-Average: " + str(total / count)) + except ZeroDivisionError: + layout.label(text="Estimated ID-Average: 0") + + if props.selection_mode == 'MULTIPLE_COMBINED': + layout.label(text="ID-Total: " + str(source.estimate_ids(context.selected_objects))) + + color = get_color(props.colors) + + layout.label(text="Colors available: " + str(color.get_count(props))) diff --git a/src/panels/panel_options.py b/src/panels/panel_options.py index 2d1445e..bc92db5 100644 --- a/src/panels/panel_options.py +++ b/src/panels/panel_options.py @@ -2,7 +2,8 @@ import textwrap import bpy -from src.types import get_source, get_target +from .. types.sources import get_source +from .. types.targets import get_target class BakeToIDOptionsPanel(bpy.types.Panel): @@ -15,19 +16,28 @@ class BakeToIDOptionsPanel(bpy.types.Panel): def draw(self, context): layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False # No animation. props = context.scene.bake_to_id_props layout.prop(props, "source") source = get_source(props.source) - source_settings_box = layout.box() - for setting in source.connected_properties: - source_settings_box.prop(props, setting) + + if len(source.connected_properties) > 0: + source_settings_box = layout.box() + for setting in source.connected_properties: + source_settings_box.prop(props, setting) layout.separator() layout.prop(props, "target") target = get_target(props.target) - target_settings_box = layout.box() - for setting in target.connected_properties: - target_settings_box.prop(props, setting) + if len(target.connected_properties) > 0: + target_settings_box = layout.box() + for setting in target.connected_properties: + target_settings_box.prop(props, setting) + + layout.separator() + + layout.prop(props, "colors") diff --git a/src/properties/bake_to_id.py b/src/properties/bake_to_id.py index 019941a..813c9d0 100644 --- a/src/properties/bake_to_id.py +++ b/src/properties/bake_to_id.py @@ -1,17 +1,30 @@ from bpy.types import (PropertyGroup) from bpy.props import (EnumProperty, BoolProperty, IntProperty, StringProperty) -from src.types import get_source_enum, get_targets_enum +from src.types.colors import get_color_enum +from src.types.sources import get_source_enum +from src.types.targets import get_targets_enum class BakeToIDProperties(PropertyGroup): + selection_mode: EnumProperty( + items=[ + ("SINGLE", "Only active element", "Only use the currently selected element", 0), + ("MULTIPLE_SEPARATE", "Use Selection, separate", "It performs the transfer for every selected element, but each get there own ids", 1), + ("MULTIPLE_COMBINED", "Use Selection, combined", "It performs the transfer for every selected element, combining the ids", 2) + ], + name="Selection Mode", + description="Specifies how the 3D View-Selection is gonna be used.", + default="MULTIPLE_SEPARATE" + ) + source: EnumProperty( items=get_source_enum(), name="Source", description="From where should the IDs be taken", - default = "MATERIAL_INDEX" - + default="MATERIAL_INDEX" ) + target: EnumProperty( items=get_targets_enum(), name="Target", @@ -19,7 +32,14 @@ class BakeToIDProperties(PropertyGroup): default=get_targets_enum()[0][0] ) - source_materials_remove_all : BoolProperty( + colors: EnumProperty( + items=get_color_enum(), + name="Color Source", + description="From where to take the colors", + default=get_color_enum()[0][0] + ) + + source_materials_remove_all: BoolProperty( name="Remove all source materials", default=False, description="Removes every material except the first one." @@ -29,6 +49,11 @@ class BakeToIDProperties(PropertyGroup): name="Color Attribute", default="ID_MASK", ) + target_vertex_color_override_attribute: BoolProperty( + name="Override Color Attribute", + default=True, + description="If set true, the attribute will be deleted and recreated, if it already exists. If set false, the data will just be overwritten." + ) adv_total_hues: IntProperty( name="Total Hues", diff --git a/src/types/__init__.py b/src/types/__init__.py deleted file mode 100644 index c814b4c..0000000 --- a/src/types/__init__.py +++ /dev/null @@ -1,43 +0,0 @@ -from . sources import material_index as source_mat_index - -from . targets import vertex_colors as target_vertex_colors - -_sources = [ - source_mat_index -] - -_targets = [ - target_vertex_colors -] - -def get_source(id): - for source in _sources: - if source.source_id == id: - return source - - raise Exception("Source not found: " + id) - -def get_target(id): - for target in _targets: - if target.target_id == id: - return target - - raise Exception("Target not found: " + id) - -def get_source_enum(): - enumList = [] - i = 0 - for source in _sources: - enumList.append((source.source_id, source.name, source.description, i)) - i += 1 - - return enumList - -def get_targets_enum(): - enumList = [] - i = 0 - for target in _targets: - enumList.append((target.target_id, target.name, target.description, i)) - i += 1 - - return enumList diff --git a/src/types/colors/__init__.py b/src/types/colors/__init__.py new file mode 100644 index 0000000..81f5434 --- /dev/null +++ b/src/types/colors/__init__.py @@ -0,0 +1,24 @@ +from . import generated + +_colors = [ + generated +] + + +def get_color(id): + for color in _colors: + if color.color_id == id: + return color + + raise Exception("Source not found: " + id) + + +def get_color_enum(): + enum_list = [] + i = 0 + for color in _colors: + enum_list.append((color.color_id, color.name, color.description, i)) + i += 1 + + return enum_list + diff --git a/src/types/colors/generated.py b/src/types/colors/generated.py new file mode 100644 index 0000000..91e7134 --- /dev/null +++ b/src/types/colors/generated.py @@ -0,0 +1,32 @@ +import colorsys +import math + +color_id = 'GENERATED' +name = 'Generated' +description = 'The colors get generated on-the-fly' + +MAX_BRIGHTNESS_STEPS = 8 +MAX_SATURATION_STEPS = 8 +MAX_HUES_STEPS = 8 + +def get_colors(props): + return gen_colors() + + +def get_count(props): + return len(gen_colors()) + + +def gen_colors(): + colors = [] + for brightnessStep in range(0, MAX_BRIGHTNESS_STEPS): + v = 1 - (brightnessStep / MAX_BRIGHTNESS_STEPS) + for satuationStep in range(0, MAX_SATURATION_STEPS): + s = 1 - (satuationStep / MAX_SATURATION_STEPS) + for hueStep in range(0, MAX_HUES_STEPS): + h = hueStep / MAX_HUES_STEPS + + color = colorsys.hsv_to_rgb(h, s, v) + colors.append(color) + + return colors \ No newline at end of file diff --git a/src/types/sources/__init__.py b/src/types/sources/__init__.py new file mode 100644 index 0000000..f52c397 --- /dev/null +++ b/src/types/sources/__init__.py @@ -0,0 +1,25 @@ +from . import material_index +from . import object + +_sources = [ + material_index, + object +] + +def get_source(id): + for source in _sources: + if source.source_id == id: + return source + + raise Exception("Source not found: " + id) + + +def get_source_enum(): + enum_list = [] + i = 0 + for source in _sources: + enum_list.append((source.source_id, source.name, source.description, i)) + i += 1 + + return enum_list + diff --git a/src/types/sources/material_index.py b/src/types/sources/material_index.py index d7b1b5b..39be292 100644 --- a/src/types/sources/material_index.py +++ b/src/types/sources/material_index.py @@ -6,9 +6,9 @@ connected_properties = [ 'source_materials_remove_all' ] -def get_targets(context): +def get_targets(objects): targets = [] - for obj in context.selected_objects: + for obj in objects: if not obj.material_slots: continue mesh = obj.data @@ -33,9 +33,9 @@ def after_painting(context, props): for obj in context.selected_objects: obj.data.materials.clear() -def estimate_ids(context, props): +def estimate_ids(objects): count = 0 - for obj in context.selected_objects: + for obj in objects: count += len(obj.material_slots) return count diff --git a/src/types/sources/object.py b/src/types/sources/object.py new file mode 100644 index 0000000..7a94968 --- /dev/null +++ b/src/types/sources/object.py @@ -0,0 +1,16 @@ +source_id = 'OBJECT' +name = 'Object ID' +description = 'Uses the object id as basis for the ID mask.' + +connected_properties = [] + +def get_targets(objects): + targets = [] + for obj in objects: + mesh = obj.data + targets.append((mesh, mesh.polygons)) + + return targets + +def estimate_ids(objects): + return len(objects) \ No newline at end of file diff --git a/src/types/targets/__init__.py b/src/types/targets/__init__.py new file mode 100644 index 0000000..95c7908 --- /dev/null +++ b/src/types/targets/__init__.py @@ -0,0 +1,23 @@ +from . import vertex_colors + +_targets = [ + vertex_colors +] + + +def get_target(id): + for target in _targets: + if target.target_id == id: + return target + + raise Exception("Target not found: " + id) + + +def get_targets_enum(): + enum_list = [] + i = 0 + for target in _targets: + enum_list.append((target.target_id, target.name, target.description, i)) + i += 1 + + return enum_list diff --git a/src/types/targets/vertex_colors.py b/src/types/targets/vertex_colors.py index f77a718..91480e3 100644 --- a/src/types/targets/vertex_colors.py +++ b/src/types/targets/vertex_colors.py @@ -3,7 +3,8 @@ name = 'Color Attribute / Vertex Color' description = 'Bakes the ID onto a color attribute (previously known as Vertex Color)' connected_properties = [ - 'target_vertex_color_attribute_name' + 'target_vertex_color_attribute_name', + 'target_vertex_color_override_attribute' ] @@ -21,12 +22,22 @@ 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 = mesh.attributes.new(name=layer_name, type='FLOAT_COLOR', domain='CORNER') + color_attribute = get_color_attribute(props, mesh.attributes, layer_name) for (indecies, color) in sorted_targets[mesh]: for polygon in indecies: for idx in polygon.loop_indices: - color_attribute.data[idx].color = (color[0], color[1], color[2], 1.0) \ No newline at end of file + color_attribute.data[idx].color = (color[0], color[1], color[2], 1.0) + +def get_color_attribute(props, attributes, layer_name): + if layer_name in attributes: + if not props.target_vertex_color_override_attribute: + return attributes[layer_name] + + attributes.remove(attributes[layer_name]) + + return attributes.new(name=layer_name, type='FLOAT_COLOR', domain='CORNER') \ No newline at end of file