From 73df699a09af261369a8e35c52c76191c49e7656 Mon Sep 17 00:00:00 2001 From: Michel Fedde <35878897+Neintonine@users.noreply.github.com> Date: Mon, 29 Jan 2024 21:11:49 +0100 Subject: [PATCH] first commit --- .gitignore | 160 ++++++++++++++++++++++++++++++++++++ __init__.py | 42 ++++++++++ operators/bake_to_id_map.py | 123 +++++++++++++++++++++++++++ panels/panel.py | 16 ++++ panels/panel_advanced.py | 24 ++++++ panels/panel_info.py | 20 +++++ panels/panel_options.py | 45 ++++++++++ properties/bake_to_id.py | 52 ++++++++++++ 8 files changed, 482 insertions(+) create mode 100644 .gitignore create mode 100644 __init__.py create mode 100644 operators/bake_to_id_map.py create mode 100644 panels/panel.py create mode 100644 panels/panel_advanced.py create mode 100644 panels/panel_info.py create mode 100644 panels/panel_options.py create mode 100644 properties/bake_to_id.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2dc53ca --- /dev/null +++ b/.gitignore @@ -0,0 +1,160 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +.idea/ diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..a4e4c14 --- /dev/null +++ b/__init__.py @@ -0,0 +1,42 @@ +import bpy + +from . panels.panel_options import BakeToIDOptionsPanel +from . panels.panel_advanced import BakeToIDAdvancedMenu +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 + +bl_info = { + "name": "Bake to ID Map", + "author": "iedSoftworks", + "description": "", + "blender": (2, 80, 0), + "category": "Object" +} + +classes = ( + BakeToIDMapOperator, + BakeToIDMapPanel, + BakeToIDInfoPanel, + BakeToIDOptionsPanel, + BakeToIDAdvancedMenu, + BakeToIDProperties, +) + +def register(): + + for cls in classes: + bpy.utils.register_class(cls) + + setattr(bpy.types.Scene, 'bake_to_id_props', bpy.props.PointerProperty(type=BakeToIDProperties)) + +def unregister(): + for cls in reversed(classes): + bpy.utils.unregister_class(cls) + + del bpy.types.Scene.bake_to_id_props + + +if __name__ == "__main__": + register() diff --git a/operators/bake_to_id_map.py b/operators/bake_to_id_map.py new file mode 100644 index 0000000..0781aed --- /dev/null +++ b/operators/bake_to_id_map.py @@ -0,0 +1,123 @@ +import colorsys +import math + +import bpy + + +class BakeToIDMapOperator(bpy.types.Operator): + bl_idname = "object.bake_to_id_map" + bl_label = "Bake to ID Map" + + def execute(self, context): + + old_mode = bpy.context.object.mode + if old_mode != "OBJECT": + bpy.ops.object.mode_set(mode='OBJECT', toggle=False) + + props = context.scene.bake_to_id_props + self.paint_id_mask(context, props) + + if old_mode != "OBJECT": + bpy.ops.object.mode_set(mode=old_mode, toggle=False) + + return {'FINISHED'} + + @staticmethod + def count_ids(context, props): + count = 0 + if props.source == 'MATERIAL_INDEX': + for obj in context.selected_objects: + count += len(obj.material_slots) + + return count + + def paint_id_mask(self, context, props): + targets = self.get_targets(context, 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)) + + self.paint_targets(props, targets, colors) + self.after_painting(context, props) + + def get_targets(self, context, props): + if props.source == 'MATERIAL_INDEX': + return self.get_material_targets(context) + + return [] + + def get_material_targets(self, context): + targets = [] + for obj in context.selected_objects: + if not obj.material_slots: continue + + mesh = obj.data + if len(obj.material_slots) < 2: + targets.append((mesh, mesh.polygons)) + continue + + polygonMaterials = {} + for poly in mesh.polygons: + if poly.material_index not in polygonMaterials: + polygonMaterials[poly.material_index] = [] + + polygonMaterials[poly.material_index].append(poly) + + for polygons in polygonMaterials.values(): + targets.append((mesh, polygons)) + + return targets + + def paint_targets(self, props, targets, colors): + if props.target == 'VERTEX_COLORS': + self.paint_targets_vertex_colors(props, targets, colors) + + def paint_targets_vertex_colors(self, props, targets, colors): + sortedTargets = {} + for i in range(len(targets)): + target = targets[i] + obj = target[0] + indecies = target[1] + + if obj not in sortedTargets: + sortedTargets[obj] = [] + + sortedTargets[obj].append((indecies, colors[i])) + + layer_name = props.target_vertex_color_attribute_name + for mesh in sortedTargets: + if layer_name in mesh.vertex_colors: + mesh.vertex_colors.remove(mesh.vertex_colors[layer_name]) + + vertex_color_layer = mesh.vertex_colors.new(name=layer_name) + + for (indecies, color) in sortedTargets[mesh]: + for polygon in indecies: + for idx in polygon.loop_indices: + vertex_color_layer.data[idx].color = (color[0], color[1], color[2], 1.0) + + def after_painting(self, context, props): + if props.source == 'MATERIAL_INDEX': + self.after_painting_source_material_index(props, context) + return + + def after_painting_source_material_index(self, props, context): + if props.source_materials_remove_all: + for obj in context.selected_objects: + obj.data.materials.clear() diff --git a/panels/panel.py b/panels/panel.py new file mode 100644 index 0000000..23a37d5 --- /dev/null +++ b/panels/panel.py @@ -0,0 +1,16 @@ +import bpy + + +class BakeToIDMapPanel(bpy.types.Panel): + bl_idname = "PANEL.BAKE_TO_ID_MAP_PT_SETTINGS" + bl_label = "Bake to ID Map" + bl_space_type = "VIEW_3D" + bl_region_type = "UI" + bl_category = "Tool" + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False # No animation. + + self.layout.operator("object.bake_to_id_map", text="Bake") diff --git a/panels/panel_advanced.py b/panels/panel_advanced.py new file mode 100644 index 0000000..9accb61 --- /dev/null +++ b/panels/panel_advanced.py @@ -0,0 +1,24 @@ +import math + +import bpy + + +class BakeToIDAdvancedMenu(bpy.types.Panel): + bl_idname = "PANEL.BAKE_TO_ID_MAP_PT_SETTINGS_ADVANCED" + bl_parent_id = "PANEL.BAKE_TO_ID_MAP_PT_SETTINGS" + bl_label = "Advanced" + bl_space_type = "VIEW_3D" + bl_region_type = "UI" + bl_category = "Tool" + bl_options = {'DEFAULT_CLOSED'} + + def draw(self, context): + layout = self.layout + + props = context.scene.bake_to_id_props + layout.label(text="Colors") + layout.prop(props, "adv_total_hues") + 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/panels/panel_info.py b/panels/panel_info.py new file mode 100644 index 0000000..22dde49 --- /dev/null +++ b/panels/panel_info.py @@ -0,0 +1,20 @@ +import bpy + +from .. operators.bake_to_id_map import BakeToIDMapOperator + + +class BakeToIDInfoPanel(bpy.types.Panel): + bl_idname = "PANEL.BAKE_TO_ID_MAP_PT_SETTINGS_INFO" + bl_parent_id = "PANEL.BAKE_TO_ID_MAP_PT_SETTINGS" + bl_label = "Infos" + bl_space_type = "VIEW_3D" + bl_region_type = "UI" + bl_category = "Tool" + + def draw(self, context): + layout = self.layout + + props = context.scene.bake_to_id_props + + layout.label(text="Selected Object-Count: " + str(len(context.selected_objects))) + layout.label(text="Estimated ID-Count: " + str(BakeToIDMapOperator.count_ids(context, props))) \ No newline at end of file diff --git a/panels/panel_options.py b/panels/panel_options.py new file mode 100644 index 0000000..3b74dbe --- /dev/null +++ b/panels/panel_options.py @@ -0,0 +1,45 @@ +import bpy + + +class BakeToIDOptionsPanel(bpy.types.Panel): + bl_idname = "PANEL.BAKE_TO_ID_MAP_PT_SETTINGS_OPTIONS" + bl_parent_id = "PANEL.BAKE_TO_ID_MAP_PT_SETTINGS" + bl_label = "Options" + bl_space_type = "VIEW_3D" + bl_region_type = "UI" + bl_category = "Tool" + + def draw(self, context): + layout = self.layout + + props = context.scene.bake_to_id_props + + layout.prop(props, "source") + source_settings_box = layout.box() + source_settings = self.get_source_settings(props) + for setting in source_settings: + source_settings_box.prop(props, setting) + + layout.separator() + + layout.prop(props, "target") + target_settings_box = layout.box() + target_settings = self.get_target_settings(props) + for setting in target_settings: + target_settings_box.prop(props, setting) + + def get_source_settings(self, props): + if props.source == 'MATERIAL_INDEX': + return [ + 'source_materials_remove_all' + ] + + return [] + + def get_target_settings(self, props): + if props.target == 'VERTEX_COLORS': + return [ + 'target_vertex_color_attribute_name' + ] + + return [] \ No newline at end of file diff --git a/properties/bake_to_id.py b/properties/bake_to_id.py new file mode 100644 index 0000000..b7be6f7 --- /dev/null +++ b/properties/bake_to_id.py @@ -0,0 +1,52 @@ +from bpy.types import (PropertyGroup) +from bpy.props import (EnumProperty, BoolProperty, IntProperty, StringProperty) + + +class BakeToIDProperties(PropertyGroup): + source: EnumProperty( + items=[ + ('MATERIAL_INDEX', 'Material Index', "It takes the material index as ID basis", 0) + ], + name="Source", + description="From where should the IDs be taken", + default = "MATERIAL_INDEX" + + ) + target: EnumProperty( + items=[('VERTEX_COLORS', 'Vertex Colors', "It bakes the ID to the vertex colors", 0)], + name="Target", + description="To where should the IDs should be baked to", + default="VERTEX_COLORS" + ) + + source_materials_remove_all : BoolProperty( + name="Remove all source materials", + default=False, + description="Removes every material except the first one." + ) + + target_vertex_color_attribute_name: StringProperty( + name="Color Attribute", + default="ID_MASK", + ) + + adv_total_hues: IntProperty( + name="Total Hues", + default=10, + min=1, + soft_max=360, + ) + + adv_total_satuations: IntProperty( + name="Total Satuations", + default=10, + min=1, + soft_max=100, + ) + + adv_total_brightnesses: IntProperty( + name="Total Brightnesses", + default=10, + min=1, + soft_max=100, + ) \ No newline at end of file